mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 19:37:24 +00:00
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 <max@caos.ch> Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com>
This commit is contained in:
parent
7ea93bd5b6
commit
43f15953c3
@ -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"
|
||||
|
@ -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",
|
||||
|
@ -1,4 +1,4 @@
|
||||
<div class="card" [ngClass]="{'nomargin': nomargin}">
|
||||
<div class="card" [ngClass]="{'nomargin': nomargin, 'warn': warn}">
|
||||
<div *ngIf="title || description" class="header" [ngClass]="{'bottom-margin': expanded}">
|
||||
<div *ngIf="title" class="row">
|
||||
<h2 class="title">{{title}}</h2>
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -55,6 +55,17 @@
|
||||
<ng-template cnslHasFeature [hasFeature]="['metadata.user']">
|
||||
<cnsl-metadata *ngIf="user?.id" [userId]="user.id"></cnsl-metadata>
|
||||
</ng-template>
|
||||
|
||||
<ng-template cnslHasRole [hasRole]="['user.self.delete']">
|
||||
<cnsl-card title="{{'USER.PAGES.DELETEACCOUNT'| translate}}" [warn]="true">
|
||||
<p>{{'USER.PAGES.DELETEACCOUNT_DESC'| translate}}</p>
|
||||
|
||||
<div class="delete-account-wrapper">
|
||||
<button color="warn" mat-raised-button (click)="deleteAccount()">{{'USER.PAGES.DELETEACCOUNT_BTN' |
|
||||
translate}}</button>
|
||||
</div>
|
||||
</cnsl-card>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<div *ngIf="user" class="side" metainfo>
|
||||
|
@ -60,3 +60,8 @@
|
||||
.side-padding {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.delete-account-wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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<RemoveMyUserResponse.AsObject> {
|
||||
const req = new RemoveMyUserRequest();
|
||||
return this.grpcService.auth.removeMyUser(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public getMyEmail(): Promise<GetMyEmailResponse.AsObject> {
|
||||
const req = new GetMyEmailRequest();
|
||||
return this.grpcService.auth.getMyEmail(req, null).then((resp) => resp.toObject());
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
103
internal/command/setup_step21.go
Normal file
103
internal/command/setup_step21.go
Normal file
@ -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()
|
||||
}
|
@ -16,6 +16,7 @@ const (
|
||||
RoleIAMOwner = "IAM_OWNER"
|
||||
RoleProjectOwner = "PROJECT_OWNER"
|
||||
RoleProjectOwnerGlobal = "PROJECT_OWNER_GLOBAL"
|
||||
RoleSelfManagementGlobal = "SELF_MANAGEMENT_GLOBAL"
|
||||
)
|
||||
|
||||
func CheckForInvalidRoles(roles []string, rolePrefix string, validRoles []authz.RoleMapping) []string {
|
||||
|
@ -23,6 +23,7 @@ const (
|
||||
Step18
|
||||
Step19
|
||||
Step20
|
||||
Step21
|
||||
//StepCount marks the the length of possible steps (StepCount-1 == last possible step)
|
||||
StepCount
|
||||
)
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user