diff --git a/app/controlplane/internal/service/organization.go b/app/controlplane/internal/service/organization.go index 1268c1bfd..2d12a2552 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 { + // if org creation restriction is disabled, allow creation to all users + if !s.authz.RestrictOrgCreation && entities.CurrentUser(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 (both users and API tokens) + 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..a035414e2 100644 --- a/app/controlplane/internal/service/service.go +++ b/app/controlplane/internal/service/service.go @@ -329,6 +329,38 @@ 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 case + 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 + } + } + + // Other cases + 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