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

@@ -6,6 +6,7 @@ import (
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain"
@@ -14,6 +15,7 @@ import (
"github.com/zitadel/zitadel/internal/id"
id_mock "github.com/zitadel/zitadel/internal/id/mock"
"github.com/zitadel/zitadel/internal/repository/project"
"github.com/zitadel/zitadel/internal/repository/user"
"github.com/zitadel/zitadel/internal/zerrors"
)
@@ -195,6 +197,155 @@ func TestCommandSide_AddProject(t *testing.T) {
},
},
},
{
name: "project, with admins and no roles, ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(),
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username1",
"firstname1",
"lastname1",
"nickname1",
"displayname1",
language.German,
domain.GenderMale,
"email1",
true,
),
),
),
expectPush(
project.NewProjectAddedEvent(
context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"project", true, true, true,
domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy,
),
project.NewProjectMemberAddedEvent(
context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"user1", []string{"PROJECT_OWNER"}...,
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "instanceID"),
project: &AddProject{
ObjectRoot: models.ObjectRoot{AggregateID: "project1", ResourceOwner: "org1"},
Name: "project",
ProjectRoleAssertion: true,
ProjectRoleCheck: true,
HasProjectCheck: true,
PrivateLabelingSetting: domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy,
Admins: []*AddProjectAdmin{
{
ID: "user1",
},
},
},
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
{
name: "project, with admins and specific roles, ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(),
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username1",
"firstname1",
"lastname1",
"nickname1",
"displayname1",
language.German,
domain.GenderMale,
"email1",
true,
),
),
),
expectPush(
project.NewProjectAddedEvent(
context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"project", true, true, true,
domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy,
),
project.NewProjectMemberAddedEvent(
context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"user1", []string{"role1", "role2"}...,
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "instanceID"),
project: &AddProject{
ObjectRoot: models.ObjectRoot{AggregateID: "project1", ResourceOwner: "org1"},
Name: "project",
ProjectRoleAssertion: true,
ProjectRoleCheck: true,
HasProjectCheck: true,
PrivateLabelingSetting: domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy,
Admins: []*AddProjectAdmin{
{
ID: "user1",
Roles: []string{"role1", "role2"},
},
},
},
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
{
name: "project, admin user not found",
fields: fields{
eventstore: expectEventstore(
expectFilter(),
expectFilter(),
),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "instanceID"),
project: &AddProject{
ObjectRoot: models.ObjectRoot{AggregateID: "project1", ResourceOwner: "org1"},
Name: "project",
ProjectRoleAssertion: true,
ProjectRoleCheck: true,
HasProjectCheck: true,
PrivateLabelingSetting: domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy,
Admins: []*AddProjectAdmin{
{
ID: "user2",
Roles: []string{"role1", "role2"},
},
},
},
},
res: res{
err: zerrors.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {