From 0929c4d2356841ec18194c33f9a16c39c71bf49f Mon Sep 17 00:00:00 2001 From: Gayathri Vijayan <66356931+grvijayan@users.noreply.github.com> Date: Fri, 15 Aug 2025 11:35:43 +0200 Subject: [PATCH] fix: create project with the right permission (#10485) # Which Problems Are Solved When a user with an `ORG_PROJECT_CREATOR` role tries to create a project, the request fails with `No matching permissions found (AUTH-AWfge)` error. This is because `project.write` was set as the required permission instead of `project.create` during project creation. # How the Problems Are Solved By setting the right required permission (`project.create`) while creating new projects. # Additional Changes N/A # Additional Context - Closes #10399 --- .../v2beta/integration_test/project_test.go | 66 ++++++++++++++++++- internal/command/permission_checks.go | 4 ++ internal/command/project.go | 2 +- internal/domain/permission.go | 1 + internal/integration/client.go | 5 +- 5 files changed, 74 insertions(+), 4 deletions(-) diff --git a/internal/api/grpc/project/v2beta/integration_test/project_test.go b/internal/api/grpc/project/v2beta/integration_test/project_test.go index d802fbc8bb..49b5b5a876 100644 --- a/internal/api/grpc/project/v2beta/integration_test/project_test.go +++ b/internal/api/grpc/project/v2beta/integration_test/project_test.go @@ -10,12 +10,16 @@ import ( "github.com/brianvoe/gofakeit/v6" "github.com/muhlemmer/gu" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/integration" + internal_permission_v2beta "github.com/zitadel/zitadel/pkg/grpc/internal_permission/v2beta" project "github.com/zitadel/zitadel/pkg/grpc/project/v2beta" ) func TestServer_CreateProject(t *testing.T) { + t.Parallel() iamOwnerCtx := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner) orgResp := instance.CreateOrganization(iamOwnerCtx, integration.OrganizationName(), gofakeit.Email()) @@ -74,6 +78,7 @@ func TestServer_CreateProject(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + t.Parallel() creationDate := time.Now().UTC() got, err := instance.Client.Projectv2Beta.CreateProject(tt.ctx, tt.req) changeDate := time.Now().UTC() @@ -88,6 +93,7 @@ func TestServer_CreateProject(t *testing.T) { } func TestServer_CreateProject_Permission(t *testing.T) { + t.Parallel() iamOwnerCtx := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner) orgResp := instance.CreateOrganization(iamOwnerCtx, integration.OrganizationName(), gofakeit.Email()) @@ -129,6 +135,30 @@ func TestServer_CreateProject_Permission(t *testing.T) { }, wantErr: true, }, + { + name: "with ORG_PROJECT_CREATOR permission, same organization, ok", + ctx: integration.WithAuthorizationToken(CTX, getOrgProjectCreatorToken(t, iamOwnerCtx, orgResp.GetOrganizationId(), orgResp.GetOrganizationId())), + req: &project.CreateProjectRequest{ + Name: integration.ProjectName(), + OrganizationId: orgResp.GetOrganizationId(), + }, + want: want{ + id: true, + creationDate: true, + }, + }, + { + name: "with ORG_PROJECT_CREATOR permission, other organization, ok", + ctx: integration.WithAuthorizationToken(CTX, getOrgProjectCreatorToken(t, iamOwnerCtx, orgResp.GetOrganizationId(), instance.DefaultOrg.GetId())), + req: &project.CreateProjectRequest{ + Name: integration.ProjectName(), + OrganizationId: instance.DefaultOrg.GetId(), + }, + want: want{ + id: true, + creationDate: true, + }, + }, { name: "organization owner, ok", ctx: instance.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner), @@ -156,6 +186,7 @@ func TestServer_CreateProject_Permission(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + t.Parallel() creationDate := time.Now().UTC() got, err := instance.Client.Projectv2Beta.CreateProject(tt.ctx, tt.req) changeDate := time.Now().UTC() @@ -169,6 +200,23 @@ func TestServer_CreateProject_Permission(t *testing.T) { } } +func getOrgProjectCreatorToken(t *testing.T, ctx context.Context, orgId1, orgId2 string) string { + // create a machine user in Org 1 + userResp := instance.CreateUserTypeMachine(ctx, orgId1) + + // assign ORG_PROJECT_CREATOR role in Org 2 + _, err := instance.Client.InternalPermissionv2Beta.CreateAdministrator(ctx, &internal_permission_v2beta.CreateAdministratorRequest{ + Resource: &internal_permission_v2beta.ResourceType{ + Resource: &internal_permission_v2beta.ResourceType_OrganizationId{OrganizationId: orgId2}, + }, + UserId: userResp.GetId(), + Roles: []string{domain.RoleOrgProjectCreator}, + }) + require.NoError(t, err) + + return instance.CreatePersonalAccessToken(ctx, userResp.GetId()).Token +} + func assertCreateProjectResponse(t *testing.T, creationDate, changeDate time.Time, expectedCreationDate, expectedID bool, actualResp *project.CreateProjectResponse) { if expectedCreationDate { if !changeDate.IsZero() { @@ -188,6 +236,7 @@ func assertCreateProjectResponse(t *testing.T, creationDate, changeDate time.Tim } func TestServer_UpdateProject(t *testing.T) { + t.Parallel() iamOwnerCtx := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner) orgResp := instance.CreateOrganization(iamOwnerCtx, integration.OrganizationName(), gofakeit.Email()) @@ -210,7 +259,6 @@ func TestServer_UpdateProject(t *testing.T) { name: "not existing", prepare: func(request *project.UpdateProjectRequest) { request.Id = "notexisting" - return }, args: args{ ctx: iamOwnerCtx, @@ -278,6 +326,7 @@ func TestServer_UpdateProject(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + t.Parallel() creationDate := time.Now().UTC() tt.prepare(tt.args.req) @@ -297,6 +346,7 @@ func TestServer_UpdateProject(t *testing.T) { } func TestServer_UpdateProject_Permission(t *testing.T) { + t.Parallel() iamOwnerCtx := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner) orgResp := instance.CreateOrganization(iamOwnerCtx, integration.OrganizationName(), gofakeit.Email()) @@ -430,6 +480,7 @@ func TestServer_UpdateProject_Permission(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + t.Parallel() creationDate := time.Now().UTC() tt.prepare(tt.args.req) @@ -461,6 +512,7 @@ func assertUpdateProjectResponse(t *testing.T, creationDate, changeDate time.Tim } func TestServer_DeleteProject(t *testing.T) { + t.Parallel() iamOwnerCtx := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner) orgResp := instance.CreateOrganization(iamOwnerCtx, integration.OrganizationName(), gofakeit.Email()) @@ -516,6 +568,7 @@ func TestServer_DeleteProject(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + t.Parallel() var creationDate, deletionDate time.Time if tt.prepare != nil { creationDate, deletionDate = tt.prepare(tt.req) @@ -532,6 +585,7 @@ func TestServer_DeleteProject(t *testing.T) { } func TestServer_DeleteProject_Permission(t *testing.T) { + t.Parallel() iamOwnerCtx := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner) orgResp := instance.CreateOrganization(iamOwnerCtx, integration.OrganizationName(), gofakeit.Email()) @@ -635,6 +689,7 @@ func TestServer_DeleteProject_Permission(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + t.Parallel() var creationDate, deletionDate time.Time if tt.prepare != nil { creationDate, deletionDate = tt.prepare(tt.req) @@ -663,6 +718,7 @@ func assertDeleteProjectResponse(t *testing.T, creationDate, deletionDate time.T } func TestServer_DeactivateProject(t *testing.T) { + t.Parallel() iamOwnerCtx := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner) orgResp := instance.CreateOrganization(iamOwnerCtx, integration.OrganizationName(), gofakeit.Email()) @@ -685,7 +741,6 @@ func TestServer_DeactivateProject(t *testing.T) { name: "not existing", prepare: func(request *project.DeactivateProjectRequest) { request.Id = "notexisting" - return }, args: args{ ctx: iamOwnerCtx, @@ -725,6 +780,7 @@ func TestServer_DeactivateProject(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + t.Parallel() creationDate := time.Now().UTC() tt.prepare(tt.args.req) @@ -744,6 +800,7 @@ func TestServer_DeactivateProject(t *testing.T) { } func TestServer_DeactivateProject_Permission(t *testing.T) { + t.Parallel() iamOwnerCtx := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner) orgResp := instance.CreateOrganization(iamOwnerCtx, integration.OrganizationName(), gofakeit.Email()) @@ -831,6 +888,7 @@ func TestServer_DeactivateProject_Permission(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + t.Parallel() creationDate := time.Now().UTC() tt.prepare(tt.args.req) @@ -862,6 +920,7 @@ func assertDeactivateProjectResponse(t *testing.T, creationDate, changeDate time } func TestServer_ActivateProject(t *testing.T) { + t.Parallel() iamOwnerCtx := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner) orgResp := instance.CreateOrganization(iamOwnerCtx, integration.OrganizationName(), gofakeit.Email()) @@ -936,6 +995,7 @@ func TestServer_ActivateProject(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + t.Parallel() creationDate := time.Now().UTC() tt.prepare(tt.args.req) @@ -955,6 +1015,7 @@ func TestServer_ActivateProject(t *testing.T) { } func TestServer_ActivateProject_Permission(t *testing.T) { + t.Parallel() iamOwnerCtx := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner) orgResp := instance.CreateOrganization(iamOwnerCtx, integration.OrganizationName(), gofakeit.Email()) @@ -1047,6 +1108,7 @@ func TestServer_ActivateProject_Permission(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + t.Parallel() creationDate := time.Now().UTC() tt.prepare(tt.args.req) diff --git a/internal/command/permission_checks.go b/internal/command/permission_checks.go index 128880341a..97bae79ba9 100644 --- a/internal/command/permission_checks.go +++ b/internal/command/permission_checks.go @@ -64,6 +64,10 @@ func (c *Commands) checkPermissionUpdateUserCredentials(ctx context.Context, res return c.checkPermissionOnUser(ctx, domain.PermissionUserCredentialWrite)(resourceOwner, userID) } +func (c *Commands) checkPermissionCreateProject(ctx context.Context, resourceOwner, projectID string) error { + return c.newPermissionCheck(ctx, domain.PermissionProjectCreate, project.AggregateType)(resourceOwner, projectID) +} + func (c *Commands) checkPermissionDeleteProject(ctx context.Context, resourceOwner, projectID string) error { return c.newPermissionCheck(ctx, domain.PermissionProjectDelete, project.AggregateType)(resourceOwner, projectID) } diff --git a/internal/command/project.go b/internal/command/project.go index 4cdf1b7373..4d060b6233 100644 --- a/internal/command/project.go +++ b/internal/command/project.go @@ -60,7 +60,7 @@ func (c *Commands) AddProject(ctx context.Context, add *AddProject) (_ *domain.O if isProjectStateExists(wm.State) { return nil, zerrors.ThrowAlreadyExists(nil, "COMMAND-opamwu", "Errors.Project.AlreadyExisting") } - if err := c.checkPermissionUpdateProject(ctx, wm.ResourceOwner, wm.AggregateID); err != nil { + if err := c.checkPermissionCreateProject(ctx, wm.ResourceOwner, wm.AggregateID); err != nil { return nil, err } diff --git a/internal/domain/permission.go b/internal/domain/permission.go index 39738b1e84..cbea7028a2 100644 --- a/internal/domain/permission.go +++ b/internal/domain/permission.go @@ -38,6 +38,7 @@ const ( PermissionOrgRead = "org.read" PermissionIDPRead = "iam.idp.read" PermissionOrgIDPRead = "org.idp.read" + PermissionProjectCreate = "project.create" PermissionProjectWrite = "project.write" PermissionProjectRead = "project.read" PermissionProjectDelete = "project.delete" diff --git a/internal/integration/client.go b/internal/integration/client.go index 5365b1fca8..ea516a34da 100644 --- a/internal/integration/client.go +++ b/internal/integration/client.go @@ -295,8 +295,11 @@ func (i *Instance) CreateUserTypeHuman(ctx context.Context, email string) *user_ } func (i *Instance) CreateUserTypeMachine(ctx context.Context, orgId string) *user_v2.CreateUserResponse { + if orgId == "" { + orgId = i.DefaultOrg.GetId() + } resp, err := i.Client.UserV2.CreateUser(ctx, &user_v2.CreateUserRequest{ - OrganizationId: i.DefaultOrg.GetId(), + OrganizationId: orgId, UserType: &user_v2.CreateUserRequest_Machine_{ Machine: &user_v2.CreateUserRequest_Machine{ Name: "machine",