diff --git a/pkg/api/org_users.go b/pkg/api/org_users.go index 98958e65cb1..401148307c8 100644 --- a/pkg/api/org_users.go +++ b/pkg/api/org_users.go @@ -326,11 +326,11 @@ func (hs *HTTPServer) searchOrgUsersHelper(c *contextmodel.ReqContext, query *or if c.QueryBool("accesscontrol") { permissions := c.SignedInUser.GetPermissions() if query.OrgID != c.SignedInUser.GetOrgID() { - permissionsList, err := hs.accesscontrolService.GetUserPermissionsInOrg(c.Req.Context(), c.SignedInUser, query.OrgID) + identity, err := hs.authnService.ResolveIdentity(c.Req.Context(), query.OrgID, c.SignedInUser.GetID()) if err != nil { return nil, err } - permissions = accesscontrol.GroupScopesByAction(permissionsList) + permissions = identity.GetPermissions() } accessControlMetadata = accesscontrol.GetResourcesMetadata(c.Req.Context(), permissions, "users:id:", userIDs) } diff --git a/pkg/services/auth/identity/requester.go b/pkg/services/auth/identity/requester.go index aa7670d6c79..dadc5e5f9fa 100644 --- a/pkg/services/auth/identity/requester.go +++ b/pkg/services/auth/identity/requester.go @@ -20,6 +20,11 @@ var ErrNotIntIdentifier = errors.New("identifier is not an int64") var ErrIdentifierNotInitialized = errors.New("identifier is not initialized") type Requester interface { + // GetID returns namespaced id for the entity + GetID() string + // GetNamespacedID returns the namespace and ID of the active entity. + // The namespace is one of the constants defined in pkg/services/auth/identity. + GetNamespacedID() (namespace string, identifier string) // GetDisplayName returns the display name of the active entity. // The display name is the name if it is set, otherwise the login or email. GetDisplayName() string @@ -31,9 +36,6 @@ type Requester interface { // GetLogin returns the login of the active entity // Can be empty. GetLogin() string - // GetNamespacedID returns the namespace and ID of the active entity. - // The namespace is one of the constants defined in pkg/services/auth/identity. - GetNamespacedID() (namespace string, identifier string) // GetOrgID returns the ID of the active organization GetOrgID() int64 // GetOrgRole returns the role of the active entity in the active organization. diff --git a/pkg/services/authn/authn.go b/pkg/services/authn/authn.go index 12ac3b826c1..27658cecb44 100644 --- a/pkg/services/authn/authn.go +++ b/pkg/services/authn/authn.go @@ -77,6 +77,10 @@ type Service interface { RedirectURL(ctx context.Context, client string, r *Request) (*Redirect, error) // Logout revokes session token and does additional clean up if client used to authenticate supports it Logout(ctx context.Context, user identity.Requester, sessionToken *usertoken.UserToken) (*Redirect, error) + + // ResolveIdentity resolves an identity from org and namespace id. + ResolveIdentity(ctx context.Context, orgID int64, namespaceID string) (*Identity, error) + // RegisterClient will register a new authn.Client that can be used for authentication RegisterClient(c Client) } diff --git a/pkg/services/authn/authnimpl/service.go b/pkg/services/authn/authnimpl/service.go index 8ebb9f93f41..6573ef3dd85 100644 --- a/pkg/services/authn/authnimpl/service.go +++ b/pkg/services/authn/authnimpl/service.go @@ -391,6 +391,20 @@ Default: return redirect, nil } +func (s *Service) ResolveIdentity(ctx context.Context, orgID int64, namespaceID string) (*authn.Identity, error) { + r := &authn.Request{} + r.OrgID = orgID + // hack to not update last seen + r.SetMeta(authn.MetaKeyIsLogin, "true") + + identity, err := s.authenticate(ctx, clients.ProvideIdentity(namespaceID), r) + if err != nil { + return nil, err + } + + return identity, nil +} + func (s *Service) RegisterClient(c authn.Client) { s.clients[c.Name()] = c if cac, ok := c.(authn.ContextAwareClient); ok { diff --git a/pkg/services/authn/authntest/fake.go b/pkg/services/authn/authntest/fake.go index 81a93636585..c83154a9d8c 100644 --- a/pkg/services/authn/authntest/fake.go +++ b/pkg/services/authn/authntest/fake.go @@ -68,7 +68,11 @@ func (f *FakeService) RedirectURL(ctx context.Context, client string, r *authn.R return f.ExpectedRedirect, f.ExpectedErr } -func (*FakeService) Logout(_ context.Context, _ identity.Requester, _ *usertoken.UserToken) (*authn.Redirect, error) { +func (f *FakeService) Logout(_ context.Context, _ identity.Requester, _ *usertoken.UserToken) (*authn.Redirect, error) { + panic("unimplemented") +} + +func (f *FakeService) ResolveIdentity(ctx context.Context, orgID int64, namespaceID string) (*authn.Identity, error) { panic("unimplemented") } diff --git a/pkg/services/authn/authntest/mock.go b/pkg/services/authn/authntest/mock.go index 2a46f97a5c8..193200e5498 100644 --- a/pkg/services/authn/authntest/mock.go +++ b/pkg/services/authn/authntest/mock.go @@ -47,6 +47,10 @@ func (*MockService) Logout(_ context.Context, _ identity.Requester, _ *usertoken panic("unimplemented") } +func (m *MockService) ResolveIdentity(ctx context.Context, orgID int64, namespaceID string) (*authn.Identity, error) { + panic("unimplemented") +} + func (m *MockService) SyncIdentity(ctx context.Context, identity *authn.Identity) error { if m.SyncIdentityFunc != nil { return m.SyncIdentityFunc(ctx, identity) diff --git a/pkg/services/authn/clients/identity.go b/pkg/services/authn/clients/identity.go new file mode 100644 index 00000000000..557f04db9d5 --- /dev/null +++ b/pkg/services/authn/clients/identity.go @@ -0,0 +1,33 @@ +package clients + +import ( + "context" + + "github.com/grafana/grafana/pkg/services/authn" +) + +var _ authn.Client = (*IdentityClient)(nil) + +func ProvideIdentity(namespaceID string) *IdentityClient { + return &IdentityClient{namespaceID} +} + +type IdentityClient struct { + namespaceID string +} + +func (i *IdentityClient) Name() string { + return "identity" +} + +// Authenticate implements authn.Client. +func (i *IdentityClient) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identity, error) { + return &authn.Identity{ + OrgID: r.OrgID, + ID: i.namespaceID, + ClientParams: authn.ClientParams{ + FetchSyncedUser: true, + SyncPermissions: true, + }, + }, nil +} diff --git a/pkg/services/authn/identity.go b/pkg/services/authn/identity.go index 4f618cd4424..5cd067fc65f 100644 --- a/pkg/services/authn/identity.go +++ b/pkg/services/authn/identity.go @@ -87,6 +87,18 @@ type Identity struct { IDToken string } +func (i *Identity) GetID() string { + return i.ID +} + +func (i *Identity) GetNamespacedID() (namespace string, identifier string) { + split := strings.Split(i.GetID(), ":") + if len(split) != 2 { + return "", "" + } + return split[0], split[1] +} + func (i *Identity) GetAuthenticatedBy() string { return i.AuthenticatedBy } @@ -122,16 +134,6 @@ func (i *Identity) GetLogin() string { return i.Login } -func (i *Identity) GetNamespacedID() (namespace string, identifier string) { - split := strings.Split(i.ID, ":") - - if len(split) != 2 { - return "", "" - } - - return split[0], split[1] -} - // GetOrgID implements identity.Requester. func (i *Identity) GetOrgID() int64 { return i.OrgID diff --git a/pkg/services/user/identity.go b/pkg/services/user/identity.go index 119bc5e9baa..4b2825f817f 100644 --- a/pkg/services/user/identity.go +++ b/pkg/services/user/identity.go @@ -2,6 +2,7 @@ package user import ( "fmt" + "strings" "time" "github.com/grafana/grafana/pkg/models/roletype" @@ -189,28 +190,31 @@ func (u *SignedInUser) GetOrgRole() roletype.RoleType { return u.OrgRole } -// GetNamespacedID returns the namespace and ID of the active entity -// The namespace is one of the constants defined in pkg/services/auth/identity -func (u *SignedInUser) GetNamespacedID() (string, string) { +// GetID returns namespaced id for the entity +func (u *SignedInUser) GetID() string { switch { case u.ApiKeyID != 0: - return identity.NamespaceAPIKey, fmt.Sprintf("%d", u.ApiKeyID) + return namespacedID(identity.NamespaceAPIKey, u.ApiKeyID) case u.IsServiceAccount: - return identity.NamespaceServiceAccount, fmt.Sprintf("%d", u.UserID) + return namespacedID(identity.NamespaceServiceAccount, u.UserID) case u.UserID > 0: - return identity.NamespaceUser, fmt.Sprintf("%d", u.UserID) + return namespacedID(identity.NamespaceUser, u.UserID) case u.IsAnonymous: - return identity.NamespaceAnonymous, "" - case u.AuthenticatedBy == "render": //import cycle render - if u.UserID == 0 { - return identity.NamespaceRenderService, "0" - } else { // this should never happen as u.UserID > 0 already catches this - return identity.NamespaceUser, fmt.Sprintf("%d", u.UserID) - } + return identity.NamespaceAnonymous + ":" + case u.AuthenticatedBy == "render" && u.UserID == 0: + return namespacedID(identity.NamespaceRenderService, 0) } // backwards compatibility - return identity.NamespaceUser, fmt.Sprintf("%d", u.UserID) + return namespacedID(identity.NamespaceUser, u.UserID) +} + +// GetNamespacedID returns the namespace and ID of the active entity +// The namespace is one of the constants defined in pkg/services/auth/identity +func (u *SignedInUser) GetNamespacedID() (string, string) { + parts := strings.Split(u.GetID(), ":") + // Safety: GetID always returns a ':' separated string + return parts[0], parts[1] } // FIXME: remove this method once all services are using an interface @@ -238,3 +242,7 @@ func (u *SignedInUser) GetAuthenticatedBy() string { func (u *SignedInUser) GetIDToken() string { return u.IDToken } + +func namespacedID(namespace string, id int64) string { + return fmt.Sprintf("%s:%d", namespace, id) +}