From 4aeb65d212698044316e0db542656bd6fd479299 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Wed, 4 Feb 2026 10:37:00 +0100 Subject: [PATCH 1/5] allow creating orgs to tokens with proper permissions Signed-off-by: Jose I. Paris --- .../internal/service/organization.go | 33 +++++++---------- app/controlplane/internal/service/service.go | 35 +++++++++++++++++++ app/controlplane/pkg/authz/authz.go | 3 +- 3 files changed, 50 insertions(+), 21 deletions(-) diff --git a/app/controlplane/internal/service/organization.go b/app/controlplane/internal/service/organization.go index 1268c1bfd..fda34b92e 100644 --- a/app/controlplane/internal/service/organization.go +++ b/app/controlplane/internal/service/organization.go @@ -43,9 +43,9 @@ func NewOrganizationService(muc *biz.MembershipUseCase, ouc *biz.OrganizationUse } } -// Create persists an organization with a given name and associate it to the current user. +// Create persists an organization with a given name and associates it with the current user. func (s *OrganizationService) Create(ctx context.Context, req *pb.OrganizationServiceCreateRequest) (*pb.OrganizationServiceCreateResponse, error) { - currentUser, err := requireCurrentUser(ctx) + currentUser, _, err := requireCurrentUserOrAPIToken(ctx) if err != nil { return nil, err } @@ -65,8 +65,11 @@ func (s *OrganizationService) Create(ctx context.Context, req *pb.OrganizationSe return nil, handleUseCaseErr(err, s.log) } - if _, err := s.membershipUC.Create(ctx, org.ID, currentUser.ID, biz.WithMembershipRole(authz.RoleOwner), biz.WithCurrentMembership()); err != nil { - return nil, handleUseCaseErr(err, s.log) + // Add membership if invoker is a user + if currentUser != nil { + if _, err := s.membershipUC.Create(ctx, org.ID, currentUser.ID, biz.WithMembershipRole(authz.RoleOwner), biz.WithCurrentMembership()); err != nil { + return nil, handleUseCaseErr(err, s.log) + } } return &pb.OrganizationServiceCreateResponse{Result: bizOrgToPb(org)}, nil @@ -211,25 +214,15 @@ func (s *OrganizationService) UpdateMembership(ctx context.Context, req *pb.Orga } func (s *OrganizationService) canCreateOrganization(ctx context.Context) (bool, error) { - // Restricted org creation is disabled, allow creation - if !s.authz.RestrictOrgCreation { + // Restricted org creation is disabled, allow creation only to users + if !s.authz.RestrictOrgCreation && entities.CurrentMembership(ctx) != nil { return true, nil } - m := entities.CurrentMembership(ctx) - for _, rm := range m.Resources { - if rm.ResourceType != authz.ResourceTypeInstance { - continue - } - - pass, err := s.authz.Enforce(ctx, string(rm.Role), authz.PolicyOrganizationCreate) - if err != nil { - return false, handleUseCaseErr(err, s.log) - } - if pass { - return true, nil - } + // otherwise, check for permissions + if err := s.checkPolicy(ctx, authz.PolicyOrganizationCreate); err != nil { + return false, err } - return false, nil + return true, nil } diff --git a/app/controlplane/internal/service/service.go b/app/controlplane/internal/service/service.go index 44830d9aa..bcdda7ee6 100644 --- a/app/controlplane/internal/service/service.go +++ b/app/controlplane/internal/service/service.go @@ -329,6 +329,41 @@ func (s *service) visibleProjects(ctx context.Context) []uuid.UUID { return projects } +// checkPolicy Checks a policy against a user or a token +func (s *service) checkPolicy(ctx context.Context, policy *authz.Policy) error { + _, token, err := requireCurrentUserOrAPIToken(ctx) + if err != nil { + return err + } + + // Token case + if token != nil { + for _, p := range token.Policies { + if p.Resource == policy.Resource && p.Action == policy.Action { + return nil + } + } + return errors.Forbidden("forbidden", "not allowed") + } + + // user case + m := entities.CurrentMembership(ctx) + if m == nil { + return errors.Forbidden("forbidden", "not allowed") + } + for _, rm := range m.Resources { + pass, err := s.authz.Enforce(ctx, string(rm.Role), authz.PolicyOrganizationCreate) + if err != nil { + return handleUseCaseErr(err, s.log) + } + if pass { + return nil + } + } + + return errors.Forbidden("forbidden", "not allowed") +} + // isUserOrgAdmin checks if the current user is an org admin or owner func isUserOrgAdmin(ctx context.Context) bool { userRole := usercontext.CurrentAuthzSubject(ctx) diff --git a/app/controlplane/pkg/authz/authz.go b/app/controlplane/pkg/authz/authz.go index bad0a6534..25d84e1fc 100644 --- a/app/controlplane/pkg/authz/authz.go +++ b/app/controlplane/pkg/authz/authz.go @@ -386,7 +386,8 @@ var ServerOperationsMap = map[string][]*Policy{ "/controlplane.v1.ContextService/Current": {PolicyOrganizationRead}, // Listing, create or selecting an organization does not have any required permissions, // since all the permissions here are in the context of an organization - // Create new organization + // Create new organization. No user required at middleware level. The endpoint will handle + // it based on diverse conditions "/controlplane.v1.OrganizationService/Create": {}, // Delete an organization makes checks at the service level since the // user can explicitly set the org they want to delete and might not be the current one From 838f00cf28a6069771a62b3594de5647b26c78bb Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Wed, 4 Feb 2026 11:12:34 +0100 Subject: [PATCH 2/5] fix Signed-off-by: Jose I. Paris --- app/controlplane/internal/service/organization.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/controlplane/internal/service/organization.go b/app/controlplane/internal/service/organization.go index fda34b92e..b4c7219ab 100644 --- a/app/controlplane/internal/service/organization.go +++ b/app/controlplane/internal/service/organization.go @@ -215,8 +215,11 @@ func (s *OrganizationService) UpdateMembership(ctx context.Context, req *pb.Orga func (s *OrganizationService) canCreateOrganization(ctx context.Context) (bool, error) { // Restricted org creation is disabled, allow creation only to users - if !s.authz.RestrictOrgCreation && entities.CurrentMembership(ctx) != nil { - return true, nil + if !s.authz.RestrictOrgCreation { + if entities.CurrentMembership(ctx) != nil { + return true, nil + } + return false, nil } // otherwise, check for permissions From 32add98be1810ffe36d5bd2a0f32489978cb76f8 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Wed, 4 Feb 2026 14:17:25 +0100 Subject: [PATCH 3/5] use enforcer Signed-off-by: Jose I. Paris --- app/controlplane/internal/service/service.go | 21 +++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/app/controlplane/internal/service/service.go b/app/controlplane/internal/service/service.go index bcdda7ee6..a035414e2 100644 --- a/app/controlplane/internal/service/service.go +++ b/app/controlplane/internal/service/service.go @@ -331,22 +331,19 @@ func (s *service) visibleProjects(ctx context.Context) []uuid.UUID { // checkPolicy Checks a policy against a user or a token func (s *service) checkPolicy(ctx context.Context, policy *authz.Policy) error { - _, token, err := requireCurrentUserOrAPIToken(ctx) - if err != nil { - return err - } - // Token case - if token != nil { - for _, p := range token.Policies { - if p.Resource == policy.Resource && p.Action == policy.Action { - return nil - } + sub := usercontext.CurrentAuthzSubject(ctx) + if sub != "" { + ok, err := s.authz.Enforce(ctx, sub, policy) + if err != nil { + return handleUseCaseErr(err, s.log) + } + if ok { + return nil } - return errors.Forbidden("forbidden", "not allowed") } - // user case + // Other cases m := entities.CurrentMembership(ctx) if m == nil { return errors.Forbidden("forbidden", "not allowed") From 0a2f322d3ccd6432494d2049c02bc5b1e89a4bd2 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Wed, 4 Feb 2026 14:23:28 +0100 Subject: [PATCH 4/5] check policies in all cases Signed-off-by: Jose I. Paris --- app/controlplane/internal/service/organization.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/app/controlplane/internal/service/organization.go b/app/controlplane/internal/service/organization.go index b4c7219ab..55e0f316f 100644 --- a/app/controlplane/internal/service/organization.go +++ b/app/controlplane/internal/service/organization.go @@ -214,15 +214,12 @@ func (s *OrganizationService) UpdateMembership(ctx context.Context, req *pb.Orga } func (s *OrganizationService) canCreateOrganization(ctx context.Context) (bool, error) { - // Restricted org creation is disabled, allow creation only to users - if !s.authz.RestrictOrgCreation { - if entities.CurrentMembership(ctx) != nil { - return true, nil - } - return false, nil + // if org creation restriction is disabled, allow creation to all users + if !s.authz.RestrictOrgCreation && entities.CurrentMembership(ctx) != nil { + return true, nil } - // otherwise, check for permissions + // otherwise, check for permissions (both users and API tokens) if err := s.checkPolicy(ctx, authz.PolicyOrganizationCreate); err != nil { return false, err } From 4803ca3f6a49a23d46af61bde499edf8a7d5e77f Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Wed, 4 Feb 2026 23:57:33 +0100 Subject: [PATCH 5/5] use currentuser Signed-off-by: Jose I. Paris --- app/controlplane/internal/service/organization.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controlplane/internal/service/organization.go b/app/controlplane/internal/service/organization.go index 55e0f316f..2d12a2552 100644 --- a/app/controlplane/internal/service/organization.go +++ b/app/controlplane/internal/service/organization.go @@ -215,7 +215,7 @@ func (s *OrganizationService) UpdateMembership(ctx context.Context, req *pb.Orga func (s *OrganizationService) canCreateOrganization(ctx context.Context) (bool, error) { // if org creation restriction is disabled, allow creation to all users - if !s.authz.RestrictOrgCreation && entities.CurrentMembership(ctx) != nil { + if !s.authz.RestrictOrgCreation && entities.CurrentUser(ctx) != nil { return true, nil }