From da766c9b3465e64bc8ca75d7bedce716ee478ba8 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Tue, 15 Jul 2025 17:25:40 +0200 Subject: [PATCH 01/24] IsAdmin method Signed-off-by: Jose I. Paris --- .../internal/service/cascredential.go | 2 +- app/controlplane/internal/service/service.go | 4 +- .../usercontext/currentuser_middleware.go | 2 +- app/controlplane/pkg/authz/authz.go | 40 ++++++++++++++++++- .../pkg/authz/middleware/middleware.go | 2 +- app/controlplane/pkg/biz/group.go | 11 ++--- app/controlplane/pkg/biz/project.go | 2 +- 7 files changed, 47 insertions(+), 16 deletions(-) diff --git a/app/controlplane/internal/service/cascredential.go b/app/controlplane/internal/service/cascredential.go index 640155827..f98ba77fc 100644 --- a/app/controlplane/internal/service/cascredential.go +++ b/app/controlplane/internal/service/cascredential.go @@ -128,7 +128,7 @@ func (s *CASCredentialsService) Get(ctx context.Context, req *pb.CASCredentialsS if mapping != nil { backend = mapping.CASBackend - } else if currentAuthzSubject == string(authz.RoleAdmin) || currentAuthzSubject == string(authz.RoleOwner) { + } else if authz.Role(currentAuthzSubject).IsAdmin() { // fallback to default mapping for admins backend = defaultBackend } diff --git a/app/controlplane/internal/service/service.go b/app/controlplane/internal/service/service.go index 4c0000249..8977dd963 100644 --- a/app/controlplane/internal/service/service.go +++ b/app/controlplane/internal/service/service.go @@ -284,7 +284,7 @@ func (s *service) userHasPermissionOnGroupMembershipsWithPolicy(ctx context.Cont } // Allow if user has admin or owner role - if userRole == string(authz.RoleAdmin) || userRole == string(authz.RoleOwner) { + if authz.Role(userRole).IsAdmin() { return nil } @@ -381,7 +381,7 @@ func rbacEnabled(ctx context.Context) bool { // we have an user currentSubject := usercontext.CurrentAuthzSubject(ctx) - return currentSubject == string(authz.RoleOrgMember) + return authz.Role(currentSubject).RBACEnabled() } // NOTE: some of these http errors get automatically translated to gRPC status codes diff --git a/app/controlplane/internal/usercontext/currentuser_middleware.go b/app/controlplane/internal/usercontext/currentuser_middleware.go index a4942588c..f243f0f20 100644 --- a/app/controlplane/internal/usercontext/currentuser_middleware.go +++ b/app/controlplane/internal/usercontext/currentuser_middleware.go @@ -129,7 +129,7 @@ func WithAttestationContextFromUser(userUC *biz.UserUseCase, membershipUC *biz.M // TODO: move to authz middleware once we add support for all the tokens // for now in that middleware we are not mapping admins nor owners to a specific role // Admins and Owners can perform any operation. Members will need additional RBAC behaviour at service layer - if subject != string(authz.RoleAdmin) && subject != string(authz.RoleOwner) && subject != string(authz.RoleOrgMember) { + if !authz.Role(subject).IsAdmin() && !authz.Role(subject).RBACEnabled() { return nil, fmt.Errorf("your user doesn't have permissions to perform attestations in this organization, role=%s, orgID=%s", subject, org.ID) } diff --git a/app/controlplane/pkg/authz/authz.go b/app/controlplane/pkg/authz/authz.go index a62ad3cb3..79fdf1bce 100644 --- a/app/controlplane/pkg/authz/authz.go +++ b/app/controlplane/pkg/authz/authz.go @@ -24,6 +24,15 @@ type Policy struct { type Role string +// RBACEnabled returns whether an org-scoped role has RBAC enabled and needs resource-scoped enforcement. +func (r Role) RBACEnabled() bool { + return r == RoleOrgMember || r == RoleOrgContributor +} + +func (r Role) IsAdmin() bool { + return r == RoleAdmin || r == RoleOwner +} + const ( // Actions @@ -67,12 +76,14 @@ const ( // New RBAC roles - // RoleOrgMember is the role that users get by default when they join an organization. - // They cannot see projects until they are invited. However, they are able to create their own projects, + // RoleOrgMember cannot see projects until they are invited. However, they are able to create their own projects, // so Casbin rules (role, resource-type, action) are NOT enough to check for permission, since we must check for ownership as well. // That last check will be done at the service level. RoleOrgMember Role = "role:org:member" + // RoleOrgContributor can work on projects they are invited to with scoped role ProjectAdmin or ProjectViewer, but they cannot create their own projects. + RoleOrgContributor Role = "role:org:contributor" + RoleProjectAdmin Role = "role:project:admin" RoleProjectViewer Role = "role:project:viewer" @@ -214,8 +225,32 @@ var RolesMap = map[Role][]*Policy{ PolicyOrganizationInvitationsCreate, // + all the policies from the viewer role inherited automatically }, + // RoleOrgMember is an org-scoped role that enables RBAC in the underlying resources. Users with this role at // the organization level will need specific project roles to access their contents + RoleOrgContributor: { + // Referrer + PolicyReferrerRead, + // Artifact + PolicyArtifactDownload, + // Attached integrations + PolicyAttachedIntegrationList, + // Metrics + PolicyOrgMetricsRead, + // Workflow Contract + PolicyWorkflowContractList, + PolicyWorkflowContractRead, + // WorkflowRun + PolicyWorkflowRunList, + PolicyWorkflowRunRead, + // Workflow + PolicyWorkflowList, + PolicyWorkflowRead, + // Organization + PolicyOrganizationRead, + }, + + // RoleOrgMember are contributors that can also create their own projects RoleOrgMember: { // Allowed endpoints. RBAC will be applied where needed PolicyWorkflowRead, @@ -437,6 +472,7 @@ func (Role) Values() (roles []string) { // RBAC roles RoleOrgMember, + RoleOrgContributor, RoleProjectAdmin, RoleProjectViewer, RoleGroupMaintainer, diff --git a/app/controlplane/pkg/authz/middleware/middleware.go b/app/controlplane/pkg/authz/middleware/middleware.go index d90514ce9..10b17637b 100644 --- a/app/controlplane/pkg/authz/middleware/middleware.go +++ b/app/controlplane/pkg/authz/middleware/middleware.go @@ -57,7 +57,7 @@ func WithAuthzMiddleware(enforcer Enforcer, logger *log.Helper) middleware.Middl // We do not have all the policies related to an admin user defined yet // so for now we skip the authorization check for admin users since they are allowed to do anything // TODO: fill out the rest of the policies in authz.ServerOperationsMap and remove this check - if subject == string(authz.RoleAdmin) || subject == string(authz.RoleOwner) { + if authz.Role(subject).IsAdmin() { logger.Infow("msg", "[authZ] skipped", "sub", subject, "operation", apiOperation) return handler(ctx, req) } diff --git a/app/controlplane/pkg/biz/group.go b/app/controlplane/pkg/biz/group.go index 3f506410b..96659c2c0 100644 --- a/app/controlplane/pkg/biz/group.go +++ b/app/controlplane/pkg/biz/group.go @@ -499,8 +499,7 @@ func (uc *GroupUseCase) validateRequesterPermissions(ctx context.Context, orgID, } // Allow if the requester is an org owner or admin - isAdminOrOwner := requesterMembership.Role == authz.RoleOwner || requesterMembership.Role == authz.RoleAdmin - if isAdminOrOwner { + if requesterMembership.Role.IsAdmin() { return nil } @@ -646,10 +645,8 @@ func (uc *GroupUseCase) RemoveMemberFromGroup(ctx context.Context, orgID uuid.UU // Check if the requester has sufficient permissions // Allow if the requester is an org owner or admin - isAdminOrOwner := requesterMembership.Role == authz.RoleOwner || requesterMembership.Role == authz.RoleAdmin - // If not an admin/owner, check if the requester is a maintainer of this group - if !isAdminOrOwner { + if !requesterMembership.Role.IsAdmin() { // Check if the requester is a maintainer of this group requesterGroupMembership, err := uc.membershipRepo.FindByUserAndResourceID(ctx, opts.RequesterID, resolvedGroupID) if err != nil && !IsNotFound(err) { @@ -744,10 +741,8 @@ func (uc *GroupUseCase) UpdateMemberMaintainerStatus(ctx context.Context, orgID // Check if the requester has sufficient permissions // Allow if the requester is an org owner or admin - isAdminOrOwner := requesterMembership.Role == authz.RoleOwner || requesterMembership.Role == authz.RoleAdmin - // If not an admin/owner, check if the requester is a maintainer of this group - if !isAdminOrOwner { + if !requesterMembership.Role.IsAdmin() { // Check if the requester is a maintainer of this group requesterGroupMembership, err := uc.membershipRepo.FindByUserAndResourceID(ctx, opts.RequesterID, resolvedGroupID) if err != nil && !IsNotFound(err) { diff --git a/app/controlplane/pkg/biz/project.go b/app/controlplane/pkg/biz/project.go index a1aa48248..ee84e4692 100644 --- a/app/controlplane/pkg/biz/project.go +++ b/app/controlplane/pkg/biz/project.go @@ -647,7 +647,7 @@ func (uc *ProjectUseCase) verifyRequesterHasPermissions(ctx context.Context, org } // Second check: if requester is an org owner or admin, they have all permissions - if currentOrgMembership.Role == authz.RoleOwner || currentOrgMembership.Role == authz.RoleAdmin { + if currentOrgMembership.Role.IsAdmin() { return nil } From 765b766d467f69ea71834087c1d1bc2140d58a04 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Tue, 15 Jul 2025 17:27:10 +0200 Subject: [PATCH 02/24] rbacenabled Signed-off-by: Jose I. Paris --- app/controlplane/pkg/biz/group.go | 2 +- app/controlplane/pkg/biz/membership.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controlplane/pkg/biz/group.go b/app/controlplane/pkg/biz/group.go index 96659c2c0..347c97900 100644 --- a/app/controlplane/pkg/biz/group.go +++ b/app/controlplane/pkg/biz/group.go @@ -552,7 +552,7 @@ func (uc *GroupUseCase) handleNonExistingUser(ctx context.Context, orgID, groupI } // Create an invitation for the user to join the organization - if _, err := uc.orgInvitationUC.Create(ctx, orgID.String(), opts.RequesterID.String(), opts.UserEmail, WithInvitationRole(authz.RoleOrgMember), WithInvitationContext(invitationContext)); err != nil { + if _, err := uc.orgInvitationUC.Create(ctx, orgID.String(), opts.RequesterID.String(), opts.UserEmail, WithInvitationRole(authz.RoleOrgContributor), WithInvitationContext(invitationContext)); err != nil { return nil, fmt.Errorf("failed to create invitation: %w", err) } diff --git a/app/controlplane/pkg/biz/membership.go b/app/controlplane/pkg/biz/membership.go index 1e6f0208f..2be7a9000 100644 --- a/app/controlplane/pkg/biz/membership.go +++ b/app/controlplane/pkg/biz/membership.go @@ -393,7 +393,7 @@ func (uc *MembershipUseCase) GetOrgsAndRBACInfoForUser(ctx context.Context, user if m.ResourceType == authz.ResourceTypeOrganization { userOrgs = append(userOrgs, m.ResourceID) // If the role in the org is member, we must enable RBAC for projects. - if m.Role == authz.RoleOrgMember { + if m.Role.RBACEnabled() { // get the list of projects in org, and match it with the memberships to build a filter. // note that appending an empty slice to a nil slice doesn't change it (it's still nil) projectIDs[m.ResourceID] = getProjectsWithMembershipInOrg(m.ResourceID, memberships) From bc3b4384a81e2caa28d6ddc57cdf226b9f783ab3 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Wed, 16 Jul 2025 15:06:50 +0200 Subject: [PATCH 03/24] contributor role, projectcreate permission Signed-off-by: Jose I. Paris --- .../internal/service/attestation.go | 6 +++- app/controlplane/internal/service/service.go | 21 ++++++++++++ app/controlplane/internal/service/workflow.go | 6 +++- app/controlplane/pkg/authz/authz.go | 33 ++++++------------- app/controlplane/pkg/authz/enforcer.go | 6 ++++ 5 files changed, 47 insertions(+), 25 deletions(-) diff --git a/app/controlplane/internal/service/attestation.go b/app/controlplane/internal/service/attestation.go index 50c9e9f9d..ebf9cb849 100644 --- a/app/controlplane/internal/service/attestation.go +++ b/app/controlplane/internal/service/attestation.go @@ -674,10 +674,14 @@ func (s *AttestationService) FindOrCreateWorkflow(ctx context.Context, req *cpAP // try to load project and apply RBAC if needed if _, err := s.userHasPermissionOnProject(ctx, apiToken.OrgID, &cpAPI.IdentityReference{Name: &req.ProjectName}, authz.PolicyWorkflowCreate); err != nil { - // if the project is not found, we ignore the error, since we'll create the project in this call + // if the project is not found, check if user can create projects if !errors.IsNotFound(err) { return nil, err } + + if err = s.userCanCreateProject(ctx); err != nil { + return nil, err + } } // we need this token to forward it to the provider service next diff --git a/app/controlplane/internal/service/service.go b/app/controlplane/internal/service/service.go index 40774d001..3a2503fc0 100644 --- a/app/controlplane/internal/service/service.go +++ b/app/controlplane/internal/service/service.go @@ -253,6 +253,27 @@ func (s *service) userHasPermissionOnProject(ctx context.Context, orgID string, return p, nil } +func (s *service) userCanCreateProject(ctx context.Context) error { + // Only org tokens can create projects + if token := entities.CurrentAPIToken(ctx); token != nil { + if token.ProjectID != nil { + return errors.Forbidden("unauthorized", "user cannot create project") + } + } + + orgRole := usercontext.CurrentAuthzSubject(ctx) + pass, err := s.enforcer.Enforce(orgRole, authz.PolicyProjectCreate) + if err != nil { + return handleUseCaseErr(err, s.log) + } + + if !pass { + return errors.Forbidden("unauthorized", "user cannot create project") + } + + return nil +} + // userHasPermissionToAddGroupMember checks if the user has permission to add members to a group func (s *service) userHasPermissionToAddGroupMember(ctx context.Context, orgID string, groupIdentifier *pb.IdentityReference) error { return s.userHasPermissionOnGroupMembershipsWithPolicy(ctx, orgID, groupIdentifier, authz.PolicyGroupAddMemberships) diff --git a/app/controlplane/internal/service/workflow.go b/app/controlplane/internal/service/workflow.go index c44625d6d..4c918f1ba 100644 --- a/app/controlplane/internal/service/workflow.go +++ b/app/controlplane/internal/service/workflow.go @@ -60,10 +60,14 @@ func (s *WorkflowService) Create(ctx context.Context, req *pb.WorkflowServiceCre } if _, err = s.userHasPermissionOnProject(ctx, currentOrg.ID, &pb.IdentityReference{Name: &req.ProjectName}, authz.PolicyWorkflowCreate); err != nil { - // if the project is not found, we ignore the error, since we'll create the project in this call + // if the project is not found, check if user can create projects if !errors.IsNotFound(err) { return nil, err } + + if err = s.userCanCreateProject(ctx); err != nil { + return nil, err + } } createOpts := &biz.WorkflowCreateOpts{ diff --git a/app/controlplane/pkg/authz/authz.go b/app/controlplane/pkg/authz/authz.go index 79fdf1bce..2bccb4e5a 100644 --- a/app/controlplane/pkg/authz/authz.go +++ b/app/controlplane/pkg/authz/authz.go @@ -55,6 +55,7 @@ const ( ResourceRobotAccount = "robot_account" ResourceWorkflowRun = "workflow_run" ResourceWorkflow = "workflow" + ResourceProject = "project" Organization = "organization" OrganizationMemberships = "organization_memberships" ResourceGroup = "group" @@ -104,6 +105,7 @@ var ManagedResources = []string{ ResourceRobotAccount, ResourceWorkflowRun, ResourceWorkflow, + ResourceProject, Organization, OrganizationMemberships, ResourceGroup, @@ -154,6 +156,8 @@ var ( PolicyWorkflowCreate = &Policy{ResourceWorkflow, ActionCreate} PolicyWorkflowUpdate = &Policy{ResourceWorkflow, ActionUpdate} PolicyWorkflowDelete = &Policy{ResourceWorkflow, ActionDelete} + // Projects + PolicyProjectCreate = &Policy{ResourceProject, ActionCreate} // User Membership PolicyOrganizationRead = &Policy{Organization, ActionRead} PolicyOrganizationListMemberships = &Policy{OrganizationMemberships, ActionList} @@ -229,29 +233,6 @@ var RolesMap = map[Role][]*Policy{ // RoleOrgMember is an org-scoped role that enables RBAC in the underlying resources. Users with this role at // the organization level will need specific project roles to access their contents RoleOrgContributor: { - // Referrer - PolicyReferrerRead, - // Artifact - PolicyArtifactDownload, - // Attached integrations - PolicyAttachedIntegrationList, - // Metrics - PolicyOrgMetricsRead, - // Workflow Contract - PolicyWorkflowContractList, - PolicyWorkflowContractRead, - // WorkflowRun - PolicyWorkflowRunList, - PolicyWorkflowRunRead, - // Workflow - PolicyWorkflowList, - PolicyWorkflowRead, - // Organization - PolicyOrganizationRead, - }, - - // RoleOrgMember are contributors that can also create their own projects - RoleOrgMember: { // Allowed endpoints. RBAC will be applied where needed PolicyWorkflowRead, PolicyWorkflowContractList, @@ -312,6 +293,12 @@ var RolesMap = map[Role][]*Policy{ // Org memberships PolicyOrganizationListMemberships, }, + + // RoleOrgMember are contributors that can also create their own projects + RoleOrgMember: { + PolicyProjectCreate, + }, + // RoleProjectViewer: has read-only permissions on a project RoleProjectViewer: { PolicyWorkflowRead, diff --git a/app/controlplane/pkg/authz/enforcer.go b/app/controlplane/pkg/authz/enforcer.go index 2301c90fe..b464dac59 100644 --- a/app/controlplane/pkg/authz/enforcer.go +++ b/app/controlplane/pkg/authz/enforcer.go @@ -258,5 +258,11 @@ func doSync(e *Enforcer, c *Config) error { return fmt.Errorf("failed to add grouping policy: %w", err) } + // Members are contributors as well + _, err = e.AddGroupingPolicy(string(RoleOrgMember), string(RoleOrgContributor)) + if err != nil { + return fmt.Errorf("failed to add grouping policy: %w", err) + } + return nil } From a0257dbbf722d96551f3025b335671a0b5c23381 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Wed, 16 Jul 2025 19:23:51 +0200 Subject: [PATCH 04/24] update protos and regenerate Signed-off-by: Jose I. Paris --- .../controlplane/v1/response_messages.pb.go | 99 ++++++++++--------- .../controlplane/v1/response_messages.proto | 1 + .../controlplane/v1/response_messages.ts | 6 ++ .../frontend/google/protobuf/descriptor.ts | 16 +-- ...plane.v1.OrgInvitationItem.jsonschema.json | 3 +- ...trolplane.v1.OrgInvitationItem.schema.json | 3 +- ...tationServiceCreateRequest.jsonschema.json | 3 +- ...InvitationServiceCreateRequest.schema.json | 3 +- ...plane.v1.OrgMembershipItem.jsonschema.json | 3 +- ...trolplane.v1.OrgMembershipItem.schema.json | 3 +- ...iceUpdateMembershipRequest.jsonschema.json | 3 +- ...ServiceUpdateMembershipRequest.schema.json | 3 +- app/controlplane/internal/service/user.go | 2 + .../pkg/data/ent/membership/membership.go | 2 +- .../pkg/data/ent/migrate/schema.go | 4 +- .../data/ent/orginvitation/orginvitation.go | 2 +- 16 files changed, 91 insertions(+), 65 deletions(-) diff --git a/app/controlplane/api/controlplane/v1/response_messages.pb.go b/app/controlplane/api/controlplane/v1/response_messages.pb.go index 7fb519a63..4eb15d017 100644 --- a/app/controlplane/api/controlplane/v1/response_messages.pb.go +++ b/app/controlplane/api/controlplane/v1/response_messages.pb.go @@ -100,11 +100,12 @@ func (RunStatus) EnumDescriptor() ([]byte, []int) { type MembershipRole int32 const ( - MembershipRole_MEMBERSHIP_ROLE_UNSPECIFIED MembershipRole = 0 - MembershipRole_MEMBERSHIP_ROLE_ORG_VIEWER MembershipRole = 1 - MembershipRole_MEMBERSHIP_ROLE_ORG_ADMIN MembershipRole = 2 - MembershipRole_MEMBERSHIP_ROLE_ORG_OWNER MembershipRole = 3 - MembershipRole_MEMBERSHIP_ROLE_ORG_MEMBER MembershipRole = 4 + MembershipRole_MEMBERSHIP_ROLE_UNSPECIFIED MembershipRole = 0 + MembershipRole_MEMBERSHIP_ROLE_ORG_VIEWER MembershipRole = 1 + MembershipRole_MEMBERSHIP_ROLE_ORG_ADMIN MembershipRole = 2 + MembershipRole_MEMBERSHIP_ROLE_ORG_OWNER MembershipRole = 3 + MembershipRole_MEMBERSHIP_ROLE_ORG_MEMBER MembershipRole = 4 + MembershipRole_MEMBERSHIP_ROLE_ORG_CONTRIBUTOR MembershipRole = 5 ) // Enum value maps for MembershipRole. @@ -115,13 +116,15 @@ var ( 2: "MEMBERSHIP_ROLE_ORG_ADMIN", 3: "MEMBERSHIP_ROLE_ORG_OWNER", 4: "MEMBERSHIP_ROLE_ORG_MEMBER", + 5: "MEMBERSHIP_ROLE_ORG_CONTRIBUTOR", } MembershipRole_value = map[string]int32{ - "MEMBERSHIP_ROLE_UNSPECIFIED": 0, - "MEMBERSHIP_ROLE_ORG_VIEWER": 1, - "MEMBERSHIP_ROLE_ORG_ADMIN": 2, - "MEMBERSHIP_ROLE_ORG_OWNER": 3, - "MEMBERSHIP_ROLE_ORG_MEMBER": 4, + "MEMBERSHIP_ROLE_UNSPECIFIED": 0, + "MEMBERSHIP_ROLE_ORG_VIEWER": 1, + "MEMBERSHIP_ROLE_ORG_ADMIN": 2, + "MEMBERSHIP_ROLE_ORG_OWNER": 3, + "MEMBERSHIP_ROLE_ORG_MEMBER": 4, + "MEMBERSHIP_ROLE_ORG_CONTRIBUTOR": 5, } ) @@ -2968,7 +2971,7 @@ var file_controlplane_v1_response_messages_proto_rawDesc = []byte{ 0x4c, 0x45, 0x44, 0x10, 0x03, 0x12, 0x16, 0x0a, 0x12, 0x52, 0x55, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x45, 0x44, 0x10, 0x04, 0x12, 0x18, 0x0a, 0x14, 0x52, 0x55, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x43, 0x41, 0x4e, 0x43, - 0x45, 0x4c, 0x4c, 0x45, 0x44, 0x10, 0x05, 0x2a, 0xaf, 0x01, 0x0a, 0x0e, 0x4d, 0x65, 0x6d, 0x62, + 0x45, 0x4c, 0x4c, 0x45, 0x44, 0x10, 0x05, 0x2a, 0xd4, 0x01, 0x0a, 0x0e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x1f, 0x0a, 0x1b, 0x4d, 0x45, 0x4d, 0x42, 0x45, 0x52, 0x53, 0x48, 0x49, 0x50, 0x5f, 0x52, 0x4f, 0x4c, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1e, 0x0a, 0x1a, 0x4d, @@ -2979,42 +2982,44 @@ var file_controlplane_v1_response_messages_proto_rawDesc = []byte{ 0x4d, 0x42, 0x45, 0x52, 0x53, 0x48, 0x49, 0x50, 0x5f, 0x52, 0x4f, 0x4c, 0x45, 0x5f, 0x4f, 0x52, 0x47, 0x5f, 0x4f, 0x57, 0x4e, 0x45, 0x52, 0x10, 0x03, 0x12, 0x1e, 0x0a, 0x1a, 0x4d, 0x45, 0x4d, 0x42, 0x45, 0x52, 0x53, 0x48, 0x49, 0x50, 0x5f, 0x52, 0x4f, 0x4c, 0x45, 0x5f, 0x4f, 0x52, 0x47, - 0x5f, 0x4d, 0x45, 0x4d, 0x42, 0x45, 0x52, 0x10, 0x04, 0x2a, 0x60, 0x0a, 0x0e, 0x41, 0x6c, 0x6c, - 0x6f, 0x77, 0x4c, 0x69, 0x73, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x20, 0x0a, 0x1c, 0x41, - 0x4c, 0x4c, 0x4f, 0x57, 0x5f, 0x4c, 0x49, 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, - 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x26, 0x0a, - 0x1c, 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x5f, 0x4c, 0x49, 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, - 0x52, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x49, 0x4e, 0x5f, 0x4c, 0x49, 0x53, 0x54, 0x10, 0x01, 0x1a, - 0x04, 0xa8, 0x45, 0x93, 0x03, 0x1a, 0x04, 0xa0, 0x45, 0xf4, 0x03, 0x2a, 0x6d, 0x0a, 0x12, 0x46, - 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x41, 0x75, 0x74, 0x68, 0x45, 0x72, 0x72, 0x6f, - 0x72, 0x12, 0x24, 0x0a, 0x20, 0x46, 0x45, 0x44, 0x45, 0x52, 0x41, 0x54, 0x45, 0x44, 0x5f, 0x41, - 0x55, 0x54, 0x48, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, - 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x2b, 0x0a, 0x21, 0x46, 0x45, 0x44, 0x45, 0x52, - 0x41, 0x54, 0x45, 0x44, 0x5f, 0x41, 0x55, 0x54, 0x48, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, - 0x55, 0x4e, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x5a, 0x45, 0x44, 0x10, 0x01, 0x1a, 0x04, - 0xa8, 0x45, 0x93, 0x03, 0x1a, 0x04, 0xa0, 0x45, 0xf4, 0x03, 0x2a, 0x84, 0x01, 0x0a, 0x19, 0x55, - 0x73, 0x65, 0x72, 0x57, 0x69, 0x74, 0x68, 0x4e, 0x6f, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, - 0x68, 0x69, 0x70, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x2d, 0x0a, 0x29, 0x55, 0x53, 0x45, 0x52, - 0x5f, 0x57, 0x49, 0x54, 0x48, 0x5f, 0x4e, 0x4f, 0x5f, 0x4d, 0x45, 0x4d, 0x42, 0x45, 0x52, 0x53, - 0x48, 0x49, 0x50, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, - 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x32, 0x0a, 0x28, 0x55, 0x53, 0x45, 0x52, 0x5f, - 0x57, 0x49, 0x54, 0x48, 0x5f, 0x4e, 0x4f, 0x5f, 0x4d, 0x45, 0x4d, 0x42, 0x45, 0x52, 0x53, 0x48, - 0x49, 0x50, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x49, 0x4e, 0x5f, - 0x4f, 0x52, 0x47, 0x10, 0x01, 0x1a, 0x04, 0xa8, 0x45, 0x93, 0x03, 0x1a, 0x04, 0xa0, 0x45, 0xf4, - 0x03, 0x2a, 0x80, 0x01, 0x0a, 0x17, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x4d, 0x65, 0x6d, - 0x62, 0x65, 0x72, 0x4f, 0x66, 0x4f, 0x72, 0x67, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x2c, 0x0a, - 0x28, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x4d, 0x45, 0x4d, 0x42, 0x45, 0x52, - 0x5f, 0x4f, 0x46, 0x5f, 0x4f, 0x52, 0x47, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x55, 0x4e, - 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x31, 0x0a, 0x27, 0x55, - 0x53, 0x45, 0x52, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x4d, 0x45, 0x4d, 0x42, 0x45, 0x52, 0x5f, 0x4f, - 0x46, 0x5f, 0x4f, 0x52, 0x47, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, - 0x49, 0x4e, 0x5f, 0x4f, 0x52, 0x47, 0x10, 0x01, 0x1a, 0x04, 0xa8, 0x45, 0x93, 0x03, 0x1a, 0x04, - 0xa0, 0x45, 0xf4, 0x03, 0x42, 0x4c, 0x5a, 0x4a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x6f, 0x6f, 0x70, 0x2d, 0x64, 0x65, 0x76, - 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x6f, 0x6f, 0x70, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x63, - 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, - 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x3b, - 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x5f, 0x4d, 0x45, 0x4d, 0x42, 0x45, 0x52, 0x10, 0x04, 0x12, 0x23, 0x0a, 0x1f, 0x4d, 0x45, 0x4d, + 0x42, 0x45, 0x52, 0x53, 0x48, 0x49, 0x50, 0x5f, 0x52, 0x4f, 0x4c, 0x45, 0x5f, 0x4f, 0x52, 0x47, + 0x5f, 0x43, 0x4f, 0x4e, 0x54, 0x52, 0x49, 0x42, 0x55, 0x54, 0x4f, 0x52, 0x10, 0x05, 0x2a, 0x60, + 0x0a, 0x0e, 0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x4c, 0x69, 0x73, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, + 0x12, 0x20, 0x0a, 0x1c, 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x5f, 0x4c, 0x49, 0x53, 0x54, 0x5f, 0x45, + 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, + 0x10, 0x00, 0x12, 0x26, 0x0a, 0x1c, 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x5f, 0x4c, 0x49, 0x53, 0x54, + 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x49, 0x4e, 0x5f, 0x4c, 0x49, + 0x53, 0x54, 0x10, 0x01, 0x1a, 0x04, 0xa8, 0x45, 0x93, 0x03, 0x1a, 0x04, 0xa0, 0x45, 0xf4, 0x03, + 0x2a, 0x6d, 0x0a, 0x12, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x41, 0x75, 0x74, + 0x68, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x24, 0x0a, 0x20, 0x46, 0x45, 0x44, 0x45, 0x52, 0x41, + 0x54, 0x45, 0x44, 0x5f, 0x41, 0x55, 0x54, 0x48, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x55, + 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x2b, 0x0a, 0x21, + 0x46, 0x45, 0x44, 0x45, 0x52, 0x41, 0x54, 0x45, 0x44, 0x5f, 0x41, 0x55, 0x54, 0x48, 0x5f, 0x45, + 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x5a, 0x45, + 0x44, 0x10, 0x01, 0x1a, 0x04, 0xa8, 0x45, 0x93, 0x03, 0x1a, 0x04, 0xa0, 0x45, 0xf4, 0x03, 0x2a, + 0x84, 0x01, 0x0a, 0x19, 0x55, 0x73, 0x65, 0x72, 0x57, 0x69, 0x74, 0x68, 0x4e, 0x6f, 0x4d, 0x65, + 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x2d, 0x0a, + 0x29, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x57, 0x49, 0x54, 0x48, 0x5f, 0x4e, 0x4f, 0x5f, 0x4d, 0x45, + 0x4d, 0x42, 0x45, 0x52, 0x53, 0x48, 0x49, 0x50, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x55, + 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x32, 0x0a, 0x28, + 0x55, 0x53, 0x45, 0x52, 0x5f, 0x57, 0x49, 0x54, 0x48, 0x5f, 0x4e, 0x4f, 0x5f, 0x4d, 0x45, 0x4d, + 0x42, 0x45, 0x52, 0x53, 0x48, 0x49, 0x50, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x4e, 0x4f, + 0x54, 0x5f, 0x49, 0x4e, 0x5f, 0x4f, 0x52, 0x47, 0x10, 0x01, 0x1a, 0x04, 0xa8, 0x45, 0x93, 0x03, + 0x1a, 0x04, 0xa0, 0x45, 0xf4, 0x03, 0x2a, 0x80, 0x01, 0x0a, 0x17, 0x55, 0x73, 0x65, 0x72, 0x4e, + 0x6f, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x4f, 0x66, 0x4f, 0x72, 0x67, 0x45, 0x72, 0x72, + 0x6f, 0x72, 0x12, 0x2c, 0x0a, 0x28, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x4d, + 0x45, 0x4d, 0x42, 0x45, 0x52, 0x5f, 0x4f, 0x46, 0x5f, 0x4f, 0x52, 0x47, 0x5f, 0x45, 0x52, 0x52, + 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, + 0x12, 0x31, 0x0a, 0x27, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x4d, 0x45, 0x4d, + 0x42, 0x45, 0x52, 0x5f, 0x4f, 0x46, 0x5f, 0x4f, 0x52, 0x47, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, + 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x49, 0x4e, 0x5f, 0x4f, 0x52, 0x47, 0x10, 0x01, 0x1a, 0x04, 0xa8, + 0x45, 0x93, 0x03, 0x1a, 0x04, 0xa0, 0x45, 0xf4, 0x03, 0x42, 0x4c, 0x5a, 0x4a, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x6f, 0x6f, + 0x70, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x6f, 0x6f, 0x70, 0x2f, + 0x61, 0x70, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, + 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, + 0x65, 0x2f, 0x76, 0x31, 0x3b, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/app/controlplane/api/controlplane/v1/response_messages.proto b/app/controlplane/api/controlplane/v1/response_messages.proto index a6cdba957..ef44d6c61 100644 --- a/app/controlplane/api/controlplane/v1/response_messages.proto +++ b/app/controlplane/api/controlplane/v1/response_messages.proto @@ -254,6 +254,7 @@ enum MembershipRole { MEMBERSHIP_ROLE_ORG_ADMIN = 2; MEMBERSHIP_ROLE_ORG_OWNER = 3; MEMBERSHIP_ROLE_ORG_MEMBER = 4; + MEMBERSHIP_ROLE_ORG_CONTRIBUTOR = 5; } message OrgItem { diff --git a/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts b/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts index 5c579c249..7838dc722 100644 --- a/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts +++ b/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts @@ -74,6 +74,7 @@ export enum MembershipRole { MEMBERSHIP_ROLE_ORG_ADMIN = 2, MEMBERSHIP_ROLE_ORG_OWNER = 3, MEMBERSHIP_ROLE_ORG_MEMBER = 4, + MEMBERSHIP_ROLE_ORG_CONTRIBUTOR = 5, UNRECOGNIZED = -1, } @@ -94,6 +95,9 @@ export function membershipRoleFromJSON(object: any): MembershipRole { case 4: case "MEMBERSHIP_ROLE_ORG_MEMBER": return MembershipRole.MEMBERSHIP_ROLE_ORG_MEMBER; + case 5: + case "MEMBERSHIP_ROLE_ORG_CONTRIBUTOR": + return MembershipRole.MEMBERSHIP_ROLE_ORG_CONTRIBUTOR; case -1: case "UNRECOGNIZED": default: @@ -113,6 +117,8 @@ export function membershipRoleToJSON(object: MembershipRole): string { return "MEMBERSHIP_ROLE_ORG_OWNER"; case MembershipRole.MEMBERSHIP_ROLE_ORG_MEMBER: return "MEMBERSHIP_ROLE_ORG_MEMBER"; + case MembershipRole.MEMBERSHIP_ROLE_ORG_CONTRIBUTOR: + return "MEMBERSHIP_ROLE_ORG_CONTRIBUTOR"; case MembershipRole.UNRECOGNIZED: default: return "UNRECOGNIZED"; diff --git a/app/controlplane/api/gen/frontend/google/protobuf/descriptor.ts b/app/controlplane/api/gen/frontend/google/protobuf/descriptor.ts index d59b21da4..0d2d2fb32 100644 --- a/app/controlplane/api/gen/frontend/google/protobuf/descriptor.ts +++ b/app/controlplane/api/gen/frontend/google/protobuf/descriptor.ts @@ -30,7 +30,7 @@ export enum Edition { EDITION_2024 = 1001, /** * EDITION_1_TEST_ONLY - Placeholder editions for testing feature resolution. These should not be - * used or relied on outside of tests. + * used or relyed on outside of tests. */ EDITION_1_TEST_ONLY = 1, EDITION_2_TEST_ONLY = 2, @@ -875,13 +875,12 @@ export interface MessageOptions { export interface FieldOptions { /** - * NOTE: ctype is deprecated. Use `features.(pb.cpp).string_type` instead. * The ctype option instructs the C++ code generator to use a different * representation of the field than it normally would. See the specific * options below. This option is only implemented to support use of * [ctype=CORD] and [ctype=STRING] (the default) on non-repeated fields of - * type "bytes" in the open source release. - * TODO: make ctype actually deprecated. + * type "bytes" in the open source release -- sorry, we'll try to include + * other types in a future version! */ ctype: FieldOptions_CType; /** @@ -1053,7 +1052,11 @@ export function fieldOptions_JSTypeToJSON(object: FieldOptions_JSType): string { } } -/** If set to RETENTION_SOURCE, the option will be omitted from the binary. */ +/** + * If set to RETENTION_SOURCE, the option will be omitted from the binary. + * Note: as of January 2023, support for this is in progress and does not yet + * have an effect (b/264593489). + */ export enum FieldOptions_OptionRetention { RETENTION_UNKNOWN = 0, RETENTION_RUNTIME = 1, @@ -1096,7 +1099,8 @@ export function fieldOptions_OptionRetentionToJSON(object: FieldOptions_OptionRe /** * This indicates the types of entities that the field may apply to when used * as an option. If it is unset, then the field may be freely used as an - * option on any kind of entity. + * option on any kind of entity. Note: as of January 2023, support for this is + * in progress and does not yet have an effect (b/264593489). */ export enum FieldOptions_OptionTargetType { TARGET_TYPE_UNKNOWN = 0, diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgInvitationItem.jsonschema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgInvitationItem.jsonschema.json index 667d4a813..822523322 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgInvitationItem.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgInvitationItem.jsonschema.json @@ -31,7 +31,8 @@ "MEMBERSHIP_ROLE_ORG_VIEWER", "MEMBERSHIP_ROLE_ORG_ADMIN", "MEMBERSHIP_ROLE_ORG_OWNER", - "MEMBERSHIP_ROLE_ORG_MEMBER" + "MEMBERSHIP_ROLE_ORG_MEMBER", + "MEMBERSHIP_ROLE_ORG_CONTRIBUTOR" ], "title": "Membership Role", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgInvitationItem.schema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgInvitationItem.schema.json index 686d42376..c0670f8fa 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgInvitationItem.schema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgInvitationItem.schema.json @@ -31,7 +31,8 @@ "MEMBERSHIP_ROLE_ORG_VIEWER", "MEMBERSHIP_ROLE_ORG_ADMIN", "MEMBERSHIP_ROLE_ORG_OWNER", - "MEMBERSHIP_ROLE_ORG_MEMBER" + "MEMBERSHIP_ROLE_ORG_MEMBER", + "MEMBERSHIP_ROLE_ORG_CONTRIBUTOR" ], "title": "Membership Role", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgInvitationServiceCreateRequest.jsonschema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgInvitationServiceCreateRequest.jsonschema.json index 44e076d7e..47c1842b3 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgInvitationServiceCreateRequest.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgInvitationServiceCreateRequest.jsonschema.json @@ -29,7 +29,8 @@ "MEMBERSHIP_ROLE_ORG_VIEWER", "MEMBERSHIP_ROLE_ORG_ADMIN", "MEMBERSHIP_ROLE_ORG_OWNER", - "MEMBERSHIP_ROLE_ORG_MEMBER" + "MEMBERSHIP_ROLE_ORG_MEMBER", + "MEMBERSHIP_ROLE_ORG_CONTRIBUTOR" ], "title": "Membership Role", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgInvitationServiceCreateRequest.schema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgInvitationServiceCreateRequest.schema.json index 4dbed371a..c17d3cfde 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgInvitationServiceCreateRequest.schema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgInvitationServiceCreateRequest.schema.json @@ -29,7 +29,8 @@ "MEMBERSHIP_ROLE_ORG_VIEWER", "MEMBERSHIP_ROLE_ORG_ADMIN", "MEMBERSHIP_ROLE_ORG_OWNER", - "MEMBERSHIP_ROLE_ORG_MEMBER" + "MEMBERSHIP_ROLE_ORG_MEMBER", + "MEMBERSHIP_ROLE_ORG_CONTRIBUTOR" ], "title": "Membership Role", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgMembershipItem.jsonschema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgMembershipItem.jsonschema.json index 10af855d4..7d05ad2c6 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgMembershipItem.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgMembershipItem.jsonschema.json @@ -31,7 +31,8 @@ "MEMBERSHIP_ROLE_ORG_VIEWER", "MEMBERSHIP_ROLE_ORG_ADMIN", "MEMBERSHIP_ROLE_ORG_OWNER", - "MEMBERSHIP_ROLE_ORG_MEMBER" + "MEMBERSHIP_ROLE_ORG_MEMBER", + "MEMBERSHIP_ROLE_ORG_CONTRIBUTOR" ], "title": "Membership Role", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgMembershipItem.schema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgMembershipItem.schema.json index dffa4d149..d6a6b3561 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgMembershipItem.schema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgMembershipItem.schema.json @@ -31,7 +31,8 @@ "MEMBERSHIP_ROLE_ORG_VIEWER", "MEMBERSHIP_ROLE_ORG_ADMIN", "MEMBERSHIP_ROLE_ORG_OWNER", - "MEMBERSHIP_ROLE_ORG_MEMBER" + "MEMBERSHIP_ROLE_ORG_MEMBER", + "MEMBERSHIP_ROLE_ORG_CONTRIBUTOR" ], "title": "Membership Role", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.OrganizationServiceUpdateMembershipRequest.jsonschema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.OrganizationServiceUpdateMembershipRequest.jsonschema.json index 4777c055d..f31133acf 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.OrganizationServiceUpdateMembershipRequest.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.OrganizationServiceUpdateMembershipRequest.jsonschema.json @@ -21,7 +21,8 @@ "MEMBERSHIP_ROLE_ORG_VIEWER", "MEMBERSHIP_ROLE_ORG_ADMIN", "MEMBERSHIP_ROLE_ORG_OWNER", - "MEMBERSHIP_ROLE_ORG_MEMBER" + "MEMBERSHIP_ROLE_ORG_MEMBER", + "MEMBERSHIP_ROLE_ORG_CONTRIBUTOR" ], "title": "Membership Role", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.OrganizationServiceUpdateMembershipRequest.schema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.OrganizationServiceUpdateMembershipRequest.schema.json index 6c30732d5..b8a0554eb 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.OrganizationServiceUpdateMembershipRequest.schema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.OrganizationServiceUpdateMembershipRequest.schema.json @@ -21,7 +21,8 @@ "MEMBERSHIP_ROLE_ORG_VIEWER", "MEMBERSHIP_ROLE_ORG_ADMIN", "MEMBERSHIP_ROLE_ORG_OWNER", - "MEMBERSHIP_ROLE_ORG_MEMBER" + "MEMBERSHIP_ROLE_ORG_MEMBER", + "MEMBERSHIP_ROLE_ORG_CONTRIBUTOR" ], "title": "Membership Role", "type": "string" diff --git a/app/controlplane/internal/service/user.go b/app/controlplane/internal/service/user.go index ae12dc08e..60bfc00c8 100644 --- a/app/controlplane/internal/service/user.go +++ b/app/controlplane/internal/service/user.go @@ -124,6 +124,8 @@ func bizRoleToPb(r authz.Role) pb.MembershipRole { return pb.MembershipRole_MEMBERSHIP_ROLE_ORG_VIEWER case authz.RoleOrgMember: return pb.MembershipRole_MEMBERSHIP_ROLE_ORG_MEMBER + case authz.RoleOrgContributor: + return pb.MembershipRole_MEMBERSHIP_ROLE_ORG_CONTRIBUTOR default: return pb.MembershipRole_MEMBERSHIP_ROLE_UNSPECIFIED } diff --git a/app/controlplane/pkg/data/ent/membership/membership.go b/app/controlplane/pkg/data/ent/membership/membership.go index db3c87067..30a4b25f9 100644 --- a/app/controlplane/pkg/data/ent/membership/membership.go +++ b/app/controlplane/pkg/data/ent/membership/membership.go @@ -104,7 +104,7 @@ var ( // RoleValidator is a validator for the "role" field enum values. It is called by the builders before save. func RoleValidator(r authz.Role) error { switch r { - case "role:org:owner", "role:org:admin", "role:org:viewer", "role:org:member", "role:project:admin", "role:project:viewer", "role:group:maintainer": + case "role:org:owner", "role:org:admin", "role:org:viewer", "role:org:member", "role:org:contributor", "role:project:admin", "role:project:viewer", "role:group:maintainer": return nil default: return fmt.Errorf("membership: invalid enum value for role field: %q", r) diff --git a/app/controlplane/pkg/data/ent/migrate/schema.go b/app/controlplane/pkg/data/ent/migrate/schema.go index fa48502c0..66e91d0d3 100644 --- a/app/controlplane/pkg/data/ent/migrate/schema.go +++ b/app/controlplane/pkg/data/ent/migrate/schema.go @@ -320,7 +320,7 @@ var ( {Name: "current", Type: field.TypeBool, Default: false}, {Name: "created_at", Type: field.TypeTime, Default: "CURRENT_TIMESTAMP"}, {Name: "updated_at", Type: field.TypeTime, Default: "CURRENT_TIMESTAMP"}, - {Name: "role", Type: field.TypeEnum, Enums: []string{"role:org:owner", "role:org:admin", "role:org:viewer", "role:org:member", "role:project:admin", "role:project:viewer", "role:group:maintainer"}}, + {Name: "role", Type: field.TypeEnum, Enums: []string{"role:org:owner", "role:org:admin", "role:org:viewer", "role:org:member", "role:org:contributor", "role:project:admin", "role:project:viewer", "role:group:maintainer"}}, {Name: "membership_type", Type: field.TypeEnum, Nullable: true, Enums: []string{"user", "group"}}, {Name: "member_id", Type: field.TypeUUID, Nullable: true}, {Name: "resource_type", Type: field.TypeEnum, Nullable: true, Enums: []string{"organization", "project", "group"}}, @@ -367,7 +367,7 @@ var ( {Name: "status", Type: field.TypeEnum, Enums: []string{"accepted", "pending"}, Default: "pending"}, {Name: "created_at", Type: field.TypeTime, Default: "CURRENT_TIMESTAMP"}, {Name: "deleted_at", Type: field.TypeTime, Nullable: true}, - {Name: "role", Type: field.TypeEnum, Nullable: true, Enums: []string{"role:org:owner", "role:org:admin", "role:org:viewer", "role:org:member", "role:project:admin", "role:project:viewer", "role:group:maintainer"}}, + {Name: "role", Type: field.TypeEnum, Nullable: true, Enums: []string{"role:org:owner", "role:org:admin", "role:org:viewer", "role:org:member", "role:org:contributor", "role:project:admin", "role:project:viewer", "role:group:maintainer"}}, {Name: "context", Type: field.TypeJSON, Nullable: true}, {Name: "organization_id", Type: field.TypeUUID}, {Name: "sender_id", Type: field.TypeUUID}, diff --git a/app/controlplane/pkg/data/ent/orginvitation/orginvitation.go b/app/controlplane/pkg/data/ent/orginvitation/orginvitation.go index cc6a3ed5b..f5c5e4f9c 100644 --- a/app/controlplane/pkg/data/ent/orginvitation/orginvitation.go +++ b/app/controlplane/pkg/data/ent/orginvitation/orginvitation.go @@ -101,7 +101,7 @@ func StatusValidator(s biz.OrgInvitationStatus) error { // RoleValidator is a validator for the "role" field enum values. It is called by the builders before save. func RoleValidator(r authz.Role) error { switch r { - case "role:org:owner", "role:org:admin", "role:org:viewer", "role:org:member", "role:project:admin", "role:project:viewer", "role:group:maintainer": + case "role:org:owner", "role:org:admin", "role:org:viewer", "role:org:member", "role:org:contributor", "role:project:admin", "role:project:viewer", "role:group:maintainer": return nil default: return fmt.Errorf("orginvitation: invalid enum value for role field: %q", r) From 37918d231523f5244ba4abebcbc92f6b2485e77a Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Wed, 16 Jul 2025 19:42:29 +0200 Subject: [PATCH 05/24] fix roles for membership Signed-off-by: Jose I. Paris --- app/controlplane/internal/service/service.go | 4 +-- app/controlplane/pkg/authz/authz.go | 32 ++++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/app/controlplane/internal/service/service.go b/app/controlplane/internal/service/service.go index 3a2503fc0..187084ee9 100644 --- a/app/controlplane/internal/service/service.go +++ b/app/controlplane/internal/service/service.go @@ -257,7 +257,7 @@ func (s *service) userCanCreateProject(ctx context.Context) error { // Only org tokens can create projects if token := entities.CurrentAPIToken(ctx); token != nil { if token.ProjectID != nil { - return errors.Forbidden("unauthorized", "user cannot create project") + return errors.Forbidden("unauthorized", "you are not allowed to create projects") } } @@ -268,7 +268,7 @@ func (s *service) userCanCreateProject(ctx context.Context) error { } if !pass { - return errors.Forbidden("unauthorized", "user cannot create project") + return errors.Forbidden("unauthorized", "you are not allowed to create projects") } return nil diff --git a/app/controlplane/pkg/authz/authz.go b/app/controlplane/pkg/authz/authz.go index 2bccb4e5a..42b34e2be 100644 --- a/app/controlplane/pkg/authz/authz.go +++ b/app/controlplane/pkg/authz/authz.go @@ -269,20 +269,18 @@ var RolesMap = map[Role][]*Policy{ PolicyOrgMetricsRead, PolicyReferrerRead, - // Groups - PolicyGroupList, - PolicyGroupRead, - - // Group Projects - PolicyGroupListProjects, - - // Group Memberships - PolicyGroupListMemberships, - // Additional check for API tokens is done at the service level PolicyAPITokenList, PolicyAPITokenCreate, PolicyAPITokenRevoke, + }, + + // RoleOrgMember inherits from RoleOrgContributor and can also create their own projects and see members + RoleOrgMember: { + PolicyProjectCreate, + + // Org memberships + PolicyOrganizationListMemberships, // Project Memberships PolicyProjectListMemberships, @@ -290,13 +288,15 @@ var RolesMap = map[Role][]*Policy{ PolicyProjectRemoveMemberships, PolicyProjectUpdateMemberships, - // Org memberships - PolicyOrganizationListMemberships, - }, + // Groups + PolicyGroupList, + PolicyGroupRead, - // RoleOrgMember are contributors that can also create their own projects - RoleOrgMember: { - PolicyProjectCreate, + // Group Projects + PolicyGroupListProjects, + + // Group Memberships + PolicyGroupListMemberships, }, // RoleProjectViewer: has read-only permissions on a project From 95b8caa527e981908bd0d201aab142020f19e8a1 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Thu, 17 Jul 2025 13:46:45 +0200 Subject: [PATCH 06/24] memberships only available to admins. group memberships also to maintainers Signed-off-by: Jose I. Paris --- app/controlplane/internal/service/group.go | 10 ++++ app/controlplane/pkg/authz/authz.go | 67 +++++++++++----------- 2 files changed, 42 insertions(+), 35 deletions(-) diff --git a/app/controlplane/internal/service/group.go b/app/controlplane/internal/service/group.go index 0180f8f5a..162c0c15e 100644 --- a/app/controlplane/internal/service/group.go +++ b/app/controlplane/internal/service/group.go @@ -20,6 +20,7 @@ import ( "fmt" pb "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1" + "github.com/chainloop-dev/chainloop/app/controlplane/pkg/authz" "github.com/chainloop-dev/chainloop/app/controlplane/pkg/biz" "github.com/go-kratos/kratos/v2/errors" @@ -246,6 +247,10 @@ func (g *GroupService) ListMembers(ctx context.Context, req *pb.GroupServiceList MemberEmail: req.MemberEmail, } + if err = g.userHasPermissionOnGroupMembershipsWithPolicy(ctx, currentOrg.ID, req.GetGroupReference(), authz.PolicyGroupListMemberships); err != nil { + return nil, err + } + // Parse groupID and groupName from the request opts.ID, opts.Name, err = req.GetGroupReference().Parse() if err != nil { @@ -486,10 +491,15 @@ func (g *GroupService) ListProjects(ctx context.Context, req *pb.GroupServiceLis return nil, err } + if err = g.userHasPermissionOnGroupMembershipsWithPolicy(ctx, currentOrg.ID, req.GetGroupReference(), authz.PolicyGroupListProjects); err != nil { + return nil, err + } + // Parse orgID orgUUID, err := uuid.Parse(currentOrg.ID) if err != nil { return nil, errors.BadRequest("invalid", "invalid organization ID") + } // Parse groupID and groupName from the request diff --git a/app/controlplane/pkg/authz/authz.go b/app/controlplane/pkg/authz/authz.go index 42b34e2be..11cf1fe58 100644 --- a/app/controlplane/pkg/authz/authz.go +++ b/app/controlplane/pkg/authz/authz.go @@ -161,17 +161,19 @@ var ( // User Membership PolicyOrganizationRead = &Policy{Organization, ActionRead} PolicyOrganizationListMemberships = &Policy{OrganizationMemberships, ActionList} + // Groups - PolicyGroupList = &Policy{ResourceGroup, ActionList} - PolicyGroupListPendingInvitations = &Policy{ResourceGroup, ActionList} - PolicyGroupRead = &Policy{ResourceGroup, ActionRead} + PolicyGroupList = &Policy{ResourceGroup, ActionList} + PolicyGroupRead = &Policy{ResourceGroup, ActionRead} + // Group Memberships - PolicyGroupListMemberships = &Policy{ResourceGroupMembership, ActionList} - PolicyGroupAddMemberships = &Policy{ResourceGroupMembership, ActionCreate} - PolicyGroupRemoveMemberships = &Policy{ResourceGroupMembership, ActionDelete} - PolicyGroupUpdateMemberships = &Policy{ResourceGroupMembership, ActionUpdate} - // Group-Projects - PolicyGroupListProjects = &Policy{ResourceGroupProjects, ActionList} + PolicyGroupListPendingInvitations = &Policy{ResourceGroup, ActionList} + PolicyGroupListMemberships = &Policy{ResourceGroupMembership, ActionList} + PolicyGroupAddMemberships = &Policy{ResourceGroupMembership, ActionCreate} + PolicyGroupRemoveMemberships = &Policy{ResourceGroupMembership, ActionDelete} + PolicyGroupUpdateMemberships = &Policy{ResourceGroupMembership, ActionUpdate} + PolicyGroupListProjects = &Policy{ResourceGroupProjects, ActionList} + // API Token PolicyAPITokenList = &Policy{ResourceAPIToken, ActionList} PolicyAPITokenCreate = &Policy{ResourceAPIToken, ActionCreate} @@ -287,16 +289,6 @@ var RolesMap = map[Role][]*Policy{ PolicyProjectAddMemberships, PolicyProjectRemoveMemberships, PolicyProjectUpdateMemberships, - - // Groups - PolicyGroupList, - PolicyGroupRead, - - // Group Projects - PolicyGroupListProjects, - - // Group Memberships - PolicyGroupListMemberships, }, // RoleProjectViewer: has read-only permissions on a project @@ -349,10 +341,13 @@ var RolesMap = map[Role][]*Policy{ }, // RoleGroupMaintainer: represents a group maintainer role. RoleGroupMaintainer: { + // Group Memberships + PolicyGroupListMemberships, PolicyGroupListPendingInvitations, PolicyGroupAddMemberships, PolicyGroupRemoveMemberships, PolicyGroupUpdateMemberships, + PolicyGroupListProjects, }, } @@ -416,32 +411,34 @@ var ServerOperationsMap = map[string][]*Policy{ "/controlplane.v1.UserService/DeleteMembership": {}, "/controlplane.v1.AuthService/DeleteAccount": {}, - "/controlplane.v1.OrganizationService/ListMemberships": {PolicyOrganizationListMemberships}, - // Groups - "/controlplane.v1.GroupService/List": {PolicyGroupList}, - "/controlplane.v1.GroupService/Get": {PolicyGroupRead}, - // Group Memberships - "/controlplane.v1.GroupService/ListMembers": {PolicyGroupListMemberships}, - "/controlplane.v1.GroupService/ListProjects": {PolicyGroupListProjects}, + // org memberships onnly available to admins + // "/controlplane.v1.OrganizationService/ListMemberships" + + // Groups (everyone see groups) + "/controlplane.v1.GroupService/List": {}, + "/controlplane.v1.GroupService/Get": {}, + // For the following endpoints, we rely on the service layer to check the permissions - // That's why we let everyone access them (empty policies) + // That's why we let everyone access them (empty policies). + // Group Memberships are only available to org admins or maintainers + "/controlplane.v1.GroupService/ListMembers": {}, + "/controlplane.v1.GroupService/ListProjects": {}, "/controlplane.v1.GroupService/AddMember": {}, "/controlplane.v1.GroupService/RemoveMember": {}, "/controlplane.v1.GroupService/ListPendingInvitations": {}, "/controlplane.v1.GroupService/UpdateMemberMaintainerStatus": {}, - // Projects: Check happen at service level + // Project Memberships + "/controlplane.v1.ProjectService/ListMembers": {PolicyProjectListMemberships}, + "/controlplane.v1.ProjectService/AddMember": {PolicyProjectAddMemberships}, + "/controlplane.v1.ProjectService/RemoveMember": {PolicyProjectRemoveMemberships}, + "/controlplane.v1.ProjectService/UpdateMemberRole": {PolicyProjectUpdateMemberships}, + "/controlplane.v1.ProjectService/ListPendingInvitations": {PolicyProjectListMemberships}, - // Project API Token + // Project API Token handled at the service level "/controlplane.v1.ProjectService/APITokenCreate": {}, "/controlplane.v1.ProjectService/APITokenList": {}, "/controlplane.v1.ProjectService/APITokenRevoke": {}, - // Project Memberships - "/controlplane.v1.ProjectService/ListMembers": {}, - "/controlplane.v1.ProjectService/AddMember": {}, - "/controlplane.v1.ProjectService/RemoveMember": {}, - "/controlplane.v1.ProjectService/UpdateMemberRole": {}, - "/controlplane.v1.ProjectService/ListPendingInvitations": {}, // API tokens RBAC are handled at the service level "/controlplane.v1.APITokenService/List": {}, From 5384317671ee093a13b69793eb8ae2c0c6b6d096 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Thu, 17 Jul 2025 13:59:22 +0200 Subject: [PATCH 07/24] expose roles to CLI Signed-off-by: Jose I. Paris --- app/cli/internal/action/membership_list.go | 12 ++++++++---- app/controlplane/pkg/authz/authz.go | 12 ++++++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/app/cli/internal/action/membership_list.go b/app/cli/internal/action/membership_list.go index 7a9b207cd..070c87984 100644 --- a/app/cli/internal/action/membership_list.go +++ b/app/cli/internal/action/membership_list.go @@ -114,10 +114,11 @@ func pbMembershipItemToAction(in *pb.OrgMembershipItem) *MembershipItem { type Role string const ( - RoleAdmin Role = "admin" - RoleOwner Role = "owner" - RoleViewer Role = "viewer" - RoleMember Role = "member" + RoleAdmin Role = "admin" + RoleOwner Role = "owner" + RoleViewer Role = "viewer" + RoleMember Role = "member" + RoleContributor Role = "contributor" ) type Roles []Role @@ -127,6 +128,7 @@ var AvailableRoles = Roles{ RoleOwner, RoleViewer, RoleMember, + RoleContributor, } func (roles Roles) String() string { @@ -147,6 +149,8 @@ func pbRoleToString(role pb.MembershipRole) Role { return RoleOwner case pb.MembershipRole_MEMBERSHIP_ROLE_ORG_MEMBER: return RoleMember + case pb.MembershipRole_MEMBERSHIP_ROLE_ORG_CONTRIBUTOR: + return RoleContributor } return "" } diff --git a/app/controlplane/pkg/authz/authz.go b/app/controlplane/pkg/authz/authz.go index 11cf1fe58..ded2df5b2 100644 --- a/app/controlplane/pkg/authz/authz.go +++ b/app/controlplane/pkg/authz/authz.go @@ -275,6 +275,12 @@ var RolesMap = map[Role][]*Policy{ PolicyAPITokenList, PolicyAPITokenCreate, PolicyAPITokenRevoke, + + // Project Memberships available to contributors if they are project admins + PolicyProjectListMemberships, + PolicyProjectAddMemberships, + PolicyProjectRemoveMemberships, + PolicyProjectUpdateMemberships, }, // RoleOrgMember inherits from RoleOrgContributor and can also create their own projects and see members @@ -283,12 +289,6 @@ var RolesMap = map[Role][]*Policy{ // Org memberships PolicyOrganizationListMemberships, - - // Project Memberships - PolicyProjectListMemberships, - PolicyProjectAddMemberships, - PolicyProjectRemoveMemberships, - PolicyProjectUpdateMemberships, }, // RoleProjectViewer: has read-only permissions on a project From ebf9f48c013e0f82525c07affbbad3a1f704aa81 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Thu, 17 Jul 2025 14:41:38 +0200 Subject: [PATCH 08/24] support role in update endpoint Signed-off-by: Jose I. Paris --- app/cli/internal/action/membership_list.go | 2 ++ app/controlplane/pkg/biz/user.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/app/cli/internal/action/membership_list.go b/app/cli/internal/action/membership_list.go index 070c87984..35338dfcb 100644 --- a/app/cli/internal/action/membership_list.go +++ b/app/cli/internal/action/membership_list.go @@ -165,6 +165,8 @@ func stringToPbRole(role Role) pb.MembershipRole { return pb.MembershipRole_MEMBERSHIP_ROLE_ORG_OWNER case RoleMember: return pb.MembershipRole_MEMBERSHIP_ROLE_ORG_MEMBER + case RoleContributor: + return pb.MembershipRole_MEMBERSHIP_ROLE_ORG_CONTRIBUTOR } return pb.MembershipRole_MEMBERSHIP_ROLE_UNSPECIFIED } diff --git a/app/controlplane/pkg/biz/user.go b/app/controlplane/pkg/biz/user.go index 3d21bc3fa..883122506 100644 --- a/app/controlplane/pkg/biz/user.go +++ b/app/controlplane/pkg/biz/user.go @@ -248,6 +248,8 @@ func PbRoleToBiz(r pb.MembershipRole) authz.Role { return authz.RoleViewer case pb.MembershipRole_MEMBERSHIP_ROLE_ORG_MEMBER: return authz.RoleOrgMember + case pb.MembershipRole_MEMBERSHIP_ROLE_ORG_CONTRIBUTOR: + return authz.RoleOrgContributor default: return "" } From 90ca06f9015463f69e31a8fe1d39fa82cfa4eaf9 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Thu, 17 Jul 2025 14:49:54 +0200 Subject: [PATCH 09/24] project admins inherit from project viewer Signed-off-by: Jose I. Paris --- app/controlplane/pkg/authz/authz.go | 10 +--------- app/controlplane/pkg/authz/enforcer.go | 6 ++++++ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/app/controlplane/pkg/authz/authz.go b/app/controlplane/pkg/authz/authz.go index ded2df5b2..aea39a6e9 100644 --- a/app/controlplane/pkg/authz/authz.go +++ b/app/controlplane/pkg/authz/authz.go @@ -301,18 +301,14 @@ var RolesMap = map[Role][]*Policy{ // Project API Token PolicyAPITokenList, }, - // RoleProjectAdmin: represents a project administrator. It's the higher role in project resources, - // and it's only considered when the org-level role is `RoleOrgMember` + // RoleProjectAdmin: inherits from ProjectViewer and represents a project administrator. RoleProjectAdmin: { // workflow contracts - PolicyWorkflowContractList, - PolicyWorkflowContractRead, PolicyWorkflowContractCreate, PolicyWorkflowContractUpdate, PolicyWorkflowContractDelete, // attestations - PolicyWorkflowRead, PolicyWorkflowCreate, PolicyWorkflowRunCreate, PolicyWorkflowRunUpdate, // to reset attestations @@ -321,15 +317,11 @@ var RolesMap = map[Role][]*Policy{ PolicyWorkflowUpdate, PolicyWorkflowDelete, - // workflow runs - PolicyWorkflowRunRead, - // integrations PolicyAttachedIntegrationAttach, PolicyAttachedIntegrationDetach, // Project API Token - PolicyAPITokenList, PolicyAPITokenCreate, PolicyAPITokenRevoke, diff --git a/app/controlplane/pkg/authz/enforcer.go b/app/controlplane/pkg/authz/enforcer.go index b464dac59..ddc5a8fee 100644 --- a/app/controlplane/pkg/authz/enforcer.go +++ b/app/controlplane/pkg/authz/enforcer.go @@ -264,5 +264,11 @@ func doSync(e *Enforcer, c *Config) error { return fmt.Errorf("failed to add grouping policy: %w", err) } + // ProjectAdmins are ProjectViewers as well + _, err = e.AddGroupingPolicy(string(RoleProjectAdmin), string(RoleProjectViewer)) + if err != nil { + return fmt.Errorf("failed to add grouping policy: %w", err) + } + return nil } From de580a91deaa5ccd0c6c5f3447397ae4a30a20f1 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Thu, 17 Jul 2025 16:03:51 +0200 Subject: [PATCH 10/24] fix test Signed-off-by: Jose I. Paris --- app/controlplane/pkg/biz/group_integration_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controlplane/pkg/biz/group_integration_test.go b/app/controlplane/pkg/biz/group_integration_test.go index b1c5cd901..5a929c1a7 100644 --- a/app/controlplane/pkg/biz/group_integration_test.go +++ b/app/controlplane/pkg/biz/group_integration_test.go @@ -834,7 +834,7 @@ func (s *groupMembersIntegrationTestSuite) TestAddMemberToGroup() { if inv.ReceiverEmail == "not-in-org@example.com" { found = true s.Equal(biz.OrgInvitationStatusPending, inv.Status) - s.Equal(string(authz.RoleOrgMember), string(inv.Role)) + s.Equal(string(authz.RoleOrgContributor), string(inv.Role)) // Verify the invitation context s.NotNil(inv.Context, "Invitation context should not be nil") From 41b43f388681ff14eacb18915ebd853c76c67ba9 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Thu, 17 Jul 2025 16:26:16 +0200 Subject: [PATCH 11/24] apply RBAC in GetContract Signed-off-by: Jose I. Paris --- app/controlplane/internal/service/attestation.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/controlplane/internal/service/attestation.go b/app/controlplane/internal/service/attestation.go index ebf9cb849..172aac7db 100644 --- a/app/controlplane/internal/service/attestation.go +++ b/app/controlplane/internal/service/attestation.go @@ -126,6 +126,12 @@ func (s *AttestationService) GetContract(ctx context.Context, req *cpAPI.Attesta return nil, errors.NotFound("not found", "contract not found") } + // If contract is private, check we have permissions on the project + scopedEntity := contractVersion.Contract.ScopedEntity + if rbacEnabled(ctx) && scopedEntity != nil && scopedEntity.Type == string(biz.ContractScopeProject) && scopedEntity.ID != wf.ProjectID { + return nil, errors.NotFound("not found", "contract not found") + } + resp := &cpAPI.AttestationServiceGetContractResponse_Result{ Workflow: bizWorkflowToPb(wf), Contract: bizWorkFlowContractVersionToPb(contractVersion.Version), From 686af7e0634ab3f25cd657b0d119087e8ae91778 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Thu, 17 Jul 2025 16:32:34 +0200 Subject: [PATCH 12/24] fix project creatino permission Signed-off-by: Jose I. Paris --- app/controlplane/internal/service/service.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/controlplane/internal/service/service.go b/app/controlplane/internal/service/service.go index 187084ee9..8fb5cb48b 100644 --- a/app/controlplane/internal/service/service.go +++ b/app/controlplane/internal/service/service.go @@ -254,6 +254,11 @@ func (s *service) userHasPermissionOnProject(ctx context.Context, orgID string, } func (s *service) userCanCreateProject(ctx context.Context) error { + // admins always can create projects + if !rbacEnabled(ctx) { + return nil + } + // Only org tokens can create projects if token := entities.CurrentAPIToken(ctx); token != nil { if token.ProjectID != nil { From 31a555cd6dbbf7f7fa0a575e81383d1c67b155d2 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Thu, 17 Jul 2025 17:22:51 +0200 Subject: [PATCH 13/24] fix api tokens Signed-off-by: Jose I. Paris --- app/controlplane/pkg/authz/authz.go | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/app/controlplane/pkg/authz/authz.go b/app/controlplane/pkg/authz/authz.go index aea39a6e9..65d23f054 100644 --- a/app/controlplane/pkg/authz/authz.go +++ b/app/controlplane/pkg/authz/authz.go @@ -221,6 +221,8 @@ var RolesMap = map[Role][]*Policy{ PolicyWorkflowRead, // Organization PolicyOrganizationRead, + // API tokens + PolicyAPITokenList, }, // RoleAdmin is an org-scoped role that provides super admin privileges (it's the higher role) RoleAdmin: { @@ -427,15 +429,10 @@ var ServerOperationsMap = map[string][]*Policy{ "/controlplane.v1.ProjectService/UpdateMemberRole": {PolicyProjectUpdateMemberships}, "/controlplane.v1.ProjectService/ListPendingInvitations": {PolicyProjectListMemberships}, - // Project API Token handled at the service level - "/controlplane.v1.ProjectService/APITokenCreate": {}, - "/controlplane.v1.ProjectService/APITokenList": {}, - "/controlplane.v1.ProjectService/APITokenRevoke": {}, - // API tokens RBAC are handled at the service level - "/controlplane.v1.APITokenService/List": {}, - "/controlplane.v1.APITokenService/Create": {}, - "/controlplane.v1.APITokenService/Revoke": {}, + "/controlplane.v1.APITokenService/List": {PolicyAPITokenList}, + "/controlplane.v1.APITokenService/Create": {PolicyAPITokenCreate}, + "/controlplane.v1.APITokenService/Revoke": {PolicyAPITokenRevoke}, } // Implements https://pkg.go.dev/entgo.io/ent/schema/field#EnumValues From 6422d3add83487344ff19fc430ba4cb05cba1f76 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Thu, 17 Jul 2025 17:37:35 +0200 Subject: [PATCH 14/24] use helper Signed-off-by: Jose I. Paris --- app/controlplane/internal/service/attestation.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controlplane/internal/service/attestation.go b/app/controlplane/internal/service/attestation.go index 172aac7db..f9bdc41e5 100644 --- a/app/controlplane/internal/service/attestation.go +++ b/app/controlplane/internal/service/attestation.go @@ -128,7 +128,7 @@ func (s *AttestationService) GetContract(ctx context.Context, req *cpAPI.Attesta // If contract is private, check we have permissions on the project scopedEntity := contractVersion.Contract.ScopedEntity - if rbacEnabled(ctx) && scopedEntity != nil && scopedEntity.Type == string(biz.ContractScopeProject) && scopedEntity.ID != wf.ProjectID { + if rbacEnabled(ctx) && contractVersion.Contract.IsProjectScoped() && scopedEntity.ID != wf.ProjectID { return nil, errors.NotFound("not found", "contract not found") } From 4b49bdbd805883989a1f0b93f98b4b0e6b858338 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Thu, 17 Jul 2025 18:19:55 +0200 Subject: [PATCH 15/24] merge main Signed-off-by: Jose I. Paris --- ...1.OrganizationServiceListMembershipsRequest.jsonschema.json | 3 ++- ...ne.v1.OrganizationServiceListMembershipsRequest.schema.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.OrganizationServiceListMembershipsRequest.jsonschema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.OrganizationServiceListMembershipsRequest.jsonschema.json index 251e3f0bf..a9be17c28 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.OrganizationServiceListMembershipsRequest.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.OrganizationServiceListMembershipsRequest.jsonschema.json @@ -33,7 +33,8 @@ "MEMBERSHIP_ROLE_ORG_VIEWER", "MEMBERSHIP_ROLE_ORG_ADMIN", "MEMBERSHIP_ROLE_ORG_OWNER", - "MEMBERSHIP_ROLE_ORG_MEMBER" + "MEMBERSHIP_ROLE_ORG_MEMBER", + "MEMBERSHIP_ROLE_ORG_CONTRIBUTOR" ], "title": "Membership Role", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.OrganizationServiceListMembershipsRequest.schema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.OrganizationServiceListMembershipsRequest.schema.json index 85d0689d8..2eba77746 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.OrganizationServiceListMembershipsRequest.schema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.OrganizationServiceListMembershipsRequest.schema.json @@ -33,7 +33,8 @@ "MEMBERSHIP_ROLE_ORG_VIEWER", "MEMBERSHIP_ROLE_ORG_ADMIN", "MEMBERSHIP_ROLE_ORG_OWNER", - "MEMBERSHIP_ROLE_ORG_MEMBER" + "MEMBERSHIP_ROLE_ORG_MEMBER", + "MEMBERSHIP_ROLE_ORG_CONTRIBUTOR" ], "title": "Membership Role", "type": "string" From 123c29d85aa15bd52fe59f9793f57ab670ad4436 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Thu, 17 Jul 2025 18:21:55 +0200 Subject: [PATCH 16/24] restore file Signed-off-by: Jose I. Paris --- .../gen/frontend/google/protobuf/descriptor.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/app/controlplane/api/gen/frontend/google/protobuf/descriptor.ts b/app/controlplane/api/gen/frontend/google/protobuf/descriptor.ts index 0d2d2fb32..d59b21da4 100644 --- a/app/controlplane/api/gen/frontend/google/protobuf/descriptor.ts +++ b/app/controlplane/api/gen/frontend/google/protobuf/descriptor.ts @@ -30,7 +30,7 @@ export enum Edition { EDITION_2024 = 1001, /** * EDITION_1_TEST_ONLY - Placeholder editions for testing feature resolution. These should not be - * used or relyed on outside of tests. + * used or relied on outside of tests. */ EDITION_1_TEST_ONLY = 1, EDITION_2_TEST_ONLY = 2, @@ -875,12 +875,13 @@ export interface MessageOptions { export interface FieldOptions { /** + * NOTE: ctype is deprecated. Use `features.(pb.cpp).string_type` instead. * The ctype option instructs the C++ code generator to use a different * representation of the field than it normally would. See the specific * options below. This option is only implemented to support use of * [ctype=CORD] and [ctype=STRING] (the default) on non-repeated fields of - * type "bytes" in the open source release -- sorry, we'll try to include - * other types in a future version! + * type "bytes" in the open source release. + * TODO: make ctype actually deprecated. */ ctype: FieldOptions_CType; /** @@ -1052,11 +1053,7 @@ export function fieldOptions_JSTypeToJSON(object: FieldOptions_JSType): string { } } -/** - * If set to RETENTION_SOURCE, the option will be omitted from the binary. - * Note: as of January 2023, support for this is in progress and does not yet - * have an effect (b/264593489). - */ +/** If set to RETENTION_SOURCE, the option will be omitted from the binary. */ export enum FieldOptions_OptionRetention { RETENTION_UNKNOWN = 0, RETENTION_RUNTIME = 1, @@ -1099,8 +1096,7 @@ export function fieldOptions_OptionRetentionToJSON(object: FieldOptions_OptionRe /** * This indicates the types of entities that the field may apply to when used * as an option. If it is unset, then the field may be freely used as an - * option on any kind of entity. Note: as of January 2023, support for this is - * in progress and does not yet have an effect (b/264593489). + * option on any kind of entity. */ export enum FieldOptions_OptionTargetType { TARGET_TYPE_UNKNOWN = 0, From 7788734721204a9ac674e83ea62cd44cd84ebeb5 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Fri, 18 Jul 2025 00:52:09 +0200 Subject: [PATCH 17/24] remove permission from member Signed-off-by: Jose I. Paris --- app/controlplane/pkg/authz/authz.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/controlplane/pkg/authz/authz.go b/app/controlplane/pkg/authz/authz.go index 65d23f054..ae299358e 100644 --- a/app/controlplane/pkg/authz/authz.go +++ b/app/controlplane/pkg/authz/authz.go @@ -288,9 +288,6 @@ var RolesMap = map[Role][]*Policy{ // RoleOrgMember inherits from RoleOrgContributor and can also create their own projects and see members RoleOrgMember: { PolicyProjectCreate, - - // Org memberships - PolicyOrganizationListMemberships, }, // RoleProjectViewer: has read-only permissions on a project From 702c5bc69ae71e82b43f8a44e32c83edf2c38a95 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Fri, 18 Jul 2025 00:58:18 +0200 Subject: [PATCH 18/24] remove apitokenlist from viewer Signed-off-by: Jose I. Paris --- app/controlplane/pkg/authz/authz.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/controlplane/pkg/authz/authz.go b/app/controlplane/pkg/authz/authz.go index ae299358e..eeea839b3 100644 --- a/app/controlplane/pkg/authz/authz.go +++ b/app/controlplane/pkg/authz/authz.go @@ -221,8 +221,6 @@ var RolesMap = map[Role][]*Policy{ PolicyWorkflowRead, // Organization PolicyOrganizationRead, - // API tokens - PolicyAPITokenList, }, // RoleAdmin is an org-scoped role that provides super admin privileges (it's the higher role) RoleAdmin: { From 63e66ef50b40e424d1f6b3fdf4668fddeef56cc3 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Fri, 18 Jul 2025 14:24:20 +0200 Subject: [PATCH 19/24] make linter happy Signed-off-by: Jose I. Paris --- app/controlplane/pkg/authz/authz.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controlplane/pkg/authz/authz.go b/app/controlplane/pkg/authz/authz.go index 02c27d780..20f113580 100644 --- a/app/controlplane/pkg/authz/authz.go +++ b/app/controlplane/pkg/authz/authz.go @@ -417,7 +417,7 @@ var ServerOperationsMap = map[string][]*Policy{ "/controlplane.v1.GroupService/RemoveMember": {}, "/controlplane.v1.GroupService/ListPendingInvitations": {}, "/controlplane.v1.GroupService/UpdateMemberMaintainerStatus": {}, - + // Project Memberships "/controlplane.v1.ProjectService/ListMembers": {PolicyProjectListMemberships}, "/controlplane.v1.ProjectService/AddMember": {PolicyProjectAddMemberships}, From 029327fe39543729716c6fc1babaf65458cc9d28 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Fri, 18 Jul 2025 16:29:37 +0200 Subject: [PATCH 20/24] allow eveyone to see visible projects attached to groups Signed-off-by: Jose I. Paris --- app/controlplane/internal/service/group.go | 6 +----- app/controlplane/pkg/authz/authz.go | 2 -- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/app/controlplane/internal/service/group.go b/app/controlplane/internal/service/group.go index adb35f88e..02a78c35b 100644 --- a/app/controlplane/internal/service/group.go +++ b/app/controlplane/internal/service/group.go @@ -509,10 +509,6 @@ func (g *GroupService) ListProjects(ctx context.Context, req *pb.GroupServiceLis return nil, err } - if err = g.userHasPermissionOnGroupMembershipsWithPolicy(ctx, currentOrg.ID, req.GetGroupReference(), authz.PolicyGroupListProjects); err != nil { - return nil, err - } - // Parse orgID orgUUID, err := uuid.Parse(currentOrg.ID) if err != nil { @@ -594,7 +590,7 @@ func (g *GroupService) userHasPermissionOnGroupMembershipsWithPolicy(ctx context } // Allow if user has admin or owner role - if userRole == string(authz.RoleAdmin) || userRole == string(authz.RoleOwner) { + if authz.Role(userRole).IsAdmin() { return nil } diff --git a/app/controlplane/pkg/authz/authz.go b/app/controlplane/pkg/authz/authz.go index 20f113580..a468e08c3 100644 --- a/app/controlplane/pkg/authz/authz.go +++ b/app/controlplane/pkg/authz/authz.go @@ -172,7 +172,6 @@ var ( PolicyGroupAddMemberships = &Policy{ResourceGroupMembership, ActionCreate} PolicyGroupRemoveMemberships = &Policy{ResourceGroupMembership, ActionDelete} PolicyGroupUpdateMemberships = &Policy{ResourceGroupMembership, ActionUpdate} - PolicyGroupListProjects = &Policy{ResourceGroupProjects, ActionList} // API Token PolicyAPITokenList = &Policy{ResourceAPIToken, ActionList} @@ -337,7 +336,6 @@ var RolesMap = map[Role][]*Policy{ PolicyGroupAddMemberships, PolicyGroupRemoveMemberships, PolicyGroupUpdateMemberships, - PolicyGroupListProjects, }, } From 0298e531d5ed8d9c976cdd63cd5f07a8cab8d814 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Fri, 18 Jul 2025 17:36:25 +0200 Subject: [PATCH 21/24] remove duplicated policy Signed-off-by: Jose I. Paris --- app/controlplane/pkg/authz/authz.go | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controlplane/pkg/authz/authz.go b/app/controlplane/pkg/authz/authz.go index a468e08c3..e9a0a9a62 100644 --- a/app/controlplane/pkg/authz/authz.go +++ b/app/controlplane/pkg/authz/authz.go @@ -332,7 +332,6 @@ var RolesMap = map[Role][]*Policy{ // Group Memberships PolicyGroupListMemberships, PolicyGroupListPendingInvitations, - PolicyGroupListMemberships, PolicyGroupAddMemberships, PolicyGroupRemoveMemberships, PolicyGroupUpdateMemberships, From 90ebdf1ca216196f0ec2eaa92e5f733a7c17e4be Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Sat, 19 Jul 2025 12:34:42 +0200 Subject: [PATCH 22/24] remove unused roles Signed-off-by: Jose I. Paris --- app/controlplane/pkg/authz/authz.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/app/controlplane/pkg/authz/authz.go b/app/controlplane/pkg/authz/authz.go index e9a0a9a62..491fa9a56 100644 --- a/app/controlplane/pkg/authz/authz.go +++ b/app/controlplane/pkg/authz/authz.go @@ -159,12 +159,7 @@ var ( // Projects PolicyProjectCreate = &Policy{ResourceProject, ActionCreate} // User Membership - PolicyOrganizationRead = &Policy{Organization, ActionRead} - PolicyOrganizationListMemberships = &Policy{OrganizationMemberships, ActionList} - - // Groups - PolicyGroupList = &Policy{ResourceGroup, ActionList} - PolicyGroupRead = &Policy{ResourceGroup, ActionRead} + PolicyOrganizationRead = &Policy{Organization, ActionRead} // Group Memberships PolicyGroupListPendingInvitations = &Policy{ResourceGroup, ActionList} From 4c4b19d08876e35ed76fe01e88ea05ddc8e08bd2 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Mon, 21 Jul 2025 11:34:43 +0200 Subject: [PATCH 23/24] remove commented out code Signed-off-by: Jose I. Paris --- app/controlplane/pkg/authz/authz.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/controlplane/pkg/authz/authz.go b/app/controlplane/pkg/authz/authz.go index 491fa9a56..81cf97077 100644 --- a/app/controlplane/pkg/authz/authz.go +++ b/app/controlplane/pkg/authz/authz.go @@ -393,14 +393,11 @@ var ServerOperationsMap = map[string][]*Policy{ "/controlplane.v1.UserService/DeleteMembership": {}, "/controlplane.v1.AuthService/DeleteAccount": {}, - // org memberships onnly available to admins - // "/controlplane.v1.OrganizationService/ListMemberships" - // Groups (everyone see groups) "/controlplane.v1.GroupService/List": {}, "/controlplane.v1.GroupService/Get": {}, - // For the following endpoints, we rely on the service layer to check the permissions + // For the following endpoints, we rely on the service layer to check the permissions. // That's why we let everyone access them (empty policies). // Group Memberships are only available to org admins or maintainers "/controlplane.v1.GroupService/ListMembers": {}, From ff66bb0bf22f23a16f612a225021b8515fb7b516 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Mon, 21 Jul 2025 11:35:28 +0200 Subject: [PATCH 24/24] fix comment Signed-off-by: Jose I. Paris --- app/controlplane/pkg/authz/authz.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controlplane/pkg/authz/authz.go b/app/controlplane/pkg/authz/authz.go index 81cf97077..16682b9b5 100644 --- a/app/controlplane/pkg/authz/authz.go +++ b/app/controlplane/pkg/authz/authz.go @@ -277,7 +277,7 @@ var RolesMap = map[Role][]*Policy{ PolicyProjectUpdateMemberships, }, - // RoleOrgMember inherits from RoleOrgContributor and can also create their own projects and see members + // RoleOrgMember inherits from RoleOrgContributor and can also create their own projects RoleOrgMember: { PolicyProjectCreate, },