")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
diff --git a/weed/credential/grpc/grpc_group.go b/weed/credential/grpc/grpc_group.go
index 9e2262dd7..394dc13ca 100644
--- a/weed/credential/grpc/grpc_group.go
+++ b/weed/credential/grpc/grpc_group.go
@@ -7,6 +7,11 @@ import (
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
)
+// NOTE: The gRPC store uses a load-modify-save pattern for all operations,
+// which is inherently subject to race conditions under concurrent access.
+// This matches the existing pattern used for identities and policies.
+// A future improvement would add dedicated gRPC RPCs for atomic group operations.
+
func (store *IamGrpcStore) CreateGroup(ctx context.Context, group *iam_pb.Group) error {
config, err := store.LoadConfiguration(ctx)
if err != nil {
diff --git a/weed/iamapi/iamapi_group_handlers.go b/weed/iamapi/iamapi_group_handlers.go
index 0af42880b..dc4071d94 100644
--- a/weed/iamapi/iamapi_group_handlers.go
+++ b/weed/iamapi/iamapi_group_handlers.go
@@ -228,21 +228,32 @@ func (iama *IamApiServer) ListGroupsForUser(s3cfg *iam_pb.S3ApiConfiguration, va
if !userFound {
return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("user %s does not exist", userName)}
}
- for _, g := range s3cfg.Groups {
- for _, m := range g.Members {
- if m == userName {
- name := g.Name
- resp.ListGroupsForUserResult.Groups = append(resp.ListGroupsForUserResult.Groups, &iam.Group{GroupName: &name})
- break
- }
- }
+ // Build reverse index for efficient lookup
+ userGroupsIndex := buildUserGroupsIndex(s3cfg)
+ for _, gName := range userGroupsIndex[userName] {
+ name := gName
+ resp.ListGroupsForUserResult.Groups = append(resp.ListGroupsForUserResult.Groups, &iam.Group{GroupName: &name})
}
return resp, nil
}
// removeUserFromAllGroups removes a user from all groups they belong to.
+// Uses a reverse index for efficient lookup of which groups to modify.
func removeUserFromAllGroups(s3cfg *iam_pb.S3ApiConfiguration, userName string) {
+ userGroupsIndex := buildUserGroupsIndex(s3cfg)
+ groupNames, found := userGroupsIndex[userName]
+ if !found {
+ return
+ }
+ // Build a set for fast group name lookup
+ targetGroups := make(map[string]bool, len(groupNames))
+ for _, gn := range groupNames {
+ targetGroups[gn] = true
+ }
for _, g := range s3cfg.Groups {
+ if !targetGroups[g.Name] {
+ continue
+ }
for i, m := range g.Members {
if m == userName {
g.Members = append(g.Members[:i], g.Members[i+1:]...)
@@ -276,6 +287,17 @@ func isPolicyAttachedToAnyGroup(s3cfg *iam_pb.S3ApiConfiguration, policyName str
return "", false
}
+// buildUserGroupsIndex builds a reverse index mapping usernames to group names.
+func buildUserGroupsIndex(s3cfg *iam_pb.S3ApiConfiguration) map[string][]string {
+ index := make(map[string][]string)
+ for _, g := range s3cfg.Groups {
+ for _, m := range g.Members {
+ index[m] = append(index[m], g.Name)
+ }
+ }
+ return index
+}
+
// policyNameFromArn extracts policy name from ARN for standalone handlers.
func policyNameFromArn(policyArn string) string {
return strings.TrimPrefix(policyArn, policyArnPrefix)
diff --git a/weed/s3api/s3api_embedded_iam.go b/weed/s3api/s3api_embedded_iam.go
index 320d8e22e..8ea95b666 100644
--- a/weed/s3api/s3api_embedded_iam.go
+++ b/weed/s3api/s3api_embedded_iam.go
@@ -1657,14 +1657,13 @@ func (e *EmbeddedIamApi) ListGroupsForUser(s3cfg *iam_pb.S3ApiConfiguration, val
if !userFound {
return resp, &iamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("user %s does not exist", userName)}
}
- for _, g := range s3cfg.Groups {
- for _, m := range g.Members {
- if m == userName {
- name := g.Name
- resp.ListGroupsForUserResult.Groups = append(resp.ListGroupsForUserResult.Groups, &iam.Group{GroupName: &name})
- break
- }
- }
+ // Use the in-memory reverse index for O(1) lookup
+ e.iam.m.RLock()
+ groupNames := e.iam.userGroups[userName]
+ e.iam.m.RUnlock()
+ for _, gName := range groupNames {
+ name := gName
+ resp.ListGroupsForUserResult.Groups = append(resp.ListGroupsForUserResult.Groups, &iam.Group{GroupName: &name})
}
return resp, nil
}