IAM: Use the new authorizer for the User resource (#111479)

* Use the new authorizer for the User resource

* Use accessClient

* Update pkg/services/authz/rbac/mapper.go

Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>

---------

Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>
This commit is contained in:
Misi
2025-09-24 11:32:29 +02:00
committed by GitHub
co-authored by Gabriel MABILLE
parent 33ff6dbb9e
commit 54a347463e
7 changed files with 58 additions and 47 deletions
+1 -33
View File
@@ -23,7 +23,6 @@ func newIAMAuthorizer(accessClient authlib.AccessClient, legacyAccessClient auth
// Identity specific resources
legacyAuthorizer := gfauthorizer.NewResourceAuthorizer(legacyAccessClient)
resourceAuthorizer[iamv0.UserResourceInfo.GetName()] = legacyAuthorizer
resourceAuthorizer[iamv0.TeamResourceInfo.GetName()] = legacyAuthorizer
resourceAuthorizer["display"] = legacyAuthorizer
@@ -33,6 +32,7 @@ func newIAMAuthorizer(accessClient authlib.AccessClient, legacyAccessClient auth
resourceAuthorizer[iamv0.RoleInfo.GetName()] = authorizer
resourceAuthorizer[iamv0.ResourcePermissionInfo.GetName()] = authorizer
resourceAuthorizer[iamv0.ServiceAccountResourceInfo.GetName()] = authorizer
resourceAuthorizer[iamv0.UserResourceInfo.GetName()] = authorizer
return &iamAuthorizer{resourceAuthorizer: resourceAuthorizer}
}
@@ -53,25 +53,6 @@ func (s *iamAuthorizer) Authorize(ctx context.Context, attr authorizer.Attribute
func newLegacyAccessClient(ac accesscontrol.AccessControl, store legacy.LegacyIdentityStore) authlib.AccessClient {
client := accesscontrol.NewLegacyAccessClient(
ac,
accesscontrol.ResourceAuthorizerOptions{
Resource: iamv0.UserResourceInfo.GetName(),
Attr: "id",
Mapping: map[string]string{
utils.VerbCreate: accesscontrol.ActionUsersCreate,
utils.VerbDelete: accesscontrol.ActionUsersDelete,
utils.VerbGet: accesscontrol.ActionOrgUsersRead,
utils.VerbList: accesscontrol.ActionOrgUsersRead,
},
Resolver: accesscontrol.ResourceResolverFunc(func(ctx context.Context, ns authlib.NamespaceInfo, name string) ([]string, error) {
res, err := store.GetUserInternalID(ctx, ns, legacy.GetUserInternalIDQuery{
UID: name,
})
if err != nil {
return nil, err
}
return []string{fmt.Sprintf("users:id:%d", res.ID)}, nil
}),
},
accesscontrol.ResourceAuthorizerOptions{
Resource: "display",
Unchecked: map[string]bool{
@@ -79,19 +60,6 @@ func newLegacyAccessClient(ac accesscontrol.AccessControl, store legacy.LegacyId
utils.VerbList: true,
},
},
accesscontrol.ResourceAuthorizerOptions{
Resource: iamv0.ServiceAccountResourceInfo.GetName(),
Attr: "id",
Resolver: accesscontrol.ResourceResolverFunc(func(ctx context.Context, ns authlib.NamespaceInfo, name string) ([]string, error) {
res, err := store.GetServiceAccountInternalID(ctx, ns, legacy.GetServiceAccountInternalIDQuery{
UID: name,
})
if err != nil {
return nil, err
}
return []string{fmt.Sprintf("serviceaccounts:id:%d", res.ID)}, nil
}),
},
accesscontrol.ResourceAuthorizerOptions{
Resource: iamv0.TeamResourceInfo.GetName(),
Attr: "id",
+4 -4
View File
@@ -98,7 +98,7 @@ type ListUserQuery struct {
}
type ListUserResult struct {
Users []common.UserWithRole
Items []common.UserWithRole
Continue int64
RV int64
}
@@ -176,9 +176,9 @@ func (s *legacySQLStore) queryUsers(ctx context.Context, sql *legacysql.LegacyDa
}
lastID = u.ID
res.Users = append(res.Users, u)
if len(res.Users) > limit {
res.Users = res.Users[0 : len(res.Users)-1]
res.Items = append(res.Items, u)
if len(res.Items) > limit {
res.Items = res.Items[0 : len(res.Items)-1]
res.Continue = lastID
break
}
+2 -2
View File
@@ -173,7 +173,7 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *ge
// User store registration
userResource := iamv0.UserResourceInfo
legacyStore := user.NewLegacyStore(b.store, b.legacyAccessClient, b.enableAuthnMutation)
legacyStore := user.NewLegacyStore(b.store, b.accessClient, b.enableAuthnMutation)
storage[userResource.StoragePath()] = legacyStore
if b.enableDualWriter {
@@ -194,7 +194,7 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *ge
// Service Accounts store registration
serviceAccountResource := iamv0.ServiceAccountResourceInfo
saLegacyStore := serviceaccount.NewLegacyStore(b.store, b.legacyAccessClient, b.enableAuthnMutation)
saLegacyStore := serviceaccount.NewLegacyStore(b.store, b.accessClient, b.enableAuthnMutation)
storage[serviceAccountResource.StoragePath()] = saLegacyStore
if b.enableDualWriter {
+2 -2
View File
@@ -123,9 +123,9 @@ func (r *LegacyDisplayREST) handleDisplay(w http.ResponseWriter, req *http.Reque
rsp := &iam.DisplayList{
Keys: keys.keys,
InvalidKeys: keys.invalid,
Items: make([]iam.Display, 0, len(users.Users)+len(keys.disp)+1),
Items: make([]iam.Display, 0, len(users.Items)+len(keys.disp)+1),
}
for _, user := range users.Users {
for _, user := range users.Items {
disp := iam.Display{
Identity: iam.IdentityRef{
Type: authlib.TypeUser,
+6 -6
View File
@@ -75,11 +75,11 @@ func (s *LegacyStore) Delete(ctx context.Context, name string, deleteValidation
if err != nil {
return nil, false, err
}
if found == nil || len(found.Users) < 1 {
if found == nil || len(found.Items) < 1 {
return nil, false, resource.NewNotFound(name)
}
userToDelete := &found.Users[0]
userToDelete := &found.Items[0]
if deleteValidation != nil {
userObj := toUserItem(userToDelete, ns.Value)
@@ -135,8 +135,8 @@ func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOpt
return nil, err
}
users := make([]iamv0alpha1.User, 0, len(found.Users))
for _, u := range found.Users {
users := make([]iamv0alpha1.User, 0, len(found.Items))
for _, u := range found.Items {
users = append(users, toUserItem(&u, ns.Value))
}
@@ -172,11 +172,11 @@ func (s *LegacyStore) Get(ctx context.Context, name string, options *metav1.GetO
if found == nil || err != nil {
return nil, resource.NewNotFound(name)
}
if len(found.Users) < 1 {
if len(found.Items) < 1 {
return nil, resource.NewNotFound(name)
}
obj := toUserItem(&found.Users[0], ns.Value)
obj := toUserItem(&found.Items[0], ns.Value)
return &obj, nil
}
+2
View File
@@ -110,6 +110,8 @@ func NewMapperRegistry() MapperRegistry {
"folders": newResourceTranslation("folders", "uid", true, false),
},
"iam.grafana.app": {
// Users is a special case. We translate user permissions from id to uid based.
"users": newResourceTranslation("users", "uid", false, true),
"serviceaccounts": newResourceTranslation("serviceaccounts", "uid", false, true),
// Teams is a special case. We translate user permissions from id to uid based.
"teams": newResourceTranslation("teams", "uid", false, true),
+41
View File
@@ -119,6 +119,44 @@ func (s *Service) newTeamNameResolver(ctx context.Context, ns types.NamespaceInf
}, nil
}
func (s *Service) fetchUsers(ctx context.Context, ns types.NamespaceInfo) (map[int64]string, error) {
users, err := s.identityStore.ListUsers(ctx, ns, legacy.ListUserQuery{})
if err != nil {
return nil, fmt.Errorf("could not fetch users: %w", err)
}
userIDs := make(map[int64]string, len(users.Items))
for _, user := range users.Items {
userIDs[user.ID] = user.UID
}
return userIDs, nil
}
// Should return an error if we fail to build the resolver.
func (s *Service) newUserNameResolver(ctx context.Context, ns types.NamespaceInfo) (ScopeResolverFunc, error) {
return func(scope string) (string, error) {
userIDs, err := s.fetchUsers(ctx, ns)
if err != nil {
return "", fmt.Errorf("could not build resolver: %w", err)
}
userIDStr := strings.TrimPrefix(scope, "users:id:")
if userIDStr == "" {
return "", fmt.Errorf("user ID is empty")
}
if userIDStr == "*" {
return "users:uid:*", nil
}
userID, err := strconv.ParseInt(userIDStr, 10, 64)
if err != nil {
return "", fmt.Errorf("invalid user ID %s: %w", userIDStr, err)
}
if userName, ok := userIDs[userID]; ok {
return "users:uid:" + userName, nil
}
return "", fmt.Errorf("user ID %s not found", userIDStr)
}, nil
}
func permissionsDelegateResolverFunc(scope string) (string, error) {
if strings.TrimPrefix(scope, "permissions:type:") == "delegate" {
// The permissions:type:delegate scope does not have any discriminating value,
@@ -138,6 +176,9 @@ func (s *Service) nameResolver(ctx context.Context, ns types.NamespaceInfo, scop
if scopePrefix == "serviceaccounts:id:" {
return s.newServiceAccountNameResolver(ctx, ns)
}
if scopePrefix == "users:id:" {
return s.newUserNameResolver(ctx, ns)
}
// No resolver found for the given scope prefix.
return nil, nil
}