Identity: Rename "namespace" to "type" in the requester interface (#90567)

This commit is contained in:
Ryan McKinley
2024-07-25 12:52:14 +03:00
committed by GitHub
parent 8cdf5ee824
commit 9db3bc926e
109 changed files with 649 additions and 632 deletions
+6 -11
View File
@@ -2,7 +2,6 @@ package accesscontrol
import (
"context"
"errors"
"fmt"
"strings"
@@ -84,8 +83,8 @@ type SearchOptions struct {
Action string
ActionSets []string
Scope string
NamespacedID string // ID of the identity (ex: user:3, service-account:4)
wildcards Wildcards // private field computed based on the Scope
TypedID identity.TypedID // ID of the identity (ex: user:3, service-account:4)
wildcards Wildcards // private field computed based on the Scope
RolePrefixes []string
}
@@ -105,21 +104,17 @@ func (s *SearchOptions) Wildcards() []string {
}
func (s *SearchOptions) ComputeUserID() (int64, error) {
if s.NamespacedID == "" {
return 0, errors.New("namespacedID must be set")
}
id, err := identity.ParseNamespaceID(s.NamespacedID)
id, err := s.TypedID.ParseInt()
if err != nil {
return 0, err
}
// Validate namespace type is user or service account
if id.Namespace() != identity.NamespaceUser && id.Namespace() != identity.NamespaceServiceAccount {
return 0, fmt.Errorf("invalid namespace: %s", id.Namespace())
if s.TypedID.Type() != identity.TypeUser && s.TypedID.Type() != identity.TypeServiceAccount {
return 0, fmt.Errorf("invalid type: %s", s.TypedID.Type())
}
return id.ParseInt()
return id, nil
}
type SyncUserRolesCommand struct {
@@ -191,6 +191,6 @@ func (a *AccessControl) RegisterScopeAttributeResolver(prefix string, resolver a
}
func (a *AccessControl) debug(ctx context.Context, ident identity.Requester, msg string, eval accesscontrol.Evaluator) {
namespace, id := ident.GetNamespacedID()
namespace, id := ident.GetTypedID()
a.log.FromContext(ctx).Debug(msg, "namespace", namespace, "id", id, "orgID", ident.GetOrgID(), "permissions", eval.GoString())
}
+5 -6
View File
@@ -25,7 +25,6 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol/database"
"github.com/grafana/grafana/pkg/services/accesscontrol/migrator"
"github.com/grafana/grafana/pkg/services/accesscontrol/pluginutils"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/authz/zanzana"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
@@ -141,7 +140,7 @@ func (s *Service) getUserPermissions(ctx context.Context, user identity.Requeste
permissions = append(permissions, SharedWithMeFolderPermission)
}
userID, err := identity.UserIdentifier(user.GetNamespacedID())
userID, err := identity.UserIdentifier(user.GetTypedID())
if err != nil {
return nil, err
}
@@ -209,10 +208,10 @@ func (s *Service) getUserDirectPermissions(ctx context.Context, user identity.Re
ctx, span := s.tracer.Start(ctx, "authz.getUserDirectPermissions")
defer span.End()
namespace, identifier := user.GetNamespacedID()
namespace, identifier := user.GetTypedID()
var userID int64
if namespace == authn.NamespaceUser || namespace == authn.NamespaceServiceAccount {
if namespace == identity.TypeUser || namespace == identity.TypeServiceAccount {
var err error
userID, err = strconv.ParseInt(identifier, 10, 64)
if err != nil {
@@ -483,7 +482,7 @@ func (s *Service) SearchUsersPermissions(ctx context.Context, usr identity.Reque
// Limit roles to available in OSS
options.RolePrefixes = OSSRolesPrefixes
if options.NamespacedID != "" {
if options.TypedID.Type() != "" {
userID, err := options.ComputeUserID()
if err != nil {
s.log.Error("Failed to resolve user ID", "error", err)
@@ -598,7 +597,7 @@ func (s *Service) SearchUserPermissions(ctx context.Context, orgID int64, search
timer := prometheus.NewTimer(metrics.MAccessPermissionsSummary)
defer timer.ObserveDuration()
if searchOptions.NamespacedID == "" {
if searchOptions.TypedID.Type() == "" {
return nil, fmt.Errorf("expected namespaced ID to be specified")
}
@@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/localcache"
"github.com/grafana/grafana/pkg/infra/log"
@@ -261,7 +262,7 @@ func benchSearchUserWithAction(b *testing.B, usersCount, resourceCount int) {
for n := 0; n < b.N; n++ {
usersPermissions, err := acService.SearchUsersPermissions(context.Background(), siu,
accesscontrol.SearchOptions{Action: "resources:action2", NamespacedID: "user:14"})
accesscontrol.SearchOptions{Action: "resources:action2", TypedID: identity.NewTypedID(identity.TypeUser, 14)})
require.NoError(b, err)
require.Len(b, usersPermissions, 1)
for _, permissions := range usersPermissions {
@@ -2,7 +2,6 @@ package acimpl
import (
"context"
"fmt"
"strings"
"testing"
@@ -544,7 +543,7 @@ func TestService_SearchUsersPermissions(t *testing.T) {
// only the user's basic roles and the user's stored permissions
name: "check namespacedId filter works correctly",
siuPermissions: listAllPerms,
searchOption: accesscontrol.SearchOptions{NamespacedID: fmt.Sprintf("%s:1", identity.NamespaceServiceAccount)},
searchOption: accesscontrol.SearchOptions{TypedID: identity.NewTypedID(identity.TypeServiceAccount, 1)},
ramRoles: map[string]*accesscontrol.RoleDTO{
string(identity.RoleEditor): {Permissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"},
@@ -616,7 +615,7 @@ func TestService_SearchUserPermissions(t *testing.T) {
name: "ram only",
searchOption: accesscontrol.SearchOptions{
ActionPrefix: "teams",
NamespacedID: fmt.Sprintf("%s:2", identity.NamespaceUser),
TypedID: identity.NewTypedID(identity.TypeUser, 2),
},
ramRoles: map[string]*accesscontrol.RoleDTO{
string(identity.RoleEditor): {Permissions: []accesscontrol.Permission{
@@ -641,7 +640,7 @@ func TestService_SearchUserPermissions(t *testing.T) {
name: "stored only",
searchOption: accesscontrol.SearchOptions{
ActionPrefix: "teams",
NamespacedID: fmt.Sprintf("%s:2", identity.NamespaceUser),
TypedID: identity.NewTypedID(identity.TypeUser, 2),
},
storedPerms: map[int64][]accesscontrol.Permission{
1: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"}},
@@ -661,7 +660,7 @@ func TestService_SearchUserPermissions(t *testing.T) {
name: "ram and stored",
searchOption: accesscontrol.SearchOptions{
ActionPrefix: "teams",
NamespacedID: fmt.Sprintf("%s:2", identity.NamespaceUser),
TypedID: identity.NewTypedID(identity.TypeUser, 2),
},
ramRoles: map[string]*accesscontrol.RoleDTO{
string(identity.RoleAdmin): {Permissions: []accesscontrol.Permission{
@@ -691,7 +690,7 @@ func TestService_SearchUserPermissions(t *testing.T) {
name: "check action prefix filter works correctly",
searchOption: accesscontrol.SearchOptions{
ActionPrefix: "teams",
NamespacedID: fmt.Sprintf("%s:1", identity.NamespaceUser),
TypedID: identity.NewTypedID(identity.TypeUser, 1),
},
ramRoles: map[string]*accesscontrol.RoleDTO{
string(identity.RoleEditor): {Permissions: []accesscontrol.Permission{
@@ -712,8 +711,8 @@ func TestService_SearchUserPermissions(t *testing.T) {
{
name: "check action filter works correctly",
searchOption: accesscontrol.SearchOptions{
Action: accesscontrol.ActionTeamsRead,
NamespacedID: fmt.Sprintf("%s:1", identity.NamespaceUser),
Action: accesscontrol.ActionTeamsRead,
TypedID: identity.NewTypedID(identity.TypeUser, 1),
},
ramRoles: map[string]*accesscontrol.RoleDTO{
string(identity.RoleEditor): {Permissions: []accesscontrol.Permission{
@@ -734,8 +733,8 @@ func TestService_SearchUserPermissions(t *testing.T) {
{
name: "check action sets are correctly included if an action is specified",
searchOption: accesscontrol.SearchOptions{
Action: "dashboards:read",
NamespacedID: fmt.Sprintf("%s:1", identity.NamespaceUser),
Action: "dashboards:read",
TypedID: identity.NewTypedID(identity.TypeUser, 1),
},
withActionSets: true,
actionSets: map[string][]string{
@@ -768,7 +767,7 @@ func TestService_SearchUserPermissions(t *testing.T) {
name: "check action sets are correctly included if an action prefix is specified",
searchOption: accesscontrol.SearchOptions{
ActionPrefix: "dashboards",
NamespacedID: fmt.Sprintf("%s:1", identity.NamespaceUser),
TypedID: identity.NewTypedID(identity.TypeUser, 1),
},
withActionSets: true,
actionSets: map[string][]string{
+10 -2
View File
@@ -5,6 +5,7 @@ import (
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/middleware/requestmeta"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
@@ -71,16 +72,23 @@ func (api *AccessControlAPI) searchUsersPermissions(c *contextmodel.ReqContext)
ActionPrefix: c.Query("actionPrefix"),
Action: c.Query("action"),
Scope: c.Query("scope"),
NamespacedID: c.Query("namespacedId"),
}
namespacedId := c.Query("namespacedId")
// Validate inputs
if searchOptions.ActionPrefix != "" && searchOptions.Action != "" {
return response.JSON(http.StatusBadRequest, "'action' and 'actionPrefix' are mutually exclusive")
}
if searchOptions.NamespacedID == "" && searchOptions.ActionPrefix == "" && searchOptions.Action == "" {
if namespacedId == "" && searchOptions.ActionPrefix == "" && searchOptions.Action == "" {
return response.JSON(http.StatusBadRequest, "at least one search option must be provided")
}
if namespacedId != "" {
var err error
searchOptions.TypedID, err = identity.ParseTypedID(namespacedId)
if err != nil {
return response.Error(http.StatusBadGateway, "invalid namespacedId", err)
}
}
// Compute metadata
permissions, err := api.Service.SearchUsersPermissions(c.Req.Context(), c.SignedInUser, searchOptions)
@@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
@@ -186,7 +187,7 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/api/endpoint", nil)
expectedIdentity := &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, tc.ctxSignedInUser.UserID),
ID: identity.NewTypedID(identity.TypeUser, tc.ctxSignedInUser.UserID),
OrgID: tc.targetOrgId,
Permissions: map[int64]map[string][]string{},
}
@@ -21,7 +21,7 @@ func TestPermissionCacheKey(t *testing.T) {
signedInUser: &user.SignedInUser{
OrgID: 1,
UserID: 1,
NamespacedID: identity.MustParseNamespaceID("user:1"),
NamespacedID: identity.MustParseTypedID("user:1"),
},
expected: "rbac-permissions-1-user-1",
},
@@ -31,7 +31,7 @@ func TestPermissionCacheKey(t *testing.T) {
OrgID: 1,
ApiKeyID: 1,
IsServiceAccount: false,
NamespacedID: identity.MustParseNamespaceID("user:1"),
NamespacedID: identity.MustParseTypedID("user:1"),
},
expected: "rbac-permissions-1-api-key-1",
},
@@ -41,7 +41,7 @@ func TestPermissionCacheKey(t *testing.T) {
OrgID: 1,
UserID: 1,
IsServiceAccount: true,
NamespacedID: identity.MustParseNamespaceID("service-account:1"),
NamespacedID: identity.MustParseTypedID("service-account:1"),
},
expected: "rbac-permissions-1-service-account-1",
},
@@ -51,7 +51,7 @@ func TestPermissionCacheKey(t *testing.T) {
OrgID: 1,
UserID: -1,
IsServiceAccount: true,
NamespacedID: identity.MustParseNamespaceID("service-account:-1"),
NamespacedID: identity.MustParseTypedID("service-account:-1"),
},
expected: "rbac-permissions-1-service-account--1",
},
@@ -60,7 +60,7 @@ func TestPermissionCacheKey(t *testing.T) {
signedInUser: &user.SignedInUser{
OrgID: 1,
OrgRole: org.RoleNone,
NamespacedID: identity.MustParseNamespaceID("user:1"),
NamespacedID: identity.MustParseTypedID("user:1"),
},
expected: "rbac-permissions-1-user-None",
},
@@ -163,8 +163,8 @@ func (s *AccessControlStore) SearchUsersPermissions(ctx context.Context, orgID i
}
dbPerms := make([]UserRBACPermission, 0)
var userID int64
if options.NamespacedID != "" {
userID := int64(-1)
if options.TypedID.Type() != "" {
var err error
userID, err = options.ComputeUserID()
if err != nil {
@@ -181,26 +181,26 @@ func (s *AccessControlStore) SearchUsersPermissions(ctx context.Context, orgID i
params := []any{}
direct := userAssignsSQL
if options.NamespacedID != "" {
if userID >= 0 {
direct += " WHERE ur.user_id = ?"
params = append(params, userID)
}
team := teamAssignsSQL
if options.NamespacedID != "" {
if userID >= 0 {
team += " WHERE tm.user_id = ?"
params = append(params, userID)
}
basic := basicRoleAssignsSQL
if options.NamespacedID != "" {
if userID >= 0 {
basic += " WHERE ou.user_id = ?"
params = append(params, userID)
}
grafanaAdmin := fmt.Sprintf(grafanaAdminAssignsSQL, s.sql.ReadReplica().Quote("user"))
params = append(params, accesscontrol.RoleGrafanaAdmin)
if options.NamespacedID != "" {
if userID >= 0 {
grafanaAdmin += " AND sa.user_id = ?"
params = append(params, userID)
}
@@ -626,7 +626,7 @@ func TestIntegrationAccessControlStore_SearchUsersPermissions(t *testing.T) {
},
options: accesscontrol.SearchOptions{
ActionPrefix: "teams:",
NamespacedID: fmt.Sprintf("%s:1", identity.NamespaceUser),
TypedID: identity.NewTypedID(identity.TypeUser, 1),
},
wantPerm: map[int64][]accesscontrol.Permission{
1: {{Action: "teams:read", Scope: "teams:id:1"}, {Action: "teams:read", Scope: "teams:id:10"},
+1 -1
View File
@@ -80,7 +80,7 @@ func deny(c *contextmodel.ReqContext, evaluator Evaluator, err error) {
if err != nil {
c.Logger.Error("Error from access control system", "error", err, "accessErrorID", id)
} else {
namespace, identifier := c.SignedInUser.GetNamespacedID()
namespace, identifier := c.SignedInUser.GetTypedID()
c.Logger.Info(
"Access denied",
"namespace", namespace,
+5 -5
View File
@@ -69,11 +69,11 @@ func (a *Anonymous) Test(ctx context.Context, r *authn.Request) bool {
return true
}
func (a *Anonymous) Namespace() string {
return authn.NamespaceAnonymous.String()
func (a *Anonymous) IdentityType() identity.IdentityType {
return identity.TypeAnonymous
}
func (a *Anonymous) ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.NamespaceID) (*authn.Identity, error) {
func (a *Anonymous) ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) {
o, err := a.orgService.GetByName(ctx, &org.GetOrgByNameQuery{Name: a.cfg.AnonymousOrgName})
if err != nil {
return nil, err
@@ -84,7 +84,7 @@ func (a *Anonymous) ResolveIdentity(ctx context.Context, orgID int64, namespaceI
}
// Anonymous identities should always have the same namespace id.
if namespaceID != authn.AnonymousNamespaceID {
if namespaceID != identity.AnonymousTypedID {
return nil, errInvalidID
}
@@ -109,7 +109,7 @@ func (a *Anonymous) Priority() uint {
func (a *Anonymous) newAnonymousIdentity(o *org.Org) *authn.Identity {
return &authn.Identity{
ID: authn.AnonymousNamespaceID,
ID: identity.AnonymousTypedID,
OrgID: o.ID,
OrgName: o.Name,
OrgRoles: map[int64]org.RoleType{o.ID: org.RoleType(a.cfg.AnonymousOrgRole)},
+11 -10
View File
@@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/anonymous/anontest"
"github.com/grafana/grafana/pkg/services/authn"
@@ -52,17 +53,17 @@ func TestAnonymous_Authenticate(t *testing.T) {
anonDeviceService: anontest.NewFakeService(),
}
identity, err := c.Authenticate(context.Background(), &authn.Request{})
user, err := c.Authenticate(context.Background(), &authn.Request{})
if err != nil {
require.Error(t, err)
require.Nil(t, identity)
require.Nil(t, user)
} else {
require.Nil(t, err)
assert.Equal(t, authn.AnonymousNamespaceID, identity.ID)
assert.Equal(t, tt.org.ID, identity.OrgID)
assert.Equal(t, tt.org.Name, identity.OrgName)
assert.Equal(t, tt.cfg.AnonymousOrgRole, string(identity.GetOrgRole()))
assert.Equal(t, identity.AnonymousTypedID, user.ID)
assert.Equal(t, tt.org.ID, user.OrgID)
assert.Equal(t, tt.org.Name, user.OrgName)
assert.Equal(t, tt.cfg.AnonymousOrgRole, string(user.GetOrgRole()))
}
})
}
@@ -73,7 +74,7 @@ func TestAnonymous_ResolveIdentity(t *testing.T) {
desc string
cfg *setting.Cfg
orgID int64
namespaceID authn.NamespaceID
namespaceID identity.TypedID
org *org.Org
orgErr error
expectedErr error
@@ -87,7 +88,7 @@ func TestAnonymous_ResolveIdentity(t *testing.T) {
AnonymousOrgName: "some org",
},
orgID: 1,
namespaceID: authn.AnonymousNamespaceID,
namespaceID: identity.AnonymousTypedID,
expectedErr: errInvalidOrg,
},
{
@@ -97,7 +98,7 @@ func TestAnonymous_ResolveIdentity(t *testing.T) {
AnonymousOrgName: "some org",
},
orgID: 1,
namespaceID: authn.MustParseNamespaceID("anonymous:1"),
namespaceID: identity.MustParseTypedID("anonymous:1"),
expectedErr: errInvalidID,
},
{
@@ -107,7 +108,7 @@ func TestAnonymous_ResolveIdentity(t *testing.T) {
AnonymousOrgName: "some org",
},
orgID: 1,
namespaceID: authn.AnonymousNamespaceID,
namespaceID: identity.AnonymousTypedID,
},
}
@@ -147,7 +147,7 @@ func testSetup(t *testing.T, opts ...setupOption) (context.Context, storage.Inte
// Test with an admin identity
ctx := identity.WithRequester(context.Background(), &identity.StaticRequester{
Namespace: identity.NamespaceUser,
Type: identity.TypeUser,
Login: "testuser",
UserID: 123,
UserUID: "u123",
+3 -3
View File
@@ -66,7 +66,7 @@ func (s *Service) SignIdentity(ctx context.Context, id identity.Requester) (stri
cacheKey := prefixCacheKey(id.GetCacheKey())
result, err, _ := s.si.Do(cacheKey, func() (interface{}, error) {
namespace, identifier := id.GetNamespacedID()
namespace, identifier := id.GetTypedID()
cachedToken, err := s.cache.Get(ctx, cacheKey)
if err == nil {
@@ -92,7 +92,7 @@ func (s *Service) SignIdentity(ctx context.Context, id identity.Requester) (stri
},
}
if identity.IsNamespace(namespace, identity.NamespaceUser) {
if identity.IsIdentityType(namespace, identity.TypeUser) {
claims.Rest.Email = id.GetEmail()
claims.Rest.EmailVerified = id.IsEmailVerified()
claims.Rest.AuthenticatedBy = id.GetAuthenticatedBy()
@@ -144,7 +144,7 @@ func (s *Service) hook(ctx context.Context, identity *authn.Identity, _ *authn.R
token, err := s.SignIdentity(ctx, identity)
if err != nil {
if shouldLogErr(err) {
namespace, id := identity.GetNamespacedID()
namespace, id := identity.GetTypedID()
s.logger.FromContext(ctx).Error("Failed to sign id token", "err", err, "namespace", namespace, "id", id)
}
// for now don't return error so we don't break authentication from this hook
+4 -3
View File
@@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/remotecache"
"github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/auth/idtest"
@@ -69,7 +70,7 @@ func TestService_SignIdentity(t *testing.T) {
featuremgmt.WithFeatures(featuremgmt.FlagIdForwarding),
&authntest.FakeService{}, nil,
)
token, err := s.SignIdentity(context.Background(), &authn.Identity{ID: authn.MustParseNamespaceID("user:1")})
token, err := s.SignIdentity(context.Background(), &authn.Identity{ID: identity.MustParseTypedID("user:1")})
require.NoError(t, err)
require.NotEmpty(t, token)
})
@@ -81,10 +82,10 @@ func TestService_SignIdentity(t *testing.T) {
&authntest.FakeService{}, nil,
)
token, err := s.SignIdentity(context.Background(), &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
AuthenticatedBy: login.AzureADAuthModule,
Login: "U1",
UID: authn.NewNamespaceIDString(authn.NamespaceUser, "edpu3nnt61se8e")})
UID: identity.NewTypedIDString(identity.TypeUser, "edpu3nnt61se8e")})
require.NoError(t, err)
parsed, err := jwt.ParseSigned(token)
+4 -4
View File
@@ -95,7 +95,7 @@ type Service interface {
// RegisterPreLogoutHook registers a hook that is called before a logout request.
RegisterPreLogoutHook(hook PreLogoutHookFn, priority uint)
// ResolveIdentity resolves an identity from org and namespace id.
ResolveIdentity(ctx context.Context, orgID int64, namespaceID NamespaceID) (*Identity, error)
ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*Identity, error)
// RegisterClient will register a new authn.Client that can be used for authentication
RegisterClient(c Client)
@@ -157,7 +157,7 @@ type RedirectClient interface {
// that should happen during logout and supports client specific redirect URL.
type LogoutClient interface {
Client
Logout(ctx context.Context, user Requester) (*Redirect, bool)
Logout(ctx context.Context, user identity.Requester) (*Redirect, bool)
}
type PasswordClient interface {
@@ -179,8 +179,8 @@ type UsageStatClient interface {
// Clients that implements this interface can resolve an full identity from an orgID and namespaceID.
type IdentityResolverClient interface {
Client
Namespace() string
ResolveIdentity(ctx context.Context, orgID int64, namespaceID NamespaceID) (*Identity, error)
IdentityType() identity.IdentityType
ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*Identity, error)
}
type Request struct {
+12 -11
View File
@@ -13,6 +13,7 @@ import (
"go.opentelemetry.io/otel/trace"
"github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/network"
"github.com/grafana/grafana/pkg/infra/tracing"
@@ -216,9 +217,9 @@ func (s *Service) Login(ctx context.Context, client string, r *authn.Request) (i
}
// Login is only supported for users
if !id.ID.IsNamespace(authn.NamespaceUser) {
if !id.ID.IsType(identity.TypeUser) {
s.metrics.failedLogin.WithLabelValues(client).Inc()
return nil, authn.ErrUnsupportedIdentity.Errorf("expected identity of type user but got: %s", id.ID.Namespace())
return nil, authn.ErrUnsupportedIdentity.Errorf("expected identity of type user but got: %s", id.ID.Type())
}
userID, err := id.ID.ParseInt()
@@ -271,7 +272,7 @@ func (s *Service) RegisterPreLogoutHook(hook authn.PreLogoutHookFn, priority uin
s.preLogoutHooks.insert(hook, priority)
}
func (s *Service) Logout(ctx context.Context, user authn.Requester, sessionToken *auth.UserToken) (*authn.Redirect, error) {
func (s *Service) Logout(ctx context.Context, user identity.Requester, sessionToken *auth.UserToken) (*authn.Redirect, error) {
ctx, span := s.tracer.Start(ctx, "authn.Logout")
defer span.End()
@@ -280,7 +281,7 @@ func (s *Service) Logout(ctx context.Context, user authn.Requester, sessionToken
redirect.URL = s.cfg.SignoutRedirectUrl
}
if !user.GetID().IsNamespace(authn.NamespaceUser) {
if !user.GetID().IsType(identity.TypeUser) {
return redirect, nil
}
@@ -327,7 +328,7 @@ Default:
return redirect, nil
}
func (s *Service) ResolveIdentity(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) {
func (s *Service) ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) {
ctx, span := s.tracer.Start(ctx, "authn.ResolveIdentity")
defer span.End()
@@ -352,7 +353,7 @@ func (s *Service) RegisterClient(c authn.Client) {
}
if rc, ok := c.(authn.IdentityResolverClient); ok {
s.idenityResolverClients[rc.Namespace()] = rc
s.idenityResolverClients[rc.IdentityType().String()] = rc
}
}
@@ -375,11 +376,11 @@ func (s *Service) SyncIdentity(ctx context.Context, identity *authn.Identity) er
return s.runPostAuthHooks(ctx, identity, r)
}
func (s *Service) resolveIdenity(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) {
func (s *Service) resolveIdenity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) {
ctx, span := s.tracer.Start(ctx, "authn.resolveIdentity")
defer span.End()
if namespaceID.IsNamespace(authn.NamespaceUser) {
if namespaceID.IsType(identity.TypeUser) {
return &authn.Identity{
OrgID: orgID,
ID: namespaceID,
@@ -390,7 +391,7 @@ func (s *Service) resolveIdenity(ctx context.Context, orgID int64, namespaceID a
}}, nil
}
if namespaceID.IsNamespace(authn.NamespaceServiceAccount) {
if namespaceID.IsType(identity.TypeServiceAccount) {
return &authn.Identity{
ID: namespaceID,
OrgID: orgID,
@@ -401,9 +402,9 @@ func (s *Service) resolveIdenity(ctx context.Context, orgID int64, namespaceID a
}}, nil
}
resolver, ok := s.idenityResolverClients[namespaceID.Namespace().String()]
resolver, ok := s.idenityResolverClients[string(namespaceID.Type())]
if !ok {
return nil, authn.ErrUnsupportedIdentity.Errorf("no resolver for : %s", namespaceID.Namespace())
return nil, authn.ErrUnsupportedIdentity.Errorf("no resolver for : %s", namespaceID.Type())
}
return resolver.ResolveIdentity(ctx, orgID, namespaceID)
}
+24 -24
View File
@@ -44,9 +44,9 @@ func TestService_Authenticate(t *testing.T) {
{
desc: "should succeed with authentication for configured client",
clients: []authn.Client{
&authntest.FakeClient{ExpectedTest: true, ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}},
&authntest.FakeClient{ExpectedTest: true, ExpectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:1")}},
},
expectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")},
expectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
},
{
desc: "should succeed with authentication for configured client for identity with fetch permissions params",
@@ -54,7 +54,7 @@ func TestService_Authenticate(t *testing.T) {
&authntest.FakeClient{
ExpectedTest: true,
ExpectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("user:2"),
ID: identity.MustParseTypedID("user:2"),
ClientParams: authn.ClientParams{
FetchPermissionsParams: authn.FetchPermissionsParams{
ActionsLookup: []string{
@@ -70,7 +70,7 @@ func TestService_Authenticate(t *testing.T) {
},
},
expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("user:2"),
ID: identity.MustParseTypedID("user:2"),
ClientParams: authn.ClientParams{
FetchPermissionsParams: authn.FetchPermissionsParams{
ActionsLookup: []string{
@@ -92,19 +92,19 @@ func TestService_Authenticate(t *testing.T) {
ExpectedName: "2",
ExpectedPriority: 2,
ExpectedTest: true,
ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2"), AuthID: "service:some-service", AuthenticatedBy: "service_auth"},
ExpectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:2"), AuthID: "service:some-service", AuthenticatedBy: "service_auth"},
},
},
expectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2"), AuthID: "service:some-service", AuthenticatedBy: "service_auth"},
expectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:2"), AuthID: "service:some-service", AuthenticatedBy: "service_auth"},
},
{
desc: "should succeed with authentication for third client when error happened in first",
clients: []authn.Client{
&authntest.FakeClient{ExpectedName: "1", ExpectedPriority: 2, ExpectedTest: false},
&authntest.FakeClient{ExpectedName: "2", ExpectedPriority: 1, ExpectedTest: true, ExpectedErr: errors.New("some error")},
&authntest.FakeClient{ExpectedName: "3", ExpectedPriority: 3, ExpectedTest: true, ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:3")}},
&authntest.FakeClient{ExpectedName: "3", ExpectedPriority: 3, ExpectedTest: true, ExpectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:3")}},
},
expectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:3")},
expectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:3")},
},
{
desc: "should return error when no client could authenticate the request",
@@ -315,10 +315,10 @@ func TestService_Login(t *testing.T) {
client: "fake",
expectedClientOK: true,
expectedClientIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
},
expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
SessionToken: &auth.UserToken{UserId: 1},
},
},
@@ -331,7 +331,7 @@ func TestService_Login(t *testing.T) {
desc: "should not login non user identity",
client: "fake",
expectedClientOK: true,
expectedClientIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("api-key:1")},
expectedClientIdentity: &authn.Identity{ID: identity.MustParseTypedID("api-key:1")},
expectedErr: authn.ErrUnsupportedIdentity,
},
}
@@ -420,31 +420,31 @@ func TestService_Logout(t *testing.T) {
tests := []TestCase{
{
desc: "should redirect to default redirect url when identity is not a user",
identity: &authn.Identity{ID: authn.NewNamespaceID(authn.NamespaceServiceAccount, 1)},
identity: &authn.Identity{ID: identity.NewTypedID(identity.TypeServiceAccount, 1)},
expectedRedirect: &authn.Redirect{URL: "http://localhost:3000/login"},
},
{
desc: "should redirect to default redirect url when no external provider was used to authenticate",
identity: &authn.Identity{ID: authn.NewNamespaceID(authn.NamespaceUser, 1)},
identity: &authn.Identity{ID: identity.NewTypedID(identity.TypeUser, 1)},
expectedRedirect: &authn.Redirect{URL: "http://localhost:3000/login"},
expectedTokenRevoked: true,
},
{
desc: "should redirect to default redirect url when client is not found",
identity: &authn.Identity{ID: authn.NewNamespaceID(authn.NamespaceUser, 1), AuthenticatedBy: "notfound"},
identity: &authn.Identity{ID: identity.NewTypedID(identity.TypeUser, 1), AuthenticatedBy: "notfound"},
expectedRedirect: &authn.Redirect{URL: "http://localhost:3000/login"},
expectedTokenRevoked: true,
},
{
desc: "should redirect to default redirect url when client do not implement logout extension",
identity: &authn.Identity{ID: authn.NewNamespaceID(authn.NamespaceUser, 1), AuthenticatedBy: "azuread"},
identity: &authn.Identity{ID: identity.NewTypedID(identity.TypeUser, 1), AuthenticatedBy: "azuread"},
expectedRedirect: &authn.Redirect{URL: "http://localhost:3000/login"},
client: &authntest.FakeClient{ExpectedName: "auth.client.azuread"},
expectedTokenRevoked: true,
},
{
desc: "should use signout redirect url if configured",
identity: &authn.Identity{ID: authn.NewNamespaceID(authn.NamespaceUser, 1), AuthenticatedBy: "azuread"},
identity: &authn.Identity{ID: identity.NewTypedID(identity.TypeUser, 1), AuthenticatedBy: "azuread"},
expectedRedirect: &authn.Redirect{URL: "some-url"},
client: &authntest.FakeClient{ExpectedName: "auth.client.azuread"},
signoutRedirectURL: "some-url",
@@ -452,7 +452,7 @@ func TestService_Logout(t *testing.T) {
},
{
desc: "should redirect to client specific url",
identity: &authn.Identity{ID: authn.NewNamespaceID(authn.NamespaceUser, 1), AuthenticatedBy: "azuread"},
identity: &authn.Identity{ID: identity.NewTypedID(identity.TypeUser, 1), AuthenticatedBy: "azuread"},
expectedRedirect: &authn.Redirect{URL: "http://idp.com/logout"},
client: &authntest.MockClient{
NameFunc: func() string { return "auth.client.azuread" },
@@ -500,26 +500,26 @@ func TestService_Logout(t *testing.T) {
func TestService_ResolveIdentity(t *testing.T) {
t.Run("should return error for for unknown namespace", func(t *testing.T) {
svc := setupTests(t)
_, err := svc.ResolveIdentity(context.Background(), 1, authn.NewNamespaceID("some", 1))
_, err := svc.ResolveIdentity(context.Background(), 1, identity.NewTypedID("some", 1))
assert.ErrorIs(t, err, authn.ErrUnsupportedIdentity)
})
t.Run("should return error for for namespace that don't have a resolver", func(t *testing.T) {
svc := setupTests(t)
_, err := svc.ResolveIdentity(context.Background(), 1, authn.MustParseNamespaceID("api-key:1"))
_, err := svc.ResolveIdentity(context.Background(), 1, identity.MustParseTypedID("api-key:1"))
assert.ErrorIs(t, err, authn.ErrUnsupportedIdentity)
})
t.Run("should resolve for user", func(t *testing.T) {
svc := setupTests(t)
identity, err := svc.ResolveIdentity(context.Background(), 1, authn.MustParseNamespaceID("user:1"))
identity, err := svc.ResolveIdentity(context.Background(), 1, identity.MustParseTypedID("user:1"))
assert.NoError(t, err)
assert.NotNil(t, identity)
})
t.Run("should resolve for service account", func(t *testing.T) {
svc := setupTests(t)
identity, err := svc.ResolveIdentity(context.Background(), 1, authn.MustParseNamespaceID("service-account:1"))
identity, err := svc.ResolveIdentity(context.Background(), 1, identity.MustParseTypedID("service-account:1"))
assert.NoError(t, err)
assert.NotNil(t, identity)
})
@@ -527,14 +527,14 @@ func TestService_ResolveIdentity(t *testing.T) {
t.Run("should resolve for valid namespace if client is registered", func(t *testing.T) {
svc := setupTests(t, func(svc *Service) {
svc.RegisterClient(&authntest.MockClient{
NamespaceFunc: func() string { return authn.NamespaceAPIKey.String() },
ResolveIdentityFunc: func(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) {
IdentityTypeFunc: func() identity.IdentityType { return identity.TypeAPIKey },
ResolveIdentityFunc: func(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) {
return &authn.Identity{}, nil
},
})
})
identity, err := svc.ResolveIdentity(context.Background(), 1, authn.MustParseNamespaceID("api-key:1"))
identity, err := svc.ResolveIdentity(context.Background(), 1, identity.MustParseTypedID("api-key:1"))
assert.NoError(t, err)
assert.NotNil(t, identity)
})
@@ -8,6 +8,7 @@ import (
"golang.org/x/sync/singleflight"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/login/social"
@@ -36,42 +37,42 @@ type OAuthTokenSync struct {
tracer tracing.Tracer
}
func (s *OAuthTokenSync) SyncOauthTokenHook(ctx context.Context, identity *authn.Identity, _ *authn.Request) error {
func (s *OAuthTokenSync) SyncOauthTokenHook(ctx context.Context, id *authn.Identity, _ *authn.Request) error {
ctx, span := s.tracer.Start(ctx, "oauth.sync.SyncOauthTokenHook")
defer span.End()
// only perform oauth token check if identity is a user
if !identity.ID.IsNamespace(authn.NamespaceUser) {
if !id.ID.IsType(identity.TypeUser) {
return nil
}
// Not authenticated through session tokens, so we can skip this hook.
if identity.SessionToken == nil {
if id.SessionToken == nil {
return nil
}
// Not authenticated with a oauth provider, so we can skip this hook.
if !strings.HasPrefix(identity.GetAuthenticatedBy(), "oauth") {
if !strings.HasPrefix(id.GetAuthenticatedBy(), "oauth") {
return nil
}
ctxLogger := s.log.FromContext(ctx).New("userID", identity.ID.ID())
ctxLogger := s.log.FromContext(ctx).New("userID", id.ID.ID())
_, err, _ := s.singleflightGroup.Do(identity.ID.String(), func() (interface{}, error) {
_, err, _ := s.singleflightGroup.Do(id.ID.String(), func() (interface{}, error) {
ctxLogger.Debug("Singleflight request for OAuth token sync")
// FIXME: Consider using context.WithoutCancel instead of context.Background after Go 1.21 update
updateCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if refreshErr := s.service.TryTokenRefresh(updateCtx, identity); refreshErr != nil {
if refreshErr := s.service.TryTokenRefresh(updateCtx, id); refreshErr != nil {
if errors.Is(refreshErr, context.Canceled) {
return nil, nil
}
token, _, err := s.service.HasOAuthEntry(ctx, identity)
token, _, err := s.service.HasOAuthEntry(ctx, id)
if err != nil {
ctxLogger.Error("Failed to get OAuth entry for verifying if token has already been refreshed", "id", identity.ID, "error", err)
ctxLogger.Error("Failed to get OAuth entry for verifying if token has already been refreshed", "id", id.ID, "error", err)
return nil, err
}
@@ -81,14 +82,14 @@ func (s *OAuthTokenSync) SyncOauthTokenHook(ctx context.Context, identity *authn
return nil, nil
}
ctxLogger.Error("Failed to refresh OAuth access token", "id", identity.ID, "error", refreshErr)
ctxLogger.Error("Failed to refresh OAuth access token", "id", id.ID, "error", refreshErr)
if err := s.service.InvalidateOAuthTokens(ctx, token); err != nil {
ctxLogger.Warn("Failed to invalidate OAuth tokens", "id", identity.ID, "error", err)
ctxLogger.Warn("Failed to invalidate OAuth tokens", "id", id.ID, "error", err)
}
if err := s.sessionService.RevokeToken(ctx, identity.SessionToken, false); err != nil {
ctxLogger.Warn("Failed to revoke session token", "id", identity.ID, "tokenId", identity.SessionToken.Id, "error", err)
if err := s.sessionService.RevokeToken(ctx, id.SessionToken, false); err != nil {
ctxLogger.Warn("Failed to revoke session token", "id", id.ID, "tokenId", id.SessionToken.Id, "error", err)
}
return nil, refreshErr
@@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/assert"
"golang.org/x/sync/singleflight"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/login/social"
@@ -41,17 +42,17 @@ func TestOAuthTokenSync_SyncOAuthTokenHook(t *testing.T) {
tests := []testCase{
{
desc: "should skip sync when identity is not a user",
identity: &authn.Identity{ID: authn.MustParseNamespaceID("service-account:1")},
identity: &authn.Identity{ID: identity.MustParseTypedID("service-account:1")},
expectTryRefreshTokenCalled: false,
},
{
desc: "should skip sync when identity is a user but is not authenticated with session token",
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")},
identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
expectTryRefreshTokenCalled: false,
},
{
desc: "should invalidate access token and session token if token refresh fails",
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1"), SessionToken: &auth.UserToken{}, AuthenticatedBy: login.AzureADAuthModule},
identity: &authn.Identity{ID: identity.MustParseTypedID("user:1"), SessionToken: &auth.UserToken{}, AuthenticatedBy: login.AzureADAuthModule},
expectHasEntryCalled: true,
expectedTryRefreshErr: errors.New("some err"),
expectTryRefreshTokenCalled: true,
@@ -62,7 +63,7 @@ func TestOAuthTokenSync_SyncOAuthTokenHook(t *testing.T) {
},
{
desc: "should refresh the token successfully",
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1"), SessionToken: &auth.UserToken{}, AuthenticatedBy: login.AzureADAuthModule},
identity: &authn.Identity{ID: identity.MustParseTypedID("user:1"), SessionToken: &auth.UserToken{}, AuthenticatedBy: login.AzureADAuthModule},
expectHasEntryCalled: false,
expectTryRefreshTokenCalled: true,
expectInvalidateOauthTokensCalled: false,
@@ -70,7 +71,7 @@ func TestOAuthTokenSync_SyncOAuthTokenHook(t *testing.T) {
},
{
desc: "should not invalidate the token if the token has already been refreshed by another request (singleflight)",
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1"), SessionToken: &auth.UserToken{}, AuthenticatedBy: login.AzureADAuthModule},
identity: &authn.Identity{ID: identity.MustParseTypedID("user:1"), SessionToken: &auth.UserToken{}, AuthenticatedBy: login.AzureADAuthModule},
expectHasEntryCalled: true,
expectTryRefreshTokenCalled: true,
expectInvalidateOauthTokensCalled: false,
@@ -92,7 +93,7 @@ func TestOAuthTokenSync_SyncOAuthTokenHook(t *testing.T) {
)
service := &oauthtokentest.MockOauthTokenService{
HasOAuthEntryFunc: func(ctx context.Context, usr authn.Requester) (*login.UserAuth, bool, error) {
HasOAuthEntryFunc: func(ctx context.Context, usr identity.Requester) (*login.UserAuth, bool, error) {
hasEntryCalled = true
return tt.expectedHasEntryToken, tt.expectedHasEntryToken != nil, nil
},
@@ -100,7 +101,7 @@ func TestOAuthTokenSync_SyncOAuthTokenHook(t *testing.T) {
invalidateTokensCalled = true
return nil
},
TryTokenRefreshFunc: func(ctx context.Context, usr authn.Requester) error {
TryTokenRefreshFunc: func(ctx context.Context, usr identity.Requester) error {
tryRefreshCalled = true
return tt.expectedTryRefreshErr
},
@@ -6,6 +6,7 @@ import (
"fmt"
"sort"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol"
@@ -38,14 +39,14 @@ func (s *OrgSync) SyncOrgRolesHook(ctx context.Context, id *authn.Identity, _ *a
ctxLogger := s.log.FromContext(ctx).New("id", id.ID, "login", id.Login)
if !id.ID.IsNamespace(authn.NamespaceUser) {
ctxLogger.Warn("Failed to sync org role, invalid namespace for identity", "namespace", id.ID.Namespace())
if !id.ID.IsType(identity.TypeUser) {
ctxLogger.Warn("Failed to sync org role, invalid namespace for identity", "type", id.ID.Type())
return nil
}
userID, err := id.ID.ParseInt()
if err != nil {
ctxLogger.Warn("Failed to sync org role, invalid ID for identity", "namespace", id.ID.Namespace(), "err", err)
ctxLogger.Warn("Failed to sync org role, invalid ID for identity", "type", id.ID.Type(), "err", err)
return nil
}
@@ -144,14 +145,14 @@ func (s *OrgSync) SetDefaultOrgHook(ctx context.Context, currentIdentity *authn.
ctxLogger := s.log.FromContext(ctx)
if !currentIdentity.ID.IsNamespace(authn.NamespaceUser) {
ctxLogger.Debug("Skipping default org sync, not a user", "namespace", currentIdentity.ID.Namespace())
if !currentIdentity.ID.IsType(identity.TypeUser) {
ctxLogger.Debug("Skipping default org sync, not a user", "type", currentIdentity.ID.Type())
return
}
userID, err := currentIdentity.ID.ParseInt()
if err != nil {
ctxLogger.Debug("Skipping default org sync, invalid ID for identity", "id", currentIdentity.ID, "namespace", currentIdentity.ID.Namespace(), "err", err)
ctxLogger.Debug("Skipping default org sync, invalid ID for identity", "id", currentIdentity.ID, "type", currentIdentity.ID.Type(), "err", err)
return
}
@@ -76,7 +76,7 @@ func TestOrgSync_SyncOrgRolesHook(t *testing.T) {
args: args{
ctx: context.Background(),
id: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
Login: "test",
Name: "test",
Email: "test",
@@ -92,7 +92,7 @@ func TestOrgSync_SyncOrgRolesHook(t *testing.T) {
},
},
wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
Login: "test",
Name: "test",
Email: "test",
@@ -139,7 +139,7 @@ func TestOrgSync_SetDefaultOrgHook(t *testing.T) {
{
name: "should set default org",
defaultOrgSetting: 2,
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")},
identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
setupMock: func(userService *usertest.MockService, orgService *orgtest.FakeOrgService) {
userService.On("Update", mock.Anything, mock.MatchedBy(func(cmd *user.UpdateUserCommand) bool {
return cmd.UserID == 1 && *cmd.OrgID == 2
@@ -149,7 +149,7 @@ func TestOrgSync_SetDefaultOrgHook(t *testing.T) {
{
name: "should skip setting the default org when default org is not set",
defaultOrgSetting: -1,
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")},
identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
},
{
name: "should skip setting the default org when identity is nil",
@@ -159,28 +159,28 @@ func TestOrgSync_SetDefaultOrgHook(t *testing.T) {
{
name: "should skip setting the default org when input err is not nil",
defaultOrgSetting: 2,
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")},
identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
inputErr: fmt.Errorf("error"),
},
{
name: "should skip setting the default org when identity is not a user",
defaultOrgSetting: 2,
identity: &authn.Identity{ID: authn.MustParseNamespaceID("service-account:1")},
identity: &authn.Identity{ID: identity.MustParseTypedID("service-account:1")},
},
{
name: "should skip setting the default org when user id is not valid",
defaultOrgSetting: 2,
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:invalid")},
identity: &authn.Identity{ID: identity.MustParseTypedID("user:invalid")},
},
{
name: "should skip setting the default org when user is not allowed to use the configured default org",
defaultOrgSetting: 3,
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")},
identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
},
{
name: "should skip setting the default org when validateUsingOrg returns error",
defaultOrgSetting: 2,
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")},
identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
setupMock: func(userService *usertest.MockService, orgService *orgtest.FakeOrgService) {
orgService.ExpectedError = fmt.Errorf("error")
},
@@ -188,7 +188,7 @@ func TestOrgSync_SetDefaultOrgHook(t *testing.T) {
{
name: "should skip the hook when the user org update was unsuccessful",
defaultOrgSetting: 2,
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")},
identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
setupMock: func(userService *usertest.MockService, orgService *orgtest.FakeOrgService) {
userService.On("Update", mock.Anything, mock.Anything).Return(fmt.Errorf("error"))
},
@@ -5,6 +5,7 @@ import (
"errors"
"github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol"
@@ -146,7 +147,7 @@ func (s *RBACSync) SyncCloudRoles(ctx context.Context, ident *authn.Identity, r
return nil
}
if !ident.ID.IsNamespace(authn.NamespaceUser) {
if !ident.ID.IsType(identity.TypeUser) {
s.log.FromContext(ctx).Debug("Skip syncing cloud role", "id", ident.ID)
return nil
}
@@ -4,6 +4,10 @@ import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol"
@@ -11,8 +15,6 @@ import (
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/org"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRBACSync_SyncPermission(t *testing.T) {
@@ -24,14 +26,14 @@ func TestRBACSync_SyncPermission(t *testing.T) {
testCases := []testCase{
{
name: "enriches the identity successfully when SyncPermissions is true",
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2"), OrgID: 1, ClientParams: authn.ClientParams{SyncPermissions: true}},
identity: &authn.Identity{ID: identity.MustParseTypedID("user:2"), OrgID: 1, ClientParams: authn.ClientParams{SyncPermissions: true}},
expectedPermissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionUsersRead},
},
},
{
name: "does not load the permissions when SyncPermissions is false",
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2"), OrgID: 1, ClientParams: authn.ClientParams{SyncPermissions: true}},
identity: &authn.Identity{ID: identity.MustParseTypedID("user:2"), OrgID: 1, ClientParams: authn.ClientParams{SyncPermissions: true}},
expectedPermissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionUsersRead},
},
@@ -65,7 +67,7 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) {
desc: "should call sync when authenticated with grafana com and has viewer role",
module: login.GrafanaComAuthModule,
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1),
ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleViewer},
},
@@ -76,7 +78,7 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) {
desc: "should call sync when authenticated with grafana com and has editor role",
module: login.GrafanaComAuthModule,
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1),
ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleEditor},
},
@@ -87,7 +89,7 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) {
desc: "should call sync when authenticated with grafana com and has admin role",
module: login.GrafanaComAuthModule,
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1),
ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin},
},
@@ -98,7 +100,7 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) {
desc: "should not call sync when authenticated with grafana com and has invalid role",
module: login.GrafanaComAuthModule,
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1),
ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleType("something else")},
},
@@ -109,7 +111,7 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) {
desc: "should not call sync when not authenticated with grafana com",
module: login.LDAPAuthModule,
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1),
ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin},
},
@@ -155,7 +157,7 @@ func TestRBACSync_cloudRolesToAddAndRemove(t *testing.T) {
{
desc: "should map Cloud Viewer to Grafana Cloud Viewer and Support ticket reader",
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1),
ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleViewer},
},
@@ -174,7 +176,7 @@ func TestRBACSync_cloudRolesToAddAndRemove(t *testing.T) {
{
desc: "should map Cloud Editor to Grafana Cloud Editor and Support ticket admin",
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1),
ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleEditor},
},
@@ -193,7 +195,7 @@ func TestRBACSync_cloudRolesToAddAndRemove(t *testing.T) {
{
desc: "should map Cloud Admin to Grafana Cloud Admin and Support ticket admin",
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1),
ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin},
},
@@ -212,7 +214,7 @@ func TestRBACSync_cloudRolesToAddAndRemove(t *testing.T) {
{
desc: "should return an error for not supported role",
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1),
ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleNone},
},
@@ -234,7 +236,7 @@ func TestRBACSync_cloudRolesToAddAndRemove(t *testing.T) {
func setupTestEnv() *RBACSync {
acMock := &acmock.Mock{
GetUserPermissionsFunc: func(ctx context.Context, siu authn.Requester, o accesscontrol.Options) ([]accesscontrol.Permission, error) {
GetUserPermissionsFunc: func(ctx context.Context, siu identity.Requester, o accesscontrol.Options) ([]accesscontrol.Permission, error) {
return []accesscontrol.Permission{
{Action: accesscontrol.ActionUsersRead},
}, nil
+37 -36
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/authn"
@@ -109,21 +110,21 @@ func (s *UserSync) SyncUserHook(ctx context.Context, id *authn.Identity, _ *auth
return nil
}
func (s *UserSync) FetchSyncedUserHook(ctx context.Context, identity *authn.Identity, r *authn.Request) error {
func (s *UserSync) FetchSyncedUserHook(ctx context.Context, id *authn.Identity, r *authn.Request) error {
ctx, span := s.tracer.Start(ctx, "user.sync.FetchSyncedUserHook")
defer span.End()
if !identity.ClientParams.FetchSyncedUser {
if !id.ClientParams.FetchSyncedUser {
return nil
}
if !identity.ID.IsNamespace(authn.NamespaceUser, authn.NamespaceServiceAccount) {
if !id.ID.IsType(identity.TypeUser, identity.TypeServiceAccount) {
return nil
}
userID, err := identity.ID.ParseInt()
userID, err := id.ID.ParseInt()
if err != nil {
s.log.FromContext(ctx).Warn("got invalid identity ID", "id", identity.ID, "err", err)
s.log.FromContext(ctx).Warn("got invalid identity ID", "id", id.ID, "err", err)
return nil
}
@@ -138,18 +139,18 @@ func (s *UserSync) FetchSyncedUserHook(ctx context.Context, identity *authn.Iden
return errFetchingSignedInUser.Errorf("failed to resolve user: %w", err)
}
if identity.ClientParams.AllowGlobalOrg && identity.OrgID == authn.GlobalOrgID {
if id.ClientParams.AllowGlobalOrg && id.OrgID == authn.GlobalOrgID {
usr.Teams = nil
usr.OrgName = ""
usr.OrgRole = org.RoleNone
usr.OrgID = authn.GlobalOrgID
}
syncSignedInUserToIdentity(usr, identity)
syncSignedInUserToIdentity(usr, id)
return nil
}
func (s *UserSync) SyncLastSeenHook(ctx context.Context, identity *authn.Identity, r *authn.Request) error {
func (s *UserSync) SyncLastSeenHook(ctx context.Context, id *authn.Identity, r *authn.Request) error {
ctx, span := s.tracer.Start(ctx, "user.sync.SyncLastSeenHook")
defer span.End()
@@ -158,13 +159,13 @@ func (s *UserSync) SyncLastSeenHook(ctx context.Context, identity *authn.Identit
return nil
}
if !identity.ID.IsNamespace(authn.NamespaceUser, authn.NamespaceServiceAccount) {
if !id.ID.IsType(identity.TypeUser, identity.TypeServiceAccount) {
return nil
}
userID, err := identity.ID.ParseInt()
userID, err := id.ID.ParseInt()
if err != nil {
s.log.FromContext(ctx).Warn("got invalid identity ID", "id", identity.ID, "err", err)
s.log.FromContext(ctx).Warn("got invalid identity ID", "id", id.ID, "err", err)
return nil
}
@@ -186,21 +187,21 @@ func (s *UserSync) SyncLastSeenHook(ctx context.Context, identity *authn.Identit
return nil
}
func (s *UserSync) EnableUserHook(ctx context.Context, identity *authn.Identity, _ *authn.Request) error {
func (s *UserSync) EnableUserHook(ctx context.Context, id *authn.Identity, _ *authn.Request) error {
ctx, span := s.tracer.Start(ctx, "user.sync.EnableUserHook")
defer span.End()
if !identity.ClientParams.EnableUser {
if !id.ClientParams.EnableUser {
return nil
}
if !identity.ID.IsNamespace(authn.NamespaceUser) {
if !id.ID.IsType(identity.TypeUser) {
return nil
}
userID, err := identity.ID.ParseInt()
userID, err := id.ID.ParseInt()
if err != nil {
s.log.FromContext(ctx).Warn("got invalid identity ID", "id", identity.ID, "err", err)
s.log.FromContext(ctx).Warn("got invalid identity ID", "id", id.ID, "err", err)
return nil
}
@@ -417,8 +418,8 @@ func (s *UserSync) lookupByOneOf(ctx context.Context, params login.UserLookupPar
// syncUserToIdentity syncs a user to an identity.
// This is used to update the identity with the latest user information.
func syncUserToIdentity(usr *user.User, id *authn.Identity) {
id.ID = authn.NewNamespaceID(authn.NamespaceUser, usr.ID)
id.UID = authn.NewNamespaceIDString(authn.NamespaceUser, usr.UID)
id.ID = identity.NewTypedID(identity.TypeUser, usr.ID)
id.UID = identity.NewTypedIDString(identity.TypeUser, usr.UID)
id.Login = usr.Login
id.Email = usr.Email
id.Name = usr.Name
@@ -427,25 +428,25 @@ func syncUserToIdentity(usr *user.User, id *authn.Identity) {
}
// syncSignedInUserToIdentity syncs a user to an identity.
func syncSignedInUserToIdentity(usr *user.SignedInUser, identity *authn.Identity) {
var ns authn.Namespace
if identity.ID.IsNamespace(authn.NamespaceServiceAccount) {
ns = authn.NamespaceServiceAccount
func syncSignedInUserToIdentity(usr *user.SignedInUser, id *authn.Identity) {
var ns identity.IdentityType
if id.ID.IsType(identity.TypeServiceAccount) {
ns = identity.TypeServiceAccount
} else {
ns = authn.NamespaceUser
ns = identity.TypeUser
}
identity.UID = authn.NewNamespaceIDString(ns, usr.UserUID)
id.UID = identity.NewTypedIDString(ns, usr.UserUID)
identity.Name = usr.Name
identity.Login = usr.Login
identity.Email = usr.Email
identity.OrgID = usr.OrgID
identity.OrgName = usr.OrgName
identity.OrgRoles = map[int64]org.RoleType{identity.OrgID: usr.OrgRole}
identity.HelpFlags1 = usr.HelpFlags1
identity.Teams = usr.Teams
identity.LastSeenAt = usr.LastSeenAt
identity.IsDisabled = usr.IsDisabled
identity.IsGrafanaAdmin = &usr.IsGrafanaAdmin
identity.EmailVerified = usr.EmailVerified
id.Name = usr.Name
id.Login = usr.Login
id.Email = usr.Email
id.OrgID = usr.OrgID
id.OrgName = usr.OrgName
id.OrgRoles = map[int64]org.RoleType{id.OrgID: usr.OrgRole}
id.HelpFlags1 = usr.HelpFlags1
id.Teams = usr.Teams
id.LastSeenAt = usr.LastSeenAt
id.IsDisabled = usr.IsDisabled
id.IsGrafanaAdmin = &usr.IsGrafanaAdmin
id.EmailVerified = usr.EmailVerified
}
@@ -7,6 +7,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/login"
@@ -163,8 +164,8 @@ func TestUserSync_SyncUserHook(t *testing.T) {
},
wantErr: false,
wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
UID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
UID: identity.MustParseTypedID("user:1"),
Login: "test",
Name: "test",
Email: "test",
@@ -202,8 +203,8 @@ func TestUserSync_SyncUserHook(t *testing.T) {
},
wantErr: false,
wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
UID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
UID: identity.MustParseTypedID("user:1"),
Login: "test",
Name: "test",
Email: "test",
@@ -243,8 +244,8 @@ func TestUserSync_SyncUserHook(t *testing.T) {
},
wantErr: false,
wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
UID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
UID: identity.MustParseTypedID("user:1"),
AuthID: "2032",
AuthenticatedBy: "oauth",
Login: "test",
@@ -315,8 +316,8 @@ func TestUserSync_SyncUserHook(t *testing.T) {
},
wantErr: false,
wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:2"),
UID: authn.MustParseNamespaceID("user:2"),
ID: identity.MustParseTypedID("user:2"),
UID: identity.MustParseTypedID("user:2"),
Login: "test_create",
Name: "test_create",
Email: "test_create",
@@ -361,8 +362,8 @@ func TestUserSync_SyncUserHook(t *testing.T) {
},
wantErr: false,
wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:3"),
UID: authn.MustParseNamespaceID("user:3"),
ID: identity.MustParseTypedID("user:3"),
UID: identity.MustParseTypedID("user:3"),
Login: "test_mod",
Name: "test_mod",
Email: "test_mod",
@@ -406,8 +407,8 @@ func TestUserSync_SyncUserHook(t *testing.T) {
},
wantErr: false,
wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:3"),
UID: authn.MustParseNamespaceID("user:3"),
ID: identity.MustParseTypedID("user:3"),
UID: identity.MustParseTypedID("user:3"),
Name: "test",
Login: "test",
Email: "test_mod@test.com",
@@ -457,7 +458,7 @@ func TestUserSync_FetchSyncedUserHook(t *testing.T) {
{
desc: "should skip hook when identity is not a user",
req: &authn.Request{},
identity: &authn.Identity{ID: authn.MustParseNamespaceID("api-key:1"), ClientParams: authn.ClientParams{FetchSyncedUser: true}},
identity: &authn.Identity{ID: identity.MustParseTypedID("api-key:1"), ClientParams: authn.ClientParams{FetchSyncedUser: true}},
},
}
@@ -483,7 +484,7 @@ func TestUserSync_EnableDisabledUserHook(t *testing.T) {
{
desc: "should skip if correct flag is not set",
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1),
ID: identity.NewTypedID(identity.TypeUser, 1),
IsDisabled: true,
ClientParams: authn.ClientParams{EnableUser: false},
},
@@ -492,7 +493,7 @@ func TestUserSync_EnableDisabledUserHook(t *testing.T) {
{
desc: "should skip if identity is not a user",
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceAPIKey, 1),
ID: identity.NewTypedID(identity.TypeAPIKey, 1),
IsDisabled: true,
ClientParams: authn.ClientParams{EnableUser: true},
},
@@ -501,7 +502,7 @@ func TestUserSync_EnableDisabledUserHook(t *testing.T) {
{
desc: "should enabled disabled user",
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1),
ID: identity.NewTypedID(identity.TypeUser, 1),
IsDisabled: true,
ClientParams: authn.ClientParams{EnableUser: true},
},
+1 -1
View File
@@ -78,7 +78,7 @@ func (f *FakeService) Logout(_ context.Context, _ identity.Requester, _ *usertok
panic("unimplemented")
}
func (f *FakeService) ResolveIdentity(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) {
func (f *FakeService) ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) {
if f.ExpectedIdentities != nil {
if f.CurrentIndex >= len(f.ExpectedIdentities) {
panic("ExpectedIdentities is empty")
+8 -8
View File
@@ -54,7 +54,7 @@ func (*MockService) Logout(_ context.Context, _ identity.Requester, _ *usertoken
panic("unimplemented")
}
func (m *MockService) ResolveIdentity(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) {
func (m *MockService) ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) {
panic("unimplemented")
}
@@ -77,8 +77,8 @@ type MockClient struct {
PriorityFunc func() uint
HookFunc func(ctx context.Context, identity *authn.Identity, r *authn.Request) error
LogoutFunc func(ctx context.Context, user identity.Requester) (*authn.Redirect, bool)
NamespaceFunc func() string
ResolveIdentityFunc func(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error)
IdentityTypeFunc func() identity.IdentityType
ResolveIdentityFunc func(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error)
}
func (m MockClient) Name() string {
@@ -127,15 +127,15 @@ func (m *MockClient) Logout(ctx context.Context, user identity.Requester) (*auth
return nil, false
}
func (m *MockClient) Namespace() string {
if m.NamespaceFunc != nil {
return m.NamespaceFunc()
func (m *MockClient) IdentityType() identity.IdentityType {
if m.IdentityTypeFunc != nil {
return m.IdentityTypeFunc()
}
return ""
return identity.TypeEmpty
}
// ResolveIdentity implements authn.IdentityResolverClient.
func (m *MockClient) ResolveIdentity(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) {
func (m *MockClient) ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) {
if m.ResolveIdentityFunc != nil {
return m.ResolveIdentityFunc(ctx, orgID, namespaceID)
}
+14 -13
View File
@@ -7,6 +7,7 @@ import (
"time"
"github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/components/apikeygen"
"github.com/grafana/grafana/pkg/components/satokengen"
"github.com/grafana/grafana/pkg/infra/log"
@@ -134,13 +135,13 @@ func (s *APIKey) Priority() uint {
return 30
}
func (s *APIKey) Namespace() string {
return authn.NamespaceAPIKey.String()
func (s *APIKey) IdentityType() identity.IdentityType {
return identity.TypeAPIKey
}
func (s *APIKey) ResolveIdentity(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) {
if !namespaceID.IsNamespace(authn.NamespaceAPIKey) {
return nil, authn.ErrInvalidNamespaceID.Errorf("got unspected namespace: %s", namespaceID.Namespace())
func (s *APIKey) ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) {
if !namespaceID.IsType(identity.TypeAPIKey) {
return nil, identity.ErrInvalidTypedID.Errorf("got unspected namespace: %s", namespaceID.Type())
}
apiKeyID, err := namespaceID.ParseInt()
@@ -160,7 +161,7 @@ func (s *APIKey) ResolveIdentity(ctx context.Context, orgID int64, namespaceID a
}
if key.ServiceAccountId != nil && *key.ServiceAccountId >= 1 {
return nil, authn.ErrInvalidNamespaceID.Errorf("api key belongs to service account")
return nil, identity.ErrInvalidTypedID.Errorf("api key belongs to service account")
}
return newAPIKeyIdentity(key), nil
@@ -187,18 +188,18 @@ func (s *APIKey) Hook(ctx context.Context, identity *authn.Identity, r *authn.Re
return nil
}
func (s *APIKey) getAPIKeyID(ctx context.Context, identity *authn.Identity, r *authn.Request) (apiKeyID int64, exists bool) {
id, err := identity.ID.ParseInt()
func (s *APIKey) getAPIKeyID(ctx context.Context, id *authn.Identity, r *authn.Request) (apiKeyID int64, exists bool) {
internalId, err := id.ID.ParseInt()
if err != nil {
s.log.Warn("Failed to parse ID from identifier", "err", err)
return -1, false
}
if identity.ID.IsNamespace(authn.NamespaceAPIKey) {
return id, true
if id.ID.IsType(identity.TypeAPIKey) {
return internalId, true
}
if identity.ID.IsNamespace(authn.NamespaceServiceAccount) {
if id.ID.IsType(identity.TypeServiceAccount) {
// When the identity is service account, the ID in from the namespace is the service account ID.
// We need to fetch the API key in this scenario, as we could use it to uniquely identify a service account token.
apiKey, err := s.getAPIKey(ctx, getTokenFromRequest(r))
@@ -255,7 +256,7 @@ func validateApiKey(orgID int64, key *apikey.APIKey) error {
func newAPIKeyIdentity(key *apikey.APIKey) *authn.Identity {
return &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceAPIKey, key.ID),
ID: identity.NewTypedID(identity.TypeAPIKey, key.ID),
OrgID: key.OrgID,
OrgRoles: map[int64]org.RoleType{key.OrgID: key.Role},
ClientParams: authn.ClientParams{SyncPermissions: true},
@@ -265,7 +266,7 @@ func newAPIKeyIdentity(key *apikey.APIKey) *authn.Identity {
func newServiceAccountIdentity(key *apikey.APIKey) *authn.Identity {
return &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceServiceAccount, *key.ServiceAccountId),
ID: identity.NewTypedID(identity.TypeServiceAccount, *key.ServiceAccountId),
OrgID: key.OrgID,
AuthenticatedBy: login.APIKeyAuthModule,
ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true},
+17 -16
View File
@@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/components/apikeygen"
"github.com/grafana/grafana/pkg/components/satokengen"
"github.com/grafana/grafana/pkg/services/apikey"
@@ -47,7 +48,7 @@ func TestAPIKey_Authenticate(t *testing.T) {
Role: org.RoleAdmin,
},
expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("api-key:1"),
ID: identity.MustParseTypedID("api-key:1"),
OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin},
ClientParams: authn.ClientParams{
@@ -70,7 +71,7 @@ func TestAPIKey_Authenticate(t *testing.T) {
ServiceAccountId: intPtr(1),
},
expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("service-account:1"),
ID: identity.MustParseTypedID("service-account:1"),
OrgID: 1,
ClientParams: authn.ClientParams{
FetchSyncedUser: true,
@@ -205,7 +206,7 @@ func TestAPIKey_GetAPIKeyIDFromIdentity(t *testing.T) {
ServiceAccountId: intPtr(1),
},
expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("service-account:1"),
ID: identity.MustParseTypedID("service-account:1"),
OrgID: 1,
Name: "test",
AuthenticatedBy: login.APIKeyAuthModule,
@@ -221,7 +222,7 @@ func TestAPIKey_GetAPIKeyIDFromIdentity(t *testing.T) {
Key: hash,
},
expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("api-key:2"),
ID: identity.MustParseTypedID("api-key:2"),
OrgID: 1,
Name: "test",
AuthenticatedBy: login.APIKeyAuthModule,
@@ -237,7 +238,7 @@ func TestAPIKey_GetAPIKeyIDFromIdentity(t *testing.T) {
Key: hash,
},
expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("user:2"),
ID: identity.MustParseTypedID("user:2"),
OrgID: 1,
Name: "test",
AuthenticatedBy: login.APIKeyAuthModule,
@@ -253,7 +254,7 @@ func TestAPIKey_GetAPIKeyIDFromIdentity(t *testing.T) {
Key: hash,
},
expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("service-account:2"),
ID: identity.MustParseTypedID("service-account:2"),
OrgID: 1,
Name: "test",
AuthenticatedBy: login.APIKeyAuthModule,
@@ -286,7 +287,7 @@ func TestAPIKey_GetAPIKeyIDFromIdentity(t *testing.T) {
func TestAPIKey_ResolveIdentity(t *testing.T) {
type testCase struct {
desc string
namespaceID authn.NamespaceID
namespaceID identity.TypedID
exptedApiKey *apikey.APIKey
@@ -297,12 +298,12 @@ func TestAPIKey_ResolveIdentity(t *testing.T) {
tests := []testCase{
{
desc: "should return error for invalid namespace",
namespaceID: authn.MustParseNamespaceID("user:1"),
expectedErr: authn.ErrInvalidNamespaceID,
namespaceID: identity.MustParseTypedID("user:1"),
expectedErr: identity.ErrInvalidTypedID,
},
{
desc: "should return error when api key has expired",
namespaceID: authn.MustParseNamespaceID("api-key:1"),
namespaceID: identity.MustParseTypedID("api-key:1"),
exptedApiKey: &apikey.APIKey{
ID: 1,
OrgID: 1,
@@ -312,7 +313,7 @@ func TestAPIKey_ResolveIdentity(t *testing.T) {
},
{
desc: "should return error when api key is revoked",
namespaceID: authn.MustParseNamespaceID("api-key:1"),
namespaceID: identity.MustParseTypedID("api-key:1"),
exptedApiKey: &apikey.APIKey{
ID: 1,
OrgID: 1,
@@ -322,17 +323,17 @@ func TestAPIKey_ResolveIdentity(t *testing.T) {
},
{
desc: "should return error when api key is connected to service account",
namespaceID: authn.MustParseNamespaceID("api-key:1"),
namespaceID: identity.MustParseTypedID("api-key:1"),
exptedApiKey: &apikey.APIKey{
ID: 1,
OrgID: 1,
ServiceAccountId: intPtr(1),
},
expectedErr: authn.ErrInvalidNamespaceID,
expectedErr: identity.ErrInvalidTypedID,
},
{
desc: "should return error when api key is belongs to different org",
namespaceID: authn.MustParseNamespaceID("api-key:1"),
namespaceID: identity.MustParseTypedID("api-key:1"),
exptedApiKey: &apikey.APIKey{
ID: 1,
OrgID: 2,
@@ -342,7 +343,7 @@ func TestAPIKey_ResolveIdentity(t *testing.T) {
},
{
desc: "should return valid idenitty",
namespaceID: authn.MustParseNamespaceID("api-key:1"),
namespaceID: identity.MustParseTypedID("api-key:1"),
exptedApiKey: &apikey.APIKey{
ID: 1,
OrgID: 1,
@@ -351,7 +352,7 @@ func TestAPIKey_ResolveIdentity(t *testing.T) {
expectedIdenity: &authn.Identity{
OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleEditor},
ID: authn.MustParseNamespaceID("api-key:1"),
ID: identity.MustParseTypedID("api-key:1"),
AuthenticatedBy: login.APIKeyAuthModule,
ClientParams: authn.ClientParams{SyncPermissions: true},
},
+3 -2
View File
@@ -7,6 +7,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/authn/authntest"
)
@@ -24,8 +25,8 @@ func TestBasic_Authenticate(t *testing.T) {
{
desc: "should success when password client return identity",
req: &authn.Request{HTTPRequest: &http.Request{Header: map[string][]string{authorizationHeaderName: {encodeBasicAuth("user", "password")}}}},
client: authntest.FakePasswordClient{ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}},
expectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")},
client: authntest.FakePasswordClient{ExpectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:1")}},
expectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
},
{
desc: "should fail when basic auth header could not be decoded",
+7 -6
View File
@@ -10,6 +10,7 @@ import (
authlib "github.com/grafana/authlib/authn"
"github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/authn"
@@ -108,21 +109,21 @@ func (s *ExtendedJWT) authenticateAsUser(
return nil, errExtJWTMisMatchedNamespaceClaims.Errorf("unexpected access token namespace: %s", accessTokenClaims.Rest.Namespace)
}
accessID, err := authn.ParseNamespaceID(accessTokenClaims.Subject)
accessID, err := identity.ParseTypedID(accessTokenClaims.Subject)
if err != nil {
return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", accessID.String())
}
if !accessID.IsNamespace(authn.NamespaceAccessPolicy) {
if !accessID.IsType(identity.TypeAccessPolicy) {
return nil, errExtJWTInvalid.Errorf("unexpected identity: %s", accessID.String())
}
userID, err := authn.ParseNamespaceID(idTokenClaims.Subject)
userID, err := identity.ParseTypedID(idTokenClaims.Subject)
if err != nil {
return nil, errExtJWTInvalid.Errorf("failed to parse id token subject: %w", err)
}
if !userID.IsNamespace(authn.NamespaceUser) {
if !userID.IsType(identity.TypeUser) {
return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", userID.String())
}
@@ -154,12 +155,12 @@ func (s *ExtendedJWT) authenticateAsService(claims *authlib.Claims[authlib.Acces
return nil, errExtJWTDisallowedNamespaceClaim.Errorf("unexpected access token namespace: %s", claims.Rest.Namespace)
}
id, err := authn.ParseNamespaceID(claims.Subject)
id, err := identity.ParseTypedID(claims.Subject)
if err != nil {
return nil, fmt.Errorf("failed to parse access token subject: %w", err)
}
if !id.IsNamespace(authn.NamespaceAccessPolicy) {
if !id.IsType(identity.TypeAccessPolicy) {
return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", id.String())
}
+8 -7
View File
@@ -17,6 +17,7 @@ import (
authnlib "github.com/grafana/authlib/authn"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/setting"
)
@@ -207,8 +208,8 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
accessToken: &validAccessTokenClaims,
orgID: 1,
want: &authn.Identity{
ID: authn.MustParseNamespaceID("access-policy:this-uid"),
UID: authn.MustParseNamespaceID("access-policy:this-uid"),
ID: identity.MustParseTypedID("access-policy:this-uid"),
UID: identity.MustParseTypedID("access-policy:this-uid"),
OrgID: 1,
AllowedKubernetesNamespace: "default",
AuthenticatedBy: "extendedjwt",
@@ -223,8 +224,8 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
accessToken: &validAcessTokenClaimsWildcard,
orgID: 1,
want: &authn.Identity{
ID: authn.MustParseNamespaceID("access-policy:this-uid"),
UID: authn.MustParseNamespaceID("access-policy:this-uid"),
ID: identity.MustParseTypedID("access-policy:this-uid"),
UID: identity.MustParseTypedID("access-policy:this-uid"),
OrgID: 1,
AllowedKubernetesNamespace: "*",
AuthenticatedBy: "extendedjwt",
@@ -240,7 +241,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
idToken: &validIDTokenClaims,
orgID: 1,
want: &authn.Identity{
ID: authn.MustParseNamespaceID("user:2"),
ID: identity.MustParseTypedID("user:2"),
OrgID: 1,
AllowedKubernetesNamespace: "default",
AuthenticatedBy: "extendedjwt",
@@ -260,7 +261,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
idToken: &validIDTokenClaims,
orgID: 1,
want: &authn.Identity{
ID: authn.MustParseNamespaceID("user:2"),
ID: identity.MustParseTypedID("user:2"),
OrgID: 1,
AllowedKubernetesNamespace: "*",
AuthenticatedBy: "extendedjwt",
@@ -285,7 +286,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
},
},
want: &authn.Identity{
ID: authn.MustParseNamespaceID("user:2"),
ID: identity.MustParseTypedID("user:2"),
OrgID: 1,
AllowedKubernetesNamespace: "stack-1234",
AuthenticatedBy: "extendedjwt",
+2 -1
View File
@@ -6,6 +6,7 @@ import (
"errors"
"net/mail"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/org"
@@ -105,7 +106,7 @@ func (c *Grafana) AuthenticatePassword(ctx context.Context, r *authn.Request, us
}
return &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, usr.ID),
ID: identity.NewTypedID(identity.TypeUser, usr.ID),
OrgID: r.OrgID,
ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true},
AuthenticatedBy: login.PasswordAuthModule,
+2 -1
View File
@@ -7,6 +7,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/org"
@@ -140,7 +141,7 @@ func TestGrafana_AuthenticatePassword(t *testing.T) {
password: "password",
findUser: true,
expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
OrgID: 1,
AuthenticatedBy: login.PasswordAuthModule,
ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true},
+3 -3
View File
@@ -245,10 +245,10 @@ func (c *OAuth) RedirectURL(ctx context.Context, r *authn.Request) (*authn.Redir
}, nil
}
func (c *OAuth) Logout(ctx context.Context, user authn.Requester) (*authn.Redirect, bool) {
func (c *OAuth) Logout(ctx context.Context, user identity.Requester) (*authn.Redirect, bool) {
token := c.oauthService.GetCurrentOAuthToken(ctx, user)
namespace, id := user.GetNamespacedID()
namespace, id := user.GetTypedID()
userID, err := identity.UserIdentifier(namespace, id)
if err != nil {
c.log.FromContext(ctx).Error("Failed to parse user id", "namespace", namespace, "id", id, "error", err)
@@ -260,7 +260,7 @@ func (c *OAuth) Logout(ctx context.Context, user authn.Requester) (*authn.Redire
AuthId: user.GetAuthID(),
AuthModule: user.GetAuthenticatedBy(),
}); err != nil {
namespace, id := user.GetNamespacedID()
namespace, id := user.GetTypedID()
c.log.FromContext(ctx).Error("Failed to invalidate tokens", "namespace", namespace, "id", id, "error", err)
}
+5 -4
View File
@@ -6,6 +6,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/authn/authntest"
"github.com/grafana/grafana/pkg/services/loginattempt/loginattempttest"
@@ -29,16 +30,16 @@ func TestPassword_AuthenticatePassword(t *testing.T) {
username: "test",
password: "test",
req: &authn.Request{},
clients: []authn.PasswordClient{authntest.FakePasswordClient{ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}}},
expectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")},
clients: []authn.PasswordClient{authntest.FakePasswordClient{ExpectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:1")}}},
expectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
},
{
desc: "should success when found in second client",
username: "test",
password: "test",
req: &authn.Request{},
clients: []authn.PasswordClient{authntest.FakePasswordClient{ExpectedErr: errIdentityNotFound}, authntest.FakePasswordClient{ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2")}}},
expectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2")},
clients: []authn.PasswordClient{authntest.FakePasswordClient{ExpectedErr: errIdentityNotFound}, authntest.FakePasswordClient{ExpectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:2")}}},
expectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:2")},
},
{
desc: "should fail for empty password",
+12 -11
View File
@@ -13,6 +13,7 @@ import (
"time"
"github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/remotecache"
"github.com/grafana/grafana/pkg/services/authn"
@@ -124,7 +125,7 @@ func (c *Proxy) retrieveIDFromCache(ctx context.Context, cacheKey string, r *aut
}
return &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, uid),
ID: identity.NewTypedID(identity.TypeUser, uid),
OrgID: r.OrgID,
// FIXME: This does not match the actual auth module used, but should not have any impact
// Maybe caching the auth module used with the user ID would be a good idea
@@ -144,18 +145,18 @@ func (c *Proxy) Priority() uint {
return 50
}
func (c *Proxy) Hook(ctx context.Context, identity *authn.Identity, r *authn.Request) error {
if identity.ClientParams.CacheAuthProxyKey == "" {
func (c *Proxy) Hook(ctx context.Context, id *authn.Identity, r *authn.Request) error {
if id.ClientParams.CacheAuthProxyKey == "" {
return nil
}
if !identity.ID.IsNamespace(authn.NamespaceUser) {
if !id.ID.IsType(identity.TypeUser) {
return nil
}
id, err := identity.ID.ParseInt()
internalId, err := id.ID.ParseInt()
if err != nil {
c.log.Warn("Failed to cache proxy user", "error", err, "userId", identity.ID.ID(), "err", err)
c.log.Warn("Failed to cache proxy user", "error", err, "userId", id.ID.ID(), "err", err)
return nil
}
@@ -175,15 +176,15 @@ func (c *Proxy) Hook(ctx context.Context, identity *authn.Identity, r *authn.Req
}
}
c.log.FromContext(ctx).Debug("Cache proxy user", "userId", id)
bytes := []byte(strconv.FormatInt(id, 10))
c.log.FromContext(ctx).Debug("Cache proxy user", "userId", internalId)
bytes := []byte(strconv.FormatInt(internalId, 10))
duration := time.Duration(c.cfg.AuthProxy.SyncTTL) * time.Minute
if err := c.cache.Set(ctx, identity.ClientParams.CacheAuthProxyKey, bytes, duration); err != nil {
c.log.Warn("Failed to cache proxy user", "error", err, "userId", id)
if err := c.cache.Set(ctx, id.ClientParams.CacheAuthProxyKey, bytes, duration); err != nil {
c.log.Warn("Failed to cache proxy user", "error", err, "userId", internalId)
}
// store current cacheKey for the user
return c.cache.Set(ctx, userKey, []byte(identity.ClientParams.CacheAuthProxyKey), duration)
return c.cache.Set(ctx, userKey, []byte(id.ClientParams.CacheAuthProxyKey), duration)
}
func (c *Proxy) isAllowedIP(r *authn.Request) bool {
+2 -1
View File
@@ -11,6 +11,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/authn/authntest"
"github.com/grafana/grafana/pkg/setting"
@@ -203,7 +204,7 @@ func TestProxy_Hook(t *testing.T) {
}
cache := &fakeCache{data: make(map[string][]byte)}
userId := int64(1)
userID := authn.NewNamespaceID(authn.NamespaceUser, userId)
userID := identity.NewTypedID(identity.TypeUser, userId)
// withRole creates a test case for a user with a specific role.
withRole := func(role string) func(t *testing.T) {
+3 -2
View File
@@ -5,6 +5,7 @@ import (
"time"
"github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/org"
@@ -42,7 +43,7 @@ func (c *Render) Authenticate(ctx context.Context, r *authn.Request) (*authn.Ide
if renderUsr.UserID <= 0 {
return &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceRenderService, 0),
ID: identity.NewTypedID(identity.TypeRenderService, 0),
OrgID: renderUsr.OrgID,
OrgRoles: map[int64]org.RoleType{renderUsr.OrgID: org.RoleType(renderUsr.OrgRole)},
ClientParams: authn.ClientParams{SyncPermissions: true},
@@ -52,7 +53,7 @@ func (c *Render) Authenticate(ctx context.Context, r *authn.Request) (*authn.Ide
}
return &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, renderUsr.UserID),
ID: identity.NewTypedID(identity.TypeUser, renderUsr.UserID),
LastSeenAt: time.Now(),
AuthenticatedBy: login.RenderModule,
ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true},
+3 -2
View File
@@ -9,6 +9,7 @@ import (
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/org"
@@ -35,7 +36,7 @@ func TestRender_Authenticate(t *testing.T) {
},
},
expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("render:0"),
ID: identity.MustParseTypedID("render:0"),
OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleViewer},
AuthenticatedBy: login.RenderModule,
@@ -56,7 +57,7 @@ func TestRender_Authenticate(t *testing.T) {
},
},
expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
AuthenticatedBy: login.RenderModule,
ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true},
},
+2 -1
View File
@@ -6,6 +6,7 @@ import (
"net/url"
"time"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/authn"
@@ -57,7 +58,7 @@ func (s *Session) Authenticate(ctx context.Context, r *authn.Request) (*authn.Id
}
ident := &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, token.UserId),
ID: identity.NewTypedID(identity.TypeUser, token.UserId),
SessionToken: token,
ClientParams: authn.ClientParams{
FetchSyncedUser: true,
+5 -4
View File
@@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/models/usertoken"
"github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/auth/authtest"
@@ -96,7 +97,7 @@ func TestSession_Authenticate(t *testing.T) {
},
args: args{r: &authn.Request{HTTPRequest: validHTTPReq}},
wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
SessionToken: validToken,
ClientParams: authn.ClientParams{
SyncPermissions: true,
@@ -129,7 +130,7 @@ func TestSession_Authenticate(t *testing.T) {
},
args: args{r: &authn.Request{HTTPRequest: validHTTPReq}},
wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
SessionToken: validToken,
ClientParams: authn.ClientParams{
SyncPermissions: true,
@@ -148,7 +149,7 @@ func TestSession_Authenticate(t *testing.T) {
},
args: args{r: &authn.Request{HTTPRequest: validHTTPReq}},
wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
AuthID: "1",
AuthenticatedBy: "oauth_azuread",
SessionToken: validToken,
@@ -170,7 +171,7 @@ func TestSession_Authenticate(t *testing.T) {
},
args: args{r: &authn.Request{HTTPRequest: validHTTPReq}},
wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
SessionToken: validToken,
ClientParams: authn.ClientParams{
+15 -15
View File
@@ -15,16 +15,14 @@ import (
const GlobalOrgID = int64(0)
type Requester = identity.Requester
var _ Requester = (*Identity)(nil)
var _ identity.Requester = (*Identity)(nil)
type Identity struct {
// ID is the unique identifier for the entity in the Grafana database.
// If the entity is not found in the DB or this entity is non-persistent, this field will be empty.
ID NamespaceID
ID identity.TypedID
// UID is a unique identifier stored for the entity in Grafana database. Not all entities support uid so it can be empty.
UID NamespaceID
UID identity.TypedID
// OrgID is the active organization for the entity.
OrgID int64
// OrgName is the name of the active organization.
@@ -74,15 +72,15 @@ type Identity struct {
IDToken string
}
func (i *Identity) GetID() NamespaceID {
func (i *Identity) GetID() identity.TypedID {
return i.ID
}
func (i *Identity) GetNamespacedID() (namespace identity.Namespace, identifier string) {
return i.ID.Namespace(), i.ID.ID()
func (i *Identity) GetTypedID() (namespace identity.IdentityType, identifier string) {
return i.ID.Type(), i.ID.ID()
}
func (i *Identity) GetUID() NamespaceID {
func (i *Identity) GetUID() identity.TypedID {
return i.UID
}
@@ -95,7 +93,7 @@ func (i *Identity) GetAuthenticatedBy() string {
}
func (i *Identity) GetCacheKey() string {
namespace, id := i.GetNamespacedID()
namespace, id := i.GetTypedID()
if !i.HasUniqueId() {
// Hack use the org role as id for identities that do not have a unique id
// e.g. anonymous and render key.
@@ -191,8 +189,10 @@ func (i *Identity) HasRole(role org.RoleType) bool {
}
func (i *Identity) HasUniqueId() bool {
namespace, _ := i.GetNamespacedID()
return namespace == NamespaceUser || namespace == NamespaceServiceAccount || namespace == NamespaceAPIKey
namespace, _ := i.GetTypedID()
return namespace == identity.TypeUser ||
namespace == identity.TypeServiceAccount ||
namespace == identity.TypeAPIKey
}
func (i *Identity) IsAuthenticatedBy(providers ...string) bool {
@@ -220,7 +220,7 @@ func (i *Identity) SignedInUser() *user.SignedInUser {
AuthID: i.AuthID,
AuthenticatedBy: i.AuthenticatedBy,
IsGrafanaAdmin: i.GetIsGrafanaAdmin(),
IsAnonymous: i.ID.IsNamespace(NamespaceAnonymous),
IsAnonymous: i.ID.IsType(identity.TypeAnonymous),
IsDisabled: i.IsDisabled,
HelpFlags1: i.HelpFlags1,
LastSeenAt: i.LastSeenAt,
@@ -230,14 +230,14 @@ func (i *Identity) SignedInUser() *user.SignedInUser {
NamespacedID: i.ID,
}
if i.ID.IsNamespace(NamespaceAPIKey) {
if i.ID.IsType(identity.TypeAPIKey) {
id, _ := i.ID.ParseInt()
u.ApiKeyID = id
} else {
id, _ := i.ID.UserID()
u.UserID = id
u.UserUID = i.UID.ID()
u.IsServiceAccount = i.ID.IsNamespace(NamespaceServiceAccount)
u.IsServiceAccount = i.ID.IsType(identity.TypeServiceAccount)
}
return u
-27
View File
@@ -1,27 +0,0 @@
package authn
import (
"github.com/grafana/grafana/pkg/apimachinery/identity"
)
const (
NamespaceUser = identity.NamespaceUser
NamespaceAPIKey = identity.NamespaceAPIKey
NamespaceServiceAccount = identity.NamespaceServiceAccount
NamespaceAnonymous = identity.NamespaceAnonymous
NamespaceRenderService = identity.NamespaceRenderService
NamespaceAccessPolicy = identity.NamespaceAccessPolicy
)
var AnonymousNamespaceID = NewNamespaceID(NamespaceAnonymous, 0)
type Namespace = identity.Namespace
type NamespaceID = identity.NamespaceID
var (
ParseNamespaceID = identity.ParseNamespaceID
MustParseNamespaceID = identity.MustParseNamespaceID
NewNamespaceID = identity.NewNamespaceID
NewNamespaceIDString = identity.NewNamespaceIDString
ErrInvalidNamespaceID = identity.ErrInvalidNamespaceID
)
+11 -1
View File
@@ -5,6 +5,7 @@ import (
authzv1 "github.com/grafana/authlib/authz/proto/v1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol"
@@ -54,7 +55,16 @@ func (s *legacyServer) Read(ctx context.Context, req *authzv1.ReadRequest) (*aut
ctxLogger := s.logger.FromContext(ctx)
ctxLogger.Debug("Read", "action", action, "subject", subject, "stackID", stackID)
permissions, err := s.acSvc.SearchUserPermissions(ctx, stackID, accesscontrol.SearchOptions{NamespacedID: subject, Action: action})
var err error
opts := accesscontrol.SearchOptions{Action: action}
if subject != "" {
opts.TypedID, err = identity.ParseTypedID(subject)
if err != nil {
return nil, err
}
}
permissions, err := s.acSvc.SearchUserPermissions(ctx, stackID, opts)
if err != nil {
ctxLogger.Error("failed to search user permissions", "error", err)
return nil, tracing.Errorf(span, "failed to search user permissions: %w", err)
@@ -6,10 +6,11 @@ import (
"fmt"
"net/http"
authnClients "github.com/grafana/grafana/pkg/services/authn/clients"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
authnClients "github.com/grafana/grafana/pkg/services/authn/clients"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
@@ -153,12 +154,12 @@ func (h *ContextHandler) addIDHeaderEndOfRequestFunc(ident identity.Requester) w
return
}
namespace, id := ident.GetNamespacedID()
if !identity.IsNamespace(
namespace, id := ident.GetTypedID()
if !identity.IsIdentityType(
namespace,
identity.NamespaceUser,
identity.NamespaceServiceAccount,
identity.NamespaceAPIKey,
identity.TypeUser,
identity.TypeServiceAccount,
identity.TypeAPIKey,
) || id == "0" {
return
}
@@ -43,7 +43,7 @@ func TestContextHandler(t *testing.T) {
})
t.Run("should set identity on successful authentication", func(t *testing.T) {
id := &authn.Identity{ID: authn.NewNamespaceID(authn.NamespaceUser, 1), OrgID: 1}
id := &authn.Identity{ID: identity.NewTypedID(identity.TypeUser, 1), OrgID: 1}
handler := contexthandler.ProvideService(
setting.NewCfg(),
tracing.InitializeTracerForTest(),
@@ -68,7 +68,7 @@ func TestContextHandler(t *testing.T) {
})
t.Run("should not set IsSignedIn on anonymous identity", func(t *testing.T) {
identity := &authn.Identity{ID: authn.AnonymousNamespaceID, OrgID: 1}
identity := &authn.Identity{ID: identity.AnonymousTypedID, OrgID: 1}
handler := contexthandler.ProvideService(
setting.NewCfg(),
tracing.InitializeTracerForTest(),
@@ -148,7 +148,7 @@ func TestContextHandler(t *testing.T) {
handler := contexthandler.ProvideService(
cfg,
tracing.InitializeTracerForTest(),
&authntest.FakeService{ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID(id)}},
&authntest.FakeService{ExpectedIdentity: &authn.Identity{ID: identity.MustParseTypedID(id)}},
)
server := webtest.NewServer(t, routing.NewRouteRegister())
@@ -109,10 +109,10 @@ func (s *ImportDashboardService) ImportDashboard(ctx context.Context, req *dashb
req.FolderUid = folder.UID
}
namespace, identifier := req.User.GetNamespacedID()
namespace, identifier := req.User.GetTypedID()
userID := int64(0)
if namespace == identity.NamespaceUser || namespace == identity.NamespaceServiceAccount {
if namespace == identity.TypeUser || namespace == identity.TypeServiceAccount {
userID, _ = identity.IntIdentifier(namespace, identifier)
}
@@ -83,7 +83,7 @@ func TestImportDashboardService(t *testing.T) {
require.NotNil(t, resp)
require.Equal(t, "UDdpyzz7z", resp.UID)
userID, err := identity.IntIdentifier(importDashboardArg.User.GetNamespacedID())
userID, err := identity.IntIdentifier(importDashboardArg.User.GetTypedID())
require.NoError(t, err)
require.NotNil(t, importDashboardArg)
@@ -149,7 +149,7 @@ func TestImportDashboardService(t *testing.T) {
require.NotNil(t, resp)
require.Equal(t, "UDdpyzz7z", resp.UID)
userID, err := identity.IntIdentifier(importDashboardArg.User.GetNamespacedID())
userID, err := identity.IntIdentifier(importDashboardArg.User.GetTypedID())
require.NoError(t, err)
require.NotNil(t, importDashboardArg)
+1 -1
View File
@@ -878,7 +878,7 @@ func (d *dashboardStore) FindDashboards(ctx context.Context, query *dashboards.F
}
// only list k6 folders when requested by a service account - prevents showing k6 folders in the UI for users
if query.SignedInUser == nil || query.SignedInUser.GetID().Namespace() != identity.NamespaceServiceAccount {
if query.SignedInUser == nil || query.SignedInUser.GetID().Type() != identity.TypeServiceAccount {
filters = append(filters, searchstore.K6FolderFilter{})
}
@@ -234,8 +234,8 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d
func resolveUserID(user identity.Requester, log log.Logger) (int64, error) {
userID := int64(0)
namespaceID, identifier := user.GetNamespacedID()
if namespaceID != identity.NamespaceUser && namespaceID != identity.NamespaceServiceAccount {
namespaceID, identifier := user.GetTypedID()
if namespaceID != identity.TypeUser && namespaceID != identity.TypeServiceAccount {
log.Debug("User does not belong to a user or service account namespace", "namespaceID", namespaceID, "userID", identifier)
} else {
var err error
@@ -503,12 +503,12 @@ func (dr *DashboardServiceImpl) setDefaultPermissions(ctx context.Context, dto *
var permissions []accesscontrol.SetResourcePermissionCommand
if !provisioned {
namespaceID, userIDstr := dto.User.GetNamespacedID()
namespaceID, userIDstr := dto.User.GetTypedID()
userID, err := identity.IntIdentifier(namespaceID, userIDstr)
if err != nil {
dr.log.Error("Could not make user admin", "dashboard", dash.Title, "namespaceID", namespaceID, "userID", userID, "error", err)
} else if namespaceID == identity.NamespaceUser && userID > 0 {
} else if namespaceID == identity.TypeUser && userID > 0 {
permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{
UserID: userID, Permission: dashboardaccess.PERMISSION_ADMIN.String(),
})
@@ -541,12 +541,12 @@ func (dr *DashboardServiceImpl) setDefaultFolderPermissions(ctx context.Context,
var permissions []accesscontrol.SetResourcePermissionCommand
if !provisioned {
namespaceID, userIDstr := cmd.SignedInUser.GetNamespacedID()
namespaceID, userIDstr := cmd.SignedInUser.GetTypedID()
userID, err := identity.IntIdentifier(namespaceID, userIDstr)
if err != nil {
dr.log.Error("Could not make user admin", "folder", cmd.Title, "namespaceID", namespaceID, "userID", userID, "error", err)
} else if namespaceID == identity.NamespaceUser && userID > 0 {
} else if namespaceID == identity.TypeUser && userID > 0 {
permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{
UserID: userID, Permission: dashboardaccess.PERMISSION_ADMIN.String(),
})
@@ -116,7 +116,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sqlStore)
assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID())
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetTypedID())
require.NoError(t, err)
assert.Equal(t, "", sc.dashboardGuardianMock.DashUID)
@@ -139,7 +139,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore)
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID())
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetTypedID())
require.NoError(t, err)
assert.Equal(t, sc.otherSavedFolder.ID, sc.dashboardGuardianMock.DashID)
@@ -162,7 +162,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore)
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID())
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetTypedID())
require.NoError(t, err)
assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID)
@@ -186,7 +186,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore)
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID())
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetTypedID())
require.NoError(t, err)
assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID)
@@ -210,7 +210,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore)
assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID())
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetTypedID())
require.NoError(t, err)
assert.Equal(t, sc.savedDashInGeneralFolder.UID, sc.dashboardGuardianMock.DashUID)
@@ -234,7 +234,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore)
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID())
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetTypedID())
require.NoError(t, err)
assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID)
@@ -258,7 +258,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore)
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID())
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetTypedID())
require.NoError(t, err)
assert.Equal(t, sc.savedDashInGeneralFolder.UID, sc.dashboardGuardianMock.DashUID)
@@ -282,7 +282,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore)
assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID())
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetTypedID())
require.NoError(t, err)
assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID)
@@ -306,7 +306,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore)
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID())
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetTypedID())
require.NoError(t, err)
assert.Equal(t, sc.savedDashInGeneralFolder.UID, sc.dashboardGuardianMock.DashUID)
@@ -330,7 +330,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore)
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID())
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetTypedID())
require.NoError(t, err)
assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID)
@@ -135,10 +135,10 @@ func (d *DashboardSnapshotStore) SearchDashboardSnapshots(ctx context.Context, q
sess.Where("name LIKE ?", query.Name)
}
namespace, id := query.SignedInUser.GetNamespacedID()
namespace, id := query.SignedInUser.GetTypedID()
var userID int64
if namespace == identity.NamespaceServiceAccount || namespace == identity.NamespaceUser {
if namespace == identity.TypeServiceAccount || namespace == identity.TypeUser {
var err error
userID, err = identity.IntIdentifier(namespace, id)
if err != nil {
@@ -150,7 +150,7 @@ func (d *DashboardSnapshotStore) SearchDashboardSnapshots(ctx context.Context, q
switch {
case query.SignedInUser.GetOrgRole() == org.RoleAdmin:
sess.Where("org_id = ?", query.SignedInUser.GetOrgID())
case namespace != identity.NamespaceAnonymous:
case namespace != identity.TypeAnonymous:
sess.Where("org_id = ? AND user_id = ?", query.OrgID, userID)
default:
queryResult = snapshots
+1 -1
View File
@@ -58,7 +58,7 @@ func CreateDashboardSnapshot(c *contextmodel.ReqContext, cfg dashboardsnapshot.S
cmd.DashboardCreateCommand.Name = "Unnamed snapshot"
}
userID, err := identity.UserIdentifier(c.SignedInUser.GetNamespacedID())
userID, err := identity.UserIdentifier(c.SignedInUser.GetTypedID())
if err != nil {
c.JsonApiErr(http.StatusInternalServerError,
"Failed to create external snapshot", err)
+7 -7
View File
@@ -580,8 +580,8 @@ func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (
userID := int64(0)
var err error
namespaceID, userIDstr := user.GetNamespacedID()
if namespaceID != identity.NamespaceUser && namespaceID != identity.NamespaceServiceAccount {
namespaceID, userIDstr := user.GetTypedID()
if namespaceID != identity.TypeUser && namespaceID != identity.TypeServiceAccount {
s.log.Debug("User does not belong to a user or service account namespace, using 0 as user ID", "namespaceID", namespaceID, "userID", userIDstr)
} else {
userID, err = identity.IntIdentifier(namespaceID, userIDstr)
@@ -668,7 +668,7 @@ func (s *Service) Update(ctx context.Context, cmd *folder.UpdateFolderCommand) (
}
if cmd.NewTitle != nil {
namespace, id := cmd.SignedInUser.GetNamespacedID()
namespace, id := cmd.SignedInUser.GetTypedID()
metrics.MFolderIDsServiceCount.WithLabelValues(metrics.Folder).Inc()
if err := s.bus.Publish(ctx, &events.FolderTitleUpdated{
@@ -725,8 +725,8 @@ func (s *Service) legacyUpdate(ctx context.Context, cmd *folder.UpdateFolderComm
}
var userID int64
namespace, id := cmd.SignedInUser.GetNamespacedID()
if namespace == identity.NamespaceUser || namespace == identity.NamespaceServiceAccount {
namespace, id := cmd.SignedInUser.GetTypedID()
if namespace == identity.TypeUser || namespace == identity.TypeServiceAccount {
userID, err = identity.IntIdentifier(namespace, id)
if err != nil {
s.log.ErrorContext(ctx, "failed to parse user ID", "namespace", namespace, "userID", id, "error", err)
@@ -1142,8 +1142,8 @@ func (s *Service) buildSaveDashboardCommand(ctx context.Context, dto *dashboards
}
userID := int64(0)
namespaceID, userIDstr := dto.User.GetNamespacedID()
if namespaceID != identity.NamespaceUser && namespaceID != identity.NamespaceServiceAccount {
namespaceID, userIDstr := dto.User.GetTypedID()
if namespaceID != identity.TypeUser && namespaceID != identity.TypeServiceAccount {
s.log.Warn("User does not belong to a user or service account namespace, using 0 as user ID", "namespaceID", namespaceID, "userID", userIDstr)
} else {
userID, err = identity.IntIdentifier(namespaceID, userIDstr)
+2 -2
View File
@@ -323,7 +323,7 @@ func (ss *sqlStore) GetChildren(ctx context.Context, q folder.GetChildrenQuery)
}
// only list k6 folders when requested by a service account - prevents showing k6 folders in the UI for users
if q.SignedInUser == nil || q.SignedInUser.GetID().Namespace() != identity.NamespaceServiceAccount {
if q.SignedInUser == nil || q.SignedInUser.GetID().Type() != identity.TypeServiceAccount {
sql.WriteString(" AND uid != ?")
args = append(args, accesscontrol.K6FolderUID)
}
@@ -484,7 +484,7 @@ func (ss *sqlStore) GetFolders(ctx context.Context, q getFoldersQuery) ([]*folde
}
// only list k6 folders when requested by a service account - prevents showing k6 folders in the UI for users
if q.SignedInUser == nil || q.SignedInUser.GetID().Namespace() != identity.NamespaceServiceAccount {
if q.SignedInUser == nil || q.SignedInUser.GetID().Type() != identity.TypeServiceAccount {
s.WriteString(" AND f0.uid != ? AND (f0.parent_uid != ? OR f0.parent_uid IS NULL)")
args = append(args, accesscontrol.K6FolderUID, accesscontrol.K6FolderUID)
}
@@ -309,7 +309,7 @@ func (a *accessControlFolderGuardian) CanCreate(folderID int64, isFolder bool) (
func (a *accessControlDashboardGuardian) evaluate(evaluator accesscontrol.Evaluator) (bool, error) {
ok, err := a.ac.Evaluate(a.ctx, a.user, evaluator)
namespaceID, userID := a.user.GetNamespacedID()
namespaceID, userID := a.user.GetTypedID()
if err != nil {
id := 0
if a.dashboard != nil {
@@ -331,7 +331,7 @@ func (a *accessControlDashboardGuardian) evaluate(evaluator accesscontrol.Evalua
func (a *accessControlFolderGuardian) evaluate(evaluator accesscontrol.Evaluator) (bool, error) {
ok, err := a.ac.Evaluate(a.ctx, a.user, evaluator)
namespaceID, userID := a.user.GetNamespacedID()
namespaceID, userID := a.user.GetTypedID()
if err != nil {
uid := ""
orgID := 0
+6 -6
View File
@@ -139,8 +139,8 @@ func (l *LibraryElementService) createLibraryElement(c context.Context, signedIn
}
userID := int64(0)
namespaceID, identifier := signedInUser.GetNamespacedID()
if namespaceID == identity.NamespaceUser || namespaceID == identity.NamespaceServiceAccount {
namespaceID, identifier := signedInUser.GetTypedID()
if namespaceID == identity.TypeUser || namespaceID == identity.TypeServiceAccount {
userID, err = identity.IntIdentifier(namespaceID, identifier)
if err != nil {
l.log.Warn("Error while parsing userID", "namespaceID", namespaceID, "userID", identifier)
@@ -593,8 +593,8 @@ func (l *LibraryElementService) patchLibraryElement(c context.Context, signedInU
}
var userID int64
namespaceID, identifier := signedInUser.GetNamespacedID()
if namespaceID == identity.NamespaceUser || namespaceID == identity.NamespaceServiceAccount {
namespaceID, identifier := signedInUser.GetTypedID()
if namespaceID == identity.TypeUser || namespaceID == identity.TypeServiceAccount {
var errID error
userID, errID = identity.IntIdentifier(namespaceID, identifier)
if errID != nil {
@@ -800,9 +800,9 @@ func (l *LibraryElementService) connectElementsToDashboardID(c context.Context,
return err
}
namespaceID, identifier := signedInUser.GetNamespacedID()
namespaceID, identifier := signedInUser.GetTypedID()
userID := int64(0)
if namespaceID == identity.NamespaceUser || namespaceID == identity.NamespaceServiceAccount {
if namespaceID == identity.TypeUser || namespaceID == identity.TypeServiceAccount {
userID, err = identity.IntIdentifier(namespaceID, identifier)
if err != nil {
l.log.Warn("Failed to parse user ID from namespace identifier", "namespace", namespaceID, "identifier", identifier, "error", err)
+1 -1
View File
@@ -38,7 +38,7 @@ type userDisplayDTO struct {
// Static function to parse a requester into a userDisplayDTO
func newUserDisplayDTOFromRequester(requester identity.Requester) *userDisplayDTO {
uid := ""
if requester.GetUID().IsNamespace(identity.NamespaceUser, identity.NamespaceServiceAccount) {
if requester.GetUID().IsType(identity.TypeUser, identity.TypeServiceAccount) {
uid = requester.GetUID().ID()
}
+2 -2
View File
@@ -284,7 +284,7 @@ func ProvideService(plugCtxProvider *plugincontext.Provider, cfg *setting.Cfg, r
g.websocketHandler = func(ctx *contextmodel.ReqContext) {
user := ctx.SignedInUser
_, identifier := user.GetNamespacedID()
_, identifier := user.GetTypedID()
// Centrifuge expects Credentials in context with a current user ID.
cred := &centrifuge.Credentials{
@@ -955,7 +955,7 @@ func (g *GrafanaLive) HandleHTTPPublish(ctx *contextmodel.ReqContext) response.R
return response.Error(http.StatusBadRequest, "invalid channel ID", nil)
}
namespaceID, userID := ctx.SignedInUser.GetNamespacedID()
namespaceID, userID := ctx.SignedInUser.GetTypedID()
logger.Debug("Publish API cmd", "namespaceID", namespaceID, "userID", userID, "channel", cmd.Channel)
user := ctx.SignedInUser
channel := cmd.Channel
+4 -4
View File
@@ -73,7 +73,7 @@ func TestStreamManager_SubmitStream_Send(t *testing.T) {
}
mockContextGetter.EXPECT().GetPluginContext(context.Background(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) {
userID, err := identity.IntIdentifier(user.GetNamespacedID())
userID, err := identity.IntIdentifier(user.GetTypedID())
require.NoError(t, err)
require.Equal(t, int64(2), userID)
require.Equal(t, int64(1), user.GetOrgID())
@@ -258,7 +258,7 @@ func TestStreamManager_SubmitStream_ErrorRestartsRunStream(t *testing.T) {
}
mockContextGetter.EXPECT().GetPluginContext(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) {
userID, err := identity.IntIdentifier(user.GetNamespacedID())
userID, err := identity.IntIdentifier(user.GetTypedID())
require.NoError(t, err)
require.Equal(t, int64(2), userID)
require.Equal(t, int64(1), user.GetOrgID())
@@ -343,7 +343,7 @@ func TestStreamManager_HandleDatasourceUpdate(t *testing.T) {
}
mockContextGetter.EXPECT().GetPluginContext(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) {
userID, err := identity.IntIdentifier(user.GetNamespacedID())
userID, err := identity.IntIdentifier(user.GetTypedID())
require.NoError(t, err)
require.Equal(t, int64(2), userID)
@@ -412,7 +412,7 @@ func TestStreamManager_HandleDatasourceDelete(t *testing.T) {
}
mockContextGetter.EXPECT().GetPluginContext(context.Background(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) {
userID, err := identity.IntIdentifier(user.GetNamespacedID())
userID, err := identity.IntIdentifier(user.GetTypedID())
require.NoError(t, err)
require.Equal(t, int64(2), userID)
require.Equal(t, int64(1), user.GetOrgID())
+1 -1
View File
@@ -297,7 +297,7 @@ func (s *ServiceImpl) getProfileNode(c *contextmodel.ReqContext) *navtree.NavLin
func (s *ServiceImpl) buildStarredItemsNavLinks(c *contextmodel.ReqContext) ([]*navtree.NavLink, error) {
starredItemsChildNavs := []*navtree.NavLink{}
userID, _ := identity.UserIdentifier(c.SignedInUser.GetNamespacedID())
userID, _ := identity.UserIdentifier(c.SignedInUser.GetTypedID())
query := star.GetUserStarsQuery{
UserID: userID,
}
+4 -4
View File
@@ -76,7 +76,7 @@ func (srv RulerSrv) RouteDeleteAlertRules(c *contextmodel.ReqContext, namespaceU
return toNamespaceErrorResponse(err)
}
userNamespace, id := c.SignedInUser.GetNamespacedID()
userNamespace, id := c.SignedInUser.GetTypedID()
var loggerCtx = []any{
"userId",
id,
@@ -283,7 +283,7 @@ func (srv RulerSrv) RouteGetRulesConfig(c *contextmodel.ReqContext) response.Res
for groupKey, rules := range configs {
folder, ok := namespaceMap[groupKey.NamespaceUID]
if !ok {
userNamespace, id := c.SignedInUser.GetNamespacedID()
userNamespace, id := c.SignedInUser.GetTypedID()
srv.log.Error("Namespace not visible to the user", "user", id, "userNamespace", userNamespace, "namespace", groupKey.NamespaceUID)
continue
}
@@ -359,7 +359,7 @@ func (srv RulerSrv) updateAlertRulesInGroup(c *contextmodel.ReqContext, groupKey
var finalChanges *store.GroupDelta
var dbConfig *ngmodels.AlertConfiguration
err := srv.xactManager.InTransaction(c.Req.Context(), func(tranCtx context.Context) error {
userNamespace, id := c.SignedInUser.GetNamespacedID()
userNamespace, id := c.SignedInUser.GetTypedID()
logger := srv.log.New("namespace_uid", groupKey.NamespaceUID, "group",
groupKey.RuleGroup, "org_id", groupKey.OrgID, "user_id", id, "userNamespace", userNamespace)
groupChanges, err := store.CalculateChanges(tranCtx, srv.store, groupKey, rules)
@@ -454,7 +454,7 @@ func (srv RulerSrv) updateAlertRulesInGroup(c *contextmodel.ReqContext, groupKey
}
if len(finalChanges.New) > 0 {
userID, _ := identity.UserIdentifier(c.SignedInUser.GetNamespacedID())
userID, _ := identity.UserIdentifier(c.SignedInUser.GetTypedID())
limitReached, err := srv.QuotaService.CheckQuotaReached(tranCtx, ngmodels.QuotaTargetSrv, &quota.ScopeParameters{
OrgID: c.SignedInUser.GetOrgID(),
UserID: userID,
@@ -660,7 +660,7 @@ func (service *AlertRuleService) DeleteAlertRule(ctx context.Context, user ident
func (service *AlertRuleService) checkLimitsTransactionCtx(ctx context.Context, user identity.Requester) error {
// default to 0 if there is no user
userID := int64(0)
u, err := identity.UserIdentifier(user.GetNamespacedID())
u, err := identity.UserIdentifier(user.GetTypedID())
if err != nil {
return fmt.Errorf("failed to check alert rule quota: %w", err)
}
+4 -4
View File
@@ -94,8 +94,8 @@ func (o *Service) HasOAuthEntry(ctx context.Context, usr identity.Requester) (*l
return nil, false, nil
}
namespace, id := usr.GetNamespacedID()
if namespace != identity.NamespaceUser {
namespace, id := usr.GetTypedID()
if namespace != identity.TypeUser {
// Not a user, therefore no token.
return nil, false, nil
}
@@ -136,8 +136,8 @@ func (o *Service) TryTokenRefresh(ctx context.Context, usr identity.Requester) e
return nil
}
namespace, id := usr.GetNamespacedID()
if namespace != identity.NamespaceUser {
namespace, id := usr.GetTypedID()
if namespace != identity.TypeUser {
// Not a user, therefore no token.
logger.Warn("Can only refresh OAuth tokens for users", "namespace", namespace, "userId", id)
return nil
+12 -12
View File
@@ -174,13 +174,13 @@ func TestService_TryTokenRefresh(t *testing.T) {
{
desc: "should skip sync when identity is not a user",
setup: func(env *environment) {
env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("service-account:1")}
env.identity = &authn.Identity{ID: identity.MustParseTypedID("service-account:1")}
},
},
{
desc: "should skip token refresh and return nil if namespace and id cannot be converted to user ID",
setup: func(env *environment) {
env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("user:invalidIdentifierFormat")}
env.identity = &authn.Identity{ID: identity.MustParseTypedID("user:invalidIdentifierFormat")}
},
},
{
@@ -203,28 +203,28 @@ func TestService_TryTokenRefresh(t *testing.T) {
env.identity = &authn.Identity{
AuthenticatedBy: login.GenericOAuthModule,
ID: authn.MustParseNamespaceID("user:1234"),
ID: identity.MustParseTypedID("user:1234"),
}
},
},
{
desc: "should skip token refresh if the expiration check has already been cached",
setup: func(env *environment) {
env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("user:1234")}
env.identity = &authn.Identity{ID: identity.MustParseTypedID("user:1234")}
env.cache.Set("oauth-refresh-token-1234", true, 1*time.Minute)
},
},
{
desc: "should skip token refresh if there's an unexpected error while looking up the user oauth entry, additionally, no error should be returned",
setup: func(env *environment) {
env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("user:1234")}
env.identity = &authn.Identity{ID: identity.MustParseTypedID("user:1234")}
env.authInfoService.ExpectedError = errors.New("some error")
},
},
{
desc: "should skip token refresh if the user doesn't have an oauth entry",
setup: func(env *environment) {
env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("user:1234")}
env.identity = &authn.Identity{ID: identity.MustParseTypedID("user:1234")}
env.authInfoService.ExpectedUserAuth = &login.UserAuth{
AuthModule: login.SAMLAuthModule,
}
@@ -233,7 +233,7 @@ func TestService_TryTokenRefresh(t *testing.T) {
{
desc: "should do token refresh if access token or id token have not expired yet",
setup: func(env *environment) {
env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("user:1234")}
env.identity = &authn.Identity{ID: identity.MustParseTypedID("user:1234")}
env.authInfoService.ExpectedUserAuth = &login.UserAuth{
AuthModule: login.GenericOAuthModule,
}
@@ -242,7 +242,7 @@ func TestService_TryTokenRefresh(t *testing.T) {
{
desc: "should skip token refresh when no oauth provider was found",
setup: func(env *environment) {
env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("user:1234")}
env.identity = &authn.Identity{ID: identity.MustParseTypedID("user:1234")}
env.authInfoService.ExpectedUserAuth = &login.UserAuth{
AuthModule: login.GenericOAuthModule,
OAuthIdToken: EXPIRED_JWT,
@@ -252,7 +252,7 @@ func TestService_TryTokenRefresh(t *testing.T) {
{
desc: "should skip token refresh when oauth provider token handling is disabled (UseRefreshToken is false)",
setup: func(env *environment) {
env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("user:1234")}
env.identity = &authn.Identity{ID: identity.MustParseTypedID("user:1234")}
env.authInfoService.ExpectedUserAuth = &login.UserAuth{
AuthModule: login.GenericOAuthModule,
OAuthIdToken: EXPIRED_JWT,
@@ -265,7 +265,7 @@ func TestService_TryTokenRefresh(t *testing.T) {
{
desc: "should skip token refresh when there is no refresh token",
setup: func(env *environment) {
env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("user:1234")}
env.identity = &authn.Identity{ID: identity.MustParseTypedID("user:1234")}
env.authInfoService.ExpectedUserAuth = &login.UserAuth{
AuthModule: login.GenericOAuthModule,
OAuthIdToken: EXPIRED_JWT,
@@ -285,7 +285,7 @@ func TestService_TryTokenRefresh(t *testing.T) {
Expiry: time.Now().Add(-time.Hour),
TokenType: "Bearer",
}
env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("user:1234")}
env.identity = &authn.Identity{ID: identity.MustParseTypedID("user:1234")}
env.socialService.ExpectedAuthInfoProvider = &social.OAuthInfo{
UseRefreshToken: true,
}
@@ -310,7 +310,7 @@ func TestService_TryTokenRefresh(t *testing.T) {
Expiry: time.Now().Add(time.Hour),
TokenType: "Bearer",
}
env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("user:1234")}
env.identity = &authn.Identity{ID: identity.MustParseTypedID("user:1234")}
env.socialService.ExpectedAuthInfoProvider = &social.OAuthInfo{
UseRefreshToken: true,
}
@@ -35,8 +35,8 @@ func (m *UserHeaderMiddleware) applyUserHeader(ctx context.Context, h backend.Fo
}
h.DeleteHTTPHeader(proxyutil.UserHeaderName)
namespace, _ := reqCtx.SignedInUser.GetNamespacedID()
if namespace != identity.NamespaceAnonymous {
namespace, _ := reqCtx.SignedInUser.GetTypedID()
if namespace != identity.TypeAnonymous {
h.SetHTTPHeader(proxyutil.UserHeaderName, reqCtx.SignedInUser.GetLogin())
}
}
+3 -2
View File
@@ -7,10 +7,10 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/middleware/requestmeta"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/authn"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/org"
@@ -99,7 +99,8 @@ func (api *ServiceAccountsAPI) CreateServiceAccount(c *contextmodel.ReqContext)
}
if api.cfg.RBAC.PermissionsOnCreation("service-account") {
if c.SignedInUser.GetID().IsNamespace(authn.NamespaceUser) {
t, _ := c.SignedInUser.GetTypedID()
if t == identity.TypeUser {
userID, err := c.SignedInUser.GetID().ParseInt()
if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to parse user id", err)
@@ -166,8 +166,8 @@ func (f *accessControlDashboardPermissionFilter) buildClauses() {
folderWildcards := accesscontrol.WildcardsFromPrefix(dashboards.ScopeFoldersPrefix)
userID := int64(0)
namespace, identifier := f.user.GetNamespacedID()
if namespace == identity.NamespaceUser || namespace == identity.NamespaceServiceAccount {
namespace, identifier := f.user.GetTypedID()
if namespace == identity.TypeUser || namespace == identity.TypeServiceAccount {
userID, _ = identity.IntIdentifier(namespace, identifier)
}
@@ -34,8 +34,8 @@ func (f *accessControlDashboardPermissionFilterNoFolderSubquery) buildClauses()
folderWildcards := accesscontrol.WildcardsFromPrefix(dashboards.ScopeFoldersPrefix)
userID := int64(0)
namespace, identifier := f.user.GetNamespacedID()
if namespace == identity.NamespaceUser || namespace == identity.NamespaceServiceAccount {
namespace, identifier := f.user.GetTypedID()
if namespace == identity.TypeUser || namespace == identity.TypeServiceAccount {
userID, _ = identity.IntIdentifier(namespace, identifier)
}
+10 -10
View File
@@ -47,8 +47,8 @@ func (api *API) getDashboardHelper(ctx context.Context, orgID int64, id int64, u
}
func (api *API) GetStars(c *contextmodel.ReqContext) response.Response {
namespace, identifier := c.SignedInUser.GetNamespacedID()
if namespace != identity.NamespaceUser && namespace != identity.NamespaceServiceAccount {
namespace, identifier := c.SignedInUser.GetTypedID()
if namespace != identity.TypeUser && namespace != identity.TypeServiceAccount {
return response.Error(http.StatusBadRequest, "Only users and service accounts can star dashboards", nil)
}
@@ -101,8 +101,8 @@ func (api *API) GetStars(c *contextmodel.ReqContext) response.Response {
// 403: forbiddenError
// 500: internalServerError
func (api *API) StarDashboard(c *contextmodel.ReqContext) response.Response {
namespace, identifier := c.SignedInUser.GetNamespacedID()
if namespace != identity.NamespaceUser && namespace != identity.NamespaceServiceAccount {
namespace, identifier := c.SignedInUser.GetTypedID()
if namespace != identity.TypeUser && namespace != identity.TypeServiceAccount {
return response.Error(http.StatusBadRequest, "Only users and service accounts can star dashboards", nil)
}
@@ -146,8 +146,8 @@ func (api *API) StarDashboardByUID(c *contextmodel.ReqContext) response.Response
return response.Error(http.StatusBadRequest, "Invalid dashboard UID", nil)
}
namespace, identifier := c.SignedInUser.GetNamespacedID()
if namespace != identity.NamespaceUser && namespace != identity.NamespaceServiceAccount {
namespace, identifier := c.SignedInUser.GetTypedID()
if namespace != identity.TypeUser && namespace != identity.TypeServiceAccount {
return response.Error(http.StatusBadRequest, "Only users and service accounts can star dashboards", nil)
}
@@ -193,8 +193,8 @@ func (api *API) UnstarDashboard(c *contextmodel.ReqContext) response.Response {
return response.Error(http.StatusBadRequest, "Invalid dashboard ID", nil)
}
namespace, identifier := c.SignedInUser.GetNamespacedID()
if namespace != identity.NamespaceUser && namespace != identity.NamespaceServiceAccount {
namespace, identifier := c.SignedInUser.GetTypedID()
if namespace != identity.TypeUser && namespace != identity.TypeServiceAccount {
return response.Error(http.StatusBadRequest, "Only users and service accounts can star dashboards", nil)
}
@@ -233,8 +233,8 @@ func (api *API) UnstarDashboardByUID(c *contextmodel.ReqContext) response.Respon
return response.Error(http.StatusBadRequest, "Invalid dashboard UID", nil)
}
namespace, identifier := c.SignedInUser.GetNamespacedID()
if namespace != identity.NamespaceUser && namespace != identity.NamespaceServiceAccount {
namespace, identifier := c.SignedInUser.GetTypedID()
if namespace != identity.TypeUser && namespace != identity.TypeServiceAccount {
return response.Error(http.StatusBadRequest, "Only users and service accounts can star dashboards", nil)
}
+2 -2
View File
@@ -49,9 +49,9 @@ func (tapi *TeamAPI) createTeam(c *contextmodel.ReqContext) response.Response {
// if the request is authenticated using API tokens
// the SignedInUser is an empty struct therefore
// an additional check whether it is an actual user is required
namespace, identifier := c.SignedInUser.GetNamespacedID()
namespace, identifier := c.SignedInUser.GetTypedID()
switch namespace {
case identity.NamespaceUser:
case identity.TypeUser:
userID, err := strconv.ParseInt(identifier, 10, 64)
if err != nil {
c.Logger.Error("Could not add creator to team because user id is not a number", "error", err)
+20 -20
View File
@@ -43,7 +43,7 @@ type SignedInUser struct {
// IDToken is a signed token representing the identity that can be forwarded to plugins and external services.
// Will only be set when featuremgmt.FlagIdForwarding is enabled.
IDToken string `json:"-" xorm:"-"`
NamespacedID identity.NamespaceID
NamespacedID identity.TypedID
}
func (u *SignedInUser) ShouldUpdateLastSeenAt() bool {
@@ -98,7 +98,7 @@ func (u *SignedInUser) GetAllowedKubernetesNamespace() string {
// GetCacheKey returns a unique key for the entity.
// Add an extra prefix to avoid collisions with other caches
func (u *SignedInUser) GetCacheKey() string {
namespace, id := u.GetNamespacedID()
namespace, id := u.GetTypedID()
if !u.HasUniqueId() {
// Hack use the org role as id for identities that do not have a unique id
// e.g. anonymous and render key.
@@ -173,46 +173,46 @@ func (u *SignedInUser) GetOrgRole() identity.RoleType {
}
// GetID returns namespaced id for the entity
func (u *SignedInUser) GetID() identity.NamespaceID {
ns, id := u.GetNamespacedID()
return identity.NewNamespaceIDString(ns, id)
func (u *SignedInUser) GetID() identity.TypedID {
ns, id := u.GetTypedID()
return identity.NewTypedIDString(ns, id)
}
// GetNamespacedID returns the namespace and ID of the active entity
// GetTypedID returns the namespace and ID of the active entity
// The namespace is one of the constants defined in pkg/apimachinery/identity
func (u *SignedInUser) GetNamespacedID() (identity.Namespace, string) {
func (u *SignedInUser) GetTypedID() (identity.IdentityType, string) {
switch {
case u.ApiKeyID != 0:
return identity.NamespaceAPIKey, strconv.FormatInt(u.ApiKeyID, 10)
return identity.TypeAPIKey, strconv.FormatInt(u.ApiKeyID, 10)
case u.IsServiceAccount:
return identity.NamespaceServiceAccount, strconv.FormatInt(u.UserID, 10)
return identity.TypeServiceAccount, strconv.FormatInt(u.UserID, 10)
case u.UserID > 0:
return identity.NamespaceUser, strconv.FormatInt(u.UserID, 10)
return identity.TypeUser, strconv.FormatInt(u.UserID, 10)
case u.IsAnonymous:
return identity.NamespaceAnonymous, "0"
return identity.TypeAnonymous, "0"
case u.AuthenticatedBy == "render" && u.UserID == 0:
return identity.NamespaceRenderService, "0"
return identity.TypeRenderService, "0"
}
return u.NamespacedID.Namespace(), u.NamespacedID.ID()
return u.NamespacedID.Type(), u.NamespacedID.ID()
}
// GetUID returns namespaced uid for the entity
func (u *SignedInUser) GetUID() identity.NamespaceID {
func (u *SignedInUser) GetUID() identity.TypedID {
switch {
case u.ApiKeyID != 0:
return identity.NewNamespaceIDString(identity.NamespaceAPIKey, strconv.FormatInt(u.ApiKeyID, 10))
return identity.NewTypedIDString(identity.TypeAPIKey, strconv.FormatInt(u.ApiKeyID, 10))
case u.IsServiceAccount:
return identity.NewNamespaceIDString(identity.NamespaceServiceAccount, u.UserUID)
return identity.NewTypedIDString(identity.TypeServiceAccount, u.UserUID)
case u.UserID > 0:
return identity.NewNamespaceIDString(identity.NamespaceUser, u.UserUID)
return identity.NewTypedIDString(identity.TypeUser, u.UserUID)
case u.IsAnonymous:
return identity.NewNamespaceIDString(identity.NamespaceAnonymous, "0")
return identity.NewTypedIDString(identity.TypeAnonymous, "0")
case u.AuthenticatedBy == "render" && u.UserID == 0:
return identity.NewNamespaceIDString(identity.NamespaceRenderService, "0")
return identity.NewTypedIDString(identity.TypeRenderService, "0")
}
return identity.NewNamespaceIDString(identity.NamespaceEmpty, "0")
return identity.NewTypedIDString(identity.TypeEmpty, "0")
}
func (u *SignedInUser) GetAuthID() string {
+2 -1
View File
@@ -8,6 +8,7 @@ import (
"time"
"github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/notifications"
@@ -152,6 +153,6 @@ func (s *Verifier) Complete(ctx context.Context, cmd user.CompleteEmailVerifyCom
// remove the current token, so a new one can be generated with correct values.
return s.is.RemoveIDToken(
ctx,
&authn.Identity{ID: authn.NewNamespaceID(authn.NamespaceUser, usr.ID), OrgID: usr.OrgID},
&authn.Identity{ID: identity.NewTypedID(identity.TypeUser, usr.ID), OrgID: usr.OrgID},
)
}