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:
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user