From 43f15953c3c6a77fc8d2138dfd4ffafdb0e8643a Mon Sep 17 00:00:00 2001 From: Livio Amstutz Date: Thu, 9 Dec 2021 09:41:21 +0100 Subject: [PATCH] feat: allow global org users to create org and self delete (#2759) * fix: grant PROJECT_OWNER_VIEWER_GLOBAL org.create permission * Update authz.yaml * feat: delete my user * console things * lint * signout after deletion * stylelint rule * Update authz.yaml * Update authz.yaml * setup step * role SELF_MANAGEMENT_GLOBAL setup * fix: change default role on global org * Apply suggestions from code review Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com> * Update console/src/assets/i18n/it.json Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com> Co-authored-by: Max Peintner Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com> --- cmd/zitadel/authz.yaml | 5 + console/.stylelintrc | 24 +--- .../src/app/modules/card/card.component.html | 32 +++--- .../src/app/modules/card/card.component.ts | 5 +- console/src/app/modules/card/card.scss | 10 +- .../auth-user-detail.component.html | 11 ++ .../auth-user-detail.component.scss | 5 + .../auth-user-detail.component.ts | 29 +++++ console/src/app/services/grpc-auth.service.ts | 7 ++ console/src/assets/i18n/de.json | 14 ++- console/src/assets/i18n/en.json | 10 +- console/src/assets/i18n/it.json | 10 +- docs/docs/apis/proto/auth.md | 30 +++++ internal/api/grpc/auth/user.go | 64 +++++++++++ internal/command/setup_step21.go | 103 ++++++++++++++++++ internal/domain/roles.go | 19 ++-- internal/domain/step.go | 1 + internal/repository/project/key.go | 3 +- internal/setup/config.go | 2 + .../login/handler/external_login_handler.go | 2 +- .../handler/external_register_handler.go | 4 +- internal/ui/login/handler/register_handler.go | 2 +- proto/zitadel/auth.proto | 19 ++++ 23 files changed, 349 insertions(+), 62 deletions(-) create mode 100644 internal/command/setup_step21.go diff --git a/cmd/zitadel/authz.yaml b/cmd/zitadel/authz.yaml index 3f69f637f7..4027b5a384 100644 --- a/cmd/zitadel/authz.yaml +++ b/cmd/zitadel/authz.yaml @@ -330,6 +330,11 @@ InternalAuthZ: - "user.global.read" - "user.grant.read" - "user.membership.read" + - Role: 'SELF_MANAGEMENT_GLOBAL' + Permissions: + - "org.create" + - "policy.read" + - "user.self.delete" - Role: 'PROJECT_OWNER_GLOBAL' Permissions: - "org.global.read" diff --git a/console/.stylelintrc b/console/.stylelintrc index 25790c73cc..b5da5bb74c 100644 --- a/console/.stylelintrc +++ b/console/.stylelintrc @@ -1,7 +1,5 @@ { - "plugins": [ - "stylelint-scss" - ], + "plugins": ["stylelint-scss"], "extends": "stylelint-config-standard", "rules": { "value-keyword-case": null, @@ -11,30 +9,20 @@ "at-rule-empty-line-before": [ "always", { - "except": [ - "blockless-after-same-name-blockless", - "first-nested" - ], - "ignore": [ - "after-comment" - ], - "ignoreAtRules": [ - "else" - ] + "except": ["blockless-after-same-name-blockless", "first-nested"], + "ignore": ["after-comment"], + "ignoreAtRules": ["else"] } ], "block-closing-brace-newline-after": [ "always", { - "ignoreAtRules": [ - "if", - "else" - ] + "ignoreAtRules": ["if", "else"] } ], "max-line-length": 125, "no-empty-source": null, - "number-leading-zero": "never", + "number-leading-zero": null, "scss/at-rule-no-unknown": null, "scss/at-else-if-parentheses-space-before": "always", "scss/at-function-parentheses-space-before": "never", diff --git a/console/src/app/modules/card/card.component.html b/console/src/app/modules/card/card.component.html index c5ea815930..7ff316a0de 100644 --- a/console/src/app/modules/card/card.component.html +++ b/console/src/app/modules/card/card.component.html @@ -1,18 +1,18 @@ -
-
-
-

{{title}}

- - - -
-

{{description}}

-
-
- +
+
+
+

{{title}}

+ + +
+

{{description}}

+
+
+ +
\ No newline at end of file diff --git a/console/src/app/modules/card/card.component.ts b/console/src/app/modules/card/card.component.ts index 204aceefba..b86b8dc477 100644 --- a/console/src/app/modules/card/card.component.ts +++ b/console/src/app/modules/card/card.component.ts @@ -11,14 +11,13 @@ import { Component, Input } from '@angular/core'; style({ height: '0', opacity: 0 }), animate('150ms ease-in-out', style({ height: '*', opacity: 1 })), ]), - transition(':leave', [ - animate('150ms ease-in-out', style({ height: '0', opacity: 0 })), - ]), + transition(':leave', [animate('150ms ease-in-out', style({ height: '0', opacity: 0 }))]), ]), ], }) export class CardComponent { @Input() public expanded: boolean = true; + @Input() public warn: boolean = false; @Input() public title: string = ''; @Input() public description: string = ''; @Input() public animate: boolean = false; diff --git a/console/src/app/modules/card/card.scss b/console/src/app/modules/card/card.scss index 8e9b83dbc3..51c61ed5a3 100644 --- a/console/src/app/modules/card/card.scss +++ b/console/src/app/modules/card/card.scss @@ -7,19 +7,23 @@ $background: map-get($theme, background); $card-background-color: mat.get-color-from-palette($background, card); $is-dark-theme: map-get($theme, is-dark); - $border-color: if($is-dark-theme, rgba(#8795a1, .2), rgba(#8795a1, .2)); + $border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2)); $border-selected-color: if($is-dark-theme, #ffffff, #000000); /* stylelint-enable */ .card { background-color: $card-background-color; - transition: background-color .3s cubic-bezier(.645, .045, .355, 1); + transition: background-color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); border: 1px solid $border-color; box-sizing: border-box; - border-radius: .5rem; + border-radius: 0.5rem; outline: none; height: 100%; + &.warn { + border-color: var(--warn); + } + .selection-icon { opacity: 0; position: absolute; diff --git a/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-detail.component.html b/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-detail.component.html index 542490bcc6..8dcacc9cfc 100644 --- a/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-detail.component.html +++ b/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-detail.component.html @@ -55,6 +55,17 @@ + + + +

{{'USER.PAGES.DELETEACCOUNT_DESC'| translate}}

+ + +
+
diff --git a/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-detail.component.scss b/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-detail.component.scss index 00d1595575..0563e8e73a 100644 --- a/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-detail.component.scss +++ b/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-detail.component.scss @@ -60,3 +60,8 @@ .side-padding { padding-top: 1rem; } + +.delete-account-wrapper { + display: flex; + justify-content: flex-end; +} diff --git a/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-detail.component.ts b/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-detail.component.ts index e6af4abbda..316e6d50ad 100644 --- a/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-detail.component.ts +++ b/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-detail.component.ts @@ -4,7 +4,9 @@ import { TranslateService } from '@ngx-translate/core'; import { Subscription } from 'rxjs'; import { ChangeType } from 'src/app/modules/changes/changes.component'; import { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource'; +import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component'; import { Email, Gender, Phone, Profile, User, UserState } from 'src/app/proto/generated/zitadel/user_pb'; +import { AuthenticationService } from 'src/app/services/authentication.service'; import { GrpcAuthService } from 'src/app/services/grpc-auth.service'; import { ToastService } from 'src/app/services/toast.service'; @@ -36,6 +38,7 @@ export class AuthUserDetailComponent implements OnDestroy { private toast: ToastService, public userService: GrpcAuthService, private dialog: MatDialog, + private auth: AuthenticationService, ) { this.loading = true; this.refreshUser(); @@ -252,4 +255,30 @@ export class AuthUserDetailComponent implements OnDestroy { break; } } + + public deleteAccount(): void { + const dialogRef = this.dialog.open(WarnDialogComponent, { + data: { + confirmKey: 'USER.DIALOG.DELETE_BTN', + cancelKey: 'ACTIONS.CANCEL', + titleKey: 'USER.DIALOG.DELETE_TITLE', + descriptionKey: 'USER.DIALOG.DELETE_AUTH_DESCRIPTION', + }, + width: '400px', + }); + + dialogRef.afterClosed().subscribe((resp) => { + if (resp) { + this.userService + .RemoveMyUser() + .then(() => { + this.toast.showInfo('USER.PAGES.DELETEACCOUNT_SUCCESS', true); + this.auth.signout(); + }) + .catch((error) => { + this.toast.showError(error); + }); + } + }); + } } diff --git a/console/src/app/services/grpc-auth.service.ts b/console/src/app/services/grpc-auth.service.ts index a5694c5102..45bc1df0df 100644 --- a/console/src/app/services/grpc-auth.service.ts +++ b/console/src/app/services/grpc-auth.service.ts @@ -56,6 +56,8 @@ import { RemoveMyPasswordlessResponse, RemoveMyPhoneRequest, RemoveMyPhoneResponse, + RemoveMyUserRequest, + RemoveMyUserResponse, ResendMyEmailVerificationRequest, ResendMyEmailVerificationResponse, ResendMyPhoneVerificationRequest, @@ -400,6 +402,11 @@ export class GrpcAuthService { return this.grpcService.auth.listMyMemberships(req, null).then((resp) => resp.toObject()); } + public RemoveMyUser(): Promise { + const req = new RemoveMyUserRequest(); + return this.grpcService.auth.removeMyUser(req, null).then((resp) => resp.toObject()); + } + public getMyEmail(): Promise { const req = new GetMyEmailRequest(); return this.grpcService.auth.getMyEmail(req, null).then((resp) => resp.toObject()); diff --git a/console/src/assets/i18n/de.json b/console/src/assets/i18n/de.json index f619e2c70c..786a8f0d8a 100644 --- a/console/src/assets/i18n/de.json +++ b/console/src/assets/i18n/de.json @@ -182,7 +182,11 @@ "STATE": "Status", "DELETE": "Benutzer löschen", "UNLOCK": "Benutzer entsperren", - "LOCKEDDESCRIPTION": "Dieser Benutzer wurde aufgrund der Überschreitung der maximalen Anmeldeversuche gesperrt und muss zur erneuten Verwendung entsperrt werden." + "LOCKEDDESCRIPTION": "Dieser Benutzer wurde aufgrund der Überschreitung der maximalen Anmeldeversuche gesperrt und muss zur erneuten Verwendung entsperrt werden.", + "DELETEACCOUNT": "Account löschen", + "DELETEACCOUNT_DESC": "Wenn du diese Aktion ausführst, wirst du abgemeldet und danach keinen Zugriff mehr auf dein Konto haben. Diese Aktion kann nicht rückgängig gemacht werden.", + "DELETEACCOUNT_BTN": "Account löschen", + "DELETEACCOUNT_SUCCESS": "Account erfolgreich gelöscht!" }, "DETAILS": { "DATECREATED": "Erstellt", @@ -190,7 +194,9 @@ }, "DIALOG": { "DELETE_TITLE": "User löschen", - "DELETE_DESCRIPTION": "Sie sind im Begriff einen Benutzer endgültig zu löschen. Wollen Sie dies wirklich tun?" + "DELETE_DESCRIPTION": "Sie sind im Begriff einen Benutzer endgültig zu löschen. Wollen Sie dies wirklich tun?", + "DELETE_AUTH_DESCRIPTION": "Sie sind im Begriff Ihren Account endgültig zu löschen. Wollen Sie dies wirklich tun?", + "DELETE_BTN": "Entgültig löschen" }, "SENDEMAILDIALOG": { "TITLE": "Email Benachrichtigung senden", @@ -807,9 +813,9 @@ }, "PRIVATELABELING_POLICY": { "TITLE": "Private Labeling", - "BTN":"Datei auswählen", + "BTN": "Datei auswählen", "DESCRIPTION": "Definiere das Erscheinungsbild des Logins.", - "ACTIVATEPREVIEW":"Preview aktivieren" + "ACTIVATEPREVIEW": "Preview aktivieren" }, "LOGIN_POLICY": { "TITLE": "Login Richtlinien", diff --git a/console/src/assets/i18n/en.json b/console/src/assets/i18n/en.json index 3d0ac6736c..16c54dfd51 100644 --- a/console/src/assets/i18n/en.json +++ b/console/src/assets/i18n/en.json @@ -182,7 +182,11 @@ "STATE": "Status", "DELETE": "Delete User", "UNLOCK": "Unlock User", - "LOCKEDDESCRIPTION": "This user has been locked out due to exceeding the maximum login attempts and must be unlocked to be used again." + "LOCKEDDESCRIPTION": "This user has been locked out due to exceeding the maximum login attempts and must be unlocked to be used again.", + "DELETEACCOUNT": "Delete Account", + "DELETEACCOUNT_DESC": "If you perform this action, you will be logged out and will no longer have access to your account. This action is not reversible, so please continue with caution.", + "DELETEACCOUNT_BTN": "Delete Account", + "DELETEACCOUNT_SUCCESS": "Account deleted successfully!" }, "DETAILS": { "DATECREATED": "Created", @@ -190,7 +194,9 @@ }, "DIALOG": { "DELETE_TITLE": "Delete User", - "DELETE_DESCRIPTION": "You are about to permanently delete a user. Are you sure?" + "DELETE_DESCRIPTION": "You are about to permanently delete a user. Are you sure?", + "DELETE_AUTH_DESCRIPTION": "You are about to permanently delete your personal account. Are you sure?", + "DELETE_BTN": "Delete permanently" }, "SENDEMAILDIALOG": { "TITLE": "Send Email Notification", diff --git a/console/src/assets/i18n/it.json b/console/src/assets/i18n/it.json index 0bc7d9ff5f..f4de1cc499 100644 --- a/console/src/assets/i18n/it.json +++ b/console/src/assets/i18n/it.json @@ -182,7 +182,11 @@ "STATE": "Stato", "DELETE": "Elimina utente", "UNLOCK": "Sblocca utente", - "LOCKEDDESCRIPTION": "Questo utente \u00e8 stato bloccato a causa del superamento dei tentativi massimi di accesso e deve essere sbloccato per essere utilizzato di nuovo." + "LOCKEDDESCRIPTION": "Questo utente \u00e8 stato bloccato a causa del superamento dei tentativi massimi di accesso e deve essere sbloccato per essere utilizzato di nuovo.", + "DELETEACCOUNT": "Elimina account personale", + "DELETEACCOUNT_DESC": "Se esegui questa azione, sarai disconnesso e non avrai più accesso al tuo account. Questa azione non può essere invertita.", + "DELETEACCOUNT_BTN": "Elimina", + "DELETEACCOUNT_SUCCESS": "Account eliminato con successo!" }, "DETAILS": { "DATECREATED": "Creato", @@ -190,7 +194,9 @@ }, "DIALOG": { "DELETE_TITLE": "Elimina utente", - "DELETE_DESCRIPTION": "Stai per eliminare definitivamente un utente. Sei sicuro?" + "DELETE_DESCRIPTION": "Stai per eliminare definitivamente un utente. Sei sicuro?", + "DELETE_AUTH_DESCRIPTION": "Stai per eleminare il tuo account personale in modo permanente. Vuoi continuare?", + "DELETE_BTN": "Elimina" }, "SENDEMAILDIALOG": { "TITLE": "Invia una notifica via e-mail", diff --git a/docs/docs/apis/proto/auth.md b/docs/docs/apis/proto/auth.md index 80ba2d5784..2127690cec 100644 --- a/docs/docs/apis/proto/auth.md +++ b/docs/docs/apis/proto/auth.md @@ -43,6 +43,18 @@ Returns my full blown user GET: /users/me +### RemoveMyUser + +> **rpc** RemoveMyUser([RemoveMyUserRequest](#removemyuserrequest)) +[RemoveMyUserResponse](#removemyuserresponse) + +Changes the user state to deleted + + + + DELETE: /users/me + + ### ListMyUserChanges > **rpc** ListMyUserChanges([ListMyUserChangesRequest](#listmyuserchangesrequest)) @@ -1250,6 +1262,24 @@ This is an empty request +### RemoveMyUserRequest +This is an empty request +the request parameters are read from the token-header + + + + +### RemoveMyUserResponse + + + +| Field | Type | Description | Validation | +| ----- | ---- | ----------- | ----------- | +| details | zitadel.v1.ObjectDetails | - | | + + + + ### ResendMyEmailVerificationRequest This is an empty request diff --git a/internal/api/grpc/auth/user.go b/internal/api/grpc/auth/user.go index 33c78dde52..0c19e9b136 100644 --- a/internal/api/grpc/auth/user.go +++ b/internal/api/grpc/auth/user.go @@ -11,7 +11,9 @@ import ( obj_grpc "github.com/caos/zitadel/internal/api/grpc/object" "github.com/caos/zitadel/internal/api/grpc/org" user_grpc "github.com/caos/zitadel/internal/api/grpc/user" + "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/eventstore/v1/models" + user_model "github.com/caos/zitadel/internal/user/model" grant_model "github.com/caos/zitadel/internal/usergrant/model" auth_pb "github.com/caos/zitadel/pkg/grpc/auth" ) @@ -24,6 +26,25 @@ func (s *Server) GetMyUser(ctx context.Context, _ *auth_pb.GetMyUserRequest) (*a return &auth_pb.GetMyUserResponse{User: user_grpc.UserToPb(user)}, nil } +func (s *Server) RemoveMyUser(ctx context.Context, _ *auth_pb.RemoveMyUserRequest) (*auth_pb.RemoveMyUserResponse, error) { + ctxData := authz.GetCtxData(ctx) + grants, err := s.repo.SearchMyUserGrants(ctx, &grant_model.UserGrantSearchRequest{Queries: []*grant_model.UserGrantSearchQuery{}}) + if err != nil { + return nil, err + } + membersShips, err := s.repo.SearchMyUserMemberships(ctx, &user_model.UserMembershipSearchRequest{Queries: []*user_model.UserMembershipSearchQuery{}}) + if err != nil { + return nil, err + } + details, err := s.command.RemoveUser(ctx, ctxData.UserID, ctxData.ResourceOwner, UserMembershipViewsToDomain(membersShips.Result), userGrantsToIDs(grants.Result)...) + if err != nil { + return nil, err + } + return &auth_pb.RemoveMyUserResponse{ + Details: obj_grpc.DomainToChangeDetailsPb(details), + }, nil +} + func (s *Server) ListMyUserChanges(ctx context.Context, req *auth_pb.ListMyUserChangesRequest) (*auth_pb.ListMyUserChangesResponse, error) { sequence, limit, asc := change.ChangeQueryToModel(req.Query) features, err := s.query.FeaturesByOrgID(ctx, authz.GetCtxData(ctx).ResourceOwner) @@ -137,3 +158,46 @@ func ListMyProjectOrgsRequestToModel(req *auth_pb.ListMyProjectOrgsRequest) (*gr Queries: queries, }, nil } + +func UserMembershipViewsToDomain(memberships []*user_model.UserMembershipView) []*domain.UserMembership { + result := make([]*domain.UserMembership, len(memberships)) + for i, membership := range memberships { + result[i] = &domain.UserMembership{ + UserID: membership.UserID, + MemberType: MemberTypeToDomain(membership.MemberType), + AggregateID: membership.AggregateID, + ObjectID: membership.ObjectID, + Roles: membership.Roles, + DisplayName: membership.DisplayName, + CreationDate: membership.CreationDate, + ChangeDate: membership.ChangeDate, + ResourceOwner: membership.ResourceOwner, + ResourceOwnerName: membership.ResourceOwnerName, + Sequence: membership.Sequence, + } + } + return result +} + +func MemberTypeToDomain(mType user_model.MemberType) domain.MemberType { + switch mType { + case user_model.MemberTypeIam: + return domain.MemberTypeIam + case user_model.MemberTypeOrganisation: + return domain.MemberTypeOrganisation + case user_model.MemberTypeProject: + return domain.MemberTypeProject + case user_model.MemberTypeProjectGrant: + return domain.MemberTypeProjectGrant + default: + return domain.MemberTypeUnspecified + } +} + +func userGrantsToIDs(userGrants []*grant_model.UserGrantView) []string { + converted := make([]string, len(userGrants)) + for i, grant := range userGrants { + converted[i] = grant.ID + } + return converted +} diff --git a/internal/command/setup_step21.go b/internal/command/setup_step21.go new file mode 100644 index 0000000000..5b9aa9a914 --- /dev/null +++ b/internal/command/setup_step21.go @@ -0,0 +1,103 @@ +package command + +import ( + "context" + + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/org" + "github.com/caos/zitadel/internal/repository/user" +) + +type Step21 struct{} + +func (s *Step21) Step() domain.Step { + return domain.Step21 +} + +func (s *Step21) execute(ctx context.Context, commandSide *Commands) error { + return commandSide.SetupStep21(ctx, s) +} + +func (c *Commands) SetupStep21(ctx context.Context, step *Step21) error { + fn := func(iam *IAMWriteModel) ([]eventstore.EventPusher, error) { + events := make([]eventstore.EventPusher, 0) + globalMembers := newGlobalOrgMemberWriteModel(iam.GlobalOrgID, domain.RoleOrgProjectCreator) + orgAgg := OrgAggregateFromWriteModel(&globalMembers.WriteModel) + if err := c.eventstore.FilterToQueryReducer(ctx, globalMembers); err != nil { + return nil, err + } + for userID, roles := range globalMembers.members { + for i, role := range roles { + if role == domain.RoleOrgProjectCreator { + roles[i] = domain.RoleSelfManagementGlobal + } + } + events = append(events, org.NewMemberChangedEvent(ctx, orgAgg, userID, roles...)) + } + return events, nil + } + return c.setup(ctx, step, fn) +} + +type globalOrgMembersWriteModel struct { + eventstore.WriteModel + + orgID string + role string + members map[string][]string +} + +func newGlobalOrgMemberWriteModel(orgID, role string) *globalOrgMembersWriteModel { + return &globalOrgMembersWriteModel{ + orgID: orgID, + role: role, + members: make(map[string][]string), + } +} + +func (wm *globalOrgMembersWriteModel) Reduce() error { + for _, event := range wm.Events { + switch e := event.(type) { + case *org.MemberAddedEvent: + for _, role := range e.Roles { + if wm.role == role { + wm.members[e.UserID] = e.Roles + break + } + } + case *org.MemberChangedEvent: + delete(wm.members, e.UserID) + for _, role := range e.Roles { + if wm.role == role { + wm.members[e.UserID] = e.Roles + break + } + } + case *org.MemberRemovedEvent: + delete(wm.members, e.UserID) + case *org.MemberCascadeRemovedEvent: + delete(wm.members, e.UserID) + case *user.UserRemovedEvent: + delete(wm.members, e.Aggregate().ID) + } + } + return wm.WriteModel.Reduce() +} + +func (wm *globalOrgMembersWriteModel) Query() *eventstore.SearchQueryBuilder { + return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). + AddQuery(). + AggregateTypes(org.AggregateType). + AggregateIDs(wm.orgID). + EventTypes( + org.MemberAddedEventType, + org.MemberChangedEventType, + org.MemberRemovedEventType, + org.MemberCascadeRemovedEventType, + ). + Or(). + AggregateTypes(user.AggregateType). + EventTypes(user.UserRemovedType). + Builder() +} diff --git a/internal/domain/roles.go b/internal/domain/roles.go index ade3a7feec..adc25aaae9 100644 --- a/internal/domain/roles.go +++ b/internal/domain/roles.go @@ -7,15 +7,16 @@ import ( ) const ( - IAMRolePrefix = "IAM" - OrgRolePrefix = "ORG" - ProjectRolePrefix = "PROJECT" - ProjectGrantRolePrefix = "PROJECT_GRANT" - RoleOrgOwner = "ORG_OWNER" - RoleOrgProjectCreator = "ORG_PROJECT_CREATOR" - RoleIAMOwner = "IAM_OWNER" - RoleProjectOwner = "PROJECT_OWNER" - RoleProjectOwnerGlobal = "PROJECT_OWNER_GLOBAL" + IAMRolePrefix = "IAM" + OrgRolePrefix = "ORG" + ProjectRolePrefix = "PROJECT" + ProjectGrantRolePrefix = "PROJECT_GRANT" + RoleOrgOwner = "ORG_OWNER" + RoleOrgProjectCreator = "ORG_PROJECT_CREATOR" + RoleIAMOwner = "IAM_OWNER" + RoleProjectOwner = "PROJECT_OWNER" + RoleProjectOwnerGlobal = "PROJECT_OWNER_GLOBAL" + RoleSelfManagementGlobal = "SELF_MANAGEMENT_GLOBAL" ) func CheckForInvalidRoles(roles []string, rolePrefix string, validRoles []authz.RoleMapping) []string { diff --git a/internal/domain/step.go b/internal/domain/step.go index 883682306b..dc3641389b 100644 --- a/internal/domain/step.go +++ b/internal/domain/step.go @@ -23,6 +23,7 @@ const ( Step18 Step19 Step20 + Step21 //StepCount marks the the length of possible steps (StepCount-1 == last possible step) StepCount ) diff --git a/internal/repository/project/key.go b/internal/repository/project/key.go index eff24e1eab..3c2fa6a054 100644 --- a/internal/repository/project/key.go +++ b/internal/repository/project/key.go @@ -3,9 +3,10 @@ package project import ( "context" "encoding/json" - "github.com/caos/zitadel/internal/eventstore" "time" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore/repository" diff --git a/internal/setup/config.go b/internal/setup/config.go index 3d75ba14e6..83da2438fa 100644 --- a/internal/setup/config.go +++ b/internal/setup/config.go @@ -26,6 +26,7 @@ type IAMSetUp struct { Step18 *command.Step18 Step19 *command.Step19 Step20 *command.Step20 + Step21 *command.Step21 } func (setup *IAMSetUp) Steps(currentDone domain.Step) ([]command.Step, error) { @@ -52,6 +53,7 @@ func (setup *IAMSetUp) Steps(currentDone domain.Step) ([]command.Step, error) { setup.Step18, setup.Step19, setup.Step20, + setup.Step21, } { if step.Step() <= currentDone { continue diff --git a/internal/ui/login/handler/external_login_handler.go b/internal/ui/login/handler/external_login_handler.go index 436e98fca6..eadbabf272 100644 --- a/internal/ui/login/handler/external_login_handler.go +++ b/internal/ui/login/handler/external_login_handler.go @@ -259,7 +259,7 @@ func (l *Login) handleAutoRegister(w http.ResponseWriter, r *http.Request, authR } resourceOwner := iam.GlobalOrgID - memberRoles := []string{domain.RoleOrgProjectCreator} + memberRoles := []string{domain.RoleSelfManagementGlobal} if authReq.RequestedOrgID != "" && authReq.RequestedOrgID != iam.GlobalOrgID { memberRoles = nil diff --git a/internal/ui/login/handler/external_register_handler.go b/internal/ui/login/handler/external_register_handler.go index 44ffc0ffd0..96db3500b4 100644 --- a/internal/ui/login/handler/external_register_handler.go +++ b/internal/ui/login/handler/external_register_handler.go @@ -136,7 +136,7 @@ func (l *Login) handleExternalUserRegister(w http.ResponseWriter, r *http.Reques func (l *Login) registerExternalUser(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, iam *iam_model.IAM, user *domain.Human, externalIDP *domain.UserIDPLink) { resourceOwner := iam.GlobalOrgID - memberRoles := []string{domain.RoleOrgProjectCreator} + memberRoles := []string{domain.RoleSelfManagementGlobal} if authReq.RequestedOrgID != "" && authReq.RequestedOrgID != resourceOwner { memberRoles = nil @@ -196,7 +196,7 @@ func (l *Login) handleExternalRegisterCheck(w http.ResponseWriter, r *http.Reque return } resourceOwner := iam.GlobalOrgID - memberRoles := []string{domain.RoleOrgProjectCreator} + memberRoles := []string{domain.RoleSelfManagementGlobal} if authReq.RequestedOrgID != "" && authReq.RequestedOrgID != iam.GlobalOrgID { memberRoles = nil diff --git a/internal/ui/login/handler/register_handler.go b/internal/ui/login/handler/register_handler.go index 7f009bb5d0..3a1946e557 100644 --- a/internal/ui/login/handler/register_handler.go +++ b/internal/ui/login/handler/register_handler.go @@ -68,7 +68,7 @@ func (l *Login) handleRegisterCheck(w http.ResponseWriter, r *http.Request) { } resourceOwner := iam.GlobalOrgID - memberRoles := []string{domain.RoleOrgProjectCreator} + memberRoles := []string{domain.RoleSelfManagementGlobal} if authRequest.RequestedOrgID != "" && authRequest.RequestedOrgID != iam.GlobalOrgID { memberRoles = nil diff --git a/proto/zitadel/auth.proto b/proto/zitadel/auth.proto index 0fcb510b8f..c06b22c862 100644 --- a/proto/zitadel/auth.proto +++ b/proto/zitadel/auth.proto @@ -83,6 +83,17 @@ service AuthService { }; } + // Changes the user state to deleted + rpc RemoveMyUser(RemoveMyUserRequest) returns (RemoveMyUserResponse) { + option (google.api.http) = { + delete: "/users/me" + }; + + option (zitadel.v1.auth_option) = { + permission: "user.self.delete" + }; + } + // Returns the history of the authorized user (each event) rpc ListMyUserChanges(ListMyUserChangesRequest) returns (ListMyUserChangesResponse) { option (google.api.http) = { @@ -614,6 +625,14 @@ message GetMyUserResponse { ]; } +//This is an empty request +// the request parameters are read from the token-header +message RemoveMyUserRequest {} + +message RemoveMyUserResponse{ + zitadel.v1.ObjectDetails details = 1; +} + message ListMyUserChangesRequest { zitadel.change.v1.ChangeQuery query = 1; }