diff --git a/weed/admin/dash/user_inline_policy_test.go b/weed/admin/dash/user_inline_policy_test.go new file mode 100644 index 000000000..93db7125a --- /dev/null +++ b/weed/admin/dash/user_inline_policy_test.go @@ -0,0 +1,62 @@ +package dash + +import ( + "context" + "testing" + + "github.com/seaweedfs/seaweedfs/weed/credential" + _ "github.com/seaweedfs/seaweedfs/weed/credential/memory" // register the memory credential store + "github.com/seaweedfs/seaweedfs/weed/pb/iam_pb" + "github.com/seaweedfs/seaweedfs/weed/s3api/policy_engine" +) + +// TestGetObjectStoreUserDetailsIncludesInlinePolicies verifies that inline user +// policies — which are stored separately from the identity record and are +// authoritative at S3 enforcement time (they take precedence over the legacy +// Actions list) — are surfaced in the admin user details. They were previously +// invisible in the admin API/UI, so an operator could not tell what actually +// governed a user's access. +func TestGetObjectStoreUserDetailsIncludesInlinePolicies(t *testing.T) { + cm, err := credential.NewCredentialManagerWithDefaults(credential.StoreTypeMemory) + if err != nil { + t.Fatalf("failed to init credential manager: %v", err) + } + ctx := context.Background() + + const username = "svc-app" + if err := cm.CreateUser(ctx, &iam_pb.Identity{Name: username}); err != nil { + t.Fatalf("failed to create user: %v", err) + } + + const inlinePolicyName = "svc-app-policy" + doc := policy_engine.PolicyDocument{ + Version: "2012-10-17", + Statement: []policy_engine.PolicyStatement{ + { + Effect: "Allow", + Action: policy_engine.NewStringOrStringSlice("s3:GetObject"), + Resource: policy_engine.NewStringOrStringSlicePtr("arn:aws:s3:::ml-artifacts/*"), + }, + }, + } + if err := cm.PutUserInlinePolicy(ctx, username, inlinePolicyName, doc); err != nil { + t.Fatalf("failed to put inline policy: %v", err) + } + + s := &AdminServer{credentialManager: cm} + details, err := s.GetObjectStoreUserDetails(username) + if err != nil { + t.Fatalf("GetObjectStoreUserDetails failed: %v", err) + } + + found := false + for _, p := range details.PolicyNames { + if p == inlinePolicyName { + found = true + break + } + } + if !found { + t.Errorf("expected inline policy %q in details.PolicyNames, got %v", inlinePolicyName, details.PolicyNames) + } +} diff --git a/weed/admin/dash/user_management.go b/weed/admin/dash/user_management.go index d1b599acc..c7ed5baf7 100644 --- a/weed/admin/dash/user_management.go +++ b/weed/admin/dash/user_management.go @@ -9,6 +9,7 @@ import ( "time" "github.com/seaweedfs/seaweedfs/weed/credential" + "github.com/seaweedfs/seaweedfs/weed/glog" "github.com/seaweedfs/seaweedfs/weed/iam" "github.com/seaweedfs/seaweedfs/weed/pb/iam_pb" ) @@ -190,6 +191,17 @@ func (s *AdminServer) GetObjectStoreUserDetails(username string) (*UserDetails, PolicyNames: identity.PolicyNames, } + // Inline user policies are stored separately from the identity record and + // are authoritative at enforcement time (they take precedence over the + // legacy Actions list). Surface their names alongside attached policies so + // the admin reflects the user's actual permissions rather than only the + // (potentially overridden) Actions. + if inlinePolicyNames, err := s.credentialManager.ListUserInlinePolicies(ctx, username); err != nil { + glog.Warningf("failed to list inline policies for user %s: %v", username, err) + } else if len(inlinePolicyNames) > 0 { + details.PolicyNames = append(details.PolicyNames, inlinePolicyNames...) + } + // Set email from account if available if identity.Account != nil { details.Email = identity.Account.EmailAddress