diff --git a/internal/authz/service.go b/internal/authz/service.go index 9db8a40..71f053a 100644 --- a/internal/authz/service.go +++ b/internal/authz/service.go @@ -76,3 +76,16 @@ func (s *Service) AuthorizeSystem(actor, actionID string) Decision { decision.Reason = "not-granted" return decision } + +func (s *Service) EffectiveSystemActions(actor string) []string { + actions := []string{} + for _, descriptor := range catalog.Catalog() { + if descriptor.Scope != catalog.ScopeSystem { + continue + } + if decision := s.AuthorizeSystem(actor, descriptor.ActionID); decision.Allowed { + actions = append(actions, descriptor.ActionID) + } + } + return actions +} diff --git a/internal/server/role_authorization_test.go b/internal/server/role_authorization_test.go index 01ed5d5..47999ef 100644 --- a/internal/server/role_authorization_test.go +++ b/internal/server/role_authorization_test.go @@ -82,6 +82,10 @@ func TestSystemUserActionsDoNotImplyRoleOrPermissionManagement(t *testing.T) { if rec := bearerRequest(h, "GET", "/api/admin/roles", "", key); rec.Code != http.StatusForbidden { t.Fatalf("users.view read roles: %d %s", rec.Code, rec.Body.String()) } + if rec := bearerRequest(h, "GET", "/api/me", "", key); rec.Code != http.StatusOK || + !strings.Contains(rec.Body.String(), `"systemActions":["system.users.view"]`) { + t.Fatalf("me system actions: %d %s", rec.Code, rec.Body.String()) + } if rec := bearerRequest(h, "GET", "/api/authz/explain?action=system.users.view", "", key); rec.Code != http.StatusOK { t.Fatalf("explain: %d %s", rec.Code, rec.Body.String()) } else { diff --git a/internal/server/server.go b/internal/server/server.go index aaeee79..bde4b06 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -885,6 +885,7 @@ func (s *Server) me(w http.ResponseWriter, r *http.Request) { out["isAdmin"] = st.IsAdmin out["mustChangePassword"] = st.MustChangePassword out["passwordSet"] = st.HasPassword + out["systemActions"] = s.authorizer.EffectiveSystemActions(owner) } writeJSON(w, http.StatusOK, out) }