mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-06 05:42:04 +00:00
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:
@@ -2,14 +2,17 @@
|
|||||||
title="{{ 'PROJECT.PAGES.CREATE' | translate }}"
|
title="{{ 'PROJECT.PAGES.CREATE' | translate }}"
|
||||||
[createSteps]="1"
|
[createSteps]="1"
|
||||||
[currentCreateStep]="1"
|
[currentCreateStep]="1"
|
||||||
(closed)="close()"
|
(closed)="location.back()"
|
||||||
>
|
>
|
||||||
<h1>{{ 'PROJECT.PAGES.CREATE_DESC' | translate }}</h1>
|
<form *ngIf="userService.user$ | async as user" cdkFocusRegionStart [formGroup]="form" (ngSubmit)="saveProject(user)">
|
||||||
<form cdkFocusRegionStart (ngSubmit)="saveProject()">
|
<mat-slide-toggle class="example-margin" color="primary" [formControl]="form.controls.selfAccount">
|
||||||
|
{{ 'PROJECT.PAGES.USERSELFACCOUNT' | translate }}
|
||||||
|
</mat-slide-toggle>
|
||||||
|
<h1>{{ 'PROJECT.PAGES.CREATE_DESC' | translate }}</h1>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<cnsl-form-field class="formfield" hintLabel="The name is required!">
|
<cnsl-form-field class="formfield" hintLabel="The name is required!">
|
||||||
<cnsl-label>{{ 'PROJECT.NAME' | translate }}</cnsl-label>
|
<cnsl-label>{{ 'PROJECT.NAME' | translate }}</cnsl-label>
|
||||||
<input cnslInput cdkFocusInitial autofocus [(ngModel)]="project.name" [ngModelOptions]="{ standalone: true }" />
|
<input cnslInput cdkFocusInitial autofocus [formControl]="form.controls.name" />
|
||||||
</cnsl-form-field>
|
</cnsl-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -17,12 +20,12 @@
|
|||||||
color="primary"
|
color="primary"
|
||||||
mat-raised-button
|
mat-raised-button
|
||||||
class="continue-button"
|
class="continue-button"
|
||||||
[disabled]="!project.name"
|
[disabled]="!form.valid"
|
||||||
cdkFocusInitial
|
cdkFocusInitial
|
||||||
type="submit"
|
type="submit"
|
||||||
data-e2e="continue-button"
|
data-e2e="continue-button"
|
||||||
>
|
>
|
||||||
{{ 'ACTIONS.CONTINUE' | translate }}
|
{{ 'ACTIONS.CONTINUE' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</form></cnsl-create-layout
|
</form>
|
||||||
>
|
</cnsl-create-layout>
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { Location } from '@angular/common';
|
import { Location } from '@angular/common';
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { AddProjectRequest, AddProjectResponse } from 'src/app/proto/generated/zitadel/management_pb';
|
|
||||||
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
|
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
|
||||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
|
||||||
import { ToastService } from 'src/app/services/toast.service';
|
import { ToastService } from 'src/app/services/toast.service';
|
||||||
|
import { NewMgmtService } from 'src/app/services/new-mgmt.service';
|
||||||
|
import { UserService } from 'src/app/services/user.service';
|
||||||
|
import { FormBuilder, FormControl, Validators } from '@angular/forms';
|
||||||
|
import { User } from '@zitadel/proto/zitadel/user/v2/user_pb';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'cnsl-project-create',
|
selector: 'cnsl-project-create',
|
||||||
@@ -12,13 +14,15 @@ import { ToastService } from 'src/app/services/toast.service';
|
|||||||
styleUrls: ['./project-create.component.scss'],
|
styleUrls: ['./project-create.component.scss'],
|
||||||
})
|
})
|
||||||
export class ProjectCreateComponent {
|
export class ProjectCreateComponent {
|
||||||
public project: AddProjectRequest.AsObject = new AddProjectRequest().toObject();
|
protected readonly form: ReturnType<typeof this.buildForm>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private router: Router,
|
private readonly router: Router,
|
||||||
private toast: ToastService,
|
private readonly toast: ToastService,
|
||||||
private mgmtService: ManagementService,
|
private readonly newMgmtService: NewMgmtService,
|
||||||
private _location: Location,
|
private readonly fb: FormBuilder,
|
||||||
|
protected readonly location: Location,
|
||||||
|
protected readonly userService: UserService,
|
||||||
breadcrumbService: BreadcrumbService,
|
breadcrumbService: BreadcrumbService,
|
||||||
) {
|
) {
|
||||||
const bread: Breadcrumb = {
|
const bread: Breadcrumb = {
|
||||||
@@ -26,21 +30,31 @@ export class ProjectCreateComponent {
|
|||||||
routerLink: ['/org'],
|
routerLink: ['/org'],
|
||||||
};
|
};
|
||||||
breadcrumbService.setBreadcrumb([bread]);
|
breadcrumbService.setBreadcrumb([bread]);
|
||||||
|
|
||||||
|
this.form = this.buildForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
public saveProject(): void {
|
private buildForm() {
|
||||||
this.mgmtService
|
return this.fb.group({
|
||||||
.addProject(this.project)
|
name: new FormControl('', { nonNullable: true, validators: [Validators.required] }),
|
||||||
.then((resp: AddProjectResponse.AsObject) => {
|
selfAccount: new FormControl(true, { nonNullable: true, validators: [Validators.required] }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected saveProject(user: User): void {
|
||||||
|
const { name, selfAccount } = this.form.getRawValue();
|
||||||
|
|
||||||
|
this.newMgmtService
|
||||||
|
.addProject({
|
||||||
|
name,
|
||||||
|
admins: selfAccount ? [{ userId: user.userId }] : undefined,
|
||||||
|
})
|
||||||
|
.then((resp) => {
|
||||||
this.toast.showInfo('PROJECT.TOAST.CREATED', true);
|
this.toast.showInfo('PROJECT.TOAST.CREATED', true);
|
||||||
this.router.navigate(['projects', resp.id], { queryParams: { new: true } });
|
return this.router.navigate(['projects', resp.id], { queryParams: { new: true } });
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.toast.showError(error);
|
this.toast.showError(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public close(): void {
|
|
||||||
this._location.back();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { A11yModule } from '@angular/cdk/a11y';
|
import { A11yModule } from '@angular/cdk/a11y';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
@@ -10,6 +10,7 @@ import { InputModule } from 'src/app/modules/input/input.module';
|
|||||||
|
|
||||||
import { ProjectCreateRoutingModule } from './project-create-routing.module';
|
import { ProjectCreateRoutingModule } from './project-create-routing.module';
|
||||||
import { ProjectCreateComponent } from './project-create.component';
|
import { ProjectCreateComponent } from './project-create.component';
|
||||||
|
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [ProjectCreateComponent],
|
declarations: [ProjectCreateComponent],
|
||||||
@@ -23,6 +24,8 @@ import { ProjectCreateComponent } from './project-create.component';
|
|||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
MatIconModule,
|
MatIconModule,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
|
MatSlideToggleModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export default class ProjectCreateModule {}
|
export default class ProjectCreateModule {}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { GrpcService } from './grpc.service';
|
import { GrpcService } from './grpc.service';
|
||||||
import {
|
import {
|
||||||
|
AddProjectRequestSchema,
|
||||||
|
AddProjectResponse,
|
||||||
GenerateMachineSecretRequestSchema,
|
GenerateMachineSecretRequestSchema,
|
||||||
GenerateMachineSecretResponse,
|
GenerateMachineSecretResponse,
|
||||||
GetDefaultPasswordComplexityPolicyResponse,
|
GetDefaultPasswordComplexityPolicyResponse,
|
||||||
@@ -19,7 +21,6 @@ import {
|
|||||||
ResendHumanInitializationResponse,
|
ResendHumanInitializationResponse,
|
||||||
ResendHumanPhoneVerificationRequestSchema,
|
ResendHumanPhoneVerificationRequestSchema,
|
||||||
ResendHumanPhoneVerificationResponse,
|
ResendHumanPhoneVerificationResponse,
|
||||||
SendHumanResetPasswordNotificationRequest_Type,
|
|
||||||
SendHumanResetPasswordNotificationRequestSchema,
|
SendHumanResetPasswordNotificationRequestSchema,
|
||||||
SendHumanResetPasswordNotificationResponse,
|
SendHumanResetPasswordNotificationResponse,
|
||||||
SetUserMetadataRequestSchema,
|
SetUserMetadataRequestSchema,
|
||||||
@@ -98,4 +99,8 @@ export class NewMgmtService {
|
|||||||
public getDefaultPasswordComplexityPolicy(): Promise<GetDefaultPasswordComplexityPolicyResponse> {
|
public getDefaultPasswordComplexityPolicy(): Promise<GetDefaultPasswordComplexityPolicyResponse> {
|
||||||
return this.grpcService.mgmtNew.getDefaultPasswordComplexityPolicy({});
|
return this.grpcService.mgmtNew.getDefaultPasswordComplexityPolicy({});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public addProject(req: MessageInitShape<typeof AddProjectRequestSchema>): Promise<AddProjectResponse> {
|
||||||
|
return this.grpcService.mgmtNew.addProject(req);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1959,6 +1959,7 @@
|
|||||||
"CREATE_DESC": "Въведете името на вашия проект.",
|
"CREATE_DESC": "Въведете името на вашия проект.",
|
||||||
"ROLE": "Роля",
|
"ROLE": "Роля",
|
||||||
"NOITEMS": "Без проекти",
|
"NOITEMS": "Без проекти",
|
||||||
|
"USERSELFACCOUNT": "Използвайте личния си акаунт като собственик на проекта.",
|
||||||
"ZITADELPROJECT": "Това принадлежи на проекта ZITADEL. ",
|
"ZITADELPROJECT": "Това принадлежи на проекта ZITADEL. ",
|
||||||
"TYPE": {
|
"TYPE": {
|
||||||
"OWNED": "Притежавани проекти",
|
"OWNED": "Притежавани проекти",
|
||||||
|
|||||||
@@ -1961,6 +1961,7 @@
|
|||||||
"CREATE_DESC": "Vložte název vašeho projektu.",
|
"CREATE_DESC": "Vložte název vašeho projektu.",
|
||||||
"ROLE": "Role",
|
"ROLE": "Role",
|
||||||
"NOITEMS": "Žádné projekty",
|
"NOITEMS": "Žádné projekty",
|
||||||
|
"USERSELFACCOUNT": "Použijte svůj osobní účet jako vlastník projektu.",
|
||||||
"ZITADELPROJECT": "Tato nastavení jsou základní nastavení instance projektu ZITADEL. Pozor: Pokud provedete změny, ZITADEL se nemusí chovat podle očekávání.",
|
"ZITADELPROJECT": "Tato nastavení jsou základní nastavení instance projektu ZITADEL. Pozor: Pokud provedete změny, ZITADEL se nemusí chovat podle očekávání.",
|
||||||
"TYPE": {
|
"TYPE": {
|
||||||
"OWNED": "Vlastní projekty",
|
"OWNED": "Vlastní projekty",
|
||||||
|
|||||||
@@ -1960,6 +1960,7 @@
|
|||||||
"CREATE_DESC": "Gebe den Namen ein.",
|
"CREATE_DESC": "Gebe den Namen ein.",
|
||||||
"ROLE": "Rolle",
|
"ROLE": "Rolle",
|
||||||
"NOITEMS": "Keine Projekte",
|
"NOITEMS": "Keine Projekte",
|
||||||
|
"USERSELFACCOUNT": "Verwenden Sie Ihr persönliches Konto als Projektinhaber.",
|
||||||
"ZITADELPROJECT": "Diese Einstellungen gehören zum ZITADEL-Projekt. Wenn Du diese änderst, verhält sich ZITADEL möglicherweise nicht wie beabsichtigt.",
|
"ZITADELPROJECT": "Diese Einstellungen gehören zum ZITADEL-Projekt. Wenn Du diese änderst, verhält sich ZITADEL möglicherweise nicht wie beabsichtigt.",
|
||||||
"TYPE": {
|
"TYPE": {
|
||||||
"OWNED": "Eigene Projekte",
|
"OWNED": "Eigene Projekte",
|
||||||
|
|||||||
@@ -1963,6 +1963,7 @@
|
|||||||
"CREATE_DESC": "Insert your project's name.",
|
"CREATE_DESC": "Insert your project's name.",
|
||||||
"ROLE": "Role",
|
"ROLE": "Role",
|
||||||
"NOITEMS": "No projects",
|
"NOITEMS": "No projects",
|
||||||
|
"USERSELFACCOUNT": "Use your personal account as project owner.",
|
||||||
"ZITADELPROJECT": "This belongs to the ZITADEL project. Beware: If you make changes ZITADEL may not behave as intended.",
|
"ZITADELPROJECT": "This belongs to the ZITADEL project. Beware: If you make changes ZITADEL may not behave as intended.",
|
||||||
"TYPE": {
|
"TYPE": {
|
||||||
"OWNED": "Owned Projects",
|
"OWNED": "Owned Projects",
|
||||||
|
|||||||
@@ -1961,6 +1961,7 @@
|
|||||||
"CREATE_DESC": "Inserta el nombre de tu proyecto.",
|
"CREATE_DESC": "Inserta el nombre de tu proyecto.",
|
||||||
"ROLE": "Rol",
|
"ROLE": "Rol",
|
||||||
"NOITEMS": "Sin proyectos",
|
"NOITEMS": "Sin proyectos",
|
||||||
|
"USERSELFACCOUNT": "Utiliza tu cuenta personal como propietario del proyecto.",
|
||||||
"ZITADELPROJECT": "Esto pertenece al proyecto ZITADEL. Cuidado: Si haces cambio puede que ZITADEL no se comporte como se espera.",
|
"ZITADELPROJECT": "Esto pertenece al proyecto ZITADEL. Cuidado: Si haces cambio puede que ZITADEL no se comporte como se espera.",
|
||||||
"TYPE": {
|
"TYPE": {
|
||||||
"OWNED": "Proyectos propios",
|
"OWNED": "Proyectos propios",
|
||||||
|
|||||||
@@ -1960,6 +1960,7 @@
|
|||||||
"CREATE_DESC": "Insérez le nom de votre projet.",
|
"CREATE_DESC": "Insérez le nom de votre projet.",
|
||||||
"ROLE": "Rôle",
|
"ROLE": "Rôle",
|
||||||
"NOITEMS": "Aucun projet",
|
"NOITEMS": "Aucun projet",
|
||||||
|
"USERSELFACCOUNT": "Utilisez votre compte personnel en tant que propriétaire du projet.",
|
||||||
"ZITADELPROJECT": "Ceci appartient au projet ZITADEL. Attention : si vous faites de changements, ZITADEL pourrait ne pas fonctionner correctement.",
|
"ZITADELPROJECT": "Ceci appartient au projet ZITADEL. Attention : si vous faites de changements, ZITADEL pourrait ne pas fonctionner correctement.",
|
||||||
"TYPE": {
|
"TYPE": {
|
||||||
"OWNED": "Projets possédés",
|
"OWNED": "Projets possédés",
|
||||||
|
|||||||
@@ -1958,6 +1958,7 @@
|
|||||||
"CREATE_DESC": "Add meg a projekted nevét.",
|
"CREATE_DESC": "Add meg a projekted nevét.",
|
||||||
"ROLE": "Szerepkör",
|
"ROLE": "Szerepkör",
|
||||||
"NOITEMS": "Nincsenek projektek",
|
"NOITEMS": "Nincsenek projektek",
|
||||||
|
"USERSELFACCOUNT": "Használja személyes fiókját a projekt tulajdonosaként.",
|
||||||
"ZITADELPROJECT": "Ez a ZITADEL projekthez tartozik. Vigyázz: Ha változtatásokat hajtasz végre, a ZITADEL lehet, hogy nem fog a szándékod szerint működni.",
|
"ZITADELPROJECT": "Ez a ZITADEL projekthez tartozik. Vigyázz: Ha változtatásokat hajtasz végre, a ZITADEL lehet, hogy nem fog a szándékod szerint működni.",
|
||||||
"TYPE": {
|
"TYPE": {
|
||||||
"OWNED": "Saját Projektek",
|
"OWNED": "Saját Projektek",
|
||||||
|
|||||||
@@ -1819,6 +1819,7 @@
|
|||||||
"CREATE_DESC": "Masukkan nama proyek Anda.",
|
"CREATE_DESC": "Masukkan nama proyek Anda.",
|
||||||
"ROLE": "Peran",
|
"ROLE": "Peran",
|
||||||
"NOITEMS": "Tidak ada proyek",
|
"NOITEMS": "Tidak ada proyek",
|
||||||
|
"USERSELFACCOUNT": "Gunakan akun pribadi Anda sebagai pemilik proyek.",
|
||||||
"ZITADELPROJECT": "Ini milik proyek ZITADEL. Hati-hati: Jika Anda membuat perubahan, ZITADEL mungkin tidak berfungsi sebagaimana mestinya.",
|
"ZITADELPROJECT": "Ini milik proyek ZITADEL. Hati-hati: Jika Anda membuat perubahan, ZITADEL mungkin tidak berfungsi sebagaimana mestinya.",
|
||||||
"TYPE": {
|
"TYPE": {
|
||||||
"OWNED": "Proyek Milik",
|
"OWNED": "Proyek Milik",
|
||||||
|
|||||||
@@ -1960,6 +1960,7 @@
|
|||||||
"CREATE_DESC": "Inserisci il nome del tuo progetto.",
|
"CREATE_DESC": "Inserisci il nome del tuo progetto.",
|
||||||
"ROLE": "Ruolo",
|
"ROLE": "Ruolo",
|
||||||
"NOITEMS": "Nessun progetto",
|
"NOITEMS": "Nessun progetto",
|
||||||
|
"USERSELFACCOUNT": "Utilizza il tuo account personale come proprietario del progetto.",
|
||||||
"ZITADELPROJECT": "Questo appartiene al progetto ZITADEL. Attenzione: se fai delle modifiche ZITADEL potrebbe non comportarsi come previsto.",
|
"ZITADELPROJECT": "Questo appartiene al progetto ZITADEL. Attenzione: se fai delle modifiche ZITADEL potrebbe non comportarsi come previsto.",
|
||||||
"TYPE": {
|
"TYPE": {
|
||||||
"OWNED": "Progetti proprietari",
|
"OWNED": "Progetti proprietari",
|
||||||
|
|||||||
@@ -1960,6 +1960,7 @@
|
|||||||
"CREATE_DESC": "プロジェクトの名前を入力します。",
|
"CREATE_DESC": "プロジェクトの名前を入力します。",
|
||||||
"ROLE": "ロール",
|
"ROLE": "ロール",
|
||||||
"NOITEMS": "プロジェクトはありません",
|
"NOITEMS": "プロジェクトはありません",
|
||||||
|
"USERSELFACCOUNT": "プロジェクトの所有者としては、ご自身の個人アカウントをご利用ください。",
|
||||||
"ZITADELPROJECT": "これはZITADELプロジェクトに属します。注意:変更した場合、ZITADELは意図したとおりに動作しないことがあります。",
|
"ZITADELPROJECT": "これはZITADELプロジェクトに属します。注意:変更した場合、ZITADELは意図したとおりに動作しないことがあります。",
|
||||||
"TYPE": {
|
"TYPE": {
|
||||||
"OWNED": "所有プロジェクト",
|
"OWNED": "所有プロジェクト",
|
||||||
|
|||||||
@@ -1960,6 +1960,7 @@
|
|||||||
"CREATE_DESC": "프로젝트의 이름을 입력하세요.",
|
"CREATE_DESC": "프로젝트의 이름을 입력하세요.",
|
||||||
"ROLE": "역할",
|
"ROLE": "역할",
|
||||||
"NOITEMS": "프로젝트가 없습니다",
|
"NOITEMS": "프로젝트가 없습니다",
|
||||||
|
"USERSELFACCOUNT": "프로젝트 소유자로 개인 계정을 사용해 주십시오",
|
||||||
"ZITADELPROJECT": "이 프로젝트는 ZITADEL 프로젝트에 속해 있습니다. 변경 시 ZITADEL이 의도한 대로 작동하지 않을 수 있습니다.",
|
"ZITADELPROJECT": "이 프로젝트는 ZITADEL 프로젝트에 속해 있습니다. 변경 시 ZITADEL이 의도한 대로 작동하지 않을 수 있습니다.",
|
||||||
"TYPE": {
|
"TYPE": {
|
||||||
"OWNED": "소유한 프로젝트",
|
"OWNED": "소유한 프로젝트",
|
||||||
|
|||||||
@@ -1961,6 +1961,7 @@
|
|||||||
"CREATE_DESC": "Внесете го името на вашиот проект.",
|
"CREATE_DESC": "Внесете го името на вашиот проект.",
|
||||||
"ROLE": "Улога",
|
"ROLE": "Улога",
|
||||||
"NOITEMS": "Нема проекти",
|
"NOITEMS": "Нема проекти",
|
||||||
|
"USERSELFACCOUNT": "Користете ја вашата лична корисничка сметка како сопственик на проектот.",
|
||||||
"ZITADELPROJECT": "Ова припаѓа на ZITADEL проектот. Внимание: Ако направите промени, ZITADEL може да не работи како што е предвидено.",
|
"ZITADELPROJECT": "Ова припаѓа на ZITADEL проектот. Внимание: Ако направите промени, ZITADEL може да не работи како што е предвидено.",
|
||||||
"TYPE": {
|
"TYPE": {
|
||||||
"OWNED": "Сопствени проекти",
|
"OWNED": "Сопствени проекти",
|
||||||
|
|||||||
@@ -1960,6 +1960,7 @@
|
|||||||
"CREATE_DESC": "Voer de naam van uw project in.",
|
"CREATE_DESC": "Voer de naam van uw project in.",
|
||||||
"ROLE": "Rol",
|
"ROLE": "Rol",
|
||||||
"NOITEMS": "Geen projecten",
|
"NOITEMS": "Geen projecten",
|
||||||
|
"USERSELFACCOUNT": "Gebruik uw persoonlijke account als projecteigenaar.",
|
||||||
"ZITADELPROJECT": "Dit behoort tot het ZITADEL-project. Pas op: Als u wijzigingen aanbrengt, werkt ZITADEL mogelijk niet zoals bedoeld.",
|
"ZITADELPROJECT": "Dit behoort tot het ZITADEL-project. Pas op: Als u wijzigingen aanbrengt, werkt ZITADEL mogelijk niet zoals bedoeld.",
|
||||||
"TYPE": {
|
"TYPE": {
|
||||||
"OWNED": "Eigen Projecten",
|
"OWNED": "Eigen Projecten",
|
||||||
|
|||||||
@@ -1959,6 +1959,7 @@
|
|||||||
"CREATE_DESC": "Wprowadź nazwę projektu.",
|
"CREATE_DESC": "Wprowadź nazwę projektu.",
|
||||||
"ROLE": "Rola",
|
"ROLE": "Rola",
|
||||||
"NOITEMS": "Brak projektów",
|
"NOITEMS": "Brak projektów",
|
||||||
|
"USERSELFACCOUNT": "Użyj swojego konta osobistego jako właściciel projektu.",
|
||||||
"ZITADELPROJECT": "To należy do projektu ZITADEL. Uwaga: Jeśli wprowadzisz zmiany, ZITADEL może nie działać zgodnie z oczekiwaniami.",
|
"ZITADELPROJECT": "To należy do projektu ZITADEL. Uwaga: Jeśli wprowadzisz zmiany, ZITADEL może nie działać zgodnie z oczekiwaniami.",
|
||||||
"TYPE": {
|
"TYPE": {
|
||||||
"OWNED": "Własne Projekty",
|
"OWNED": "Własne Projekty",
|
||||||
|
|||||||
@@ -1960,6 +1960,7 @@
|
|||||||
"CREATE_DESC": "Insira o nome do seu projeto.",
|
"CREATE_DESC": "Insira o nome do seu projeto.",
|
||||||
"ROLE": "Função",
|
"ROLE": "Função",
|
||||||
"NOITEMS": "Nenhum projeto",
|
"NOITEMS": "Nenhum projeto",
|
||||||
|
"USERSELFACCOUNT": "Use a sua conta pessoal como proprietário do projeto.",
|
||||||
"ZITADELPROJECT": "Isto pertence ao projeto ZITADEL. Atenção: Se você fizer alterações, o ZITADEL pode não funcionar como o esperado.",
|
"ZITADELPROJECT": "Isto pertence ao projeto ZITADEL. Atenção: Se você fizer alterações, o ZITADEL pode não funcionar como o esperado.",
|
||||||
"TYPE": {
|
"TYPE": {
|
||||||
"OWNED": "Projetos Próprios",
|
"OWNED": "Projetos Próprios",
|
||||||
|
|||||||
@@ -1958,6 +1958,7 @@
|
|||||||
"CREATE_DESC": "Introduceți numele proiectului dvs.",
|
"CREATE_DESC": "Introduceți numele proiectului dvs.",
|
||||||
"ROLE": "Rol",
|
"ROLE": "Rol",
|
||||||
"NOITEMS": "Niciun proiect",
|
"NOITEMS": "Niciun proiect",
|
||||||
|
"USERSELFACCOUNT": "Utilizați contul personal ca proprietar al proiectului.",
|
||||||
"ZITADELPROJECT": "Acesta aparține proiectului ZITADEL. Atenție: Dacă faceți modificări, ZITADEL ar putea să nu se comporte conform intențiilor.",
|
"ZITADELPROJECT": "Acesta aparține proiectului ZITADEL. Atenție: Dacă faceți modificări, ZITADEL ar putea să nu se comporte conform intențiilor.",
|
||||||
"TYPE": {
|
"TYPE": {
|
||||||
"OWNED": "Proiecte deținute",
|
"OWNED": "Proiecte deținute",
|
||||||
|
|||||||
@@ -2045,6 +2045,7 @@
|
|||||||
"CREATE_DESC": "Введите название вашего проекта.",
|
"CREATE_DESC": "Введите название вашего проекта.",
|
||||||
"ROLE": "Роль",
|
"ROLE": "Роль",
|
||||||
"NOITEMS": "Нет проектов",
|
"NOITEMS": "Нет проектов",
|
||||||
|
"USERSELFACCOUNT": "Используйте свою личную учетную запись в качестве владельца проекта.",
|
||||||
"ZITADELPROJECT": "Это принадлежит проекту ZITADEL. Осторожно: Если вы внесёте изменения, ZITADEL может вести себя не так, как предполагалось.",
|
"ZITADELPROJECT": "Это принадлежит проекту ZITADEL. Осторожно: Если вы внесёте изменения, ZITADEL может вести себя не так, как предполагалось.",
|
||||||
"TYPE": {
|
"TYPE": {
|
||||||
"OWNED": "Собственные проекты",
|
"OWNED": "Собственные проекты",
|
||||||
|
|||||||
@@ -1964,6 +1964,7 @@
|
|||||||
"CREATE_DESC": "Ange ditt projekts namn.",
|
"CREATE_DESC": "Ange ditt projekts namn.",
|
||||||
"ROLE": "Roll",
|
"ROLE": "Roll",
|
||||||
"NOITEMS": "Inga projekt",
|
"NOITEMS": "Inga projekt",
|
||||||
|
"USERSELFACCOUNT": "Använd ditt personliga konto som projektägare.",
|
||||||
"ZITADELPROJECT": "Detta tillhör ZITADEL-projektet. Observera: Om du gör ändringar kan ZITADEL inte fungera som avsett.",
|
"ZITADELPROJECT": "Detta tillhör ZITADEL-projektet. Observera: Om du gör ändringar kan ZITADEL inte fungera som avsett.",
|
||||||
"TYPE": {
|
"TYPE": {
|
||||||
"OWNED": "Ägda Projekt",
|
"OWNED": "Ägda Projekt",
|
||||||
|
|||||||
@@ -1963,6 +1963,7 @@
|
|||||||
"CREATE_DESC": "Projenizin adını girin.",
|
"CREATE_DESC": "Projenizin adını girin.",
|
||||||
"ROLE": "Rol",
|
"ROLE": "Rol",
|
||||||
"NOITEMS": "Proje yok",
|
"NOITEMS": "Proje yok",
|
||||||
|
"USERSELFACCOUNT": "Proje sahibi olarak kişisel hesabınızı kullanın.",
|
||||||
"ZITADELPROJECT": "Bu ZITADEL projesine aittir. Dikkat: Değişiklik yaparsanız ZITADEL amaçlandığı gibi davranmayabilir.",
|
"ZITADELPROJECT": "Bu ZITADEL projesine aittir. Dikkat: Değişiklik yaparsanız ZITADEL amaçlandığı gibi davranmayabilir.",
|
||||||
"TYPE": {
|
"TYPE": {
|
||||||
"OWNED": "Sahip Olunan Projeler",
|
"OWNED": "Sahip Olunan Projeler",
|
||||||
|
|||||||
@@ -1963,6 +1963,7 @@
|
|||||||
"CREATE_DESC": "插入您的项目名称。",
|
"CREATE_DESC": "插入您的项目名称。",
|
||||||
"ROLE": "角色",
|
"ROLE": "角色",
|
||||||
"NOITEMS": "没有项目",
|
"NOITEMS": "没有项目",
|
||||||
|
"USERSELFACCOUNT": "使用您的个人账户作为项目所有者",
|
||||||
"ZITADELPROJECT": "这属于 ZITADEL 项目。注意:如果您修改了设置,ZITADEL 可能无法正常运行。",
|
"ZITADELPROJECT": "这属于 ZITADEL 项目。注意:如果您修改了设置,ZITADEL 可能无法正常运行。",
|
||||||
"TYPE": {
|
"TYPE": {
|
||||||
"OWNED": "拥有的项目",
|
"OWNED": "拥有的项目",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func ProjectCreateToCommand(req *mgmt_pb.AddProjectRequest, projectID string, resourceOwner string) *command.AddProject {
|
func ProjectCreateToCommand(req *mgmt_pb.AddProjectRequest, projectID string, resourceOwner string) *command.AddProject {
|
||||||
|
admins := projectCreateAdminsToCommand(req.GetAdmins())
|
||||||
return &command.AddProject{
|
return &command.AddProject{
|
||||||
ObjectRoot: models.ObjectRoot{
|
ObjectRoot: models.ObjectRoot{
|
||||||
AggregateID: projectID,
|
AggregateID: projectID,
|
||||||
@@ -28,6 +29,7 @@ func ProjectCreateToCommand(req *mgmt_pb.AddProjectRequest, projectID string, re
|
|||||||
ProjectRoleCheck: req.ProjectRoleCheck,
|
ProjectRoleCheck: req.ProjectRoleCheck,
|
||||||
HasProjectCheck: req.HasProjectCheck,
|
HasProjectCheck: req.HasProjectCheck,
|
||||||
PrivateLabelingSetting: privateLabelingSettingToDomain(req.PrivateLabelingSetting),
|
PrivateLabelingSetting: privateLabelingSettingToDomain(req.PrivateLabelingSetting),
|
||||||
|
Admins: admins,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,6 +58,20 @@ func privateLabelingSettingToDomain(setting proj_pb.PrivateLabelingSetting) doma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func projectCreateAdminsToCommand(requestAdmins []*mgmt_pb.AddProjectRequest_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 AddProjectRoleRequestToCommand(req *mgmt_pb.AddProjectRoleRequest, resourceOwner string) *command.AddProjectRole {
|
func AddProjectRoleRequestToCommand(req *mgmt_pb.AddProjectRoleRequest, resourceOwner string) *command.AddProjectRole {
|
||||||
return &command.AddProjectRole{
|
return &command.AddProjectRole{
|
||||||
ObjectRoot: models.ObjectRoot{
|
ObjectRoot: models.ObjectRoot{
|
||||||
|
|||||||
@@ -96,6 +96,12 @@ func TestServer_CreateProject_Permission(t *testing.T) {
|
|||||||
iamOwnerCtx := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner)
|
iamOwnerCtx := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner)
|
||||||
orgResp := instance.CreateOrganization(iamOwnerCtx, integration.OrganizationName(), integration.Email())
|
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 {
|
type want struct {
|
||||||
id bool
|
id bool
|
||||||
creationDate bool
|
creationDate bool
|
||||||
@@ -136,7 +142,7 @@ func TestServer_CreateProject_Permission(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "with ORG_PROJECT_CREATOR permission, same organization, ok",
|
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{
|
req: &project.CreateProjectRequest{
|
||||||
Name: integration.ProjectName(),
|
Name: integration.ProjectName(),
|
||||||
OrganizationId: orgResp.GetOrganizationId(),
|
OrganizationId: orgResp.GetOrganizationId(),
|
||||||
@@ -148,7 +154,7 @@ func TestServer_CreateProject_Permission(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "with ORG_PROJECT_CREATOR permission, other organization, ok",
|
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{
|
req: &project.CreateProjectRequest{
|
||||||
Name: integration.ProjectName(),
|
Name: integration.ProjectName(),
|
||||||
OrganizationId: instance.DefaultOrg.GetId(),
|
OrganizationId: instance.DefaultOrg.GetId(),
|
||||||
@@ -158,6 +164,43 @@ func TestServer_CreateProject_Permission(t *testing.T) {
|
|||||||
creationDate: true,
|
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",
|
name: "organization owner, ok",
|
||||||
ctx: instance.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
|
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
|
// create a machine user in Org 1
|
||||||
userResp := instance.CreateUserTypeMachine(ctx, orgId1)
|
userResp := instance.CreateUserTypeMachine(ctx, orgId1)
|
||||||
|
|
||||||
@@ -213,7 +256,7 @@ func getOrgProjectCreatorToken(t *testing.T, ctx context.Context, orgId1, orgId2
|
|||||||
})
|
})
|
||||||
require.NoError(t, err)
|
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) {
|
func assertCreateProjectResponse(t *testing.T, creationDate, changeDate time.Time, expectedCreationDate, expectedID bool, actualResp *project.CreateProjectResponse) {
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ func (s *Server) CreateProject(ctx context.Context, req *connect.Request[project
|
|||||||
}
|
}
|
||||||
|
|
||||||
func projectCreateToCommand(req *project_pb.CreateProjectRequest) *command.AddProject {
|
func projectCreateToCommand(req *project_pb.CreateProjectRequest) *command.AddProject {
|
||||||
|
admins := projectCreateAdminsToCommand(req.GetAdmins())
|
||||||
var aggregateID string
|
var aggregateID string
|
||||||
if req.Id != nil {
|
if req.Id != nil {
|
||||||
aggregateID = *req.Id
|
aggregateID = *req.Id
|
||||||
@@ -45,9 +46,24 @@ func projectCreateToCommand(req *project_pb.CreateProjectRequest) *command.AddPr
|
|||||||
ProjectRoleCheck: req.AuthorizationRequired,
|
ProjectRoleCheck: req.AuthorizationRequired,
|
||||||
HasProjectCheck: req.ProjectAccessRequired,
|
HasProjectCheck: req.ProjectAccessRequired,
|
||||||
PrivateLabelingSetting: privateLabelingSettingToDomain(req.PrivateLabelingSetting),
|
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 {
|
func privateLabelingSettingToDomain(setting project_pb.PrivateLabelingSetting) domain.PrivateLabelingSetting {
|
||||||
switch setting {
|
switch setting {
|
||||||
case project_pb.PrivateLabelingSetting_PRIVATE_LABELING_SETTING_ALLOW_LOGIN_USER_RESOURCE_OWNER_POLICY:
|
case project_pb.PrivateLabelingSetting_PRIVATE_LABELING_SETTING_ALLOW_LOGIN_USER_RESOURCE_OWNER_POLICY:
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ type AddProject struct {
|
|||||||
ProjectRoleCheck bool
|
ProjectRoleCheck bool
|
||||||
HasProjectCheck bool
|
HasProjectCheck bool
|
||||||
PrivateLabelingSetting domain.PrivateLabelingSetting
|
PrivateLabelingSetting domain.PrivateLabelingSetting
|
||||||
|
Admins []*AddProjectAdmin
|
||||||
|
}
|
||||||
|
|
||||||
|
type AddProjectAdmin struct {
|
||||||
|
ID string
|
||||||
|
Roles []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *AddProject) IsValid() error {
|
func (p *AddProject) IsValid() error {
|
||||||
@@ -74,6 +80,12 @@ func (c *Commands) AddProject(ctx context.Context, add *AddProject) (_ *domain.O
|
|||||||
add.HasProjectCheck,
|
add.HasProjectCheck,
|
||||||
add.PrivateLabelingSetting),
|
add.PrivateLabelingSetting),
|
||||||
}
|
}
|
||||||
|
projectMemberEvents, err := c.addProjectMemberEvents(ctx, add.ResourceOwner, add.AggregateID, add.Admins)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
events = append(events, projectMemberEvents...)
|
||||||
|
|
||||||
postCommit, err := c.projectCreatedMilestone(ctx, &events)
|
postCommit, err := c.projectCreatedMilestone(ctx, &events)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -187,6 +199,32 @@ func (c *Commands) checkProjectExists(ctx context.Context, projectID, resourceOw
|
|||||||
return agg.ResourceOwner, nil
|
return agg.ResourceOwner, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Commands) addProjectMemberEvents(ctx context.Context, resourceOwner, projectID string, admins []*AddProjectAdmin) ([]eventstore.Command, error) {
|
||||||
|
if len(admins) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
events := make([]eventstore.Command, 0)
|
||||||
|
for _, admin := range admins {
|
||||||
|
_, err := c.checkUserExists(ctx, admin.ID, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
roles := admin.Roles
|
||||||
|
if len(roles) == 0 {
|
||||||
|
roles = append(roles, domain.RoleProjectOwner)
|
||||||
|
}
|
||||||
|
|
||||||
|
projectMemberWriteModel := NewProjectMemberWriteModel(projectID, admin.ID, resourceOwner)
|
||||||
|
events = append(events, project.NewProjectMemberAddedEvent(ctx,
|
||||||
|
ProjectAggregateFromWriteModelWithCTX(ctx, &projectMemberWriteModel.WriteModel),
|
||||||
|
admin.ID,
|
||||||
|
roles...,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
return events, nil
|
||||||
|
}
|
||||||
|
|
||||||
type ChangeProject struct {
|
type ChangeProject struct {
|
||||||
models.ObjectRoot
|
models.ObjectRoot
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/muhlemmer/gu"
|
"github.com/muhlemmer/gu"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/api/authz"
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
@@ -14,6 +15,7 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/id"
|
"github.com/zitadel/zitadel/internal/id"
|
||||||
id_mock "github.com/zitadel/zitadel/internal/id/mock"
|
id_mock "github.com/zitadel/zitadel/internal/id/mock"
|
||||||
"github.com/zitadel/zitadel/internal/repository/project"
|
"github.com/zitadel/zitadel/internal/repository/project"
|
||||||
|
"github.com/zitadel/zitadel/internal/repository/user"
|
||||||
"github.com/zitadel/zitadel/internal/zerrors"
|
"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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|||||||
@@ -9850,6 +9850,12 @@ message ListProjectChangesResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message AddProjectRequest {
|
message AddProjectRequest {
|
||||||
|
message Admin {
|
||||||
|
string user_id = 1;
|
||||||
|
// specify the Project Member Roles for the provided user (default is PROJECT_OWNER if roles are empty
|
||||||
|
repeated string roles = 3;
|
||||||
|
}
|
||||||
|
|
||||||
string name = 1 [
|
string name = 1 [
|
||||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||||
(google.api.field_behavior) = REQUIRED,
|
(google.api.field_behavior) = REQUIRED,
|
||||||
@@ -9880,6 +9886,8 @@ message AddProjectRequest {
|
|||||||
description: "Define which private labeling/branding should trigger when getting to a login of this project.";
|
description: "Define which private labeling/branding should trigger when getting to a login of this project.";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
// List of users and Project Member roles (PROJECT_OWNER, by default) to be assigned to those users.
|
||||||
|
repeated Admin admins = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AddProjectResponse {
|
message AddProjectResponse {
|
||||||
|
|||||||
@@ -6,14 +6,16 @@ import "google/api/annotations.proto";
|
|||||||
import "google/api/field_behavior.proto";
|
import "google/api/field_behavior.proto";
|
||||||
import "google/protobuf/duration.proto";
|
import "google/protobuf/duration.proto";
|
||||||
import "google/protobuf/struct.proto";
|
import "google/protobuf/struct.proto";
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
import "protoc-gen-openapiv2/options/annotations.proto";
|
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||||
import "validate/validate.proto";
|
import "validate/validate.proto";
|
||||||
|
|
||||||
import "zitadel/protoc_gen_zitadel/v2/options.proto";
|
import "zitadel/protoc_gen_zitadel/v2/options.proto";
|
||||||
|
|
||||||
import "zitadel/project/v2beta/query.proto";
|
|
||||||
import "google/protobuf/timestamp.proto";
|
|
||||||
import "zitadel/filter/v2beta/filter.proto";
|
import "zitadel/filter/v2beta/filter.proto";
|
||||||
|
import "zitadel/project/v2beta/query.proto";
|
||||||
|
import "zitadel/user/v2/user.proto";
|
||||||
|
import "zitadel/user/v2/user_service.proto";
|
||||||
|
|
||||||
option go_package = "github.com/zitadel/zitadel/pkg/grpc/project/v2beta;project";
|
option go_package = "github.com/zitadel/zitadel/pkg/grpc/project/v2beta;project";
|
||||||
|
|
||||||
@@ -669,6 +671,12 @@ service ProjectService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message CreateProjectRequest {
|
message CreateProjectRequest {
|
||||||
|
message Admin {
|
||||||
|
string user_id = 1;
|
||||||
|
// specify the Project Member Roles for the provided user (default is PROJECT_OWNER if roles are empty
|
||||||
|
repeated string roles = 3;
|
||||||
|
}
|
||||||
|
|
||||||
// The unique identifier of the organization the project belongs to.
|
// The unique identifier of the organization the project belongs to.
|
||||||
string organization_id = 1 [
|
string organization_id = 1 [
|
||||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||||
@@ -707,6 +715,8 @@ message CreateProjectRequest {
|
|||||||
PrivateLabelingSetting private_labeling_setting = 7 [
|
PrivateLabelingSetting private_labeling_setting = 7 [
|
||||||
(validate.rules).enum = {defined_only: true}
|
(validate.rules).enum = {defined_only: true}
|
||||||
];
|
];
|
||||||
|
// List of users and Project Member roles (PROJECT_OWNER, by default) to be assigned to those users.
|
||||||
|
repeated Admin admins = 8;
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
|
||||||
example: "{\"organizationId\":\"69629026806489455\",\"name\":\"MyProject\",\"projectRoleAssertion\":true,\"projectRoleCheck\":true,\"hasProjectCheck\":true,\"privateLabelingSetting\":\"PRIVATE_LABELING_SETTING_UNSPECIFIED\"}";
|
example: "{\"organizationId\":\"69629026806489455\",\"name\":\"MyProject\",\"projectRoleAssertion\":true,\"projectRoleCheck\":true,\"hasProjectCheck\":true,\"privateLabelingSetting\":\"PRIVATE_LABELING_SETTING_UNSPECIFIED\"}";
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user