diff --git a/internal/api/grpc/org/v2beta/integration_test/org_test.go b/internal/api/grpc/org/v2beta/integration_test/org_test.go index 2f141ccbd3..a8a507bab3 100644 --- a/internal/api/grpc/org/v2beta/integration_test/org_test.go +++ b/internal/api/grpc/org/v2beta/integration_test/org_test.go @@ -47,14 +47,17 @@ func TestMain(m *testing.M) { func TestServer_CreateOrganization(t *testing.T) { idpResp := Instance.AddGenericOAuthProvider(CTX, Instance.DefaultOrg.Id) - tests := []struct { - name string - ctx context.Context - req *v2beta_org.CreateOrganizationRequest - id string - want *v2beta_org.CreateOrganizationResponse - wantErr bool - }{ + type test struct { + name string + ctx context.Context + req *v2beta_org.CreateOrganizationRequest + id string + testFunc func(ctx context.Context, t *testing.T) + want *v2beta_org.CreateOrganizationResponse + wantErr bool + } + + tests := []test{ { name: "missing permission", ctx: Instance.WithAuthorization(CTX, integration.UserTypeOrgOwner), @@ -73,6 +76,25 @@ func TestServer_CreateOrganization(t *testing.T) { }, wantErr: true, }, + func() test { + orgName := gofakeit.Name() + return test{ + name: "adding org with same name twice", + ctx: CTX, + req: &v2beta_org.CreateOrganizationRequest{ + Name: orgName, + Admins: nil, + }, + testFunc: func(ctx context.Context, t *testing.T) { + // create org initially + _, err := Client.CreateOrganization(ctx, &v2beta_org.CreateOrganizationRequest{ + Name: orgName, + }) + require.NoError(t, err) + }, + wantErr: true, + } + }(), { name: "invalid admin type", ctx: CTX, @@ -212,9 +234,34 @@ func TestServer_CreateOrganization(t *testing.T) { Id: "custom_id", }, }, + func() test { + orgID := gofakeit.Name() + return test{ + name: "adding org with same ID twice", + ctx: CTX, + req: &v2beta_org.CreateOrganizationRequest{ + Id: &orgID, + Name: gofakeit.Name(), + Admins: nil, + }, + testFunc: func(ctx context.Context, t *testing.T) { + // create org initially + _, err := Client.CreateOrganization(ctx, &v2beta_org.CreateOrganizationRequest{ + Id: &orgID, + Name: gofakeit.Name(), + }) + require.NoError(t, err) + }, + wantErr: true, + } + }(), } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + if tt.testFunc != nil { + tt.testFunc(tt.ctx, t) + } + got, err := Client.CreateOrganization(tt.ctx, tt.req) if tt.wantErr { require.Error(t, err) diff --git a/internal/command/org.go b/internal/command/org.go index ff0208e5e2..215fe0b5cc 100644 --- a/internal/command/org.go +++ b/internal/command/org.go @@ -276,6 +276,15 @@ func (c *Commands) SetUpOrg(ctx context.Context, o *OrgSetup, allowInitialMail b } } + // because users can choose their own ID, we must check that an org with the same ID does not already exist + existingOrg, err := c.getOrgWriteModelByID(ctx, o.OrgID) + if err != nil { + return nil, err + } + if existingOrg.State.Exists() { + return nil, zerrors.ThrowAlreadyExists(nil, "ORG-laho2n", "Errors.Org.AlreadyExisting") + } + return c.setUpOrgWithIDs(ctx, o, o.OrgID, allowInitialMail, userIDs...) } @@ -327,12 +336,13 @@ func (c *Commands) AddOrgWithID(ctx context.Context, name, userID, resourceOwner ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() + // because users can choose their own ID, we must check that an org with the same ID does not already exist existingOrg, err := c.getOrgWriteModelByID(ctx, orgID) if err != nil { return nil, err } - if existingOrg.State != domain.OrgStateUnspecified { - return nil, zerrors.ThrowNotFound(nil, "ORG-lapo2m", "Errors.Org.AlreadyExisting") + if existingOrg.State.Exists() { + return nil, zerrors.ThrowAlreadyExists(nil, "ORG-lapo2n", "Errors.Org.AlreadyExisting") } return c.addOrgWithIDAndMember(ctx, name, userID, resourceOwner, orgID, setOrgInactive, claimedUserIDs) diff --git a/internal/command/org_test.go b/internal/command/org_test.go index a5d00d9bfd..c07d5f7678 100644 --- a/internal/command/org_test.go +++ b/internal/command/org_test.go @@ -1293,7 +1293,9 @@ func TestCommandSide_SetUpOrg(t *testing.T) { { name: "org name empty, error", fields: fields{ - eventstore: expectEventstore(), + eventstore: expectEventstore( + expectFilter(), // org already exists check + ), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "orgID"), }, args: args{ @@ -1326,6 +1328,7 @@ func TestCommandSide_SetUpOrg(t *testing.T) { name: "userID not existing, error", fields: fields{ eventstore: expectEventstore( + expectFilter(), // org already exists check expectFilter(), ), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "orgID"), @@ -1348,7 +1351,9 @@ func TestCommandSide_SetUpOrg(t *testing.T) { { name: "human invalid, error", fields: fields{ - eventstore: expectEventstore(), + eventstore: expectEventstore( + expectFilter(), // org already exists check + ), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "orgID", "userID"), }, args: args{ @@ -1381,6 +1386,7 @@ func TestCommandSide_SetUpOrg(t *testing.T) { fields: fields{ eventstore: expectEventstore( expectFilter(), // add human exists check + expectFilter(), expectFilter( eventFromEventPusher( org.NewDomainPolicyAddedEvent(context.Background(), @@ -1501,10 +1507,82 @@ func TestCommandSide_SetUpOrg(t *testing.T) { }, }, }, + { + name: "org already exists", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + org.NewOrgAddedEvent(context.Background(), + &org.NewAggregate("custom-org-ID").Aggregate, "Org"), + ), + ), + ), + }, + args: args{ + ctx: http_util.WithRequestedHost(context.Background(), "iam-domain"), + setupOrg: &OrgSetup{ + Name: "Org", + OrgID: "custom-org-ID", + }, + }, + res: res{ + err: zerrors.ThrowAlreadyExists(nil, "ORG-laho2n", "Errors.Org.AlreadyExisting"), + }, + }, + { + name: "org with same id deleted", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + org.NewOrgAddedEvent(context.Background(), + &org.NewAggregate("custom-org-ID").Aggregate, "Org"), + ), + org.NewOrgRemovedEvent( + context.Background(), &org.NewAggregate("custom-org-ID").Aggregate, + "Org", []string{}, false, []string{}, []*domain.UserIDPLink{}, []string{}), + ), + expectPush( + eventFromEventPusher(org.NewOrgAddedEvent(context.Background(), + &org.NewAggregate("custom-org-ID").Aggregate, + "Org", + )), + eventFromEventPusher(org.NewDomainAddedEvent(context.Background(), + &org.NewAggregate("custom-org-ID").Aggregate, "org.iam-domain", + )), + eventFromEventPusher(org.NewDomainVerifiedEvent(context.Background(), + &org.NewAggregate("custom-org-ID").Aggregate, + "org.iam-domain", + )), + eventFromEventPusher(org.NewDomainPrimarySetEvent(context.Background(), + &org.NewAggregate("custom-org-ID").Aggregate, + "org.iam-domain", + )), + ), + ), + }, + args: args{ + ctx: http_util.WithRequestedHost(context.Background(), "iam-domain"), + setupOrg: &OrgSetup{ + Name: "Org", + OrgID: "custom-org-ID", + }, + }, + res: res{ + createdOrg: &CreatedOrg{ + ObjectDetails: &domain.ObjectDetails{ + ResourceOwner: "custom-org-ID", + }, + OrgAdmins: []OrgAdmin{}, + }, + }, + }, { name: "no human added, custom org ID", fields: fields{ eventstore: expectEventstore( + expectFilter(), // org already exists check expectPush( eventFromEventPusher(org.NewOrgAddedEvent(context.Background(), &org.NewAggregate("custom-org-ID").Aggregate, @@ -1544,6 +1622,7 @@ func TestCommandSide_SetUpOrg(t *testing.T) { name: "existing human added", fields: fields{ eventstore: expectEventstore( + expectFilter(), // org already exists check expectFilter( eventFromEventPusher( user.NewHumanAddedEvent(context.Background(), @@ -1616,6 +1695,7 @@ func TestCommandSide_SetUpOrg(t *testing.T) { fields: fields{ eventstore: expectEventstore( expectFilter(), // add machine exists check + expectFilter(), expectFilter( eventFromEventPusher( org.NewDomainPolicyAddedEvent(context.Background(), diff --git a/internal/domain/org.go b/internal/domain/org.go index d016dde99c..f9b1140d2b 100644 --- a/internal/domain/org.go +++ b/internal/domain/org.go @@ -43,3 +43,7 @@ const ( func (s OrgState) Valid() bool { return s > OrgStateUnspecified && s < orgStateMax } + +func (s OrgState) Exists() bool { + return s != OrgStateRemoved && s != OrgStateUnspecified +} diff --git a/internal/static/i18n/bg.yaml b/internal/static/i18n/bg.yaml index 5c7742b5f7..076577f3fa 100644 --- a/internal/static/i18n/bg.yaml +++ b/internal/static/i18n/bg.yaml @@ -196,7 +196,7 @@ Errors: AlreadyExists: Екземплярът вече съществува NotChanged: Екземплярът не е променен Org: - AlreadyExists: Името на организацията вече е заето + AlreadyExists: Името или идентификационният номер на организацията вече е зает. Invalid: Организацията е невалидна AlreadyDeactivated: Организацията вече е деактивирана AlreadyActive: Организацията вече е активна diff --git a/internal/static/i18n/cs.yaml b/internal/static/i18n/cs.yaml index 26e7c5b6f4..45ca1d8a37 100644 --- a/internal/static/i18n/cs.yaml +++ b/internal/static/i18n/cs.yaml @@ -194,7 +194,7 @@ Errors: AlreadyExists: Instance již existuje NotChanged: Instance nezměněna Org: - AlreadyExists: Název organizace je již obsazen + AlreadyExists: Името или идентификационният номер на организацията вече е зает Invalid: Organizace je neplatná AlreadyDeactivated: Organizace je již deaktivována AlreadyActive: Organizace je již aktivní diff --git a/internal/static/i18n/de.yaml b/internal/static/i18n/de.yaml index b7e36febf4..cbcb61febd 100644 --- a/internal/static/i18n/de.yaml +++ b/internal/static/i18n/de.yaml @@ -194,7 +194,7 @@ Errors: AlreadyExists: Instanz exisitiert bereits NotChanged: Instanz wurde nicht verändert Org: - AlreadyExists: Organisationsname existiert bereits + AlreadyExists: Der Name oder die ID der Organisation ist bereits vorhanden Invalid: Organisation ist ungültig AlreadyDeactivated: Organisation ist bereits deaktiviert AlreadyActive: Organisation ist bereits aktiv diff --git a/internal/static/i18n/en.yaml b/internal/static/i18n/en.yaml index 7385a4a025..581e3426d5 100644 --- a/internal/static/i18n/en.yaml +++ b/internal/static/i18n/en.yaml @@ -195,7 +195,7 @@ Errors: AlreadyExists: Instance already exists NotChanged: Instance not changed Org: - AlreadyExists: Organisation's name already taken + AlreadyExists: Organisation's name or id already taken Invalid: Organisation is invalid AlreadyDeactivated: Organisation is already deactivated AlreadyActive: Organisation is already active diff --git a/internal/static/i18n/es.yaml b/internal/static/i18n/es.yaml index a41c7818c6..71c4cdc595 100644 --- a/internal/static/i18n/es.yaml +++ b/internal/static/i18n/es.yaml @@ -194,7 +194,7 @@ Errors: AlreadyExists: La instancia ya existe NotChanged: La instancia no ha cambiado Org: - AlreadyExists: El nombre de la organización ya está cogido + AlreadyExists: El nombre o id de la organización ya está tomado Invalid: El nombre de la organización no es válido AlreadyDeactivated: La organización ya está desactivada AlreadyActive: La organización ya está activada diff --git a/internal/static/i18n/fr.yaml b/internal/static/i18n/fr.yaml index f414419216..4c038f7804 100644 --- a/internal/static/i18n/fr.yaml +++ b/internal/static/i18n/fr.yaml @@ -194,7 +194,7 @@ Errors: AlreadyExists: L'instance existe déjà NotChanged: L'instance n'a pas changé Org: - AlreadyExists: Le nom de l'organisation est déjà pris + AlreadyExists: Le nom de l'organisation ou l'identifiant est déjà pris Invalid: L'organisation n'est pas valide AlreadyDeactivated: L'organisation est déjà désactivée AlreadyActive: L'organisation est déjà active diff --git a/internal/static/i18n/hu.yaml b/internal/static/i18n/hu.yaml index 60f3d87074..7aa276c140 100644 --- a/internal/static/i18n/hu.yaml +++ b/internal/static/i18n/hu.yaml @@ -194,7 +194,7 @@ Errors: AlreadyExists: Az instance már létezik NotChanged: Az instance nem változott Org: - AlreadyExists: A szervezet neve már foglalt + AlreadyExists: A szervezet neve vagy azonosítója már foglalt Invalid: A szervezet érvénytelen AlreadyDeactivated: A szervezet már deaktiválva van AlreadyActive: A szervezet már aktív diff --git a/internal/static/i18n/id.yaml b/internal/static/i18n/id.yaml index 2a9a8ee2c3..8ea9a1eb8b 100644 --- a/internal/static/i18n/id.yaml +++ b/internal/static/i18n/id.yaml @@ -194,7 +194,7 @@ Errors: AlreadyExists: Contoh sudah ada NotChanged: Contoh tidak berubah Org: - AlreadyExists: Nama organisasi sudah dipakai + AlreadyExists: Nama atau ID organisasi sudah digunakan Invalid: Organisasi tidak valid AlreadyDeactivated: Organisasi sudah dinonaktifkan AlreadyActive: Organisasi sudah aktif diff --git a/internal/static/i18n/it.yaml b/internal/static/i18n/it.yaml index cbae5543e2..903e948aa6 100644 --- a/internal/static/i18n/it.yaml +++ b/internal/static/i18n/it.yaml @@ -194,7 +194,7 @@ Errors: AlreadyExists: L'istanza esiste già NotChanged: Istanza non modificata Org: - AlreadyExists: Nome dell'organizzazione già preso + AlreadyExists: Nome o ID dell'organizzazione già utilizzato Invalid: L'organizzazione non è valida AlreadyDeactivated: L'organizzazione è già disattivata AlreadyActive: L'organizzazione è già attiva diff --git a/internal/static/i18n/ja.yaml b/internal/static/i18n/ja.yaml index 1d28dc16e5..5ca2c920fa 100644 --- a/internal/static/i18n/ja.yaml +++ b/internal/static/i18n/ja.yaml @@ -195,7 +195,7 @@ Errors: AlreadyExists: すでに存在するインスタンス NotChanged: インスタンスは変更されていません Org: - AlreadyExists: 組織の名前はすでに使用されています + AlreadyExists: 組織名またはIDはすでに使用されています Invalid: 無効な組織です AlreadyDeactivated: 組織はすでに非アクティブです AlreadyActive: 組織はすでにアクティブです diff --git a/internal/static/i18n/ko.yaml b/internal/static/i18n/ko.yaml index f0b19c6caa..729494c1f9 100644 --- a/internal/static/i18n/ko.yaml +++ b/internal/static/i18n/ko.yaml @@ -195,7 +195,7 @@ Errors: AlreadyExists: 인스턴스가 이미 존재합니다 NotChanged: 인스턴스가 변경되지 않았습니다 Org: - AlreadyExists: 조직 이름이 이미 사용 중입니다 + AlreadyExists: 조직 이름 또는 ID가 이미 사용 중입니다 Invalid: 조직이 유효하지 않습니다 AlreadyDeactivated: 조직이 이미 비활성화되었습니다 AlreadyActive: 조직이 이미 활성화되었습니다 diff --git a/internal/static/i18n/mk.yaml b/internal/static/i18n/mk.yaml index 7d03f93ee0..9ef147696d 100644 --- a/internal/static/i18n/mk.yaml +++ b/internal/static/i18n/mk.yaml @@ -193,7 +193,7 @@ Errors: AlreadyExists: Инстанцата веќе постои NotChanged: Инстанцата не е променета Org: - AlreadyExists: Името на организацијата е веќе зафатено + AlreadyExists: Името или ID-то на организацијата е веќе зафатено Invalid: Организацијата е невалидна AlreadyDeactivated: Организацијата е веќе деактивирана AlreadyActive: Организацијата е веќе активна diff --git a/internal/static/i18n/nl.yaml b/internal/static/i18n/nl.yaml index 2fb1b9ac3b..ddc831b3d4 100644 --- a/internal/static/i18n/nl.yaml +++ b/internal/static/i18n/nl.yaml @@ -194,7 +194,7 @@ Errors: AlreadyExists: Instantie bestaat al NotChanged: Instantie is niet veranderd Org: - AlreadyExists: Organisatienaam is al in gebruik + AlreadyExists: Organisatienaam of -id is al in gebruik Invalid: Organisatie is ongeldig AlreadyDeactivated: Organisatie is al gedeactiveerd AlreadyActive: Organisatie is al actief diff --git a/internal/static/i18n/pl.yaml b/internal/static/i18n/pl.yaml index a45173cf63..04d3c64fa3 100644 --- a/internal/static/i18n/pl.yaml +++ b/internal/static/i18n/pl.yaml @@ -194,7 +194,7 @@ Errors: AlreadyExists: Instancja już istnieje NotChanged: Instancja nie zmieniona Org: - AlreadyExists: Nazwa organizacji jest już zajęta + AlreadyExists: Nazwa lub identyfikator organizacji jest już zajęty Invalid: Organizacja jest nieprawidłowa AlreadyDeactivated: Organizacja jest już deaktywowana AlreadyActive: Organizacja jest już aktywna diff --git a/internal/static/i18n/pt.yaml b/internal/static/i18n/pt.yaml index 866f041e6e..73571476bd 100644 --- a/internal/static/i18n/pt.yaml +++ b/internal/static/i18n/pt.yaml @@ -193,7 +193,7 @@ Errors: AlreadyExists: Instância já existe NotChanged: Instância não alterada Org: - AlreadyExists: Nome da organização já está em uso + AlreadyExists: O nome ou ID da organização já está em uso Invalid: Organização é inválida AlreadyDeactivated: Organização já está desativada AlreadyActive: Organização já está ativa diff --git a/internal/static/i18n/ro.yaml b/internal/static/i18n/ro.yaml index f56dfd13d2..fede8eb85a 100644 --- a/internal/static/i18n/ro.yaml +++ b/internal/static/i18n/ro.yaml @@ -195,7 +195,7 @@ Errors: AlreadyExists: Instanța există deja NotChanged: Instanța nu a fost schimbată Org: - AlreadyExists: Numele organizației este deja luat + AlreadyExists: Numele sau ID-ul organizației este deja utilizat Invalid: Organizația este invalidă AlreadyDeactivated: Organizația este deja dezactivată AlreadyActive: Organizația este deja activă diff --git a/internal/static/i18n/ru.yaml b/internal/static/i18n/ru.yaml index 34eb6b9837..39654d8b12 100644 --- a/internal/static/i18n/ru.yaml +++ b/internal/static/i18n/ru.yaml @@ -194,7 +194,7 @@ Errors: AlreadyExists: Экземпляр уже существует NotChanged: Экземпляр не изменён Org: - AlreadyExists: Название организации уже занято + AlreadyExists: Название организации или идентификатор уже занят Invalid: Организация недействительна AlreadyDeactivated: Организация уже деактивирована AlreadyActive: Организация уже активна diff --git a/internal/static/i18n/sv.yaml b/internal/static/i18n/sv.yaml index c8c13e683e..daf8da5cf2 100644 --- a/internal/static/i18n/sv.yaml +++ b/internal/static/i18n/sv.yaml @@ -194,7 +194,7 @@ Errors: AlreadyExists: Instans finns redan NotChanged: Instans ändrades inte Org: - AlreadyExists: Organisationens namn är redan taget + AlreadyExists: Organisationens namn eller ID är redan upptaget Invalid: Organisationen är ogiltigt AlreadyDeactivated: Organisation är redan avaktiverad AlreadyActive: Organisationen är redan aktiv diff --git a/internal/static/i18n/zh.yaml b/internal/static/i18n/zh.yaml index 1ef3daadcd..fcb0257ffa 100644 --- a/internal/static/i18n/zh.yaml +++ b/internal/static/i18n/zh.yaml @@ -194,7 +194,7 @@ Errors: AlreadyExists: 实例已经存在 NotChanged: 实例没有改变 Org: - AlreadyExists: 组织名称已被占用 + AlreadyExists: 该组织名称或 ID 已被占用 Invalid: 组织无效 AlreadyDeactivated: 组织已停用 AlreadyActive: 组织已处于启用状态 diff --git a/proto/zitadel/management.proto b/proto/zitadel/management.proto index 09095e3b78..8bbb0cc3a0 100644 --- a/proto/zitadel/management.proto +++ b/proto/zitadel/management.proto @@ -3101,7 +3101,7 @@ service ManagementService { rpc UpdateProjectRole(UpdateProjectRoleRequest) returns (UpdateProjectRoleResponse) { option (google.api.http) = { - put: "/projects/{project_id}/roles/{role_key}" + put: "/projects/{project_id}/roles/{role_key=**}" body: "*" }; @@ -3127,7 +3127,7 @@ service ManagementService { rpc RemoveProjectRole(RemoveProjectRoleRequest) returns (RemoveProjectRoleResponse) { option (google.api.http) = { - delete: "/projects/{project_id}/roles/{role_key}" + delete: "/projects/{project_id}/roles/{role_key=**}" }; option (zitadel.v1.auth_option) = {