fix(project): include an option to add project members during project creation (#10654)

# Which Problems Are Solved
When a project is created by a user with only the `PROJECT_CREATOR`
role, they can no longer view/manage the created project. Although the
project is created, the user sees the following error: `No matching
permissions found (AUTH-3jknH)`. This is due to the
[removal](https://github.com/zitadel/zitadel/pull/9317) of
auto-assignment of the `PROJECT_OWNER` role when a project is newly
created.

# How the Problems Are Solved
By introducing optional fields in the CreateProject API to include a
list of users and a list of project member roles to be assigned to the
users. When there are no roles mentioned, the `PROJECT_OWNER` role is
assigned by default to all the users mentioned in the list.

# Additional Changes
N/A

# Additional Context
- Closes #10561 
- Closes #10592
- Should be backported as this issue is not specific to v4

---------

Co-authored-by: conblem <mail@conblem.me>
Co-authored-by: Stefan Benz <46600784+stebenz@users.noreply.github.com>
Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
Gayathri Vijayan
2025-09-12 11:16:49 +02:00
committed by GitHub
parent b892fc9b28
commit d7f202d20f
31 changed files with 358 additions and 31 deletions

View File

@@ -96,6 +96,12 @@ func TestServer_CreateProject_Permission(t *testing.T) {
iamOwnerCtx := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner)
orgResp := instance.CreateOrganization(iamOwnerCtx, integration.OrganizationName(), integration.Email())
// user with ORG_PROJECT_CREATOR role in same org
user1Id, token1 := getOrgProjectCreator(t, iamOwnerCtx, orgResp.GetOrganizationId(), orgResp.GetOrganizationId())
// user with ORG_PROJECT_CREATOR role in a different org
_, token2 := getOrgProjectCreator(t, iamOwnerCtx, orgResp.GetOrganizationId(), instance.DefaultOrg.GetId())
type want struct {
id bool
creationDate bool
@@ -136,7 +142,7 @@ func TestServer_CreateProject_Permission(t *testing.T) {
},
{
name: "with ORG_PROJECT_CREATOR permission, same organization, ok",
ctx: integration.WithAuthorizationToken(CTX, getOrgProjectCreatorToken(t, iamOwnerCtx, orgResp.GetOrganizationId(), orgResp.GetOrganizationId())),
ctx: integration.WithAuthorizationToken(CTX, token1),
req: &project.CreateProjectRequest{
Name: integration.ProjectName(),
OrganizationId: orgResp.GetOrganizationId(),
@@ -148,7 +154,7 @@ func TestServer_CreateProject_Permission(t *testing.T) {
},
{
name: "with ORG_PROJECT_CREATOR permission, other organization, ok",
ctx: integration.WithAuthorizationToken(CTX, getOrgProjectCreatorToken(t, iamOwnerCtx, orgResp.GetOrganizationId(), instance.DefaultOrg.GetId())),
ctx: integration.WithAuthorizationToken(CTX, token2),
req: &project.CreateProjectRequest{
Name: integration.ProjectName(),
OrganizationId: instance.DefaultOrg.GetId(),
@@ -158,6 +164,43 @@ func TestServer_CreateProject_Permission(t *testing.T) {
creationDate: true,
},
},
{
name: "with ORG_PROJECT_CREATOR permission, with admins and roles, ok",
ctx: integration.WithAuthorizationToken(CTX, token1),
req: &project.CreateProjectRequest{
Name: integration.ProjectName(),
OrganizationId: orgResp.GetOrganizationId(),
Admins: []*project.CreateProjectRequest_Admin{
{
UserId: user1Id,
Roles: []string{"role1", "role2"},
},
},
},
want: want{
id: true,
creationDate: true,
},
},
{
name: "with ORG_PROJECT_CREATOR permission, missing user from the admins list, ok",
ctx: integration.WithAuthorizationToken(CTX, token1),
req: &project.CreateProjectRequest{
Name: integration.ProjectName(),
OrganizationId: orgResp.GetOrganizationId(),
Admins: []*project.CreateProjectRequest_Admin{
{
UserId: user1Id,
Roles: []string{"role1", "role2"},
},
{
UserId: "random_user",
Roles: []string{"role1", "role2"},
},
},
},
wantErr: true,
},
{
name: "organization owner, ok",
ctx: instance.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
@@ -199,7 +242,7 @@ func TestServer_CreateProject_Permission(t *testing.T) {
}
}
func getOrgProjectCreatorToken(t *testing.T, ctx context.Context, orgId1, orgId2 string) string {
func getOrgProjectCreator(t *testing.T, ctx context.Context, orgId1, orgId2 string) (string, string) {
// create a machine user in Org 1
userResp := instance.CreateUserTypeMachine(ctx, orgId1)
@@ -213,7 +256,7 @@ func getOrgProjectCreatorToken(t *testing.T, ctx context.Context, orgId1, orgId2
})
require.NoError(t, err)
return instance.CreatePersonalAccessToken(ctx, userResp.GetId()).Token
return userResp.GetId(), instance.CreatePersonalAccessToken(ctx, userResp.GetId()).Token
}
func assertCreateProjectResponse(t *testing.T, creationDate, changeDate time.Time, expectedCreationDate, expectedID bool, actualResp *project.CreateProjectResponse) {

View File

@@ -31,6 +31,7 @@ func (s *Server) CreateProject(ctx context.Context, req *connect.Request[project
}
func projectCreateToCommand(req *project_pb.CreateProjectRequest) *command.AddProject {
admins := projectCreateAdminsToCommand(req.GetAdmins())
var aggregateID string
if req.Id != nil {
aggregateID = *req.Id
@@ -45,9 +46,24 @@ func projectCreateToCommand(req *project_pb.CreateProjectRequest) *command.AddPr
ProjectRoleCheck: req.AuthorizationRequired,
HasProjectCheck: req.ProjectAccessRequired,
PrivateLabelingSetting: privateLabelingSettingToDomain(req.PrivateLabelingSetting),
Admins: admins,
}
}
func projectCreateAdminsToCommand(requestAdmins []*project_pb.CreateProjectRequest_Admin) []*command.AddProjectAdmin {
if len(requestAdmins) == 0 {
return nil
}
admins := make([]*command.AddProjectAdmin, len(requestAdmins))
for i, admin := range requestAdmins {
admins[i] = &command.AddProjectAdmin{
ID: admin.GetUserId(),
Roles: admin.GetRoles(),
}
}
return admins
}
func privateLabelingSettingToDomain(setting project_pb.PrivateLabelingSetting) domain.PrivateLabelingSetting {
switch setting {
case project_pb.PrivateLabelingSetting_PRIVATE_LABELING_SETTING_ALLOW_LOGIN_USER_RESOURCE_OWNER_POLICY: