AuthN: Implement requester interface for identity (#75618)

* AuthN: Implement identity.Requester interface for authn.Identity

* AuthN: Replace OrgRole with GetOrgRole

* IDForwarding: skip converting to SignedInUser

* Pass identity directly in permission sync hook
This commit is contained in:
Karl Persson 2023-09-28 16:37:32 +02:00 committed by GitHub
parent 2fe4ecde19
commit fd2235b5ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 121 additions and 21 deletions

View File

@ -62,7 +62,7 @@ func TestAnonymous_Authenticate(t *testing.T) {
assert.Equal(t, true, identity.ID == "") assert.Equal(t, true, identity.ID == "")
assert.Equal(t, tt.org.ID, identity.OrgID) assert.Equal(t, tt.org.ID, identity.OrgID)
assert.Equal(t, tt.org.Name, identity.OrgName) assert.Equal(t, tt.org.Name, identity.OrgName)
assert.Equal(t, tt.cfg.AnonymousOrgRole, string(identity.Role())) assert.Equal(t, tt.cfg.AnonymousOrgRole, string(identity.GetOrgRole()))
} }
}) })
} }

View File

@ -80,9 +80,8 @@ func (s *Service) SignIdentity(ctx context.Context, id identity.Requester) (stri
} }
func (s *Service) hook(ctx context.Context, identity *authn.Identity, _ *authn.Request) error { func (s *Service) hook(ctx context.Context, identity *authn.Identity, _ *authn.Request) error {
// FIXME(kalleep): implement identity.Requester for authn.Identity
// FIXME(kalleep): we should probably lazy load this // FIXME(kalleep): we should probably lazy load this
token, err := s.SignIdentity(ctx, identity.SignedInUser()) token, err := s.SignIdentity(ctx, identity)
if err != nil { if err != nil {
namespace, id := identity.NamespacedID() namespace, id := identity.NamespacedID()
s.logger.Error("Failed to sign id token", "err", err, "namespace", namespace, "id", id) s.logger.Error("Failed to sign id token", "err", err, "namespace", namespace, "id", id)

View File

@ -30,8 +30,7 @@ func (s *PermissionsSync) SyncPermissionsHook(ctx context.Context, identity *aut
return nil return nil
} }
permissions, err := s.ac.GetUserPermissions(ctx, identity.SignedInUser(), permissions, err := s.ac.GetUserPermissions(ctx, identity, accesscontrol.Options{ReloadCache: false})
accesscontrol.Options{ReloadCache: false})
if err != nil { if err != nil {
s.log.FromContext(ctx).Error("Failed to fetch permissions from db", "error", err, "user_id", identity.ID) s.log.FromContext(ctx).Error("Failed to fetch permissions from db", "error", err, "user_id", identity.ID)
return errSyncPermissionsForbidden return errSyncPermissionsForbidden

View File

@ -8,6 +8,7 @@ import (
"golang.org/x/oauth2" "golang.org/x/oauth2"
"github.com/grafana/grafana/pkg/models/roletype"
"github.com/grafana/grafana/pkg/models/usertoken" "github.com/grafana/grafana/pkg/models/usertoken"
"github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/login"
@ -28,6 +29,8 @@ const (
NamespaceRenderService = identity.NamespaceRenderService NamespaceRenderService = identity.NamespaceRenderService
) )
var _ identity.Requester = (*Identity)(nil)
type Identity struct { type Identity struct {
// OrgID is the active organization for the entity. // OrgID is the active organization for the entity.
OrgID int64 OrgID int64
@ -81,12 +84,107 @@ type Identity struct {
IDToken string IDToken string
} }
// Role returns the role of the identity in the active organization. func (i *Identity) GetAuthenticatedBy() string {
func (i *Identity) Role() org.RoleType { return i.AuthenticatedBy
return i.OrgRoles[i.OrgID] }
func (i *Identity) GetCacheKey() string {
namespace, id := i.GetNamespacedID()
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.
id = string(i.GetOrgRole())
}
return fmt.Sprintf("%d-%s-%s", i.GetOrgID(), namespace, id)
}
func (i *Identity) GetDisplayName() string {
return i.Name
}
func (i *Identity) GetEmail() string {
return i.Email
}
func (i *Identity) GetIDToken() string {
return i.IDToken
}
func (i *Identity) GetIsGrafanaAdmin() bool {
return i.IsGrafanaAdmin != nil && *i.IsGrafanaAdmin
}
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
}
func (i *Identity) GetOrgName() string {
return i.OrgName
}
func (i *Identity) GetOrgRole() roletype.RoleType {
if i.OrgRoles == nil {
return roletype.RoleNone
}
if i.OrgRoles[i.GetOrgID()] == "" {
return roletype.RoleNone
}
return i.OrgRoles[i.GetOrgID()]
}
func (i *Identity) GetPermissions() map[string][]string {
if i.Permissions == nil {
return make(map[string][]string)
}
if i.Permissions[i.GetOrgID()] == nil {
return make(map[string][]string)
}
return i.Permissions[i.GetOrgID()]
}
func (i *Identity) GetTeams() []int64 {
return i.Teams
}
func (i *Identity) HasRole(role roletype.RoleType) bool {
if i.GetIsGrafanaAdmin() {
return true
}
return i.GetOrgRole().Includes(role)
}
func (i *Identity) HasUniqueId() bool {
namespace, _ := i.GetNamespacedID()
return namespace == NamespaceUser || namespace == NamespaceServiceAccount || namespace == NamespaceAPIKey
}
func (i *Identity) IsNil() bool {
return i == nil
} }
// NamespacedID returns the namespace, e.g. "user" and the id for that namespace // NamespacedID returns the namespace, e.g. "user" and the id for that namespace
// FIXME(kalleep): Replace with GetNamespacedID
func (i *Identity) NamespacedID() (string, int64) { func (i *Identity) NamespacedID() (string, int64) {
split := strings.Split(i.ID, ":") split := strings.Split(i.ID, ":")
if len(split) != 2 { if len(split) != 2 {
@ -104,21 +202,15 @@ func (i *Identity) NamespacedID() (string, int64) {
// SignedInUser returns a SignedInUser from the identity. // SignedInUser returns a SignedInUser from the identity.
func (i *Identity) SignedInUser() *user.SignedInUser { func (i *Identity) SignedInUser() *user.SignedInUser {
var isGrafanaAdmin bool
if i.IsGrafanaAdmin != nil {
isGrafanaAdmin = *i.IsGrafanaAdmin
}
u := &user.SignedInUser{ u := &user.SignedInUser{
UserID: 0,
OrgID: i.OrgID, OrgID: i.OrgID,
OrgName: i.OrgName, OrgName: i.OrgName,
OrgRole: i.Role(), OrgRole: i.GetOrgRole(),
Login: i.Login, Login: i.Login,
Name: i.Name, Name: i.Name,
Email: i.Email, Email: i.Email,
AuthenticatedBy: i.AuthenticatedBy, AuthenticatedBy: i.AuthenticatedBy,
IsGrafanaAdmin: isGrafanaAdmin, IsGrafanaAdmin: i.GetIsGrafanaAdmin(),
IsAnonymous: i.IsAnonymous, IsAnonymous: i.IsAnonymous,
IsDisabled: i.IsDisabled, IsDisabled: i.IsDisabled,
HelpFlags1: i.HelpFlags1, HelpFlags1: i.HelpFlags1,
@ -128,24 +220,34 @@ func (i *Identity) SignedInUser() *user.SignedInUser {
IDToken: i.IDToken, IDToken: i.IDToken,
} }
namespace, id := i.NamespacedID() namespace, id := i.GetNamespacedID()
if namespace == NamespaceAPIKey { if namespace == NamespaceAPIKey {
u.ApiKeyID = id u.ApiKeyID = intIdentifier(id)
} else { } else {
u.UserID = id u.UserID = intIdentifier(id)
u.IsServiceAccount = namespace == NamespaceServiceAccount u.IsServiceAccount = namespace == NamespaceServiceAccount
} }
return u return u
} }
func intIdentifier(identifier string) int64 {
id, err := strconv.ParseInt(identifier, 10, 64)
if err != nil {
// FIXME (kalleep): Improve error handling
return -1
}
return id
}
func (i *Identity) ExternalUserInfo() login.ExternalUserInfo { func (i *Identity) ExternalUserInfo() login.ExternalUserInfo {
_, id := i.NamespacedID() _, id := i.GetNamespacedID()
return login.ExternalUserInfo{ return login.ExternalUserInfo{
OAuthToken: i.OAuthToken, OAuthToken: i.OAuthToken,
AuthModule: i.AuthenticatedBy, AuthModule: i.AuthenticatedBy,
AuthId: i.AuthID, AuthId: i.AuthID,
UserId: id, UserId: intIdentifier(id),
Email: i.Email, Email: i.Email,
Login: i.Login, Login: i.Login,
Name: i.Name, Name: i.Name,