Merge branch 'master' into new-eventstore

# Conflicts:
#	go.mod
#	internal/admin/repository/eventsourcing/eventstore/iam.go
#	internal/authz/repository/eventsourcing/repository.go
#	internal/eventstore/eventstore.go
#	internal/setup/config.go
#	pkg/grpc/management/mock/management.proto.mock.go
This commit is contained in:
Livio Amstutz 2021-01-05 09:27:42 +01:00
commit 5b84c9b619
283 changed files with 7264 additions and 2500 deletions

1
.gitignore vendored
View File

@ -44,4 +44,5 @@ tmp/
console/src/app/proto/generated/
pkg/grpc/*/*.pb.*
pkg/grpc/*/mock/*.mock.go
pkg/grpc/*/*.swagger.json

View File

@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Copyright 2020 CAOS AG
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -67,7 +67,7 @@ export ZITADEL_SHORT_CACHE_SHARED_MAXAGE=15m
export ZITADEL_CONSOLE_ENV_DIR=../../console/src/assets/
#Org
export ZITADEL_DEFAULT_DOMAIN=zitadel.ch
export ZITADEL_DEFAULT_DOMAIN=localhost
#Setup

View File

@ -99,3 +99,5 @@ SetUp:
DefaultSecondFactor: 1 #SecondFactorTypeOTP
Step8:
DefaultSecondFactor: 2 #SecondFactorTypeU2F
Step9:
Passwordless: true

View File

@ -49,11 +49,11 @@ AuthZ:
Key: $CR_AUTHZ_KEY
Spooler:
ConcurrentWorkers: 1
BulkLimit: 100
BulkLimit: 10000
FailureCountUntilSkip: 5
Auth:
SearchLimit: 100
SearchLimit: 1000
Domain: $ZITADEL_DEFAULT_DOMAIN
Eventstore:
ServiceName: 'authAPI'
@ -98,7 +98,7 @@ Auth:
Key: $CR_AUTH_KEY
Spooler:
ConcurrentWorkers: 1
BulkLimit: 100
BulkLimit: 10000
FailureCountUntilSkip: 5
KeyConfig:
Size: 2048
@ -109,7 +109,7 @@ Auth:
SigningKeyRotation: 10s
Admin:
SearchLimit: 100
SearchLimit: 1000
Domain: $ZITADEL_DEFAULT_DOMAIN
Eventstore:
ServiceName: 'Admin'
@ -142,11 +142,11 @@ Admin:
Key: $CR_ADMINAPI_KEY
Spooler:
ConcurrentWorkers: 1
BulkLimit: 100
BulkLimit: 10000
FailureCountUntilSkip: 5
Mgmt:
SearchLimit: 100
SearchLimit: 1000
Domain: $ZITADEL_DEFAULT_DOMAIN
Eventstore:
ServiceName: 'ManagementAPI'
@ -179,7 +179,7 @@ Mgmt:
Key: $CR_MANAGEMENT_KEY
Spooler:
ConcurrentWorkers: 1
BulkLimit: 100
BulkLimit: 10000
FailureCountUntilSkip: 5
API:
@ -292,10 +292,6 @@ Notification:
Key: $CR_NOTIFICATION_KEY
Spooler:
ConcurrentWorkers: 1
BulkLimit: 100
BulkLimit: 10000
FailureCountUntilSkip: 5
Handlers:
Notification:
MinimumCycleDuration: 5s
User:
MinimumCycleDuration: 5s

View File

@ -126,7 +126,7 @@ SystemDefaults:
Text: 'DomainClaimed.Text'
ButtonText: 'DomainClaimed.ButtonText'
WebAuthN:
ID: $ZITADEL_COOKIE_DOMAIN
ID: $ZITADEL_DEFAULT_DOMAIN
OriginLogin: $ZITADEL_ACCOUNTS
OriginConsole: $ZITADEL_CONSOLE
DisplayName: ZITADEL

File diff suppressed because it is too large Load Diff

View File

@ -40,17 +40,17 @@
"rxjs": "~6.6.3",
"ts-protoc-gen": "^0.13.0",
"tslib": "^2.0.0",
"uuid": "^8.3.1",
"uuid": "^8.3.2",
"zone.js": "~0.11.3"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.1100.3",
"@angular/cli": "~11.0.3",
"@angular-devkit/build-angular": "~0.1100.4",
"@angular/cli": "~11.0.4",
"@angular/compiler-cli": "~11.0.0",
"@types/jasmine": "~3.6.2",
"@angular/language-service": "~11.0.3",
"@angular/language-service": "~11.0.4",
"@types/jasminewd2": "~2.0.3",
"@types/node": "^14.14.10",
"@types/node": "^14.14.13",
"codelyzer": "^6.0.0",
"jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~6.0.0",
@ -64,7 +64,7 @@
"stylelint": "^13.8.0",
"stylelint-config-standard": "^20.0.0",
"stylelint-scss": "^3.18.0",
"ts-node": "~9.1.0",
"ts-node": "~9.1.1",
"tslint": "~6.1.3",
"typescript": "^4.0.5"
}

View File

@ -42,7 +42,6 @@ export class ChangesComponent implements OnInit, OnDestroy {
this.init();
if (this.refresh) {
this.refresh.pipe(takeUntil(this.destroyed$), debounceTime(2000)).subscribe(() => {
console.log('asdf');
this.init();
});
}

View File

@ -0,0 +1,19 @@
<h1 mat-dialog-title class="title"><span>{{data.title | translate}}</span></h1>
<div mat-dialog-content>
<p class="desc">{{data.desc | translate}}</p>
<cnsl-form-field class="form-field" label="Access Code" required="true">
<cnsl-label>{{'MFA.TYPE' | translate}}</cnsl-label>
<mat-select [(ngModel)]="newMfaType">
<mat-option *ngFor="let mfa of availableMfaTypes" [value]="mfa">
{{(data.componentType == LoginMethodComponentType.SecondFactor ? 'MFA.SECONDFACTORTYPES.': LoginMethodComponentType.MultiFactor ? 'MFA.MULTIFACTORTYPES.': '')+mfa | translate}}
</mat-option>
</mat-select>
</cnsl-form-field>
</div>
<div mat-dialog-actions class="action">
<button mat-button (click)="closeDialog()"><span>{{'ACTIONS.CLOSE' | translate}}</span></button>
<button [disabled]="newMfaType == undefined" mat-raised-button class="ok-button" color="primary"
(click)="closeDialogWithCode()"><span>{{'ACTIONS.OK' | translate}}</span>
</button>
</div>

View File

@ -0,0 +1,22 @@
.title {
font-size: 1.5rem;
}
.desc {
font-size: 14px;
color: var(--grey);
}
.form-field {
width: 100%;
}
.action {
display: flex;
justify-content: flex-end;
button {
margin-left: .5rem;
border-radius: .5rem;
}
}

View File

@ -0,0 +1,33 @@
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { MultiFactorType as AdminMultiFactorType } from 'src/app/proto/generated/admin_pb';
import { MultiFactorType as MgmtMultiFactorType } from 'src/app/proto/generated/management_pb';
enum LoginMethodComponentType {
MultiFactor = 1,
SecondFactor = 2,
}
@Component({
selector: 'app-dialog-add-type',
templateUrl: './dialog-add-type.component.html',
styleUrls: ['./dialog-add-type.component.scss'],
})
export class DialogAddTypeComponent {
public LoginMethodComponentType: any = LoginMethodComponentType;
public newMfaType!: AdminMultiFactorType | MgmtMultiFactorType;
public availableMfaTypes: Array<AdminMultiFactorType | MgmtMultiFactorType> = [];
constructor(public dialogRef: MatDialogRef<DialogAddTypeComponent>,
@Inject(MAT_DIALOG_DATA) public data: any) {
this.availableMfaTypes = data.types;
}
public closeDialog(): void {
this.dialogRef.close();
}
public closeDialogWithCode(): void {
this.dialogRef.close(this.newMfaType);
}
}

View File

@ -0,0 +1,15 @@
<div class="sp_wrapper">
<mat-spinner diameter="30" *ngIf="loading$ | async"></mat-spinner>
</div>
<div class="mfa-list">
<div class="mfa" *ngFor="let mfa of mfas">
<button [disabled]="disabled" mat-icon-button (click)="removeMfa(mfa)" class="rm">
<mat-icon matTooltip="{{'ACTIONS.REMOVE' | translate}}">
remove_circle</mat-icon>
</button>
{{(componentType == LoginMethodComponentType.SecondFactor ? 'MFA.SECONDFACTORTYPES.': LoginMethodComponentType.MultiFactor ? 'MFA.MULTIFACTORTYPES.': '')+mfa | translate}}
</div>
<div class="mfa" (click)="addMfa()">
<mat-icon>add</mat-icon>
</div>
</div>

View File

@ -0,0 +1,42 @@
.t .sp_wrapper {
display: block;
}
.mfa-list {
display: flex;
flex-wrap: wrap;
margin: 0 -.5rem;
.mfa {
border: 1px solid var(--grey);
border-radius: .5rem;
display: grid;
align-items: center;
justify-content: center;
margin: .5rem;
padding: 10px;
cursor: pointer;
position: relative;
min-height: 70px;
min-width: 150px;
.rm {
position: absolute;
top: 0;
left: 0;
transform: translateX(-50%) translateY(-50%);
cursor: pointer;
&[disabled] {
display: none;
}
}
&:not(.disabled) {
&:hover {
background-color: #ffffff10;
}
}
}
}

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { MfaTableComponent } from './mfa-table.component';
describe('MfaTableComponent', () => {
let component: MfaTableComponent;
let fixture: ComponentFixture<MfaTableComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [MfaTableComponent],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MfaTableComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,232 @@
import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatPaginator } from '@angular/material/paginator';
import { TranslateService } from '@ngx-translate/core';
import { BehaviorSubject, Observable } from 'rxjs';
import {
MultiFactor as AdminMultiFactor,
MultiFactorType as AdminMultiFactorType,
SecondFactor as AdminSecondFactor,
SecondFactorType as AdminSecondFactorType,
} from 'src/app/proto/generated/admin_pb';
import {
MultiFactor as MgmtMultiFactor,
MultiFactorType as MgmtMultiFactorType,
SecondFactor as MgmtSecondFactor,
SecondFactorType as MgmtSecondFactorType,
} from 'src/app/proto/generated/management_pb';
import { AdminService } from 'src/app/services/admin.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
import { PolicyComponentServiceType } from '../policies/policy-component-types.enum';
import { WarnDialogComponent } from '../warn-dialog/warn-dialog.component';
import { DialogAddTypeComponent } from './dialog-add-type/dialog-add-type.component';
export enum LoginMethodComponentType {
MultiFactor = 1,
SecondFactor = 2,
}
@Component({
selector: 'app-mfa-table',
templateUrl: './mfa-table.component.html',
styleUrls: ['./mfa-table.component.scss'],
})
export class MfaTableComponent implements OnInit {
public LoginMethodComponentType: any = LoginMethodComponentType;
@Input() componentType!: LoginMethodComponentType;
@Input() public serviceType!: PolicyComponentServiceType;
@Input() service!: AdminService | ManagementService;
@Input() disabled: boolean = false;
@ViewChild(MatPaginator) public paginator!: MatPaginator;
public mfas: Array<AdminMultiFactorType | MgmtMultiFactorType | MgmtSecondFactorType | AdminSecondFactorType> = [];
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
public PolicyComponentServiceType: any = PolicyComponentServiceType;
constructor(public translate: TranslateService, private toast: ToastService, private dialog: MatDialog) { }
public ngOnInit(): void {
this.getData();
}
public removeMfa(type: MgmtMultiFactorType | AdminMultiFactorType | MgmtSecondFactorType | AdminSecondFactorType): void {
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.DELETE',
cancelKey: 'ACTIONS.CANCEL',
titleKey: 'MFA.DELETE.TITLE',
descriptionKey: 'MFA.DELETE.DESCRIPTION',
},
width: '400px',
});
dialogRef.afterClosed().subscribe(resp => {
if (resp) {
if (this.serviceType === PolicyComponentServiceType.MGMT) {
if (this.componentType === LoginMethodComponentType.MultiFactor) {
const req = new MgmtMultiFactor();
req.setMultiFactor(type as MgmtMultiFactorType);
(this.service as ManagementService).RemoveMultiFactorFromLoginPolicy(req).then(() => {
this.toast.showInfo('MFA.TOAST.DELETED', true);
this.refreshPageAfterTimout(2000);
});
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
const req = new MgmtSecondFactor();
req.setSecondFactor(type as MgmtSecondFactorType);
(this.service as ManagementService).RemoveSecondFactorFromLoginPolicy(req).then(() => {
this.toast.showInfo('MFA.TOAST.DELETED', true);
this.refreshPageAfterTimout(2000);
});
}
} else if (this.serviceType === PolicyComponentServiceType.ADMIN) {
if (this.componentType === LoginMethodComponentType.MultiFactor) {
const req = new AdminMultiFactor();
req.setMultiFactor(type as AdminMultiFactorType);
(this.service as AdminService).RemoveMultiFactorFromDefaultLoginPolicy(req).then(() => {
this.toast.showInfo('MFA.TOAST.DELETED', true);
this.refreshPageAfterTimout(2000);
});
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
const req = new AdminSecondFactor();
req.setSecondFactor(type as AdminSecondFactorType);
(this.service as AdminService).RemoveSecondFactorFromDefaultLoginPolicy(req).then(() => {
this.toast.showInfo('MFA.TOAST.DELETED', true);
this.refreshPageAfterTimout(2000);
});
}
}
}
});
}
public addMfa(): void {
let selection: any[] = [];
if (this.componentType === LoginMethodComponentType.MultiFactor) {
selection = this.serviceType === PolicyComponentServiceType.MGMT ?
[MgmtMultiFactorType.MULTIFACTORTYPE_U2F_WITH_PIN] :
this.serviceType === PolicyComponentServiceType.ADMIN ?
[AdminMultiFactorType.MULTIFACTORTYPE_U2F_WITH_PIN] :
[];
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
selection = this.serviceType === PolicyComponentServiceType.MGMT ?
[MgmtSecondFactorType.SECONDFACTORTYPE_U2F, MgmtSecondFactorType.SECONDFACTORTYPE_OTP] :
this.serviceType === PolicyComponentServiceType.ADMIN ?
[AdminSecondFactorType.SECONDFACTORTYPE_OTP, AdminSecondFactorType.SECONDFACTORTYPE_U2F] :
[];
}
this.mfas.forEach(mfa => {
const index = selection.findIndex(sel => sel === mfa);
if (index > -1) {
selection.splice(index, 1);
}
});
const dialogRef = this.dialog.open(DialogAddTypeComponent, {
data: {
title: 'MFA.CREATE.TITLE',
desc: 'MFA.CREATE.DESCRIPTION',
componentType: this.componentType,
types: selection,
},
width: '400px',
});
dialogRef.afterClosed().subscribe((mfaType: AdminMultiFactorType | MgmtMultiFactorType |
AdminSecondFactorType | MgmtSecondFactorType) => {
if (mfaType) {
if (this.serviceType === PolicyComponentServiceType.MGMT) {
if (this.componentType === LoginMethodComponentType.MultiFactor) {
const req = new MgmtMultiFactor();
req.setMultiFactor(mfaType as MgmtMultiFactorType);
(this.service as ManagementService).AddMultiFactorToLoginPolicy(req).then(() => {
this.refreshPageAfterTimout(2000);
}).catch(error => {
this.toast.showError(error);
});
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
const req = new MgmtSecondFactor();
req.setSecondFactor(mfaType as MgmtSecondFactorType);
(this.service as ManagementService).AddSecondFactorToLoginPolicy(req).then(() => {
this.refreshPageAfterTimout(2000);
}).catch(error => {
this.toast.showError(error);
});
}
} else if (this.serviceType === PolicyComponentServiceType.ADMIN) {
if (this.componentType === LoginMethodComponentType.MultiFactor) {
const req = new AdminMultiFactor();
req.setMultiFactor(mfaType as AdminMultiFactorType);
(this.service as AdminService).addMultiFactorToDefaultLoginPolicy(req).then(() => {
this.refreshPageAfterTimout(2000);
}).catch(error => {
this.toast.showError(error);
});
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
const req = new AdminSecondFactor();
req.setSecondFactor(mfaType as AdminSecondFactorType);
(this.service as AdminService).AddSecondFactorToDefaultLoginPolicy(req).then(() => {
this.refreshPageAfterTimout(2000);
}).catch(error => {
this.toast.showError(error);
});
}
}
}
});
}
private async getData(): Promise<void> {
this.loadingSubject.next(true);
if (this.serviceType === PolicyComponentServiceType.MGMT) {
if (this.componentType === LoginMethodComponentType.MultiFactor) {
(this.service as ManagementService).GetLoginPolicyMultiFactors().then(resp => {
this.mfas = resp.toObject().multiFactorsList;
this.loadingSubject.next(false);
}).catch(error => {
this.toast.showError(error);
this.loadingSubject.next(false);
});
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
(this.service as ManagementService).GetLoginPolicySecondFactors().then(resp => {
this.mfas = resp.toObject().secondFactorsList;
this.loadingSubject.next(false);
}).catch(error => {
this.toast.showError(error);
this.loadingSubject.next(false);
});
}
} else if (this.serviceType === PolicyComponentServiceType.ADMIN) {
if (this.componentType === LoginMethodComponentType.MultiFactor) {
(this.service as AdminService).getDefaultLoginPolicyMultiFactors().then(resp => {
this.mfas = resp.toObject().multiFactorsList;
this.loadingSubject.next(false);
}).catch(error => {
this.toast.showError(error);
this.loadingSubject.next(false);
});
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
(this.service as AdminService).GetDefaultLoginPolicySecondFactors().then(resp => {
this.mfas = resp.toObject().secondFactorsList;
this.loadingSubject.next(false);
}).catch(error => {
this.toast.showError(error);
this.loadingSubject.next(false);
});
}
}
}
public refreshPageAfterTimout(to: number): void {
setTimeout(() => {
this.getData();
}, to);
}
}

View File

@ -0,0 +1,44 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatIconModule } from '@angular/material/icon';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatTableModule } from '@angular/material/table';
import { MatTooltipModule } from '@angular/material/tooltip';
import { RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { RefreshTableModule } from 'src/app/modules/refresh-table/refresh-table.module';
import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe/localized-date-pipe.module';
import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe/timestamp-to-date-pipe.module';
import { TruncatePipeModule } from 'src/app/pipes/truncate-pipe/truncate-pipe.module';
import { MfaTableComponent } from './mfa-table.component';
import { DialogAddTypeComponent } from './dialog-add-type/dialog-add-type.component';
import { InputModule } from '../input/input.module';
import { MatSelectModule } from '@angular/material/select';
@NgModule({
declarations: [MfaTableComponent, DialogAddTypeComponent],
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
MatButtonModule,
MatIconModule,
InputModule,
MatSelectModule,
MatTooltipModule,
TranslateModule,
TimestampToDatePipeModule,
HasRoleModule,
MatProgressSpinnerModule,
],
exports: [
MfaTableComponent,
],
})
export class MfaTableModule { }

View File

@ -44,8 +44,46 @@
</mat-slide-toggle>
<p> {{'POLICY.DATA.ALLOWEXTERNALIDP_DESC' | translate}} </p>
</div>
<div class="row">
<mat-slide-toggle class="toggle" color="primary" [disabled]="disabled" ngDefaultControl
[(ngModel)]="loginData.forceMfa">
{{'POLICY.DATA.FORCEMFA' | translate}}
</mat-slide-toggle>
<p> {{'POLICY.DATA.FORCEMFA_DESC' | translate}} </p>
</div>
<div class="row">
<cnsl-form-field class="form-field" label="Access Code" required="true">
<cnsl-label>{{'LOGINPOLICY.PASSWORDLESS' | translate}}</cnsl-label>
<mat-select [(ngModel)]="loginData.passwordlessType">
<mat-option *ngFor="let pt of passwordlessTypes" [value]="pt">
{{'LOGINPOLICY.PASSWORDLESSTYPE.'+pt | translate}}
</mat-option>
</mat-select>
</cnsl-form-field>
</div>
</div>
<button [disabled]="disabled" class="save-button" (click)="savePolicy()" color="primary" type="submit"
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
<div class="divider"></div>
<ng-container *ngIf="!isDefault">
<h3 class="subheader">{{ 'MFA.LIST.MULTIFACTORTITLE' | translate }}</h3>
<p class="subdesc">{{ 'MFA.LIST.MULTIFACTORDESCRIPTION' | translate }}</p>
<app-mfa-table [service]="service" [serviceType]="serviceType"
[componentType]="LoginMethodComponentType.MultiFactor"
[disabled]="([serviceType == PolicyComponentServiceType.ADMIN ? 'iam.policy.write' : serviceType == PolicyComponentServiceType.MGMT ? 'iam.policy.write' : ''] | hasRole | async) == false">
</app-mfa-table>
<h3 class="subheader">{{ 'MFA.LIST.SECONDFACTORTITLE' | translate }}</h3>
<p class="subdesc">{{ 'MFA.LIST.SECONDFACTORDESCRIPTION' | translate }}</p>
<app-mfa-table [service]="service" [serviceType]="serviceType"
[componentType]="LoginMethodComponentType.SecondFactor"
[disabled]="([serviceType == PolicyComponentServiceType.ADMIN ? 'iam.policy.write' : serviceType == PolicyComponentServiceType.MGMT ? 'iam.policy.write' : ''] | hasRole | async) == false">
</app-mfa-table>
</ng-container>
<h3 class="subheader">{{'LOGINPOLICY.IDPS' | translate}}</h3>
<div class="idps">
@ -63,9 +101,6 @@
</div>
</div>
<button [disabled]="disabled" class="save-button" (click)="savePolicy()" color="primary" type="submit"
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
<ng-template appHasRole [appHasRole]="['org.idp.read']">
<h2>{{ 'IDP.LIST.TITLE' | translate }}</h2>
<p>{{ 'IDP.LIST.DESCRIPTION' | translate }}</p>

View File

@ -37,6 +37,11 @@
width: 100%;
}
.subdesc {
color: var(--grey);
font-size: 14px;
}
.idps {
display: flex;
margin: 0 -.5rem;
@ -93,3 +98,10 @@
}
}
}
.divider {
width: 100%;
height: 1px;
background-color: var(--grey);
margin: 1rem 0;
}

View File

@ -3,18 +3,23 @@ import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { LoginMethodComponentType } from 'src/app/modules/mfa-table/mfa-table.component';
import {
DefaultLoginPolicy,
DefaultLoginPolicyRequest,
DefaultLoginPolicyView,
IdpProviderView as AdminIdpProviderView,
IdpView as AdminIdpView,
PasswordlessType as AdminPasswordlessType,
} from 'src/app/proto/generated/admin_pb';
import {
IdpProviderType,
IdpProviderView as MgmtIdpProviderView,
IdpView as MgmtIdpView,
LoginPolicy,
LoginPolicyRequest,
LoginPolicyView,
PasswordlessType as MgmtPasswordlessType,
} from 'src/app/proto/generated/management_pb';
import { AdminService } from 'src/app/services/admin.service';
import { ManagementService } from 'src/app/services/mgmt.service';
@ -29,6 +34,8 @@ import { AddIdpDialogComponent } from './add-idp-dialog/add-idp-dialog.component
styleUrls: ['./login-policy.component.scss'],
})
export class LoginPolicyComponent implements OnDestroy {
public LoginMethodComponentType: any = LoginMethodComponentType;
public passwordlessTypes: Array<AdminPasswordlessType | MgmtPasswordlessType> = [];
public loginData!: LoginPolicyView.AsObject | DefaultLoginPolicyView.AsObject;
private sub: Subscription = new Subscription();
@ -50,9 +57,13 @@ export class LoginPolicyComponent implements OnDestroy {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
this.service = this.injector.get(ManagementService as Type<ManagementService>);
this.passwordlessTypes = [MgmtPasswordlessType.PASSWORDLESSTYPE_ALLOWED,
MgmtPasswordlessType.PASSWORDLESSTYPE_NOT_ALLOWED];
break;
case PolicyComponentServiceType.ADMIN:
this.service = this.injector.get(AdminService as Type<AdminService>);
this.passwordlessTypes = [AdminPasswordlessType.PASSWORDLESSTYPE_ALLOWED,
AdminPasswordlessType.PASSWORDLESSTYPE_NOT_ALLOWED];
break;
}
@ -108,20 +119,28 @@ export class LoginPolicyComponent implements OnDestroy {
Promise<LoginPolicy | DefaultLoginPolicy> {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
const mgmtreq = new LoginPolicy();
const mgmtreq = new LoginPolicyRequest();
mgmtreq.setAllowExternalIdp(this.loginData.allowExternalIdp);
mgmtreq.setAllowRegister(this.loginData.allowRegister);
mgmtreq.setAllowUsernamePassword(this.loginData.allowUsernamePassword);
mgmtreq.setForceMfa(this.loginData.forceMfa);
mgmtreq.setPasswordlessType(this.loginData.passwordlessType);
// console.log(mgmtreq.toObject());
if ((this.loginData as LoginPolicyView.AsObject).pb_default) {
return (this.service as ManagementService).CreateLoginPolicy(mgmtreq);
} else {
return (this.service as ManagementService).UpdateLoginPolicy(mgmtreq);
}
case PolicyComponentServiceType.ADMIN:
const adminreq = new DefaultLoginPolicy();
const adminreq = new DefaultLoginPolicyRequest();
adminreq.setAllowExternalIdp(this.loginData.allowExternalIdp);
adminreq.setAllowRegister(this.loginData.allowRegister);
adminreq.setAllowUsernamePassword(this.loginData.allowUsernamePassword);
adminreq.setForceMfa(this.loginData.forceMfa);
adminreq.setPasswordlessType(this.loginData.passwordlessType);
// console.log(adminreq.toObject());
return (this.service as AdminService).UpdateDefaultLoginPolicy(adminreq);
}
}

View File

@ -4,6 +4,7 @@ import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core';
@ -12,6 +13,7 @@ import { CardModule } from 'src/app/modules/card/card.module';
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module';
import { IdpTableModule } from 'src/app/modules/idp-table/idp-table.module';
import { InputModule } from 'src/app/modules/input/input.module';
import { MfaTableModule } from 'src/app/modules/mfa-table/mfa-table.module';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
import { AddIdpDialogModule } from './add-idp-dialog/add-idp-dialog.module';
@ -36,7 +38,9 @@ import { LoginPolicyComponent } from './login-policy.component';
DetailLayoutModule,
AddIdpDialogModule,
IdpTableModule,
MfaTableModule,
MatProgressSpinnerModule,
MatSelectModule,
],
})
export class LoginPolicyModule { }

View File

@ -127,6 +127,22 @@
<span>{{'APP.OIDC.IDTOKENROLEASSERTION_DESCRIPTION' | translate}}</span>
</cnsl-info-section>
<mat-checkbox class="full-width" style="margin-bottom: .5rem"
formControlName="idTokenUserinfoAssertion" color="primary">
{{'APP.OIDC.IDTOKENUSERINFOASSERTION' | translate}}</mat-checkbox>
<cnsl-info-section class="full-width desc">
<span>{{'APP.OIDC.IDTOKENUSERINFOASSERTION_DESCRIPTION' | translate}}</span>
</cnsl-info-section>
<p class="clockskew-title">ClockSkew</p>
<mat-slider color="primary" formControlName="clockSkewSeconds" class="clockskew-slider" thumbLabel
[displayWith]="formatClockSkewLabel" tickInterval=".1" min="0" [step]="1" max="5">
</mat-slider>
<cnsl-info-section class="full-width desc">
<span>{{'APP.OIDC.CLOCKSKEW' | translate}}</span>
</cnsl-info-section>
<div class="divider"></div>
<p class="full-width section-title">{{'APP.OIDC.REDIRECTSECTIONTITLE' | translate}}</p>

View File

@ -128,6 +128,17 @@
margin-right: .5rem;
}
.clockskew-title {
font-size: 14px;
color: var(--grey);
margin: 1rem .5rem 0 .5rem;
}
.clockskew-slider {
width: 100%;
margin: 0 .5rem;
}
.desc {
color: var(--grey);
font-size: 14px;

View File

@ -6,6 +6,7 @@ import { MatButtonToggleChange } from '@angular/material/button-toggle';
import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute, Params } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { Duration } from 'google-protobuf/google/protobuf/duration_pb';
import { Subscription } from 'rxjs';
import { take } from 'rxjs/operators';
import { ChangeType } from 'src/app/modules/changes/changes.component';
@ -15,6 +16,7 @@ import {
OIDCApplicationType,
OIDCAuthMethodType,
OIDCConfig,
OIDCConfigUpdate,
OIDCGrantType,
OIDCResponseType,
OIDCTokenType,
@ -116,9 +118,15 @@ export class AppDetailComponent implements OnInit, OnDestroy {
accessTokenType: [{ value: '', disabled: true }],
accessTokenRoleAssertion: [{ value: false, disabled: true }],
idTokenRoleAssertion: [{ value: false, disabled: true }],
idTokenUserinfoAssertion: [{ value: false, disabled: true }],
clockSkewSeconds: [{ value: 0, disabled: true }],
});
}
public formatClockSkewLabel(seconds: number): string {
return seconds + 's';
}
public ngOnInit(): void {
this.subscription = this.route.params.subscribe(params => this.getData(params));
}
@ -132,11 +140,12 @@ export class AppDetailComponent implements OnInit, OnDestroy {
this.mgmtService.GetIam().then(iam => {
this.isZitadel = iam.toObject().iamProjectId === this.projectId;
});
this.authService.isAllowed(['project.app.write$', 'project.app.write:' + id]).pipe(take(1)).subscribe((allowed) => {
this.authService.isAllowed(['project.app.write$', 'project.app.write:' + projectid]).pipe(take(1)).subscribe((allowed) => {
this.canWrite = allowed;
this.mgmtService.GetApplicationById(projectid, id).then(app => {
this.app = app.toObject();
this.appNameForm.patchValue(this.app);
console.log(this.app);
if (allowed) {
this.appNameForm.enable();
this.appForm.enable();
@ -150,6 +159,11 @@ export class AppDetailComponent implements OnInit, OnDestroy {
if (this.app.oidcConfig?.postLogoutRedirectUrisList) {
this.postLogoutRedirectUrisList = this.app.oidcConfig.postLogoutRedirectUrisList;
}
if (this.app.oidcConfig?.clockSkew) {
const inSecs = this.app.oidcConfig?.clockSkew.seconds + this.app.oidcConfig?.clockSkew.nanos / 100000;
console.log(inSecs);
this.appForm.controls['clockSkewSeconds'].setValue(inSecs);
}
if (this.app.oidcConfig) {
this.appForm.patchValue(this.app.oidcConfig);
}
@ -159,8 +173,6 @@ export class AppDetailComponent implements OnInit, OnDestroy {
this.errorMessage = error.message;
});
});
this.docs = (await this.mgmtService.GetZitadelDocs()).toObject();
}
@ -247,9 +259,32 @@ export class AppDetailComponent implements OnInit, OnDestroy {
this.app.oidcConfig.accessTokenType = this.accessTokenType?.value;
this.app.oidcConfig.accessTokenRoleAssertion = this.accessTokenRoleAssertion?.value;
this.app.oidcConfig.idTokenRoleAssertion = this.idTokenRoleAssertion?.value;
this.app.oidcConfig.idTokenUserinfoAssertion = this.idTokenUserinfoAssertion?.value;
const req = new OIDCConfigUpdate();
req.setProjectId(this.projectId);
req.setApplicationId(this.app.id);
req.setRedirectUrisList(this.app.oidcConfig.redirectUrisList);
req.setResponseTypesList(this.app.oidcConfig.responseTypesList);
req.setAuthMethodType(this.app.oidcConfig.authMethodType);
req.setPostLogoutRedirectUrisList(this.app.oidcConfig.postLogoutRedirectUrisList);
req.setGrantTypesList(this.app.oidcConfig.grantTypesList);
req.setApplicationType(this.app.oidcConfig.applicationType);
req.setDevMode(this.app.oidcConfig.devMode);
req.setAccessTokenType(this.app.oidcConfig.accessTokenType);
req.setAccessTokenRoleAssertion(this.app.oidcConfig.accessTokenRoleAssertion);
req.setIdTokenRoleAssertion(this.app.oidcConfig.idTokenRoleAssertion);
req.setIdTokenUserinfoAssertion(this.app.oidcConfig.idTokenUserinfoAssertion);
if (this.clockSkewSeconds?.value) {
const dur = new Duration();
dur.setSeconds(Math.floor(this.clockSkewSeconds?.value));
dur.setNanos((Math.floor(this.clockSkewSeconds?.value % 1) * 10000));
req.setClockSkew(dur);
}
console.log(req.toObject());
this.mgmtService
.UpdateOIDCAppConfig(this.projectId, this.app.id, this.app.oidcConfig)
.UpdateOIDCAppConfig(req)
.then(() => {
this.toast.showInfo('APP.TOAST.OIDCUPDATED', true);
})
@ -319,4 +354,12 @@ export class AppDetailComponent implements OnInit, OnDestroy {
public get accessTokenRoleAssertion(): AbstractControl | null {
return this.appForm.get('accessTokenRoleAssertion');
}
public get idTokenUserinfoAssertion(): AbstractControl | null {
return this.appForm.get('idTokenUserinfoAssertion');
}
public get clockSkewSeconds(): AbstractControl | null {
return this.appForm.get('clockSkewSeconds');
}
}

View File

@ -13,6 +13,7 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatRadioModule } from '@angular/material/radio';
import { MatSelectModule } from '@angular/material/select';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatSliderModule } from '@angular/material/slider';
import { MatStepperModule } from '@angular/material/stepper';
import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core';
@ -61,6 +62,7 @@ import { AppsRoutingModule } from './apps-routing.module';
MatSlideToggleModule,
InputModule,
MetaLayoutModule,
MatSliderModule,
ChangesModule,
InfoSectionModule,
],

View File

@ -0,0 +1,50 @@
<app-card title="{{'USER.PASSWORDLESS.TITLE' | translate}}"
description="{{'USER.PASSWORDLESS.DESCRIPTION' | translate}}">
<app-refresh-table [loading]="loading$ | async" (refreshed)="getPasswordless()"
[dataSize]="dataSource?.data?.length">
<table class="table" mat-table [dataSource]="dataSource">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> {{ 'USER.PASSWORDLESS.NAME' | translate }} </th>
<td mat-cell *matCellDef="let mfa"><span *ngIf="mfa?.name" class="centered">
{{ mfa?.name }}
</span>
</td>
</ng-container>
<ng-container matColumnDef="state">
<th mat-header-cell *matHeaderCellDef> {{ 'USER.PASSWORDLESS.TABLESTATE' | translate }} </th>
<td mat-cell *matCellDef="let mfa"><span class="centered">
{{'USER.PASSWORDLESS.STATE.'+ mfa.state | translate}}
<i matTooltip="verified" *ngIf="mfa.state === MFAState.MFASTATE_READY"
class="verified las la-check-circle"></i>
</span>
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef> {{ 'USER.PASSWORDLESS.TABLEACTIONS' | translate }} </th>
<td mat-cell *matCellDef="let mfa">
<button matTooltip="{{'ACTIONS.REMOVE' | translate}}" color="warn" mat-icon-button
(click)="deletePasswordless(mfa.id)">
<i class="las la-trash"></i>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</app-refresh-table>
<div class="add-row">
<button class="button" (click)="addPasswordless()" mat-stroked-button color="primary"
matTooltip="{{'ACTIONS.NEW' | translate}}">
<i class="las la-fingerprint"></i>
{{'USER.PASSWORDLESS.U2F' | translate}}
</button>
</div>
<div class="table-wrapper">
<div class="spinner-container" *ngIf="loading$ | async">
<mat-spinner diameter="50"></mat-spinner>
</div>
</div>
</app-card>

View File

@ -0,0 +1,41 @@
.add-row {
display: flex;
margin: -.5rem;
flex-wrap: wrap;
.button {
margin: .5rem;
margin-top: 1rem;
.icon {
margin-right: .5rem;
}
}
}
.centered {
display: flex;
align-items: center;
i {
margin-left: 1rem;
color: var(--color-main);
}
}
.table {
width: 100%;
td,
th {
&:first-child {
padding-left: 0;
padding-right: 1rem;
}
&:last-child {
padding-right: 0;
}
}
}

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { AuthPasswordlessComponent } from './auth-passwordless.component';
describe('AuthPasswordlessComponent', () => {
let component: AuthPasswordlessComponent;
let fixture: ComponentFixture<AuthPasswordlessComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [AuthPasswordlessComponent],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AuthPasswordlessComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,121 @@
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatSort } from '@angular/material/sort';
import { MatTable, MatTableDataSource } from '@angular/material/table';
import { BehaviorSubject, Observable } from 'rxjs';
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
import { MFAState, WebAuthNResponse, WebAuthNToken } from 'src/app/proto/generated/auth_pb';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ToastService } from 'src/app/services/toast.service';
import { _base64ToArrayBuffer } from '../../u2f-util';
import { DialogU2FComponent, U2FComponentDestination } from '../dialog-u2f/dialog-u2f.component';
export interface WebAuthNOptions {
challenge: string;
rp: { name: string, id: string; };
user: { name: string, id: string, displayName: string; };
pubKeyCredParams: any;
authenticatorSelection: { userVerification: string; };
timeout: number;
attestation: string;
}
@Component({
selector: 'app-auth-passwordless',
templateUrl: './auth-passwordless.component.html',
styleUrls: ['./auth-passwordless.component.scss'],
})
export class AuthPasswordlessComponent implements OnInit, OnDestroy {
public displayedColumns: string[] = ['name', 'state', 'actions'];
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
@ViewChild(MatTable) public table!: MatTable<WebAuthNToken.AsObject>;
@ViewChild(MatSort) public sort!: MatSort;
public dataSource!: MatTableDataSource<WebAuthNToken.AsObject>;
public MFAState: any = MFAState;
public error: string = '';
constructor(private service: GrpcAuthService,
private toast: ToastService,
private dialog: MatDialog) { }
public ngOnInit(): void {
this.getPasswordless();
}
public ngOnDestroy(): void {
this.loadingSubject.complete();
}
public addPasswordless(): void {
this.service.AddMyPasswordless().then((u2fresp) => {
const webauthn: WebAuthNResponse.AsObject = u2fresp.toObject();
const credOptions: CredentialCreationOptions = JSON.parse(atob(webauthn.publicKey as string));
if (credOptions.publicKey?.challenge) {
credOptions.publicKey.challenge = _base64ToArrayBuffer(credOptions.publicKey.challenge as any);
credOptions.publicKey.user.id = _base64ToArrayBuffer(credOptions.publicKey.user.id as any);
if (credOptions.publicKey.excludeCredentials) {
credOptions.publicKey.excludeCredentials.map(cred => {
cred.id = _base64ToArrayBuffer(cred.id as any);
return cred;
});
}
console.log(credOptions);
const dialogRef = this.dialog.open(DialogU2FComponent, {
width: '400px',
data: {
credOptions,
type: U2FComponentDestination.PASSWORDLESS,
},
});
dialogRef.afterClosed().subscribe(done => {
if (done) {
this.getPasswordless();
} else {
this.getPasswordless();
}
});
}
}, error => {
this.toast.showError(error);
});
}
public getPasswordless(): void {
this.service.GetMyPasswordless().then(passwordless => {
this.dataSource = new MatTableDataSource(passwordless.toObject().tokensList);
this.dataSource.sort = this.sort;
}).catch(error => {
this.error = error.message;
});
}
public deletePasswordless(id?: string): void {
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.DELETE',
cancelKey: 'ACTIONS.CANCEL',
titleKey: 'USER.PASSWORDLESS.DIALOG.DELETE_TITLE',
descriptionKey: 'USER.PASSWORDLESS.DIALOG.DELETE_DESCRIPTION',
},
width: '400px',
});
dialogRef.afterClosed().subscribe(resp => {
if (resp && id) {
this.service.RemoveMyPasswordless(id).then(() => {
this.toast.showInfo('USER.TOAST.PASSWORDLESSREMOVED', true);
this.getPasswordless();
}).catch(error => {
this.toast.showError(error);
});
}
});
}
}

View File

@ -51,6 +51,8 @@
</app-contact>
</app-card>
<app-auth-passwordless *ngIf="user" #mfaComponent></app-auth-passwordless>
<app-auth-user-mfa *ngIf="user" #mfaComponent></app-auth-user-mfa>
<app-card *ngIf="user?.id" title="{{ 'GRANTS.USER.TITLE' | translate }}"

View File

@ -1,11 +1,19 @@
<app-card title="{{'USER.MFA.TITLE' | translate}}" description="{{'USER.MFA.DESCRIPTION' | translate}}">
<app-refresh-table [loading]="loading$ | async" (refreshed)="getOTP()" [dataSize]="dataSource?.data?.length">
<app-refresh-table [loading]="loading$ | async" (refreshed)="getMFAs()" [dataSize]="dataSource?.data?.length">
<table class="table" mat-table [dataSource]="dataSource">
<ng-container matColumnDef="type">
<th mat-header-cell *matHeaderCellDef> {{ 'USER.MFA.TABLETYPE' | translate }} </th>
<td mat-cell *matCellDef="let mfa"> {{'USER.MFA.TYPE.'+ mfa.type | translate}} </td>
</ng-container>
<ng-container matColumnDef="attr">
<th mat-header-cell *matHeaderCellDef> {{ 'USER.MFA.ATTRIBUTE' | translate }} </th>
<td mat-cell *matCellDef="let mfa"><span *ngIf="mfa?.attribute" class="centered">
{{ mfa?.attribute }}
</span>
</td>
</ng-container>
<ng-container matColumnDef="state">
<th mat-header-cell *matHeaderCellDef> {{ 'USER.MFA.TABLESTATE' | translate }} </th>
<td mat-cell *matCellDef="let mfa"><span class="centered">
@ -20,7 +28,7 @@
<th mat-header-cell *matHeaderCellDef> {{ 'USER.MFA.TABLEACTIONS' | translate }} </th>
<td mat-cell *matCellDef="let mfa">
<button matTooltip="{{'ACTIONS.REMOVE' | translate}}" color="warn" mat-icon-button
(click)="deleteMFA(mfa.type)">
(click)="deleteMFA(mfa.type, mfa.id)">
<i class="las la-trash"></i>
</button>
</td>
@ -35,6 +43,11 @@
matTooltip="{{'ACTIONS.NEW' | translate}}">
<mat-icon class="icon" svgIcon="mdi_radar"></mat-icon>{{'USER.MFA.OTP' | translate}}
</button>
<button class="button" (click)="addU2F()" mat-stroked-button color="primary"
matTooltip="{{'ACTIONS.NEW' | translate}}">
<i class="las la-fingerprint"></i>
{{'USER.MFA.U2F' | translate}}
</button>
</div>
<div class="table-wrapper">
<div class="spinner-container" *ngIf="loading$ | async">

View File

@ -2,6 +2,7 @@
.add-row {
display: flex;
margin: -.5rem;
flex-wrap: wrap;
.button {
margin: .5rem;

View File

@ -4,11 +4,23 @@ import { MatSort } from '@angular/material/sort';
import { MatTable, MatTableDataSource } from '@angular/material/table';
import { BehaviorSubject, Observable } from 'rxjs';
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
import { MfaOtpResponse, MFAState, MfaType, MultiFactor } from 'src/app/proto/generated/auth_pb';
import { MfaOtpResponse, MFAState, MfaType, MultiFactor, WebAuthNResponse } from 'src/app/proto/generated/auth_pb';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ToastService } from 'src/app/services/toast.service';
import { _base64ToArrayBuffer } from '../../u2f-util';
import { DialogOtpComponent } from '../dialog-otp/dialog-otp.component';
import { DialogU2FComponent, U2FComponentDestination } from '../dialog-u2f/dialog-u2f.component';
export interface WebAuthNOptions {
challenge: string;
rp: { name: string, id: string; };
user: { name: string, id: string, displayName: string; };
pubKeyCredParams: any;
authenticatorSelection: { userVerification: string; };
timeout: number;
attestation: string;
}
@Component({
selector: 'app-auth-user-mfa',
@ -16,7 +28,7 @@ import { DialogOtpComponent } from '../dialog-otp/dialog-otp.component';
styleUrls: ['./auth-user-mfa.component.scss'],
})
export class AuthUserMfaComponent implements OnInit, OnDestroy {
public displayedColumns: string[] = ['type', 'state', 'actions'];
public displayedColumns: string[] = ['type', 'attr', 'state', 'actions'];
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
@ -29,10 +41,13 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
public error: string = '';
public otpAvailable: boolean = false;
constructor(private service: GrpcAuthService, private toast: ToastService, private dialog: MatDialog) { }
constructor(private service: GrpcAuthService,
private toast: ToastService,
private dialog: MatDialog) { }
public ngOnInit(): void {
this.getOTP();
this.getMFAs();
}
public ngOnDestroy(): void {
@ -50,7 +65,7 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
dialogRef.afterClosed().subscribe((code) => {
if (code) {
this.service.VerifyMfaOTP(code).then(() => {
this.getOTP();
this.getMFAs();
});
}
});
@ -59,7 +74,44 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
});
}
public getOTP(): void {
public addU2F(): void {
this.service.AddMyMfaU2F().then((u2fresp) => {
const webauthn: WebAuthNResponse.AsObject = u2fresp.toObject();
const credOptions: CredentialCreationOptions = JSON.parse(atob(webauthn.publicKey as string));
if (credOptions.publicKey?.challenge) {
credOptions.publicKey.challenge = _base64ToArrayBuffer(credOptions.publicKey.challenge as any);
credOptions.publicKey.user.id = _base64ToArrayBuffer(credOptions.publicKey.user.id as any);
if (credOptions.publicKey.excludeCredentials) {
credOptions.publicKey.excludeCredentials.map(cred => {
cred.id = _base64ToArrayBuffer(cred.id as any);
return cred;
});
}
console.log(credOptions);
const dialogRef = this.dialog.open(DialogU2FComponent, {
width: '400px',
data: {
credOptions,
type: U2FComponentDestination.MFA,
},
});
dialogRef.afterClosed().subscribe(done => {
if (done) {
this.getMFAs();
} else {
this.getMFAs();
}
});
}
}, error => {
this.toast.showError(error);
});
}
public getMFAs(): void {
this.service.GetMyMfas().then(mfas => {
this.dataSource = new MatTableDataSource(mfas.toObject().mfasList);
this.dataSource.sort = this.sort;
@ -73,13 +125,13 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
});
}
public deleteMFA(type: MfaType): void {
public deleteMFA(type: MfaType, id?: string): void {
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.DELETE',
cancelKey: 'ACTIONS.CANCEL',
titleKey: 'USER.MFA.DIALOG.OTP_DELETE_TITLE',
descriptionKey: 'USER.MFA.DIALOG.OTP_DELETE_DESCRIPTION',
titleKey: 'USER.MFA.DIALOG.MFA_DELETE_TITLE',
descriptionKey: 'USER.MFA.DIALOG.MFA_DELETE_DESCRIPTION',
},
width: '400px',
});
@ -94,7 +146,19 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
if (index > -1) {
this.dataSource.data.splice(index, 1);
}
this.getOTP();
this.getMFAs();
}).catch(error => {
this.toast.showError(error);
});
} else if (type === MfaType.MFATYPE_U2F && id) {
this.service.RemoveMyMfaU2F(id).then(() => {
this.toast.showInfo('USER.TOAST.U2FREMOVED', true);
const index = this.dataSource.data.findIndex(mfa => mfa.type === type);
if (index > -1) {
this.dataSource.data.splice(index, 1);
}
this.getMFAs();
}).catch(error => {
this.toast.showError(error);
});
@ -102,4 +166,6 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
}
});
}
}

View File

@ -1,6 +1,6 @@
<h1 mat-dialog-title>{{'USER.MFA.OTP_DIALOG_TITLE' | translate}}</h1>
<div mat-dialog-content>
<p translate>{{'USER.MFA.OTP_DIALOG_DESCRIPTION' | translate}}</p>
<p>{{'USER.MFA.OTP_DIALOG_DESCRIPTION' | translate}}</p>
<div class="qrcode-wrapper">
<qrcode *ngIf="data" class="qrcode" [qrdata]="data" [width]="150" [errorCorrectionLevel]="'M'"></qrcode>
</div>

View File

@ -0,0 +1,19 @@
<h1 mat-dialog-title>{{'USER.MFA.U2F_DIALOG_TITLE' | translate}}</h1>
<div mat-dialog-content>
<p>{{'USER.MFA.U2F_DIALOG_DESCRIPTION' | translate}}</p>
<cnsl-form-field class="form-field" label="Name" required="true">
<cnsl-label>{{'USER.MFA.U2F_NAME' | translate}}</cnsl-label>
<input cnslInput [(ngModel)]="name" required (keydown.enter)="name ? closeDialogWithCode() : null" />
</cnsl-form-field>
<mat-spinner diameter="30" *ngIf="loading"></mat-spinner>
<p class="error">{{error}}</p>
</div>
<div mat-dialog-actions class="action">
<button mat-button (click)="closeDialog()">{{'ACTIONS.CLOSE' | translate}}</button>
<button cdkFocusInitial [disabled]="!name" mat-raised-button class="ok-button" color="primary"
(click)="closeDialogWithCode()">{{'ACTIONS.VERIFY' | translate}}
</button>
</div>

View File

@ -0,0 +1,27 @@
.qrcode-wrapper {
display: flex;
align-content: center;
.qrcode {
margin: 1rem auto;
}
}
.form-field {
width: 100%;
}
.error {
color: #f44336;
font-size: 14px;
}
.action {
display: flex;
justify-content: flex-end;
button {
margin-left: .5rem;
border-radius: .5rem;
}
}

View File

@ -0,0 +1,110 @@
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { TranslateService } from '@ngx-translate/core';
import { take } from 'rxjs/operators';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ToastService } from 'src/app/services/toast.service';
export function _arrayBufferToBase64(buffer: any): string {
let binary = '';
const bytes = new Uint8Array(buffer);
const len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary).replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}
export enum U2FComponentDestination {
MFA = 'mfa',
PASSWORDLESS = 'passwordless',
}
@Component({
selector: 'app-dialog-u2f',
templateUrl: './dialog-u2f.component.html',
styleUrls: ['./dialog-u2f.component.scss'],
})
export class DialogU2FComponent {
private type!: U2FComponentDestination;
public name: string = '';
public error: string = '';
public loading: boolean = false;
constructor(public dialogRef: MatDialogRef<DialogU2FComponent>,
@Inject(MAT_DIALOG_DATA) public data: { credOptions: any; type: U2FComponentDestination; },
private service: GrpcAuthService, private translate: TranslateService, private toast: ToastService) {
this.type = data.type;
}
public closeDialog(): void {
this.dialogRef.close();
}
public closeDialogWithCode(): void {
this.error = '';
this.loading = true;
if (this.name && this.data.credOptions.publicKey) {
// this.data.credOptions.publicKey.rp.id = 'localhost';
navigator.credentials.create(this.data.credOptions).then((resp) => {
if (resp &&
(resp as any).response.attestationObject &&
(resp as any).response.clientDataJSON &&
(resp as any).rawId) {
const attestationObject = (resp as any).response.attestationObject;
const clientDataJSON = (resp as any).response.clientDataJSON;
const rawId = (resp as any).rawId;
const data = JSON.stringify({
id: resp.id,
rawId: _arrayBufferToBase64(rawId),
type: resp.type,
response: {
attestationObject: _arrayBufferToBase64(attestationObject),
clientDataJSON: _arrayBufferToBase64(clientDataJSON),
},
});
const base64 = btoa(data);
if (this.type === U2FComponentDestination.MFA) {
this.service.VerifyMyMfaU2F(base64, this.name).then(() => {
this.translate.get('USER.MFA.U2F_SUCCESS').pipe(take(1)).subscribe(msg => {
this.toast.showInfo(msg);
});
this.dialogRef.close(true);
this.loading = false;
}).catch(error => {
this.loading = false;
this.toast.showError(error);
});
} else if (this.type === U2FComponentDestination.PASSWORDLESS) {
this.service.verifyMyPasswordless(base64, this.name).then(() => {
this.translate.get('USER.PASSWORDLESS.U2F_SUCCESS').pipe(take(1)).subscribe(msg => {
this.toast.showInfo(msg);
});
this.dialogRef.close(true);
this.loading = false;
}).catch(error => {
this.loading = false;
this.toast.showError(error);
});
}
} else {
this.loading = false;
this.translate.get('USER.MFA.U2F_ERROR').pipe(take(1)).subscribe(msg => {
this.toast.showInfo(msg);
});
this.dialogRef.close(true);
}
}).catch(error => {
this.loading = false;
this.error = error;
this.toast.showInfo(error.message);
});
}
}
}

View File

@ -5,7 +5,7 @@
<div mat-dialog-content>
<cnsl-form-field class="formfield">
<cnsl-label>{{data.labelKey | translate }}</cnsl-label>
<input cnslInput [(ngModel)]="value" />
<input cnslInput [(ngModel)]="value" (keydown.enter)="value ? closeDialogWithValue(value) : null" />
</cnsl-form-field>
</div>
<div mat-dialog-actions class="action">

View File

@ -0,0 +1,10 @@
export function _base64ToArrayBuffer(base64: string): any {
const binaryString = atob(base64);
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
}

View File

@ -29,10 +29,12 @@ import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.mod
import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe/localized-date-pipe.module';
import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe/timestamp-to-date-pipe.module';
import { AuthPasswordlessComponent } from './auth-user-detail/auth-passwordless/auth-passwordless.component';
import { AuthUserDetailComponent } from './auth-user-detail/auth-user-detail.component';
import { AuthUserMfaComponent } from './auth-user-detail/auth-user-mfa/auth-user-mfa.component';
import { CodeDialogComponent } from './auth-user-detail/code-dialog/code-dialog.component';
import { DialogOtpComponent } from './auth-user-detail/dialog-otp/dialog-otp.component';
import { DialogU2FComponent } from './auth-user-detail/dialog-u2f/dialog-u2f.component';
import { EditDialogComponent } from './auth-user-detail/edit-dialog/edit-dialog.component';
import { ResendEmailDialogComponent } from './auth-user-detail/resend-email-dialog/resend-email-dialog.component';
import { ThemeSettingComponent } from './auth-user-detail/theme-setting/theme-setting.component';
@ -46,6 +48,7 @@ import { ShowKeyDialogModule } from './machine-keys/show-key-dialog/show-key-dia
import { MembershipsComponent } from './memberships/memberships.component';
import { PasswordComponent } from './password/password.component';
import { UserDetailRoutingModule } from './user-detail-routing.module';
import { PasswordlessComponent } from './user-detail/passwordless/passwordless.component';
import { UserDetailComponent } from './user-detail/user-detail.component';
import { UserMfaComponent } from './user-detail/user-mfa/user-mfa.component';
@ -56,7 +59,9 @@ import { UserMfaComponent } from './user-detail/user-mfa/user-mfa.component';
DialogOtpComponent,
EditDialogComponent,
AuthUserMfaComponent,
AuthPasswordlessComponent,
UserMfaComponent,
PasswordlessComponent,
ThemeSettingComponent,
PasswordComponent,
CodeDialogComponent,
@ -65,6 +70,7 @@ import { UserMfaComponent } from './user-detail/user-mfa/user-mfa.component';
ExternalIdpsComponent,
ContactComponent,
ResendEmailDialogComponent,
DialogU2FComponent,
],
imports: [
UserDetailRoutingModule,

View File

@ -0,0 +1,43 @@
<app-card title="{{'USER.PASSWORDLESS.TITLE' | translate}}"
description="{{'USER.PASSWORDLESS.DESCRIPTION' | translate}}">
<app-refresh-table [loading]="loading$ | async" (refreshed)="getPasswordless()"
[dataSize]="dataSource?.data?.length">
<table class="table" mat-table [dataSource]="dataSource">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> {{ 'USER.PASSWORDLESS.NAME' | translate }} </th>
<td mat-cell *matCellDef="let mfa"><span *ngIf="mfa?.name" class="centered">
{{ mfa?.name }}
</span>
</td>
</ng-container>
<ng-container matColumnDef="state">
<th mat-header-cell *matHeaderCellDef> {{ 'USER.PASSWORDLESS.TABLESTATE' | translate }} </th>
<td mat-cell *matCellDef="let mfa"><span class="centered">
{{'USER.PASSWORDLESS.STATE.'+ mfa.state | translate}}
<i matTooltip="verified" *ngIf="mfa.state === MFAState.MFASTATE_READY"
class="verified las la-check-circle"></i>
</span>
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef> {{ 'USER.PASSWORDLESS.TABLEACTIONS' | translate }} </th>
<td mat-cell *matCellDef="let mfa">
<button matTooltip="{{'ACTIONS.REMOVE' | translate}}" color="warn" mat-icon-button
(click)="deletePasswordless(mfa.id)">
<i class="las la-trash"></i>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</app-refresh-table>
<div class="table-wrapper">
<div class="spinner-container" *ngIf="loading$ | async">
<mat-spinner diameter="50"></mat-spinner>
</div>
</div>
</app-card>

View File

@ -0,0 +1,26 @@
.centered {
display: flex;
align-items: center;
i {
margin-left: 1rem;
color: var(--color-main);
}
}
.table {
width: 100%;
td,
th {
&:first-child {
padding-left: 0;
padding-right: 1rem;
}
&:last-child {
padding-right: 0;
}
}
}

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { AuthPasswordlessComponent } from './auth-passwordless.component';
describe('AuthPasswordlessComponent', () => {
let component: AuthPasswordlessComponent;
let fixture: ComponentFixture<AuthPasswordlessComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [AuthPasswordlessComponent],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AuthPasswordlessComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,84 @@
import { Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatSort } from '@angular/material/sort';
import { MatTable, MatTableDataSource } from '@angular/material/table';
import { BehaviorSubject, Observable } from 'rxjs';
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
import { MFAState, WebAuthNToken } from 'src/app/proto/generated/auth_pb';
import { UserView } from 'src/app/proto/generated/management_pb';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
export interface WebAuthNOptions {
challenge: string;
rp: { name: string, id: string; };
user: { name: string, id: string, displayName: string; };
pubKeyCredParams: any;
authenticatorSelection: { userVerification: string; };
timeout: number;
attestation: string;
}
@Component({
selector: 'app-passwordless',
templateUrl: './passwordless.component.html',
styleUrls: ['./passwordless.component.scss'],
})
export class PasswordlessComponent implements OnInit, OnDestroy {
@Input() private user!: UserView.AsObject;
public displayedColumns: string[] = ['name', 'state', 'actions'];
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
@ViewChild(MatTable) public table!: MatTable<WebAuthNToken.AsObject>;
@ViewChild(MatSort) public sort!: MatSort;
public dataSource!: MatTableDataSource<WebAuthNToken.AsObject>;
public MFAState: any = MFAState;
public error: string = '';
constructor(private service: ManagementService,
private toast: ToastService,
private dialog: MatDialog) { }
public ngOnInit(): void {
this.getPasswordless();
}
public ngOnDestroy(): void {
this.loadingSubject.complete();
}
public getPasswordless(): void {
this.service.GetPasswordless(this.user.id).then(passwordless => {
console.log(passwordless.toObject().tokensList);
this.dataSource = new MatTableDataSource(passwordless.toObject().tokensList);
this.dataSource.sort = this.sort;
}).catch(error => {
this.error = error.message;
});
}
public deletePasswordless(id?: string): void {
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.DELETE',
cancelKey: 'ACTIONS.CANCEL',
titleKey: 'USER.PASSWORDLESS.DIALOG.DELETE_TITLE',
descriptionKey: 'USER.PASSWORDLESS.DIALOG.DELETE_DESCRIPTION',
},
width: '400px',
});
dialogRef.afterClosed().subscribe(resp => {
if (resp && id) {
this.service.RemovePasswordless(id, this.user.id).then(() => {
this.toast.showInfo('USER.TOAST.PASSWORDLESSREMOVED', true);
this.getPasswordless();
}).catch(error => {
this.toast.showError(error);
});
}
});
}
}

View File

@ -84,6 +84,8 @@
</app-contact>
</app-card>
<app-passwordless *ngIf="user && user.human" [user]="user"></app-passwordless>
<app-user-mfa *ngIf="user && user.human" [user]="user"></app-user-mfa>
<app-card *ngIf="user?.id" title="{{ 'GRANTS.USER.TITLE' | translate }}"

View File

@ -1,11 +1,19 @@
<app-card title="{{'USER.MFA.TITLE' | translate}}" description="{{'USER.MFA.DESCRIPTION' | translate}}">
<app-refresh-table [loading]="loading$ | async" (refreshed)="getOTP()" [dataSize]="dataSource?.data?.length">
<app-refresh-table [loading]="loading$ | async" (refreshed)="getMFAs()" [dataSize]="dataSource?.data?.length">
<table class="table" mat-table [dataSource]="dataSource">
<ng-container matColumnDef="type">
<th mat-header-cell *matHeaderCellDef> {{ 'USER.MFA.TABLETYPE' | translate }} </th>
<td mat-cell *matCellDef="let mfa"> {{'USER.MFA.TYPE.'+ mfa.type | translate}} </td>
</ng-container>
<ng-container matColumnDef="attr">
<th mat-header-cell *matHeaderCellDef> {{ 'USER.MFA.ATTRIBUTE' | translate }} </th>
<td mat-cell *matCellDef="let mfa"><span *ngIf="mfa?.attribute" class="centered">
{{ mfa.attribute }}
</span>
</td>
</ng-container>
<ng-container matColumnDef="state">
<th mat-header-cell *matHeaderCellDef> {{ 'USER.MFA.TABLESTATE' | translate }} </th>
<td mat-cell *matCellDef="let mfa">
@ -21,7 +29,7 @@
<th mat-header-cell *matHeaderCellDef> {{ 'USER.MFA.TABLEACTIONS' | translate }} </th>
<td mat-cell *matCellDef="let mfa">
<button matTooltip="{{'ACTIONS.REMOVE' | translate}}" color="warn" mat-icon-button
(click)="deleteMFA(mfa.type)">
(click)="deleteMFA(mfa.type, mfa.id)">
<i class="las la-trash"></i>
</button>
</td>

View File

@ -20,7 +20,7 @@ export interface MFAItem {
styleUrls: ['./user-mfa.component.scss'],
})
export class UserMfaComponent implements OnInit, OnDestroy {
public displayedColumns: string[] = ['type', 'state', 'actions'];
public displayedColumns: string[] = ['type', 'attr', 'state', 'actions'];
@Input() private user!: UserView.AsObject;
public mfaSubject: BehaviorSubject<UserMultiFactor.AsObject[]> = new BehaviorSubject<UserMultiFactor.AsObject[]>([]);
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
@ -37,7 +37,7 @@ export class UserMfaComponent implements OnInit, OnDestroy {
constructor(private mgmtUserService: ManagementService, private dialog: MatDialog, private toast: ToastService) { }
public ngOnInit(): void {
this.getOTP();
this.getMFAs();
}
public ngOnDestroy(): void {
@ -45,8 +45,9 @@ export class UserMfaComponent implements OnInit, OnDestroy {
this.loadingSubject.complete();
}
public getOTP(): void {
public getMFAs(): void {
this.mgmtUserService.getUserMfas(this.user.id).then(mfas => {
console.log(mfas.toObject().mfasList);
this.dataSource = new MatTableDataSource(mfas.toObject().mfasList);
this.dataSource.sort = this.sort;
}).catch(error => {
@ -54,13 +55,14 @@ export class UserMfaComponent implements OnInit, OnDestroy {
});
}
public deleteMFA(type: MfaType): void {
public deleteMFA(type: MfaType, id?: string): void {
console.log(type, id);
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.DELETE',
cancelKey: 'ACTIONS.CANCEL',
titleKey: 'USER.MFA.DIALOG.OTP_DELETE_TITLE',
descriptionKey: 'USER.MFA.DIALOG.OTP_DELETE_DESCRIPTION',
titleKey: 'USER.MFA.DIALOG.MFA_DELETE_TITLE',
descriptionKey: 'USER.MFA.DIALOG.MFA_DELETE_DESCRIPTION',
},
width: '400px',
});
@ -75,7 +77,20 @@ export class UserMfaComponent implements OnInit, OnDestroy {
if (index > -1) {
this.dataSource.data.splice(index, 1);
}
this.getOTP();
this.getMFAs();
}).catch(error => {
this.toast.showError(error);
});
} else if (type === MfaType.MFATYPE_U2F && id) {
console.log(this.user.id, id);
this.mgmtUserService.RemoveMfaU2F(this.user.id, id).then(() => {
this.toast.showInfo('USER.TOAST.U2FREMOVED', true);
const index = this.dataSource.data.findIndex(mfa => mfa.type === type);
if (index > -1) {
this.dataSource.data.splice(index, 1);
}
this.getMFAs();
}).catch(error => {
this.toast.showError(error);
});

View File

@ -1,4 +1,4 @@
<app-refresh-table [loading]="loading$ | async" (refreshed)="refreshPage()" [dataSize]="dataSource.data.length"
<app-refresh-table [loading]="loading$ | async" (refreshed)="refreshPage()" [dataSize]="userResult?.totalResult"
[timestamp]="userResult?.viewTimestamp" [selection]="selection"
[emitRefreshOnPreviousRoutes]="refreshOnPreviousRoutes">
<cnsl-form-field @appearfade *ngIf="userSearchKey != undefined" actions class="filtername">

View File

@ -11,6 +11,7 @@ import {
DefaultLabelPolicyUpdate,
DefaultLabelPolicyView,
DefaultLoginPolicy,
DefaultLoginPolicyRequest,
DefaultLoginPolicyView,
DefaultPasswordAgePolicyRequest,
DefaultPasswordAgePolicyView,
@ -36,6 +37,8 @@ import {
IdpSearchRequest,
IdpSearchResponse,
IdpView,
MultiFactor,
MultiFactorsResult,
OidcIdpConfig,
OidcIdpConfigCreate,
OidcIdpConfigUpdate,
@ -46,6 +49,8 @@ import {
OrgSetUpRequest,
OrgSetUpResponse,
RemoveIamMemberRequest,
SecondFactor,
SecondFactorsResult,
ViewID,
Views,
} from '../proto/generated/admin_pb';
@ -73,6 +78,32 @@ export class AdminService {
return this.grpcService.admin.setUpOrg(req);
}
public getDefaultLoginPolicyMultiFactors(): Promise<MultiFactorsResult> {
const req = new Empty();
return this.grpcService.admin.getDefaultLoginPolicyMultiFactors(req);
}
public addMultiFactorToDefaultLoginPolicy(req: MultiFactor): Promise<MultiFactor> {
return this.grpcService.admin.addMultiFactorToDefaultLoginPolicy(req);
}
public RemoveMultiFactorFromDefaultLoginPolicy(req: MultiFactor): Promise<Empty> {
return this.grpcService.admin.removeMultiFactorFromDefaultLoginPolicy(req);
}
public GetDefaultLoginPolicySecondFactors(): Promise<SecondFactorsResult> {
const req = new Empty();
return this.grpcService.admin.getDefaultLoginPolicySecondFactors(req);
}
public AddSecondFactorToDefaultLoginPolicy(req: SecondFactor): Promise<SecondFactor> {
return this.grpcService.admin.addSecondFactorToDefaultLoginPolicy(req);
}
public RemoveSecondFactorFromDefaultLoginPolicy(req: SecondFactor): Promise<Empty> {
return this.grpcService.admin.removeSecondFactorFromDefaultLoginPolicy(req);
}
public GetIamMemberRoles(): Promise<IamMemberRoles> {
const req = new Empty();
return this.grpcService.admin.getIamMemberRoles(req);
@ -184,7 +215,7 @@ export class AdminService {
return this.grpcService.admin.getDefaultLoginPolicy(req);
}
public UpdateDefaultLoginPolicy(req: DefaultLoginPolicy): Promise<DefaultLoginPolicy> {
public UpdateDefaultLoginPolicy(req: DefaultLoginPolicyRequest): Promise<DefaultLoginPolicy> {
return this.grpcService.admin.updateDefaultLoginPolicy(req);
}

View File

@ -33,6 +33,10 @@ import {
UserView,
VerifyMfaOtp,
VerifyUserPhoneRequest,
VerifyWebAuthN,
WebAuthNResponse,
WebAuthNTokenID,
WebAuthNTokens,
} from '../proto/generated/auth_pb';
import { GrpcService } from './grpc.service';
import { StorageKey, StorageService } from './storage.service';
@ -328,6 +332,56 @@ export class GrpcAuthService {
);
}
public AddMyMfaU2F(): Promise<WebAuthNResponse> {
return this.grpcService.auth.addMyMfaU2F(
new Empty(),
);
}
public RemoveMyMfaU2F(id: string): Promise<Empty> {
const req = new WebAuthNTokenID();
req.setId(id);
return this.grpcService.auth.removeMyMfaU2F(req);
}
public VerifyMyMfaU2F(credential: string, tokenname: string): Promise<Empty> {
const req = new VerifyWebAuthN();
req.setPublicKeyCredential(credential);
req.setTokenName(tokenname);
return this.grpcService.auth.verifyMyMfaU2F(
req,
);
}
public GetMyPasswordless(): Promise<WebAuthNTokens> {
return this.grpcService.auth.getMyPasswordless(
new Empty(),
);
}
public AddMyPasswordless(): Promise<WebAuthNResponse> {
return this.grpcService.auth.addMyPasswordless(
new Empty(),
);
}
public RemoveMyPasswordless(id: string): Promise<Empty> {
const req = new WebAuthNTokenID();
req.setId(id);
return this.grpcService.auth.removeMyPasswordless(req);
}
public verifyMyPasswordless(credential: string, tokenname: string): Promise<Empty> {
const req = new VerifyWebAuthN();
req.setPublicKeyCredential(credential);
req.setTokenName(tokenname);
return this.grpcService.auth.verifyMyPasswordless(
req,
);
}
public RemoveMfaOTP(): Promise<Empty> {
return this.grpcService.auth.removeMfaOTP(
new Empty(),

View File

@ -3,6 +3,7 @@ import { Empty } from 'google-protobuf/google/protobuf/empty_pb';
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
import { BehaviorSubject } from 'rxjs';
import { MultiFactorsResult } from '../proto/generated/admin_pb';
import {
AddMachineKeyRequest,
AddMachineKeyResponse,
@ -50,6 +51,7 @@ import {
MachineKeySearchResponse,
MachineKeyType,
MachineResponse,
MultiFactor,
NotificationType,
OIDCApplicationCreate,
OIDCConfig,
@ -122,6 +124,8 @@ import {
ProjectView,
RemoveOrgDomainRequest,
RemoveOrgMemberRequest,
SecondFactor,
SecondFactorsResult,
SetPasswordNotificationRequest,
UpdateMachineRequest,
UpdateUserAddressRequest,
@ -152,6 +156,8 @@ import {
UserSearchResponse,
UserView,
ValidateOrgDomainRequest,
WebAuthNTokenID,
WebAuthNTokens,
ZitadelDocs,
} from '../proto/generated/management_pb';
import { GrpcService } from './grpc.service';
@ -185,6 +191,45 @@ export class ManagementService {
return this.grpcService.mgmt.searchIdps(req);
}
public GetPasswordless(userId: string): Promise<WebAuthNTokens> {
const req = new UserID();
req.setId(userId);
return this.grpcService.mgmt.getPasswordless(req);
}
public RemovePasswordless(id: string, userId: string): Promise<Empty> {
const req = new WebAuthNTokenID();
req.setId(id);
req.setUserId(userId);
return this.grpcService.mgmt.removePasswordless(req);
}
public GetLoginPolicyMultiFactors(): Promise<MultiFactorsResult> {
const req = new Empty();
return this.grpcService.mgmt.getLoginPolicyMultiFactors(req);
}
public AddMultiFactorToLoginPolicy(req: MultiFactor): Promise<MultiFactor> {
return this.grpcService.mgmt.addMultiFactorToLoginPolicy(req);
}
public RemoveMultiFactorFromLoginPolicy(req: MultiFactor): Promise<Empty> {
return this.grpcService.mgmt.removeMultiFactorFromLoginPolicy(req);
}
public GetLoginPolicySecondFactors(): Promise<SecondFactorsResult> {
const req = new Empty();
return this.grpcService.mgmt.getLoginPolicySecondFactors(req);
}
public AddSecondFactorToLoginPolicy(req: SecondFactor): Promise<SecondFactor> {
return this.grpcService.mgmt.addSecondFactorToLoginPolicy(req);
}
public RemoveSecondFactorFromLoginPolicy(req: SecondFactor): Promise<Empty> {
return this.grpcService.mgmt.removeSecondFactorFromLoginPolicy(req);
}
public GetLoginPolicy(): Promise<LoginPolicyView> {
const req = new Empty();
return this.grpcService.mgmt.getLoginPolicy(req);
@ -683,6 +728,13 @@ export class ManagementService {
return this.grpcService.mgmt.removeMfaOTP(req);
}
public RemoveMfaU2F(userid: string, id: string): Promise<Empty> {
const req = new WebAuthNTokenID();
req.setId(id);
req.setUserId(userid);
return this.grpcService.mgmt.removeMfaU2F(req);
}
public SaveUserProfile(
id: string,
firstName?: string,
@ -1307,21 +1359,7 @@ export class ManagementService {
return this.grpcService.mgmt.updateApplication(req);
}
public UpdateOIDCAppConfig(projectId: string,
appId: string, oidcConfig: OIDCConfig.AsObject): Promise<OIDCConfig> {
const req = new OIDCConfigUpdate();
req.setProjectId(projectId);
req.setApplicationId(appId);
req.setRedirectUrisList(oidcConfig.redirectUrisList);
req.setResponseTypesList(oidcConfig.responseTypesList);
req.setAuthMethodType(oidcConfig.authMethodType);
req.setPostLogoutRedirectUrisList(oidcConfig.postLogoutRedirectUrisList);
req.setGrantTypesList(oidcConfig.grantTypesList);
req.setApplicationType(oidcConfig.applicationType);
req.setDevMode(oidcConfig.devMode);
req.setAccessTokenType(oidcConfig.accessTokenType);
req.setAccessTokenRoleAssertion(oidcConfig.accessTokenRoleAssertion);
req.setIdTokenRoleAssertion(oidcConfig.idTokenRoleAssertion);
public UpdateOIDCAppConfig(req: OIDCConfigUpdate): Promise<OIDCConfig> {
return this.grpcService.mgmt.updateApplicationOIDCConfig(req);
}
}

View File

@ -152,16 +152,20 @@
},
"EMPTY":"Keine Einträge"
},
"MFA": {
"PASSWORDLESS": {
"TABLETYPE":"Typ",
"TABLESTATE":"Status",
"NAME":"Name",
"TABLEACTIONS":"Aktionen",
"TITLE": "Multifaktor-Authentisierung",
"DESCRIPTION": "Füge einen zusätzlichen Faktor hinzu, um Dein Konto optimal zu schützen.",
"TITLE": "Passwortlose Authentifizierungsmethoden",
"DESCRIPTION": "Füge WebAuthn kompatible Authentifikatoren hinzu um dich passwortlos anzumelden.",
"MANAGE_DESCRIPTION": "Verwalte die Multifaktor-Merkmale Deiner Benutzer.",
"OTP": "OTP konfigurieren",
"OTP_DIALOG_TITLE": "OTP hinzufügen",
"OTP_DIALOG_DESCRIPTION": "Scanne den QR-Code mit einer Authenticator App und verifiziere den erhaltenen Code, um OTP zu aktivieren.",
"U2F":"Authentifikator hinzufügen",
"U2F_DIALOG_TITLE": "Authentifikator hinzufügen",
"U2F_DIALOG_DESCRIPTION": "Gib einen Namen für den von dir verwendeten Login an.",
"U2F_SUCCESS":"Passwordless erfolgreich erstellt!",
"U2F_ERROR":"Ein Fehler ist aufgetreten!",
"U2F_NAME":"Authentifikator Name",
"TYPE": {
"0":"Keine MFA definiert",
"1":"OTP",
@ -174,8 +178,41 @@
"3": "Gelöscht"
},
"DIALOG": {
"OTP_DELETE_TITLE":"OTP Faktor entfernen",
"OTP_DELETE_DESCRIPTION":"Sie sind im Begriff OTP als Zweitfaktormethode zu entfernen. Sind sie sicher?"
"DELETE_TITLE":"Passwordless entfernen",
"DELETE_DESCRIPTION":"Sie sind im Begriff eine Passwortlose Authentifizierungsmethode zu entfernen. Sind sie sicher?"
}
},
"MFA": {
"TABLETYPE":"Typ",
"TABLESTATE":"Status",
"ATTRIBUTE":"Attribut",
"TABLEACTIONS":"Aktionen",
"TITLE": "Multifaktor-Authentisierung",
"DESCRIPTION": "Füge einen zusätzlichen Faktor hinzu, um Dein Konto optimal zu schützen.",
"MANAGE_DESCRIPTION": "Verwalte die Multifaktor-Merkmale Deiner Benutzer.",
"OTP": "OTP konfigurieren",
"OTP_DIALOG_TITLE": "OTP hinzufügen",
"OTP_DIALOG_DESCRIPTION": "Scanne den QR-Code mit einer Authenticator App und verifiziere den erhaltenen Code, um OTP zu aktivieren.",
"U2F":"U2F hinzufügen",
"U2F_DIALOG_TITLE": "U2F hinzufügen",
"U2F_DIALOG_DESCRIPTION": "Gib einen Namen für den von dir verwendeten Universellen Multifaktor an.",
"U2F_SUCCESS":"U2F erfolgreich erstellt!",
"U2F_ERROR":"Ein Fehler ist aufgetreten!",
"U2F_NAME":"U2F Name",
"TYPE": {
"0":"Keine MFA definiert",
"1":"OTP",
"2":"U2F"
},
"STATE": {
"0": "Kein Status",
"1": "Nicht bereit",
"2": "Bereit",
"3": "Gelöscht"
},
"DIALOG": {
"MFA_DELETE_TITLE":"Zweiten Faktor entfernen",
"MFA_DELETE_DESCRIPTION":"Sie sind im Begriff eine Zweitfaktormethode zu entfernen. Sind sie sicher?"
}
},
"EXTERNALIDP": {
@ -345,6 +382,8 @@
"PHONEVERIFICATIONSENT":"Bestätigungscode per Telefonnummer gesendet.",
"EMAILVERIFICATIONSENT":"Bestätigungscode per E-Mail gesendet.",
"OTPREMOVED":"OTP entfernt.",
"U2FREMOVED":"U2F entfernt.",
"PASSWORDLESSREMOVED":"Passwordless entfernt.",
"INITIALPASSWORDSET":"Initiales Passwort gesetzt.",
"PASSWORDNOTIFICATIONSENT":"Passwortänderung mittgeteilt.",
"PASSWORDCHANGED":"Passwort geändert.",
@ -551,7 +590,9 @@
"ALLOWREGISTER":"Registrieren erlaubt",
"ALLOWUSERNAMEPASSWORD_DESC":"Der konventionelle Login mit Benutzername und Passwort wird erlaubt.",
"ALLOWEXTERNALIDP_DESC":"Der Login wird für die darunter liegenden Identity Provider erlaubt.",
"ALLOWREGISTER_DESC":"Ist die Option gewählt, erscheint im Login ein zusätzlicher Schritt zum Registrieren eines Benutzers."
"ALLOWREGISTER_DESC":"Ist die Option gewählt, erscheint im Login ein zusätzlicher Schritt zum Registrieren eines Benutzers.",
"FORCEMFA":"Mfa erzwingen",
"FORCEMFA_DESC":"Ist die Option gewählt, müssen Benutzer einen zweiten Faktor für den Login verwenden."
},
"RESET":"Richtlinie zurücksetzen",
"CREATECUSTOM":"Benutzerdefinierte Richtlinie erstellen",
@ -763,7 +804,7 @@
},
"IDP":{
"LIST": {
"TITLE":"Identitäts Providers",
"TITLE":"Identitäts Provider",
"DESCRIPTION":"Definieren Sie hier Ihre zusätzlichen Idps, die sie für die Authentifizierung in Ihren Organisationen verwenden können."
},
"CREATE": {
@ -826,6 +867,37 @@
"DELETED":"Idp erfolgreich gelöscht!"
}
},
"MFA":{
"LIST": {
"MULTIFACTORTITLE":"Multifaktoren",
"MULTIFACTORDESCRIPTION":"Definieren Sie hier Ihre Multifaktoren, die sie für die Authentifizierung verwenden können.",
"SECONDFACTORTITLE":"Zweitfaktoren",
"SECONDFACTORDESCRIPTION":"Definieren Sie hier Ihre Zweitfaktoren, die sie für die Authentifizierung verwenden können."
},
"CREATE": {
"TITLE":"Neuer Faktor",
"DESCRIPTION":"Definieren Sie hier den gewünschten Typ."
},
"DELETE": {
"TITLE":"Faktor löschen",
"DESCRIPTION":"Sie sind im Begriff einen Faktor zu löschen. Die daruch hervorgerufenen Änderungen sind unwiederruflich. Wollen Sie dies wirklich tun?"
},
"TOAST": {
"ADDED":"Erfolgreich hinzugefügt.",
"SAVED": "Erfolgreich gespeichert.",
"DELETED":"Mfa erfolgreich gelöscht!"
},
"TYPE":"Typ",
"MULTIFACTORTYPES": {
"0":"Unknown",
"1":"U2F with Pin"
},
"SECONDFACTORTYPES": {
"0":"Unknown",
"1":"OTP",
"2":"U2F"
}
},
"LOGINPOLICY": {
"CREATE": {
"TITLE":"Login Policy",
@ -836,6 +908,11 @@
"TITLE":"Identity Provider hinzufügen",
"DESCRIPTION":"Sie können vordefinierte oder selbsterstellten Provider auswählen",
"SELECTIDPS":"Identity Provider"
},
"PASSWORDLESS":"Passwordloser Login",
"PASSWORDLESSTYPE": {
"0":"Nicht erlaubt",
"1":"Erlaubt"
}
},
"APP": {
@ -910,8 +987,11 @@
"OVERVIEWTITLE":"Deine Konfiguration ist bereit. Du kannst sie hier nochmals prüfen.",
"ACCESSTOKENROLEASSERTION":"Benutzerrollen dem Access Token hinzufügen",
"ACCESSTOKENROLEASSERTION_DESCRIPTION":"Bei Auswahl werden dem Access Token die Rollen des Authentifizierten Benutzers hinzugefügt.",
"IDTOKENROLEASSERTION":"Benutzerrollen dem Id Token hinzufügen",
"IDTOKENROLEASSERTION_DESCRIPTION":"Bei Auswahl werden dem Id Token die Rollen des Authentifizierten Benutzers hinzugefügt."
"IDTOKENROLEASSERTION":"Benutzerrollen im ID Token",
"IDTOKENROLEASSERTION_DESCRIPTION":"Bei Auswahl werden dem Id Token die Rollen des Authentifizierten Benutzers hinzugefügt.",
"IDTOKENUSERINFOASSERTION":"User Info im ID Token",
"IDTOKENUSERINFOASSERTION_DESCRIPTION":"Ermöglich OIDC clients claims von profile, email, phone und address direkt vom ID Token zu beziehen.",
"CLOCKSKEW":"ermöglicht Clients, den Taktversatz von OP und Client zu verarbeiten. Die Dauer (0-5s) wird der exp addiert und von iats, auth_time und nbf abgezogen."
},
"TOAST": {
"REACTIVATED":"Anwendung reaktiviert.",

View File

@ -152,16 +152,20 @@
},
"EMPTY":"No entries"
},
"MFA": {
"PASSWORDLESS": {
"TABLETYPE":"Type",
"TABLESTATE":"Status",
"NAME":"Name",
"TABLEACTIONS":"Actions",
"TITLE": "Multifactor Authentication",
"DESCRIPTION": "Add a second factor to ensure optimal security for your account.",
"TITLE": "Passwordless Authentication",
"DESCRIPTION": "Add WebAuthn based Authentication Methods to log onto ZITADEL passwordless.",
"MANAGE_DESCRIPTION": "Manage the second factor methods of your users.",
"OTP": "Configure OTP",
"OTP_DIALOG_TITLE": "Add OTP",
"OTP_DIALOG_DESCRIPTION": "Scan the QR code with an authenticator app and enter the code below to verify and activate the OTP method.",
"U2F":"Add authenticator",
"U2F_DIALOG_TITLE": "Verify authenticator",
"U2F_DIALOG_DESCRIPTION": "Enter a name for your used passwordless Login",
"U2F_SUCCESS":"Passwordless Auth created successfully!",
"U2F_ERROR":"An error during U2F setup occurred!",
"U2F_NAME":"Authenticator Name",
"TYPE": {
"0": "No MFA defined",
"1": "OTP",
@ -174,8 +178,41 @@
"3": "Deleted"
},
"DIALOG": {
"OTP_DELETE_TITLE":"Remove OTP Factor",
"OTP_DELETE_DESCRIPTION":"You are about to delete OTP as second factor. Are you sure?"
"DELETE_TITLE":"Remove Passwordless Authentication Method",
"DELETE_DESCRIPTION":"You are about to delete a passwordless Authentication method. Are you sure?"
}
},
"MFA": {
"TABLETYPE":"Type",
"TABLESTATE":"Status",
"ATTRIBUTE":"Attribut",
"TABLEACTIONS":"Actions",
"TITLE": "Multifactor Authentication",
"DESCRIPTION": "Add a second factor to ensure optimal security for your account.",
"MANAGE_DESCRIPTION": "Manage the second factor methods of your users.",
"OTP": "Configure OTP",
"OTP_DIALOG_TITLE": "Add OTP",
"OTP_DIALOG_DESCRIPTION": "Scan the QR code with an authenticator app and enter the code below to verify and activate the OTP method.",
"U2F":"Add U2F",
"U2F_DIALOG_TITLE": "Verify U2F",
"U2F_DIALOG_DESCRIPTION": "Enter a name for your used universal Multifactor.",
"U2F_SUCCESS":"U2F created successfully!",
"U2F_ERROR":"An error during U2F setup occurred!",
"U2F_NAME":"U2F Name",
"TYPE": {
"0": "No MFA defined",
"1": "OTP",
"2": "U2F"
},
"STATE": {
"0": "No State",
"1": "Not Ready",
"2": "Ready",
"3": "Deleted"
},
"DIALOG": {
"MFA_DELETE_TITLE":"Remove Secondfactor",
"MFA_DELETE_DESCRIPTION":"You are about to delete a second factor. Are you sure?"
}
},
"EXTERNALIDP": {
@ -345,6 +382,8 @@
"PHONEVERIFICATIONSENT":"Phone verification code sent.",
"EMAILVERIFICATIONSENT":"E-mail verification code sent.",
"OTPREMOVED":"OTP removed.",
"U2FREMOVED":"U2F removed.",
"PASSWORDLESSREMOVED":"Passwordless removed.",
"INITIALPASSWORDSET":"Initial password set.",
"PASSWORDNOTIFICATIONSENT":"Password change notification sent.",
"PASSWORDCHANGED":"Password changed successfully.",
@ -551,7 +590,9 @@
"ALLOWREGISTER":"Register allowed",
"ALLOWUSERNAMEPASSWORD_DESC":"The conventional login with user name and password is allowed.",
"ALLOWEXTERNALIDP_DESC":"The login is allowed for the underlying identity providers",
"ALLOWREGISTER_DESC":"If the option is selected, an additional step for registering a user appears in the login."
"ALLOWREGISTER_DESC":"If the option is selected, an additional step for registering a user appears in the login.",
"FORCEMFA":"Force MFA",
"FORCEMFA_DESC":"If the option is selected, users have to configure a second factor for login."
},
"RESET":"Reset Policy",
"CREATECUSTOM":"Create Custom Policy",
@ -826,6 +867,37 @@
"DELETED":"Idp removed successfully!"
}
},
"MFA":{
"LIST": {
"MULTIFACTORTITLE":"Multifactors",
"MULTIFACTORDESCRIPTION":"Define your Multifactors for Authentication here.",
"SECONDFACTORTITLE":"Secondfactors",
"SECONDFACTORDESCRIPTION":"Define your Secondfactors for Authentication here."
},
"CREATE": {
"TITLE":"New Factor",
"DESCRIPTION":"Select your new Factor type."
},
"DELETE": {
"TITLE":"Delete Factor",
"DESCRIPTION":"You are about to delete a Factor from Login Policy. Are you sure?"
},
"TOAST": {
"ADDED":"Added successfully.",
"SAVED": "Saved successfully.",
"DELETED":"Removed successfully"
},
"TYPE":"Type",
"MULTIFACTORTYPES": {
"0":"Unknown",
"1":"U2F with Pin"
},
"SECONDFACTORTYPES": {
"0":"Unknown",
"1":"OTP",
"2":"U2F"
}
},
"LOGINPOLICY": {
"CREATE": {
"TITLE":"Login Policy",
@ -836,6 +908,11 @@
"TITLE":"Add Identity Provider",
"DESCRIPTION":"You can select predefined or selfcreated providers for authentication.",
"SELECTIDPS":"Identity providers"
},
"PASSWORDLESS":"Passwordless Login",
"PASSWORDLESSTYPE": {
"0":"Not allowed",
"1":"Allowed"
}
},
"APP": {
@ -910,8 +987,11 @@
"OVERVIEWTITLE":"You are now done. Review your configuration.",
"ACCESSTOKENROLEASSERTION":"Add user roles to the access token",
"ACCESSTOKENROLEASSERTION_DESCRIPTION":"If selected, the roles of the authenticated user are added to the access token.",
"IDTOKENROLEASSERTION":"Add user roles to the id token",
"IDTOKENROLEASSERTION_DESCRIPTION":"If selected, the roles of the authenticated user are added to the id token."
"IDTOKENROLEASSERTION":"User roles inside ID Token",
"IDTOKENROLEASSERTION_DESCRIPTION":"If selected, the roles of the authenticated user are added to the ID token.",
"IDTOKENUSERINFOASSERTION":"User Info inside ID Token",
"IDTOKENUSERINFOASSERTION_DESCRIPTION":"Enables clients to retrieve profile, email, phone and address claims from ID token.",
"CLOCKSKEW":"Enables clients to handle clock skew of OP and client. The duration (0-5s) will be added to exp claim and subtracted from iats, auth_time and nbf."
},
"TOAST": {
"REACTIVATED":"Application reactivated.",

9
go.mod
View File

@ -15,8 +15,8 @@ require (
github.com/allegro/bigcache v1.2.1
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc
github.com/caos/logging v0.0.2
github.com/caos/oidc v0.13.1
github.com/cockroachdb/cockroach-go/v2 v2.0.8
github.com/caos/oidc v0.13.2
github.com/cockroachdb/cockroach-go/v2 v2.1.0
github.com/duo-labs/webauthn v0.0.0-20200714211715-1daaee874e43
github.com/envoyproxy/protoc-gen-validate v0.4.1
github.com/ghodss/yaml v1.0.0
@ -40,9 +40,8 @@ require (
github.com/kevinburke/rest v0.0.0-20200429221318-0d2892b400f8 // indirect
github.com/kevinburke/twilio-go v0.0.0-20200810163702-320748330fac
github.com/kr/text v0.2.0 // indirect
github.com/lib/pq v1.8.0
github.com/lib/pq v1.9.0
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
github.com/mitchellh/copystructure v1.0.0 // indirect
github.com/mitchellh/reflectwalk v1.0.1 // indirect
github.com/nicksnyder/go-i18n/v2 v2.1.1
@ -69,7 +68,7 @@ require (
golang.org/x/tools v0.0.0-20201103235415-b653051172e4
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20201103154000-415bd0cd5df6
google.golang.org/grpc v1.33.1
google.golang.org/grpc v1.34.0
google.golang.org/protobuf v1.25.0
gopkg.in/square/go-jose.v2 v2.5.1
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect

55
go.sum
View File

@ -110,8 +110,8 @@ github.com/caos/logging v0.0.2 h1:ebg5C/HN0ludYR+WkvnFjwSExF4wvyiWPyWGcKMYsoo=
github.com/caos/logging v0.0.2 h1:ebg5C/HN0ludYR+WkvnFjwSExF4wvyiWPyWGcKMYsoo=
github.com/caos/logging v0.0.2/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0=
github.com/caos/logging v0.0.2/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0=
github.com/caos/oidc v0.13.1 h1:8890xd3XJpev5xuU4Dn2l49c1lCA3Qd1xu4KsPSVo38=
github.com/caos/oidc v0.13.1/go.mod h1:dLvfYUiAt9ORfl77L/KkcWuR/N0ll8Ry1nD2ERsamDY=
github.com/caos/oidc v0.13.2 h1:52oP3KB1UrZuwraBTLuwM9ItRIhJQMYOm1J5uQ0sYXw=
github.com/caos/oidc v0.13.2/go.mod h1:dLvfYUiAt9ORfl77L/KkcWuR/N0ll8Ry1nD2ERsamDY=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
@ -132,10 +132,11 @@ github.com/cloudflare/cfssl v0.0.0-20190726000631-633726f6bcb7 h1:Puu1hUwfps3+1C
github.com/cloudflare/cfssl v0.0.0-20190726000631-633726f6bcb7/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f h1:WBZRG4aNOuI15bLRrCgN8fCq8E5Xuty6jGbmSNEvSsU=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/cockroachdb/cockroach-go/v2 v2.0.8 h1:50C/7ptrrfdxDccCjDU0xsdeBca+S0/AYW4Mo8RyzFE=
github.com/cockroachdb/cockroach-go/v2 v2.0.8/go.mod h1:nkf7rUmgPdawp3EwRjXIumihI2AYg9usGNWbJ2hsJqI=
github.com/cockroachdb/cockroach-go/v2 v2.1.0 h1:zicZlBhWZu6wfK7Ezg4Owdc3HamLpRdBllPTT9tb+2k=
github.com/cockroachdb/cockroach-go/v2 v2.1.0/go.mod h1:ilhrLnPDDwGHL+iK2UxQhp1UzUhst8sfItSAgCYwAyg=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
@ -169,6 +170,7 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4 h1:rEvIZUSZ3fx39WIi3JkQqQBitGwpELBIYWeBVh6wn+E=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v0.4.1 h1:7dLaJvASGRD7X49jSCSXXHwKPm0ZN9r9kJD+p+vS7dM=
@ -199,7 +201,6 @@ github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80n
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
@ -378,8 +379,12 @@ github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgO
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk=
github.com/jackc/pgconn v1.5.0 h1:oFSOilzIZkyg787M1fEmyMfOUUvwj0daqYMfaWwNL4o=
github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
github.com/jackc/pgconn v1.7.0/go.mod h1:sF/lPpNEMEOp+IYhyQGdAvrG20gWf6A1tKlr0v7JMeA=
github.com/jackc/pgconn v1.7.2/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2 h1:JVX6jT/XfzNqIjye4717ITLaNwV9mWbJx0dLCpcRzdA=
@ -394,31 +399,41 @@ github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvW
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.1 h1:Rdjp4NFjwHnEslx2b66FfCI2S0LhO4itac3hXz6WX9M=
github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.0.5/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8 h1:Q3tB+ExeflWUW7AFcAhXqk40s9mnNYLk1nOkKNZ5GnU=
github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgtype v1.3.0 h1:l8JvKrby3RI7Kg3bYEeU9TA4vqC38QDpFCfcrC7KuN0=
github.com/jackc/pgtype v1.3.0/go.mod h1:b0JqxHvPmljG+HQ5IsvQ0yqeSi4nGcDTVjFoiLDb0Ik=
github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o=
github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0=
github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po=
github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ=
github.com/jackc/pgtype v1.5.0/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig=
github.com/jackc/pgtype v1.6.1/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.6.0 h1:Fh0O9GdlG4gYpjpwOqjdEodJUQM9jzN3Hdv7PN0xmm0=
github.com/jackc/pgx/v4 v4.6.0/go.mod h1:vPh43ZzxijXUVJ+t/EmXBtFmbFVO72cuneCT9oAlxAg=
github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA=
github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o=
github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg=
github.com/jackc/pgx/v4 v4.9.0/go.mod h1:MNGWmViCgqbZck9ujOOBN63gK9XVGILXWCvKLGKmnms=
github.com/jackc/pgx/v4 v4.9.2/go.mod h1:Jt/xJDqjUDUOMSv8VMWPQlCObVgF2XOgqKsW8S4ROYA=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.0 h1:musOWczZC/rSbqut475Vfcczg7jJsdUQf0D6oKPLgNU=
github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs=
github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.2/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o=
github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E=
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
@ -466,9 +481,10 @@ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.4.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8=
github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/lyft/protoc-gen-star v0.5.1/go.mod h1:9toiA3cC7z5uVbODF7kEQ91Xn7XNFkVUl+SrEe+ZORU=
@ -476,6 +492,7 @@ github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0Q
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
@ -489,7 +506,6 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
@ -611,6 +627,7 @@ github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v0.0.0-20200419222939-1884f454f8ea h1:jaXWVFZ98/ihXniiDzqNXQgMSgklX4kjfDWZTE3ZtdU=
github.com/shopspring/decimal v0.0.0-20200419222939-1884f454f8ea/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
@ -699,9 +716,11 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
@ -718,6 +737,7 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@ -1076,6 +1096,8 @@ google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1 h1:DGeFlSan2f+WEtCERJ4J9GJWk15TxUi8QGagfI87Xyc=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.34.0 h1:raiipEjMOIC/TO2AvyTxP25XFdLxNIBwzDh3FM3XztI=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -1123,6 +1145,9 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/postgres v1.0.5/go.mod h1:qrD92UurYzNctBMVCJ8C3VQEjffEuphycXtxOudXNCA=
gorm.io/gorm v1.20.4/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
gorm.io/gorm v1.20.6/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -2,10 +2,11 @@ package eventstore
import (
"context"
"time"
"github.com/caos/zitadel/internal/admin/repository/eventsourcing/view"
view_model "github.com/caos/zitadel/internal/view/model"
"github.com/caos/zitadel/internal/view/repository"
"time"
)
var dbList = []string{"management", "auth", "authz", "adminapi", "notification"}
@ -47,7 +48,7 @@ func (repo *AdministratorRepo) GetViews() ([]*view_model.View, error) {
}
func (repo *AdministratorRepo) GetSpoolerDiv(database, view string) int64 {
sequence, err := repo.View.GetCurrentSequence(database, view)
sequence, err := repo.View.GetCurrentSequence(database, view, "")
if err != nil {
return 0

View File

@ -69,7 +69,7 @@ func (repo *IAMRepository) RemoveIAMMember(ctx context.Context, userID string) e
func (repo *IAMRepository) SearchIAMMembers(ctx context.Context, request *iam_model.IAMMemberSearchRequest) (*iam_model.IAMMemberSearchResponse, error) {
request.EnsureLimit(repo.SearchLimit)
sequence, err := repo.View.GetLatestIAMMemberSequence()
sequence, err := repo.View.GetLatestIAMMemberSequence("")
logging.Log("EVENT-Slkci").OnError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Warn("could not read latest iam sequence")
members, count, err := repo.View.SearchIAMMembers(request)
if err != nil {
@ -194,7 +194,7 @@ func (repo *IAMRepository) ChangeOidcIDPConfig(ctx context.Context, oidcConfig *
func (repo *IAMRepository) SearchIDPConfigs(ctx context.Context, request *iam_model.IDPConfigSearchRequest) (*iam_model.IDPConfigSearchResponse, error) {
request.EnsureLimit(repo.SearchLimit)
sequence, err := repo.View.GetLatestIDPConfigSequence()
sequence, err := repo.View.GetLatestIDPConfigSequence("")
logging.Log("EVENT-Dk8si").OnError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Warn("could not read latest idp config sequence")
idps, count, err := repo.View.SearchIDPConfigs(request)
if err != nil {
@ -298,7 +298,7 @@ func (repo *IAMRepository) ChangeDefaultLoginPolicy(ctx context.Context, policy
func (repo *IAMRepository) SearchDefaultIDPProviders(ctx context.Context, request *iam_model.IDPProviderSearchRequest) (*iam_model.IDPProviderSearchResponse, error) {
request.EnsureLimit(repo.SearchLimit)
request.AppendAggregateIDQuery(repo.SystemDefaults.IamID)
sequence, err := repo.View.GetLatestIDPProviderSequence()
sequence, err := repo.View.GetLatestIDPProviderSequence("")
logging.Log("EVENT-Tuiks").OnError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Warn("could not read latest iam sequence")
providers, count, err := repo.View.SearchIDPProviders(request)
if err != nil {

View File

@ -87,7 +87,7 @@ func (repo *OrgRepo) OrgByID(ctx context.Context, id string) (*org_model.Org, er
func (repo *OrgRepo) SearchOrgs(ctx context.Context, query *org_model.OrgSearchRequest) (*org_model.OrgSearchResult, error) {
query.EnsureLimit(repo.SearchLimit)
sequence, err := repo.View.GetLatestOrgSequence()
sequence, err := repo.View.GetLatestOrgSequence("")
logging.Log("EVENT-LXo9w").OnError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Warn("could not read latest iam sequence")
orgs, count, err := repo.View.SearchOrgs(query)
if err != nil {

View File

@ -3,13 +3,12 @@ package handler
import (
"time"
"github.com/caos/zitadel/internal/config/systemdefaults"
iam_event "github.com/caos/zitadel/internal/iam/repository/eventsourcing"
"github.com/caos/zitadel/internal/admin/repository/eventsourcing/view"
"github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/config/types"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/query"
iam_event "github.com/caos/zitadel/internal/iam/repository/eventsourcing"
org_event "github.com/caos/zitadel/internal/org/repository/eventsourcing"
usr_event "github.com/caos/zitadel/internal/user/repository/eventsourcing"
)
@ -25,6 +24,12 @@ type handler struct {
bulkLimit uint64
cycleDuration time.Duration
errorCountUntilSkip uint64
es eventstore.Eventstore
}
func (h *handler) Eventstore() eventstore.Eventstore {
return h.es
}
type EventstoreRepos struct {
@ -33,31 +38,49 @@ type EventstoreRepos struct {
OrgEvents *org_event.OrgEventstore
}
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, eventstore eventstore.Eventstore, repos EventstoreRepos, defaults systemdefaults.SystemDefaults) []query.Handler {
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es eventstore.Eventstore, repos EventstoreRepos, defaults systemdefaults.SystemDefaults) []query.Handler {
return []query.Handler{
&Org{handler: handler{view, bulkLimit, configs.cycleDuration("Org"), errorCount}},
&IamMember{handler: handler{view, bulkLimit, configs.cycleDuration("IamMember"), errorCount},
userEvents: repos.UserEvents},
&IDPConfig{handler: handler{view, bulkLimit, configs.cycleDuration("IDPConfig"), errorCount}},
&LabelPolicy{handler: handler{view, bulkLimit, configs.cycleDuration("LabelPolicy"), errorCount}},
&LoginPolicy{handler: handler{view, bulkLimit, configs.cycleDuration("LoginPolicy"), errorCount}},
&IDPProvider{handler: handler{view, bulkLimit, configs.cycleDuration("IDPProvider"), errorCount},
systemDefaults: defaults, iamEvents: repos.IamEvents, orgEvents: repos.OrgEvents},
&User{handler: handler{view, bulkLimit, configs.cycleDuration("User"), errorCount},
eventstore: eventstore, orgEvents: repos.OrgEvents, iamEvents: repos.IamEvents, systemDefaults: defaults},
&PasswordComplexityPolicy{handler: handler{view, bulkLimit, configs.cycleDuration("PasswordComplexityPolicy"), errorCount}},
&PasswordAgePolicy{handler: handler{view, bulkLimit, configs.cycleDuration("PasswordAgePolicy"), errorCount}},
&PasswordLockoutPolicy{handler: handler{view, bulkLimit, configs.cycleDuration("PasswordLockoutPolicy"), errorCount}},
&OrgIAMPolicy{handler: handler{view, bulkLimit, configs.cycleDuration("OrgIAMPolicy"), errorCount}},
&ExternalIDP{handler: handler{view, bulkLimit, configs.cycleDuration("ExternalIDP"), errorCount},
orgEvents: repos.OrgEvents, iamEvents: repos.IamEvents, systemDefaults: defaults},
newOrg(
handler{view, bulkLimit, configs.cycleDuration("Org"), errorCount, es}),
newIAMMember(
handler{view, bulkLimit, configs.cycleDuration("IamMember"), errorCount, es},
repos.UserEvents),
newIDPConfig(
handler{view, bulkLimit, configs.cycleDuration("IDPConfig"), errorCount, es}),
newLabelPolicy(
handler{view, bulkLimit, configs.cycleDuration("LabelPolicy"), errorCount, es}),
newLoginPolicy(
handler{view, bulkLimit, configs.cycleDuration("LoginPolicy"), errorCount, es}),
newIDPProvider(
handler{view, bulkLimit, configs.cycleDuration("IDPProvider"), errorCount, es},
defaults,
repos.IamEvents,
repos.OrgEvents),
newUser(
handler{view, bulkLimit, configs.cycleDuration("User"), errorCount, es},
repos.OrgEvents,
repos.IamEvents,
defaults),
newPasswordComplexityPolicy(
handler{view, bulkLimit, configs.cycleDuration("PasswordComplexityPolicy"), errorCount, es}),
newPasswordAgePolicy(
handler{view, bulkLimit, configs.cycleDuration("PasswordAgePolicy"), errorCount, es}),
newPasswordLockoutPolicy(
handler{view, bulkLimit, configs.cycleDuration("PasswordLockoutPolicy"), errorCount, es}),
newOrgIAMPolicy(
handler{view, bulkLimit, configs.cycleDuration("OrgIAMPolicy"), errorCount, es}),
newExternalIDP(
handler{view, bulkLimit, configs.cycleDuration("ExternalIDP"), errorCount, es},
defaults,
repos.IamEvents,
repos.OrgEvents),
}
}
func (configs Configs) cycleDuration(viewModel string) time.Duration {
c, ok := configs[viewModel]
if !ok {
return 1 * time.Second
return 3 * time.Minute
}
return c.MinimumCycleDuration.Duration
}
@ -66,6 +89,10 @@ func (h *handler) MinimumCycleDuration() time.Duration {
return h.cycleDuration
}
func (h *handler) LockDuration() time.Duration {
return h.cycleDuration / 3
}
func (h *handler) QueryLimit() uint64 {
return h.bulkLimit
}

View File

@ -4,9 +4,9 @@ import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/eventstore"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/eventstore/query"
"github.com/caos/zitadel/internal/eventstore/spooler"
"github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
@ -15,40 +15,73 @@ import (
usr_es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
)
type IamMember struct {
handler
userEvents *usr_event.UserEventstore
}
const (
iamMemberTable = "adminapi.iam_members"
)
func (m *IamMember) ViewModel() string {
type IAMMember struct {
handler
userEvents *usr_event.UserEventstore
subscription *eventstore.Subscription
}
func newIAMMember(handler handler, userEvents *usr_event.UserEventstore) *IAMMember {
iamMember := &IAMMember{
handler: handler,
userEvents: userEvents,
}
iamMember.subscribe()
return iamMember
}
func (m *IAMMember) subscribe() {
m.subscription = m.es.Subscribe(m.AggregateTypes()...)
go func() {
for event := range m.subscription.Events {
query.ReduceEvent(m, event)
}
}()
}
func (m *IAMMember) CurrentSequence(event *es_models.Event) (uint64, error) {
sequence, err := m.view.GetLatestIAMMemberSequence(string(event.AggregateType))
if err != nil {
return 0, err
}
return sequence.CurrentSequence, nil
}
func (m *IAMMember) ViewModel() string {
return iamMemberTable
}
func (m *IamMember) EventQuery() (*models.SearchQuery, error) {
sequence, err := m.view.GetLatestIAMMemberSequence()
func (m *IAMMember) AggregateTypes() []es_models.AggregateType {
return []es_models.AggregateType{model.IAMAggregate, usr_es_model.UserAggregate}
}
func (m *IAMMember) EventQuery() (*es_models.SearchQuery, error) {
sequence, err := m.view.GetLatestIAMMemberSequence("")
if err != nil {
return nil, err
}
return es_models.NewSearchQuery().
AggregateTypeFilter(model.IAMAggregate, usr_es_model.UserAggregate).
AggregateTypeFilter(m.AggregateTypes()...).
LatestSequenceFilter(sequence.CurrentSequence), nil
}
func (m *IamMember) Reduce(event *models.Event) (err error) {
func (m *IAMMember) Reduce(event *es_models.Event) (err error) {
switch event.AggregateType {
case model.IAMAggregate:
err = m.processIamMember(event)
err = m.processIAMMember(event)
case usr_es_model.UserAggregate:
err = m.processUser(event)
}
return err
}
func (m *IamMember) processIamMember(event *models.Event) (err error) {
func (m *IAMMember) processIAMMember(event *es_models.Event) (err error) {
member := new(iam_model.IAMMemberView)
switch event.Type {
case model.IAMMemberAdded:
@ -72,17 +105,17 @@ func (m *IamMember) processIamMember(event *models.Event) (err error) {
if err != nil {
return err
}
return m.view.DeleteIAMMember(event.AggregateID, member.UserID, event.Sequence, event.CreationDate)
return m.view.DeleteIAMMember(event.AggregateID, member.UserID, event)
default:
return m.view.ProcessedIAMMemberSequence(event.Sequence, event.CreationDate)
return m.view.ProcessedIAMMemberSequence(event)
}
if err != nil {
return err
}
return m.view.PutIAMMember(member, member.Sequence, event.CreationDate)
return m.view.PutIAMMember(member, event)
}
func (m *IamMember) processUser(event *models.Event) (err error) {
func (m *IAMMember) processUser(event *es_models.Event) (err error) {
switch event.Type {
case usr_es_model.UserProfileChanged,
usr_es_model.UserEmailChanged,
@ -94,7 +127,7 @@ func (m *IamMember) processUser(event *models.Event) (err error) {
return err
}
if len(members) == 0 {
return m.view.ProcessedIAMMemberSequence(event.Sequence, event.CreationDate)
return m.view.ProcessedIAMMemberSequence(event)
}
user, err := m.userEvents.UserByID(context.Background(), event.AggregateID)
if err != nil {
@ -103,16 +136,15 @@ func (m *IamMember) processUser(event *models.Event) (err error) {
for _, member := range members {
m.fillUserData(member, user)
}
return m.view.PutIAMMembers(members, event.Sequence, event.CreationDate)
return m.view.PutIAMMembers(members, event)
case usr_es_model.UserRemoved:
return m.view.DeleteIAMMembersByUserID(event.AggregateID, event.Sequence, event.CreationDate)
return m.view.DeleteIAMMembersByUserID(event.AggregateID, event)
default:
return m.view.ProcessedIAMMemberSequence(event.Sequence, event.CreationDate)
return m.view.ProcessedIAMMemberSequence(event)
}
return nil
}
func (m *IamMember) fillData(member *iam_model.IAMMemberView) (err error) {
func (m *IAMMember) fillData(member *iam_model.IAMMemberView) (err error) {
user, err := m.userEvents.UserByID(context.Background(), member.UserID)
if err != nil {
return err
@ -121,7 +153,7 @@ func (m *IamMember) fillData(member *iam_model.IAMMemberView) (err error) {
return nil
}
func (m *IamMember) fillUserData(member *iam_model.IAMMemberView, user *usr_model.User) {
func (m *IAMMember) fillUserData(member *iam_model.IAMMemberView, user *usr_model.User) {
member.UserName = user.UserName
if user.Human != nil {
member.FirstName = user.FirstName
@ -133,11 +165,11 @@ func (m *IamMember) fillUserData(member *iam_model.IAMMemberView, user *usr_mode
member.DisplayName = user.Machine.Name
}
}
func (m *IamMember) OnError(event *models.Event, err error) error {
func (m *IAMMember) OnError(event *es_models.Event, err error) error {
logging.LogWithFields("SPOOL-Ld9ow", "id", event.AggregateID).WithError(err).Warn("something went wrong in iammember handler")
return spooler.HandleError(event, err, m.view.GetLatestIAMMemberFailedEvent, m.view.ProcessedIAMMemberFailedEvent, m.view.ProcessedIAMMemberSequence, m.errorCountUntilSkip)
}
func (m *IamMember) OnSuccess() error {
func (m *IAMMember) OnSuccess() error {
return spooler.HandleSuccess(m.view.UpdateIAMMemberSpoolerRunTimestamp)
}

View File

@ -2,38 +2,70 @@ package handler
import (
"github.com/caos/logging"
iam_model "github.com/caos/zitadel/internal/iam/model"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/eventstore"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/eventstore/query"
"github.com/caos/zitadel/internal/eventstore/spooler"
iam_model "github.com/caos/zitadel/internal/iam/model"
"github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
iam_view_model "github.com/caos/zitadel/internal/iam/repository/view/model"
)
type IDPConfig struct {
handler
}
const (
idpConfigTable = "adminapi.idp_configs"
)
type IDPConfig struct {
handler
subscription *eventstore.Subscription
}
func newIDPConfig(handler handler) *IDPConfig {
h := &IDPConfig{
handler: handler,
}
h.subscribe()
return h
}
func (i *IDPConfig) subscribe() {
i.subscription = i.es.Subscribe(i.AggregateTypes()...)
go func() {
for event := range i.subscription.Events {
query.ReduceEvent(i, event)
}
}()
}
func (i *IDPConfig) ViewModel() string {
return idpConfigTable
}
func (i *IDPConfig) EventQuery() (*models.SearchQuery, error) {
sequence, err := i.view.GetLatestIDPConfigSequence()
func (i *IDPConfig) AggregateTypes() []es_models.AggregateType {
return []es_models.AggregateType{model.IAMAggregate}
}
func (i *IDPConfig) CurrentSequence(event *es_models.Event) (uint64, error) {
sequence, err := i.view.GetLatestIDPConfigSequence(string(event.AggregateType))
if err != nil {
return 0, err
}
return sequence.CurrentSequence, nil
}
func (i *IDPConfig) EventQuery() (*es_models.SearchQuery, error) {
sequence, err := i.view.GetLatestIDPConfigSequence("")
if err != nil {
return nil, err
}
return es_models.NewSearchQuery().
AggregateTypeFilter(model.IAMAggregate).
AggregateTypeFilter(i.AggregateTypes()...).
LatestSequenceFilter(sequence.CurrentSequence), nil
}
func (i *IDPConfig) Reduce(event *models.Event) (err error) {
func (i *IDPConfig) Reduce(event *es_models.Event) (err error) {
switch event.AggregateType {
case model.IAMAggregate:
err = i.processIDPConfig(event)
@ -41,7 +73,7 @@ func (i *IDPConfig) Reduce(event *models.Event) (err error) {
return err
}
func (i *IDPConfig) processIDPConfig(event *models.Event) (err error) {
func (i *IDPConfig) processIDPConfig(event *es_models.Event) (err error) {
idp := new(iam_view_model.IDPConfigView)
switch event.Type {
case model.IDPConfigAdded:
@ -63,17 +95,17 @@ func (i *IDPConfig) processIDPConfig(event *models.Event) (err error) {
if err != nil {
return err
}
return i.view.DeleteIDPConfig(idp.IDPConfigID, event.Sequence, event.CreationDate)
return i.view.DeleteIDPConfig(idp.IDPConfigID, event)
default:
return i.view.ProcessedIDPConfigSequence(event.Sequence, event.CreationDate)
return i.view.ProcessedIDPConfigSequence(event)
}
if err != nil {
return err
}
return i.view.PutIDPConfig(idp, idp.Sequence, event.CreationDate)
return i.view.PutIDPConfig(idp, event)
}
func (i *IDPConfig) OnError(event *models.Event, err error) error {
func (i *IDPConfig) OnError(event *es_models.Event, err error) error {
logging.LogWithFields("SPOOL-Mslo9", "id", event.AggregateID).WithError(err).Warn("something went wrong in idp config handler")
return spooler.HandleError(event, err, i.view.GetLatestIDPConfigFailedEvent, i.view.ProcessedIDPConfigFailedEvent, i.view.ProcessedIDPConfigSequence, i.errorCountUntilSkip)
}

View File

@ -2,18 +2,23 @@ package handler
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/iam/repository/eventsourcing"
org_events "github.com/caos/zitadel/internal/org/repository/eventsourcing"
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/eventstore"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/eventstore/query"
"github.com/caos/zitadel/internal/eventstore/spooler"
iam_model "github.com/caos/zitadel/internal/iam/model"
"github.com/caos/zitadel/internal/iam/repository/eventsourcing"
"github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
iam_view_model "github.com/caos/zitadel/internal/iam/repository/view/model"
org_events "github.com/caos/zitadel/internal/org/repository/eventsourcing"
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
)
const (
idpProviderTable = "adminapi.idp_providers"
)
type IDPProvider struct {
@ -21,27 +26,63 @@ type IDPProvider struct {
systemDefaults systemdefaults.SystemDefaults
iamEvents *eventsourcing.IAMEventstore
orgEvents *org_events.OrgEventstore
subscription *eventstore.Subscription
}
const (
idpProviderTable = "adminapi.idp_providers"
)
func newIDPProvider(
handler handler,
systemDefaults systemdefaults.SystemDefaults,
iamEvents *eventsourcing.IAMEventstore,
orgEvents *org_events.OrgEventstore,
) *IDPProvider {
h := &IDPProvider{
handler: handler,
systemDefaults: systemDefaults,
iamEvents: iamEvents,
orgEvents: orgEvents,
}
h.subscribe()
return h
}
func (i *IDPProvider) subscribe() {
i.subscription = i.es.Subscribe(i.AggregateTypes()...)
go func() {
for event := range i.subscription.Events {
query.ReduceEvent(i, event)
}
}()
}
func (i *IDPProvider) ViewModel() string {
return idpProviderTable
}
func (i *IDPProvider) EventQuery() (*models.SearchQuery, error) {
sequence, err := i.view.GetLatestIDPProviderSequence()
func (i *IDPProvider) AggregateTypes() []es_models.AggregateType {
return []es_models.AggregateType{model.IAMAggregate, org_es_model.OrgAggregate}
}
func (i *IDPProvider) CurrentSequence(event *es_models.Event) (uint64, error) {
sequence, err := i.view.GetLatestIDPProviderSequence(string(event.AggregateType))
if err != nil {
return 0, err
}
return sequence.CurrentSequence, nil
}
func (i *IDPProvider) EventQuery() (*es_models.SearchQuery, error) {
sequence, err := i.view.GetLatestIDPProviderSequence("")
if err != nil {
return nil, err
}
return es_models.NewSearchQuery().
AggregateTypeFilter(model.IAMAggregate, org_es_model.OrgAggregate).
AggregateTypeFilter(i.AggregateTypes()...).
LatestSequenceFilter(sequence.CurrentSequence), nil
}
func (i *IDPProvider) Reduce(event *models.Event) (err error) {
func (i *IDPProvider) Reduce(event *es_models.Event) (err error) {
switch event.AggregateType {
case model.IAMAggregate, org_es_model.OrgAggregate:
err = i.processIdpProvider(event)
@ -49,7 +90,7 @@ func (i *IDPProvider) Reduce(event *models.Event) (err error) {
return err
}
func (i *IDPProvider) processIdpProvider(event *models.Event) (err error) {
func (i *IDPProvider) processIdpProvider(event *es_models.Event) (err error) {
provider := new(iam_view_model.IDPProviderView)
switch event.Type {
case model.LoginPolicyIDPProviderAdded, org_es_model.LoginPolicyIDPProviderAdded:
@ -64,7 +105,7 @@ func (i *IDPProvider) processIdpProvider(event *models.Event) (err error) {
if err != nil {
return err
}
return i.view.DeleteIDPProvider(event.AggregateID, provider.IDPConfigID, event.Sequence, event.CreationDate)
return i.view.DeleteIDPProvider(event.AggregateID, provider.IDPConfigID, event)
case model.IDPConfigChanged, org_es_model.IDPConfigChanged:
esConfig := new(iam_view_model.IDPConfigView)
providerType := iam_model.IDPProviderTypeSystem
@ -83,14 +124,14 @@ func (i *IDPProvider) processIdpProvider(event *models.Event) (err error) {
for _, provider := range providers {
i.fillConfigData(provider, config)
}
return i.view.PutIDPProviders(event.Sequence, event.CreationDate, providers...)
return i.view.PutIDPProviders(event, providers...)
default:
return i.view.ProcessedIDPProviderSequence(event.Sequence, event.CreationDate)
return i.view.ProcessedIDPProviderSequence(event)
}
if err != nil {
return err
}
return i.view.PutIDPProvider(provider, provider.Sequence, event.CreationDate)
return i.view.PutIDPProvider(provider, event)
}
func (i *IDPProvider) fillData(provider *iam_view_model.IDPProviderView) (err error) {
@ -114,7 +155,7 @@ func (i *IDPProvider) fillConfigData(provider *iam_view_model.IDPProviderView, c
provider.IDPState = int32(config.State)
}
func (i *IDPProvider) OnError(event *models.Event, err error) error {
func (i *IDPProvider) OnError(event *es_models.Event, err error) error {
logging.LogWithFields("SPOOL-Msj8c", "id", event.AggregateID).WithError(err).Warn("something went wrong in idp provider handler")
return spooler.HandleError(event, err, i.view.GetLatestIDPProviderFailedEvent, i.view.ProcessedIDPProviderFailedEvent, i.view.ProcessedIDPProviderSequence, i.errorCountUntilSkip)
}

View File

@ -2,37 +2,69 @@ package handler
import (
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/eventstore"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/eventstore/query"
"github.com/caos/zitadel/internal/eventstore/spooler"
"github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
)
type LabelPolicy struct {
handler
}
const (
labelPolicyTable = "adminapi.label_policies"
)
type LabelPolicy struct {
handler
subscription *eventstore.Subscription
}
func newLabelPolicy(handler handler) *LabelPolicy {
h := &LabelPolicy{
handler: handler,
}
h.subscribe()
return h
}
func (p *LabelPolicy) subscribe() {
p.subscription = p.es.Subscribe(p.AggregateTypes()...)
go func() {
for event := range p.subscription.Events {
query.ReduceEvent(p, event)
}
}()
}
func (p *LabelPolicy) ViewModel() string {
return labelPolicyTable
}
func (p *LabelPolicy) EventQuery() (*models.SearchQuery, error) {
sequence, err := p.view.GetLatestLabelPolicySequence()
func (p *LabelPolicy) AggregateTypes() []es_models.AggregateType {
return []es_models.AggregateType{model.IAMAggregate}
}
func (p *LabelPolicy) EventQuery() (*es_models.SearchQuery, error) {
sequence, err := p.view.GetLatestLabelPolicySequence("")
if err != nil {
return nil, err
}
return es_models.NewSearchQuery().
AggregateTypeFilter(model.IAMAggregate).
AggregateTypeFilter(p.AggregateTypes()...).
LatestSequenceFilter(sequence.CurrentSequence), nil
}
func (p *LabelPolicy) Reduce(event *models.Event) (err error) {
func (p *LabelPolicy) CurrentSequence(event *es_models.Event) (uint64, error) {
sequence, err := p.view.GetLatestLabelPolicySequence(string(event.AggregateType))
if err != nil {
return 0, err
}
return sequence.CurrentSequence, nil
}
func (p *LabelPolicy) Reduce(event *es_models.Event) (err error) {
switch event.AggregateType {
case model.IAMAggregate:
err = p.processLabelPolicy(event)
@ -40,7 +72,7 @@ func (p *LabelPolicy) Reduce(event *models.Event) (err error) {
return err
}
func (p *LabelPolicy) processLabelPolicy(event *models.Event) (err error) {
func (p *LabelPolicy) processLabelPolicy(event *es_models.Event) (err error) {
policy := new(iam_model.LabelPolicyView)
switch event.Type {
case model.LabelPolicyAdded:
@ -52,15 +84,15 @@ func (p *LabelPolicy) processLabelPolicy(event *models.Event) (err error) {
}
err = policy.AppendEvent(event)
default:
return p.view.ProcessedLabelPolicySequence(event.Sequence, event.CreationDate)
return p.view.ProcessedLabelPolicySequence(event)
}
if err != nil {
return err
}
return p.view.PutLabelPolicy(policy, policy.Sequence, event.CreationDate)
return p.view.PutLabelPolicy(policy, event)
}
func (p *LabelPolicy) OnError(event *models.Event, err error) error {
func (p *LabelPolicy) OnError(event *es_models.Event, err error) error {
logging.LogWithFields("SPOOL-Wj8sf", "id", event.AggregateID).WithError(err).Warn("something went wrong in label policy handler")
return spooler.HandleError(event, err, p.view.GetLatestLabelPolicyFailedEvent, p.view.ProcessedLabelPolicyFailedEvent, p.view.ProcessedLabelPolicySequence, p.errorCountUntilSkip)
}

View File

@ -2,36 +2,69 @@ package handler
import (
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/models"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/eventstore/query"
"github.com/caos/zitadel/internal/eventstore/spooler"
"github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
)
type LoginPolicy struct {
handler
}
const (
loginPolicyTable = "adminapi.login_policies"
)
type LoginPolicy struct {
handler
subscription *eventstore.Subscription
}
func newLoginPolicy(handler handler) *LoginPolicy {
h := &LoginPolicy{
handler: handler,
}
h.subscribe()
return h
}
func (p *LoginPolicy) subscribe() {
p.subscription = p.es.Subscribe(p.AggregateTypes()...)
go func() {
for event := range p.subscription.Events {
query.ReduceEvent(p, event)
}
}()
}
func (p *LoginPolicy) ViewModel() string {
return loginPolicyTable
}
func (p *LoginPolicy) AggregateTypes() []models.AggregateType {
return []models.AggregateType{model.IAMAggregate}
}
func (p *LoginPolicy) EventQuery() (*models.SearchQuery, error) {
sequence, err := p.view.GetLatestLoginPolicySequence()
sequence, err := p.view.GetLatestLoginPolicySequence("")
if err != nil {
return nil, err
}
return es_models.NewSearchQuery().
AggregateTypeFilter(model.IAMAggregate).
AggregateTypeFilter(p.AggregateTypes()...).
LatestSequenceFilter(sequence.CurrentSequence), nil
}
func (p *LoginPolicy) CurrentSequence(event *models.Event) (uint64, error) {
sequence, err := p.view.GetLatestLoginPolicySequence(string(event.AggregateType))
if err != nil {
return 0, err
}
return sequence.CurrentSequence, nil
}
func (p *LoginPolicy) Reduce(event *models.Event) (err error) {
switch event.AggregateType {
case model.IAMAggregate:
@ -56,12 +89,12 @@ func (p *LoginPolicy) processLoginPolicy(event *models.Event) (err error) {
}
err = policy.AppendEvent(event)
default:
return p.view.ProcessedLoginPolicySequence(event.Sequence, event.CreationDate)
return p.view.ProcessedLoginPolicySequence(event)
}
if err != nil {
return err
}
return p.view.PutLoginPolicy(policy, policy.Sequence, event.CreationDate)
return p.view.PutLoginPolicy(policy, event)
}
func (p *LoginPolicy) OnError(event *models.Event, err error) error {

View File

@ -3,33 +3,67 @@ package handler
import (
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/eventstore/query"
"github.com/caos/zitadel/internal/eventstore/spooler"
"github.com/caos/zitadel/internal/org/repository/eventsourcing"
"github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
org_model "github.com/caos/zitadel/internal/org/repository/view/model"
)
type Org struct {
handler
}
const (
orgTable = "adminapi.orgs"
)
type Org struct {
handler
subscription *eventstore.Subscription
}
func newOrg(handler handler) *Org {
h := &Org{
handler: handler,
}
h.subscribe()
return h
}
func (o *Org) subscribe() {
o.subscription = o.es.Subscribe(o.AggregateTypes()...)
go func() {
for event := range o.subscription.Events {
query.ReduceEvent(o, event)
}
}()
}
func (o *Org) ViewModel() string {
return orgTable
}
func (o *Org) AggregateTypes() []es_models.AggregateType {
return []es_models.AggregateType{model.OrgAggregate}
}
func (o *Org) EventQuery() (*es_models.SearchQuery, error) {
sequence, err := o.view.GetLatestOrgSequence()
sequence, err := o.view.GetLatestOrgSequence("")
if err != nil {
return nil, err
}
return eventsourcing.OrgQuery(sequence.CurrentSequence), nil
}
func (o *Org) CurrentSequence(event *es_models.Event) (uint64, error) {
sequence, err := o.view.GetLatestOrgSequence(string(event.AggregateType))
if err != nil {
return 0, err
}
return sequence.CurrentSequence, nil
}
func (o *Org) Reduce(event *es_models.Event) error {
org := new(org_model.OrgView)
@ -53,10 +87,10 @@ func (o *Org) Reduce(event *es_models.Event) error {
return err
}
default:
return o.view.ProcessedOrgSequence(event.Sequence, event.CreationDate)
return o.view.ProcessedOrgSequence(event)
}
return o.view.PutOrg(org, event.CreationDate)
return o.view.PutOrg(org, event)
}
func (o *Org) OnError(event *es_models.Event, spoolerErr error) error {

View File

@ -2,38 +2,70 @@ package handler
import (
"github.com/caos/logging"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/eventstore"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/eventstore/query"
"github.com/caos/zitadel/internal/eventstore/spooler"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
"github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
)
type OrgIAMPolicy struct {
handler
}
const (
orgIAMPolicyTable = "adminapi.org_iam_policies"
)
type OrgIAMPolicy struct {
handler
subscription *eventstore.Subscription
}
func newOrgIAMPolicy(handler handler) *OrgIAMPolicy {
h := &OrgIAMPolicy{
handler: handler,
}
h.subscribe()
return h
}
func (p *OrgIAMPolicy) subscribe() {
p.subscription = p.es.Subscribe(p.AggregateTypes()...)
go func() {
for event := range p.subscription.Events {
query.ReduceEvent(p, event)
}
}()
}
func (p *OrgIAMPolicy) ViewModel() string {
return orgIAMPolicyTable
}
func (p *OrgIAMPolicy) EventQuery() (*models.SearchQuery, error) {
sequence, err := p.view.GetLatestOrgIAMPolicySequence()
func (p *OrgIAMPolicy) AggregateTypes() []es_models.AggregateType {
return []es_models.AggregateType{model.OrgAggregate, iam_es_model.IAMAggregate}
}
func (p *OrgIAMPolicy) EventQuery() (*es_models.SearchQuery, error) {
sequence, err := p.view.GetLatestOrgIAMPolicySequence("")
if err != nil {
return nil, err
}
return es_models.NewSearchQuery().
AggregateTypeFilter(model.OrgAggregate, iam_es_model.IAMAggregate).
AggregateTypeFilter(p.AggregateTypes()...).
LatestSequenceFilter(sequence.CurrentSequence), nil
}
func (p *OrgIAMPolicy) Reduce(event *models.Event) (err error) {
func (p *OrgIAMPolicy) CurrentSequence(event *es_models.Event) (uint64, error) {
sequence, err := p.view.GetLatestOrgIAMPolicySequence(string(event.AggregateType))
if err != nil {
return 0, err
}
return sequence.CurrentSequence, nil
}
func (p *OrgIAMPolicy) Reduce(event *es_models.Event) (err error) {
switch event.AggregateType {
case model.OrgAggregate, iam_es_model.IAMAggregate:
err = p.processOrgIAMPolicy(event)
@ -41,7 +73,7 @@ func (p *OrgIAMPolicy) Reduce(event *models.Event) (err error) {
return err
}
func (p *OrgIAMPolicy) processOrgIAMPolicy(event *models.Event) (err error) {
func (p *OrgIAMPolicy) processOrgIAMPolicy(event *es_models.Event) (err error) {
policy := new(iam_model.OrgIAMPolicyView)
switch event.Type {
case iam_es_model.OrgIAMPolicyAdded, model.OrgIAMPolicyAdded:
@ -53,17 +85,17 @@ func (p *OrgIAMPolicy) processOrgIAMPolicy(event *models.Event) (err error) {
}
err = policy.AppendEvent(event)
case model.OrgIAMPolicyRemoved:
return p.view.DeleteOrgIAMPolicy(event.AggregateID, event.Sequence, event.CreationDate)
return p.view.DeleteOrgIAMPolicy(event.AggregateID, event)
default:
return p.view.ProcessedOrgIAMPolicySequence(event.Sequence, event.CreationDate)
return p.view.ProcessedOrgIAMPolicySequence(event)
}
if err != nil {
return err
}
return p.view.PutOrgIAMPolicy(policy, policy.Sequence, event.CreationDate)
return p.view.PutOrgIAMPolicy(policy, event)
}
func (p *OrgIAMPolicy) OnError(event *models.Event, err error) error {
func (p *OrgIAMPolicy) OnError(event *es_models.Event, err error) error {
logging.LogWithFields("SPOOL-Wm8fs", "id", event.AggregateID).WithError(err).Warn("something went wrong in orgIAM policy handler")
return spooler.HandleError(event, err, p.view.GetLatestOrgIAMPolicyFailedEvent, p.view.ProcessedOrgIAMPolicyFailedEvent, p.view.ProcessedOrgIAMPolicySequence, p.errorCountUntilSkip)
}

View File

@ -2,34 +2,68 @@ package handler
import (
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
"github.com/caos/zitadel/internal/eventstore/models"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/eventstore/query"
"github.com/caos/zitadel/internal/eventstore/spooler"
iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
"github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
)
type PasswordAgePolicy struct {
handler
}
const (
passwordAgePolicyTable = "adminapi.password_age_policies"
)
type PasswordAgePolicy struct {
handler
subscription *eventstore.Subscription
}
func newPasswordAgePolicy(handler handler) *PasswordAgePolicy {
h := &PasswordAgePolicy{
handler: handler,
}
h.subscribe()
return h
}
func (p *PasswordAgePolicy) subscribe() {
p.subscription = p.es.Subscribe(p.AggregateTypes()...)
go func() {
for event := range p.subscription.Events {
query.ReduceEvent(p, event)
}
}()
}
func (p *PasswordAgePolicy) ViewModel() string {
return passwordAgePolicyTable
}
func (p *PasswordAgePolicy) AggregateTypes() []models.AggregateType {
return []models.AggregateType{model.OrgAggregate, iam_es_model.IAMAggregate}
}
func (p *PasswordAgePolicy) CurrentSequence(event *models.Event) (uint64, error) {
sequence, err := p.view.GetLatestPasswordAgePolicySequence(string(event.AggregateType))
if err != nil {
return 0, err
}
return sequence.CurrentSequence, nil
}
func (p *PasswordAgePolicy) EventQuery() (*models.SearchQuery, error) {
sequence, err := p.view.GetLatestPasswordAgePolicySequence()
sequence, err := p.view.GetLatestPasswordAgePolicySequence("")
if err != nil {
return nil, err
}
return es_models.NewSearchQuery().
AggregateTypeFilter(model.OrgAggregate, iam_es_model.IAMAggregate).
AggregateTypeFilter(p.AggregateTypes()...).
LatestSequenceFilter(sequence.CurrentSequence), nil
}
@ -53,14 +87,14 @@ func (p *PasswordAgePolicy) processPasswordAgePolicy(event *models.Event) (err e
}
err = policy.AppendEvent(event)
case model.PasswordAgePolicyRemoved:
return p.view.DeletePasswordAgePolicy(event.AggregateID, event.Sequence, event.CreationDate)
return p.view.DeletePasswordAgePolicy(event.AggregateID, event)
default:
return p.view.ProcessedPasswordAgePolicySequence(event.Sequence, event.CreationDate)
return p.view.ProcessedPasswordAgePolicySequence(event)
}
if err != nil {
return err
}
return p.view.PutPasswordAgePolicy(policy, policy.Sequence, event.CreationDate)
return p.view.PutPasswordAgePolicy(policy, event)
}
func (p *PasswordAgePolicy) OnError(event *models.Event, err error) error {

View File

@ -2,34 +2,68 @@ package handler
import (
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
"github.com/caos/zitadel/internal/eventstore/models"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/eventstore/query"
"github.com/caos/zitadel/internal/eventstore/spooler"
iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
"github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
)
type PasswordComplexityPolicy struct {
handler
}
const (
passwordComplexityPolicyTable = "adminapi.password_complexity_policies"
)
type PasswordComplexityPolicy struct {
handler
subscription *eventstore.Subscription
}
func newPasswordComplexityPolicy(handler handler) *PasswordComplexityPolicy {
h := &PasswordComplexityPolicy{
handler: handler,
}
h.subscribe()
return h
}
func (p *PasswordComplexityPolicy) subscribe() {
p.subscription = p.es.Subscribe(p.AggregateTypes()...)
go func() {
for event := range p.subscription.Events {
query.ReduceEvent(p, event)
}
}()
}
func (p *PasswordComplexityPolicy) ViewModel() string {
return passwordComplexityPolicyTable
}
func (p *PasswordComplexityPolicy) AggregateTypes() []models.AggregateType {
return []models.AggregateType{model.OrgAggregate, iam_es_model.IAMAggregate}
}
func (p *PasswordComplexityPolicy) CurrentSequence(event *models.Event) (uint64, error) {
sequence, err := p.view.GetLatestPasswordComplexityPolicySequence(string(event.AggregateType))
if err != nil {
return 0, err
}
return sequence.CurrentSequence, nil
}
func (p *PasswordComplexityPolicy) EventQuery() (*models.SearchQuery, error) {
sequence, err := p.view.GetLatestPasswordComplexityPolicySequence()
sequence, err := p.view.GetLatestPasswordComplexityPolicySequence("")
if err != nil {
return nil, err
}
return es_models.NewSearchQuery().
AggregateTypeFilter(model.OrgAggregate, iam_es_model.IAMAggregate).
AggregateTypeFilter(p.AggregateTypes()...).
LatestSequenceFilter(sequence.CurrentSequence), nil
}
@ -53,14 +87,14 @@ func (p *PasswordComplexityPolicy) processPasswordComplexityPolicy(event *models
}
err = policy.AppendEvent(event)
case model.PasswordComplexityPolicyRemoved:
return p.view.DeletePasswordComplexityPolicy(event.AggregateID, event.Sequence, event.CreationDate)
return p.view.DeletePasswordComplexityPolicy(event.AggregateID, event)
default:
return p.view.ProcessedPasswordComplexityPolicySequence(event.Sequence, event.CreationDate)
return p.view.ProcessedPasswordComplexityPolicySequence(event)
}
if err != nil {
return err
}
return p.view.PutPasswordComplexityPolicy(policy, policy.Sequence, event.CreationDate)
return p.view.PutPasswordComplexityPolicy(policy, event)
}
func (p *PasswordComplexityPolicy) OnError(event *models.Event, err error) error {

View File

@ -2,34 +2,68 @@ package handler
import (
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
"github.com/caos/zitadel/internal/eventstore/models"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/eventstore/query"
"github.com/caos/zitadel/internal/eventstore/spooler"
iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
"github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
)
type PasswordLockoutPolicy struct {
handler
}
const (
passwordLockoutPolicyTable = "adminapi.password_lockout_policies"
)
type PasswordLockoutPolicy struct {
handler
subscription *eventstore.Subscription
}
func newPasswordLockoutPolicy(handler handler) *PasswordLockoutPolicy {
h := &PasswordLockoutPolicy{
handler: handler,
}
h.subscribe()
return h
}
func (p *PasswordLockoutPolicy) subscribe() {
p.subscription = p.es.Subscribe(p.AggregateTypes()...)
go func() {
for event := range p.subscription.Events {
query.ReduceEvent(p, event)
}
}()
}
func (p *PasswordLockoutPolicy) ViewModel() string {
return passwordLockoutPolicyTable
}
func (p *PasswordLockoutPolicy) AggregateTypes() []models.AggregateType {
return []models.AggregateType{model.OrgAggregate, iam_es_model.IAMAggregate}
}
func (p *PasswordLockoutPolicy) CurrentSequence(event *models.Event) (uint64, error) {
sequence, err := p.view.GetLatestPasswordLockoutPolicySequence(string(event.AggregateType))
if err != nil {
return 0, err
}
return sequence.CurrentSequence, nil
}
func (p *PasswordLockoutPolicy) EventQuery() (*models.SearchQuery, error) {
sequence, err := p.view.GetLatestPasswordLockoutPolicySequence()
sequence, err := p.view.GetLatestPasswordLockoutPolicySequence("")
if err != nil {
return nil, err
}
return es_models.NewSearchQuery().
AggregateTypeFilter(model.OrgAggregate, iam_es_model.IAMAggregate).
AggregateTypeFilter(p.AggregateTypes()...).
LatestSequenceFilter(sequence.CurrentSequence), nil
}
@ -53,14 +87,14 @@ func (p *PasswordLockoutPolicy) processPasswordLockoutPolicy(event *models.Event
}
err = policy.AppendEvent(event)
case model.PasswordLockoutPolicyRemoved:
return p.view.DeletePasswordLockoutPolicy(event.AggregateID, event.Sequence, event.CreationDate)
return p.view.DeletePasswordLockoutPolicy(event.AggregateID, event)
default:
return p.view.ProcessedPasswordLockoutPolicySequence(event.Sequence, event.CreationDate)
return p.view.ProcessedPasswordLockoutPolicySequence(event)
}
if err != nil {
return err
}
return p.view.PutPasswordLockoutPolicy(policy, policy.Sequence, event.CreationDate)
return p.view.PutPasswordLockoutPolicy(policy, event)
}
func (p *PasswordLockoutPolicy) OnError(event *models.Event, err error) error {

View File

@ -2,6 +2,7 @@ package handler
import (
"context"
"github.com/caos/zitadel/internal/config/systemdefaults"
iam_es "github.com/caos/zitadel/internal/iam/repository/eventsourcing"
@ -10,6 +11,7 @@ import (
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/models"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/eventstore/query"
"github.com/caos/zitadel/internal/eventstore/spooler"
org_model "github.com/caos/zitadel/internal/org/model"
org_events "github.com/caos/zitadel/internal/org/repository/eventsourcing"
@ -18,29 +20,69 @@ import (
view_model "github.com/caos/zitadel/internal/user/repository/view/model"
)
const (
userTable = "adminapi.users"
)
type User struct {
handler
eventstore eventstore.Eventstore
orgEvents *org_events.OrgEventstore
iamEvents *iam_es.IAMEventstore
systemDefaults systemdefaults.SystemDefaults
subscription *eventstore.Subscription
}
const (
userTable = "adminapi.users"
)
func newUser(
handler handler,
orgEvents *org_events.OrgEventstore,
iamEvents *iam_es.IAMEventstore,
systemDefaults systemdefaults.SystemDefaults,
) *User {
h := &User{
handler: handler,
orgEvents: orgEvents,
iamEvents: iamEvents,
systemDefaults: systemDefaults,
}
h.subscribe()
return h
}
func (u *User) subscribe() {
u.subscription = u.es.Subscribe(u.AggregateTypes()...)
go func() {
for event := range u.subscription.Events {
query.ReduceEvent(u, event)
}
}()
}
func (u *User) ViewModel() string {
return userTable
}
func (u *User) AggregateTypes() []models.AggregateType {
return []models.AggregateType{es_model.UserAggregate, org_es_model.OrgAggregate}
}
func (u *User) CurrentSequence(event *models.Event) (uint64, error) {
sequence, err := u.view.GetLatestUserSequence(string(event.AggregateType))
if err != nil {
return 0, err
}
return sequence.CurrentSequence, nil
}
func (u *User) EventQuery() (*models.SearchQuery, error) {
sequence, err := u.view.GetLatestUserSequence()
sequence, err := u.view.GetLatestUserSequence("")
if err != nil {
return nil, err
}
return es_models.NewSearchQuery().
AggregateTypeFilter(es_model.UserAggregate, org_es_model.OrgAggregate).
AggregateTypeFilter(u.AggregateTypes()...).
LatestSequenceFilter(sequence.CurrentSequence), nil
}
@ -116,14 +158,14 @@ func (u *User) ProcessUser(event *models.Event) (err error) {
}
err = u.fillLoginNames(user)
case es_model.UserRemoved:
return u.view.DeleteUser(event.AggregateID, event.Sequence, event.CreationDate)
return u.view.DeleteUser(event.AggregateID, event)
default:
return u.view.ProcessedUserSequence(event.Sequence, event.CreationDate)
return u.view.ProcessedUserSequence(event)
}
if err != nil {
return err
}
return u.view.PutUser(user, user.Sequence, event.CreationDate)
return u.view.PutUser(user, event)
}
func (u *User) ProcessOrg(event *models.Event) (err error) {
@ -137,7 +179,7 @@ func (u *User) ProcessOrg(event *models.Event) (err error) {
case org_es_model.OrgDomainPrimarySet:
return u.fillPreferredLoginNamesOnOrgUsers(event)
default:
return u.view.ProcessedUserSequence(event.Sequence, event.CreationDate)
return u.view.ProcessedUserSequence(event)
}
}
@ -160,7 +202,7 @@ func (u *User) fillLoginNamesOnOrgUsers(event *models.Event) error {
for _, user := range users {
user.SetLoginNames(policy, org.Domains)
}
return u.view.PutUsers(users, event.Sequence, event.CreationDate)
return u.view.PutUsers(users, event)
}
func (u *User) fillPreferredLoginNamesOnOrgUsers(event *models.Event) error {
@ -185,7 +227,7 @@ func (u *User) fillPreferredLoginNamesOnOrgUsers(event *models.Event) error {
for _, user := range users {
user.PreferredLoginName = user.GenerateLoginName(org.GetPrimaryDomain().Domain, policy.UserLoginMustBeDomain)
}
return u.view.PutUsers(users, event.Sequence, event.CreationDate)
return u.view.PutUsers(users, event)
}
func (u *User) fillLoginNames(user *view_model.UserView) (err error) {

View File

@ -2,9 +2,11 @@ package handler
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/config/systemdefaults"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/iam/repository/eventsourcing"
iam_view_model "github.com/caos/zitadel/internal/iam/repository/view/model"
org_es "github.com/caos/zitadel/internal/org/repository/eventsourcing"
@ -14,33 +16,74 @@ import (
"github.com/caos/zitadel/internal/eventstore/models"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/eventstore/query"
"github.com/caos/zitadel/internal/eventstore/spooler"
iam_model "github.com/caos/zitadel/internal/iam/model"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
)
const (
externalIDPTable = "adminapi.user_external_idps"
)
type ExternalIDP struct {
handler
systemDefaults systemdefaults.SystemDefaults
iamEvents *eventsourcing.IAMEventstore
orgEvents *org_es.OrgEventstore
subscription *eventstore.Subscription
}
const (
externalIDPTable = "adminapi.user_external_idps"
)
func newExternalIDP(
handler handler,
systemDefaults systemdefaults.SystemDefaults,
iamEvents *eventsourcing.IAMEventstore,
orgEvents *org_es.OrgEventstore,
) *ExternalIDP {
h := &ExternalIDP{
handler: handler,
systemDefaults: systemDefaults,
iamEvents: iamEvents,
orgEvents: orgEvents,
}
h.subscribe()
return h
}
func (i *ExternalIDP) subscribe() {
i.subscription = i.es.Subscribe(i.AggregateTypes()...)
go func() {
for event := range i.subscription.Events {
query.ReduceEvent(i, event)
}
}()
}
func (i *ExternalIDP) ViewModel() string {
return externalIDPTable
}
func (i *ExternalIDP) AggregateTypes() []models.AggregateType {
return []models.AggregateType{model.UserAggregate, iam_es_model.IAMAggregate, org_es_model.OrgAggregate}
}
func (i *ExternalIDP) CurrentSequence(event *models.Event) (uint64, error) {
sequence, err := i.view.GetLatestExternalIDPSequence(string(event.AggregateType))
if err != nil {
return 0, err
}
return sequence.CurrentSequence, nil
}
func (i *ExternalIDP) EventQuery() (*models.SearchQuery, error) {
sequence, err := i.view.GetLatestExternalIDPSequence()
sequence, err := i.view.GetLatestExternalIDPSequence("")
if err != nil {
return nil, err
}
return es_models.NewSearchQuery().
AggregateTypeFilter(model.UserAggregate, iam_es_model.IAMAggregate, org_es_model.OrgAggregate).
AggregateTypeFilter(i.AggregateTypes()...).
LatestSequenceFilter(sequence.CurrentSequence), nil
}
@ -68,16 +111,16 @@ func (i *ExternalIDP) processUser(event *models.Event) (err error) {
if err != nil {
return err
}
return i.view.DeleteExternalIDP(externalIDP.ExternalUserID, externalIDP.IDPConfigID, event.Sequence, event.CreationDate)
return i.view.DeleteExternalIDP(externalIDP.ExternalUserID, externalIDP.IDPConfigID, event)
case model.UserRemoved:
return i.view.DeleteExternalIDPsByUserID(event.AggregateID, event.Sequence, event.CreationDate)
return i.view.DeleteExternalIDPsByUserID(event.AggregateID, event)
default:
return i.view.ProcessedExternalIDPSequence(event.Sequence, event.CreationDate)
return i.view.ProcessedExternalIDPSequence(event)
}
if err != nil {
return err
}
return i.view.PutExternalIDP(externalIDP, externalIDP.Sequence, event.CreationDate)
return i.view.PutExternalIDP(externalIDP, event)
}
func (i *ExternalIDP) processIdpConfig(event *models.Event) (err error) {
@ -105,11 +148,10 @@ func (i *ExternalIDP) processIdpConfig(event *models.Event) (err error) {
for _, provider := range exterinalIDPs {
i.fillConfigData(provider, config)
}
return i.view.PutExternalIDPs(event.Sequence, event.CreationDate, exterinalIDPs...)
return i.view.PutExternalIDPs(event, exterinalIDPs...)
default:
return i.view.ProcessedExternalIDPSequence(event.Sequence, event.CreationDate)
return i.view.ProcessedExternalIDPSequence(event)
}
return nil
}
func (i *ExternalIDP) fillData(externalIDP *usr_view_model.ExternalIDPView) error {

View File

@ -2,28 +2,18 @@ package spooler
import (
"database/sql"
"time"
es_locker "github.com/caos/zitadel/internal/eventstore/locker"
"time"
)
const (
lockTable = "adminapi.locks"
lockedUntilKey = "locked_until"
lockerIDKey = "locker_id"
objectTypeKey = "object_type"
lockTable = "adminapi.locks"
)
type locker struct {
dbClient *sql.DB
}
type lock struct {
LockerID string `gorm:"column:locker_id;primary_key"`
LockedUntil time.Time `gorm:"column:locked_until"`
ViewName string `gorm:"column:object_type;primary_key"`
}
func (l *locker) Renew(lockerID, viewModel string, waitTime time.Duration) error {
return es_locker.Renew(l.dbClient, lockTable, lockerID, viewModel, waitTime)
}

View File

@ -2,11 +2,11 @@ package view
import (
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
usr_model "github.com/caos/zitadel/internal/user/model"
"github.com/caos/zitadel/internal/user/repository/view"
"github.com/caos/zitadel/internal/user/repository/view/model"
global_view "github.com/caos/zitadel/internal/view/repository"
"time"
)
const (
@ -33,44 +33,44 @@ func (v *View) SearchExternalIDPs(request *usr_model.ExternalIDPSearchRequest) (
return view.SearchExternalIDPs(v.Db, externalIDPTable, request)
}
func (v *View) PutExternalIDP(externalIDP *model.ExternalIDPView, sequence uint64, eventTimestamp time.Time) error {
func (v *View) PutExternalIDP(externalIDP *model.ExternalIDPView, event *models.Event) error {
err := view.PutExternalIDP(v.Db, externalIDPTable, externalIDP)
if err != nil {
return err
}
return v.ProcessedExternalIDPSequence(sequence, eventTimestamp)
return v.ProcessedExternalIDPSequence(event)
}
func (v *View) PutExternalIDPs(sequence uint64, eventTimestamp time.Time, externalIDPs ...*model.ExternalIDPView) error {
func (v *View) PutExternalIDPs(event *models.Event, externalIDPs ...*model.ExternalIDPView) error {
err := view.PutExternalIDPs(v.Db, externalIDPTable, externalIDPs...)
if err != nil {
return err
}
return v.ProcessedExternalIDPSequence(sequence, eventTimestamp)
return v.ProcessedExternalIDPSequence(event)
}
func (v *View) DeleteExternalIDP(externalUserID, idpConfigID string, eventSequence uint64, eventTimestamp time.Time) error {
func (v *View) DeleteExternalIDP(externalUserID, idpConfigID string, event *models.Event) error {
err := view.DeleteExternalIDP(v.Db, externalIDPTable, externalUserID, idpConfigID)
if err != nil && !errors.IsNotFound(err) {
return err
}
return v.ProcessedExternalIDPSequence(eventSequence, eventTimestamp)
return v.ProcessedExternalIDPSequence(event)
}
func (v *View) DeleteExternalIDPsByUserID(userID string, eventSequence uint64, eventTimestamp time.Time) error {
func (v *View) DeleteExternalIDPsByUserID(userID string, event *models.Event) error {
err := view.DeleteExternalIDPsByUserID(v.Db, externalIDPTable, userID)
if err != nil {
return err
}
return v.ProcessedExternalIDPSequence(eventSequence, eventTimestamp)
return v.ProcessedExternalIDPSequence(event)
}
func (v *View) GetLatestExternalIDPSequence() (*global_view.CurrentSequence, error) {
return v.latestSequence(externalIDPTable)
func (v *View) GetLatestExternalIDPSequence(aggregateType string) (*global_view.CurrentSequence, error) {
return v.latestSequence(externalIDPTable, aggregateType)
}
func (v *View) ProcessedExternalIDPSequence(eventSequence uint64, eventTimestamp time.Time) error {
return v.saveCurrentSequence(externalIDPTable, eventSequence, eventTimestamp)
func (v *View) ProcessedExternalIDPSequence(event *models.Event) error {
return v.saveCurrentSequence(externalIDPTable, event)
}
func (v *View) UpdateExternalIDPSpoolerRunTimestamp() error {

View File

@ -2,11 +2,11 @@ package view
import (
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
iam_model "github.com/caos/zitadel/internal/iam/model"
"github.com/caos/zitadel/internal/iam/repository/view"
"github.com/caos/zitadel/internal/iam/repository/view/model"
global_view "github.com/caos/zitadel/internal/view/repository"
"time"
)
const (
@ -25,44 +25,44 @@ func (v *View) IAMMembersByUserID(userID string) ([]*model.IAMMemberView, error)
return view.IAMMembersByUserID(v.Db, iamMemberTable, userID)
}
func (v *View) PutIAMMember(org *model.IAMMemberView, sequence uint64, eventTimestamp time.Time) error {
func (v *View) PutIAMMember(org *model.IAMMemberView, event *models.Event) error {
err := view.PutIAMMember(v.Db, iamMemberTable, org)
if err != nil {
return err
}
return v.ProcessedIAMMemberSequence(sequence, eventTimestamp)
return v.ProcessedIAMMemberSequence(event)
}
func (v *View) PutIAMMembers(members []*model.IAMMemberView, sequence uint64, eventTimestamp time.Time) error {
func (v *View) PutIAMMembers(members []*model.IAMMemberView, event *models.Event) error {
err := view.PutIAMMembers(v.Db, iamMemberTable, members...)
if err != nil {
return err
}
return v.ProcessedIAMMemberSequence(sequence, eventTimestamp)
return v.ProcessedIAMMemberSequence(event)
}
func (v *View) DeleteIAMMember(iamID, userID string, eventSequence uint64, eventTimestamp time.Time) error {
func (v *View) DeleteIAMMember(iamID, userID string, event *models.Event) error {
err := view.DeleteIAMMember(v.Db, iamMemberTable, iamID, userID)
if err != nil && !errors.IsNotFound(err) {
return err
}
return v.ProcessedIAMMemberSequence(eventSequence, eventTimestamp)
return v.ProcessedIAMMemberSequence(event)
}
func (v *View) DeleteIAMMembersByUserID(userID string, eventSequence uint64, eventTimestamp time.Time) error {
func (v *View) DeleteIAMMembersByUserID(userID string, event *models.Event) error {
err := view.DeleteIAMMembersByUserID(v.Db, iamMemberTable, userID)
if err != nil {
return err
}
return v.ProcessedIAMMemberSequence(eventSequence, eventTimestamp)
return v.ProcessedIAMMemberSequence(event)
}
func (v *View) GetLatestIAMMemberSequence() (*global_view.CurrentSequence, error) {
return v.latestSequence(iamMemberTable)
func (v *View) GetLatestIAMMemberSequence(aggregateType string) (*global_view.CurrentSequence, error) {
return v.latestSequence(iamMemberTable, aggregateType)
}
func (v *View) ProcessedIAMMemberSequence(eventSequence uint64, eventTimestamp time.Time) error {
return v.saveCurrentSequence(iamMemberTable, eventSequence, eventTimestamp)
func (v *View) ProcessedIAMMemberSequence(event *models.Event) error {
return v.saveCurrentSequence(iamMemberTable, event)
}
func (v *View) UpdateIAMMemberSpoolerRunTimestamp() error {

View File

@ -2,11 +2,11 @@ package view
import (
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
iam_model "github.com/caos/zitadel/internal/iam/model"
"github.com/caos/zitadel/internal/iam/repository/view"
"github.com/caos/zitadel/internal/iam/repository/view/model"
global_view "github.com/caos/zitadel/internal/view/repository"
"time"
)
const (
@ -21,28 +21,28 @@ func (v *View) SearchIDPConfigs(request *iam_model.IDPConfigSearchRequest) ([]*m
return view.SearchIDPs(v.Db, idpConfigTable, request)
}
func (v *View) PutIDPConfig(idp *model.IDPConfigView, sequence uint64, eventTimestamp time.Time) error {
func (v *View) PutIDPConfig(idp *model.IDPConfigView, event *models.Event) error {
err := view.PutIDP(v.Db, idpConfigTable, idp)
if err != nil {
return err
}
return v.ProcessedIDPConfigSequence(sequence, eventTimestamp)
return v.ProcessedIDPConfigSequence(event)
}
func (v *View) DeleteIDPConfig(idpID string, eventSequence uint64, eventTimestamp time.Time) error {
func (v *View) DeleteIDPConfig(idpID string, event *models.Event) error {
err := view.DeleteIDP(v.Db, idpConfigTable, idpID)
if err != nil && !errors.IsNotFound(err) {
return err
}
return v.ProcessedIDPConfigSequence(eventSequence, eventTimestamp)
return v.ProcessedIDPConfigSequence(event)
}
func (v *View) GetLatestIDPConfigSequence() (*global_view.CurrentSequence, error) {
return v.latestSequence(idpConfigTable)
func (v *View) GetLatestIDPConfigSequence(aggregateType string) (*global_view.CurrentSequence, error) {
return v.latestSequence(idpConfigTable, aggregateType)
}
func (v *View) ProcessedIDPConfigSequence(eventSequence uint64, eventTimestamp time.Time) error {
return v.saveCurrentSequence(idpConfigTable, eventSequence, eventTimestamp)
func (v *View) ProcessedIDPConfigSequence(event *models.Event) error {
return v.saveCurrentSequence(idpConfigTable, event)
}
func (v *View) UpdateIDPConfigSpoolerRunTimestamp() error {

View File

@ -2,11 +2,11 @@ package view
import (
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
iam_model "github.com/caos/zitadel/internal/iam/model"
"github.com/caos/zitadel/internal/iam/repository/view"
"github.com/caos/zitadel/internal/iam/repository/view/model"
global_view "github.com/caos/zitadel/internal/view/repository"
"time"
)
const (
@ -25,36 +25,36 @@ func (v *View) SearchIDPProviders(request *iam_model.IDPProviderSearchRequest) (
return view.SearchIDPProviders(v.Db, idpProviderTable, request)
}
func (v *View) PutIDPProvider(provider *model.IDPProviderView, sequence uint64, eventTimestamp time.Time) error {
func (v *View) PutIDPProvider(provider *model.IDPProviderView, event *models.Event) error {
err := view.PutIDPProvider(v.Db, idpProviderTable, provider)
if err != nil {
return err
}
return v.ProcessedIDPProviderSequence(sequence, eventTimestamp)
return v.ProcessedIDPProviderSequence(event)
}
func (v *View) PutIDPProviders(sequence uint64, eventTimestamp time.Time, providers ...*model.IDPProviderView) error {
func (v *View) PutIDPProviders(event *models.Event, providers ...*model.IDPProviderView) error {
err := view.PutIDPProviders(v.Db, idpProviderTable, providers...)
if err != nil {
return err
}
return v.ProcessedIDPProviderSequence(sequence, eventTimestamp)
return v.ProcessedIDPProviderSequence(event)
}
func (v *View) DeleteIDPProvider(aggregateID, idpConfigID string, eventSequence uint64, eventTimestamp time.Time) error {
func (v *View) DeleteIDPProvider(aggregateID, idpConfigID string, event *models.Event) error {
err := view.DeleteIDPProvider(v.Db, idpProviderTable, aggregateID, idpConfigID)
if err != nil && !errors.IsNotFound(err) {
return err
}
return v.ProcessedIDPProviderSequence(eventSequence, eventTimestamp)
return v.ProcessedIDPProviderSequence(event)
}
func (v *View) GetLatestIDPProviderSequence() (*global_view.CurrentSequence, error) {
return v.latestSequence(idpProviderTable)
func (v *View) GetLatestIDPProviderSequence(aggregateType string) (*global_view.CurrentSequence, error) {
return v.latestSequence(idpProviderTable, aggregateType)
}
func (v *View) ProcessedIDPProviderSequence(eventSequence uint64, eventTimestamp time.Time) error {
return v.saveCurrentSequence(idpProviderTable, eventSequence, eventTimestamp)
func (v *View) ProcessedIDPProviderSequence(event *models.Event) error {
return v.saveCurrentSequence(idpProviderTable, event)
}
func (v *View) UpdateIDPProviderSpoolerRunTimestamp() error {

View File

@ -1,10 +1,10 @@
package view
import (
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/iam/repository/view"
"github.com/caos/zitadel/internal/iam/repository/view/model"
global_view "github.com/caos/zitadel/internal/view/repository"
"time"
)
const (
@ -15,20 +15,20 @@ func (v *View) LabelPolicyByAggregateID(aggregateID string) (*model.LabelPolicyV
return view.GetLabelPolicyByAggregateID(v.Db, labelPolicyTable, aggregateID)
}
func (v *View) PutLabelPolicy(policy *model.LabelPolicyView, sequence uint64, eventTimestamp time.Time) error {
func (v *View) PutLabelPolicy(policy *model.LabelPolicyView, event *models.Event) error {
err := view.PutLabelPolicy(v.Db, labelPolicyTable, policy)
if err != nil {
return err
}
return v.ProcessedLabelPolicySequence(sequence, eventTimestamp)
return v.ProcessedLabelPolicySequence(event)
}
func (v *View) GetLatestLabelPolicySequence() (*global_view.CurrentSequence, error) {
return v.latestSequence(labelPolicyTable)
func (v *View) GetLatestLabelPolicySequence(aggregateType string) (*global_view.CurrentSequence, error) {
return v.latestSequence(labelPolicyTable, aggregateType)
}
func (v *View) ProcessedLabelPolicySequence(eventSequence uint64, eventTimestamp time.Time) error {
return v.saveCurrentSequence(labelPolicyTable, eventSequence, eventTimestamp)
func (v *View) ProcessedLabelPolicySequence(event *models.Event) error {
return v.saveCurrentSequence(labelPolicyTable, event)
}
func (v *View) UpdateLabelPolicySpoolerRunTimestamp() error {

View File

@ -2,10 +2,10 @@ package view
import (
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/iam/repository/view"
"github.com/caos/zitadel/internal/iam/repository/view/model"
global_view "github.com/caos/zitadel/internal/view/repository"
"time"
)
const (
@ -16,28 +16,28 @@ func (v *View) LoginPolicyByAggregateID(aggregateID string) (*model.LoginPolicyV
return view.GetLoginPolicyByAggregateID(v.Db, loginPolicyTable, aggregateID)
}
func (v *View) PutLoginPolicy(policy *model.LoginPolicyView, sequence uint64, eventTimestamp time.Time) error {
func (v *View) PutLoginPolicy(policy *model.LoginPolicyView, event *models.Event) error {
err := view.PutLoginPolicy(v.Db, loginPolicyTable, policy)
if err != nil {
return err
}
return v.ProcessedLoginPolicySequence(sequence, eventTimestamp)
return v.ProcessedLoginPolicySequence(event)
}
func (v *View) DeleteLoginPolicy(aggregateID string, eventSequence uint64, eventTimestamp time.Time) error {
func (v *View) DeleteLoginPolicy(aggregateID string, event *models.Event) error {
err := view.DeleteLoginPolicy(v.Db, loginPolicyTable, aggregateID)
if err != nil && !errors.IsNotFound(err) {
return err
}
return v.ProcessedLoginPolicySequence(eventSequence, eventTimestamp)
return v.ProcessedLoginPolicySequence(event)
}
func (v *View) GetLatestLoginPolicySequence() (*global_view.CurrentSequence, error) {
return v.latestSequence(loginPolicyTable)
func (v *View) GetLatestLoginPolicySequence(aggregateType string) (*global_view.CurrentSequence, error) {
return v.latestSequence(loginPolicyTable, aggregateType)
}
func (v *View) ProcessedLoginPolicySequence(eventSequence uint64, eventTimestamp time.Time) error {
return v.saveCurrentSequence(loginPolicyTable, eventSequence, eventTimestamp)
func (v *View) ProcessedLoginPolicySequence(event *models.Event) error {
return v.saveCurrentSequence(loginPolicyTable, event)
}
func (v *View) UpdateLoginPolicySpoolerRunTimestamp() error {

View File

@ -1,11 +1,11 @@
package view
import (
"github.com/caos/zitadel/internal/eventstore/models"
org_model "github.com/caos/zitadel/internal/org/model"
org_view "github.com/caos/zitadel/internal/org/repository/view"
"github.com/caos/zitadel/internal/org/repository/view/model"
"github.com/caos/zitadel/internal/view/repository"
"time"
)
const (
@ -20,12 +20,12 @@ func (v *View) SearchOrgs(query *org_model.OrgSearchRequest) ([]*model.OrgView,
return org_view.SearchOrgs(v.Db, orgTable, query)
}
func (v *View) PutOrg(org *model.OrgView, eventTimestamp time.Time) error {
func (v *View) PutOrg(org *model.OrgView, event *models.Event) error {
err := org_view.PutOrg(v.Db, orgTable, org)
if err != nil {
return err
}
return v.ProcessedOrgSequence(org.Sequence, eventTimestamp)
return v.ProcessedOrgSequence(event)
}
func (v *View) GetLatestOrgFailedEvent(sequence uint64) (*repository.FailedEvent, error) {
@ -40,10 +40,10 @@ func (v *View) UpdateOrgSpoolerRunTimestamp() error {
return v.updateSpoolerRunSequence(orgTable)
}
func (v *View) GetLatestOrgSequence() (*repository.CurrentSequence, error) {
return v.latestSequence(orgTable)
func (v *View) GetLatestOrgSequence(aggregateType string) (*repository.CurrentSequence, error) {
return v.latestSequence(orgTable, aggregateType)
}
func (v *View) ProcessedOrgSequence(eventSequence uint64, eventTimestamp time.Time) error {
return v.saveCurrentSequence(orgTable, eventSequence, eventTimestamp)
func (v *View) ProcessedOrgSequence(event *models.Event) error {
return v.saveCurrentSequence(orgTable, event)
}

View File

@ -2,10 +2,10 @@ package view
import (
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/iam/repository/view"
"github.com/caos/zitadel/internal/iam/repository/view/model"
global_view "github.com/caos/zitadel/internal/view/repository"
"time"
)
const (
@ -16,28 +16,28 @@ func (v *View) OrgIAMPolicyByAggregateID(aggregateID string) (*model.OrgIAMPolic
return view.GetOrgIAMPolicyByAggregateID(v.Db, orgIAMPolicyTable, aggregateID)
}
func (v *View) PutOrgIAMPolicy(policy *model.OrgIAMPolicyView, sequence uint64, eventTimestamp time.Time) error {
func (v *View) PutOrgIAMPolicy(policy *model.OrgIAMPolicyView, event *models.Event) error {
err := view.PutOrgIAMPolicy(v.Db, orgIAMPolicyTable, policy)
if err != nil {
return err
}
return v.ProcessedOrgIAMPolicySequence(sequence, eventTimestamp)
return v.ProcessedOrgIAMPolicySequence(event)
}
func (v *View) DeleteOrgIAMPolicy(aggregateID string, eventSequence uint64, eventTimestamp time.Time) error {
func (v *View) DeleteOrgIAMPolicy(aggregateID string, event *models.Event) error {
err := view.DeleteOrgIAMPolicy(v.Db, orgIAMPolicyTable, aggregateID)
if err != nil && !errors.IsNotFound(err) {
return err
}
return v.ProcessedOrgIAMPolicySequence(eventSequence, eventTimestamp)
return v.ProcessedOrgIAMPolicySequence(event)
}
func (v *View) GetLatestOrgIAMPolicySequence() (*global_view.CurrentSequence, error) {
return v.latestSequence(orgIAMPolicyTable)
func (v *View) GetLatestOrgIAMPolicySequence(aggregateType string) (*global_view.CurrentSequence, error) {
return v.latestSequence(orgIAMPolicyTable, aggregateType)
}
func (v *View) ProcessedOrgIAMPolicySequence(eventSequence uint64, eventTimestamp time.Time) error {
return v.saveCurrentSequence(orgIAMPolicyTable, eventSequence, eventTimestamp)
func (v *View) ProcessedOrgIAMPolicySequence(event *models.Event) error {
return v.saveCurrentSequence(orgIAMPolicyTable, event)
}
func (v *View) UpdateOrgIAMPolicySpoolerRunTimestamp() error {

View File

@ -2,10 +2,10 @@ package view
import (
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/iam/repository/view"
"github.com/caos/zitadel/internal/iam/repository/view/model"
global_view "github.com/caos/zitadel/internal/view/repository"
"time"
)
const (
@ -16,28 +16,28 @@ func (v *View) PasswordAgePolicyByAggregateID(aggregateID string) (*model.Passwo
return view.GetPasswordAgePolicyByAggregateID(v.Db, passwordAgePolicyTable, aggregateID)
}
func (v *View) PutPasswordAgePolicy(policy *model.PasswordAgePolicyView, sequence uint64, eventTimestamp time.Time) error {
func (v *View) PutPasswordAgePolicy(policy *model.PasswordAgePolicyView, event *models.Event) error {
err := view.PutPasswordAgePolicy(v.Db, passwordAgePolicyTable, policy)
if err != nil {
return err
}
return v.ProcessedPasswordAgePolicySequence(sequence, eventTimestamp)
return v.ProcessedPasswordAgePolicySequence(event)
}
func (v *View) DeletePasswordAgePolicy(aggregateID string, eventSequence uint64, eventTimestamp time.Time) error {
func (v *View) DeletePasswordAgePolicy(aggregateID string, event *models.Event) error {
err := view.DeletePasswordAgePolicy(v.Db, passwordAgePolicyTable, aggregateID)
if err != nil && !errors.IsNotFound(err) {
return err
}
return v.ProcessedPasswordAgePolicySequence(eventSequence, eventTimestamp)
return v.ProcessedPasswordAgePolicySequence(event)
}
func (v *View) GetLatestPasswordAgePolicySequence() (*global_view.CurrentSequence, error) {
return v.latestSequence(passwordAgePolicyTable)
func (v *View) GetLatestPasswordAgePolicySequence(aggregateType string) (*global_view.CurrentSequence, error) {
return v.latestSequence(passwordAgePolicyTable, aggregateType)
}
func (v *View) ProcessedPasswordAgePolicySequence(eventSequence uint64, eventTimestamp time.Time) error {
return v.saveCurrentSequence(passwordAgePolicyTable, eventSequence, eventTimestamp)
func (v *View) ProcessedPasswordAgePolicySequence(event *models.Event) error {
return v.saveCurrentSequence(passwordAgePolicyTable, event)
}
func (v *View) UpdateProcessedPasswordAgePolicySpoolerRunTimestamp() error {

View File

@ -2,10 +2,10 @@ package view
import (
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/iam/repository/view"
"github.com/caos/zitadel/internal/iam/repository/view/model"
global_view "github.com/caos/zitadel/internal/view/repository"
"time"
)
const (
@ -16,28 +16,28 @@ func (v *View) PasswordComplexityPolicyByAggregateID(aggregateID string) (*model
return view.GetPasswordComplexityPolicyByAggregateID(v.Db, passwordComplexityPolicyTable, aggregateID)
}
func (v *View) PutPasswordComplexityPolicy(policy *model.PasswordComplexityPolicyView, sequence uint64, eventTimestamp time.Time) error {
func (v *View) PutPasswordComplexityPolicy(policy *model.PasswordComplexityPolicyView, event *models.Event) error {
err := view.PutPasswordComplexityPolicy(v.Db, passwordComplexityPolicyTable, policy)
if err != nil {
return err
}
return v.ProcessedPasswordComplexityPolicySequence(sequence, eventTimestamp)
return v.ProcessedPasswordComplexityPolicySequence(event)
}
func (v *View) DeletePasswordComplexityPolicy(aggregateID string, eventSequence uint64, eventTimestamp time.Time) error {
func (v *View) DeletePasswordComplexityPolicy(aggregateID string, event *models.Event) error {
err := view.DeletePasswordComplexityPolicy(v.Db, passwordComplexityPolicyTable, aggregateID)
if err != nil && !errors.IsNotFound(err) {
return err
}
return v.ProcessedPasswordComplexityPolicySequence(eventSequence, eventTimestamp)
return v.ProcessedPasswordComplexityPolicySequence(event)
}
func (v *View) GetLatestPasswordComplexityPolicySequence() (*global_view.CurrentSequence, error) {
return v.latestSequence(passwordComplexityPolicyTable)
func (v *View) GetLatestPasswordComplexityPolicySequence(aggregateType string) (*global_view.CurrentSequence, error) {
return v.latestSequence(passwordComplexityPolicyTable, aggregateType)
}
func (v *View) ProcessedPasswordComplexityPolicySequence(eventSequence uint64, eventTimestamp time.Time) error {
return v.saveCurrentSequence(passwordComplexityPolicyTable, eventSequence, eventTimestamp)
func (v *View) ProcessedPasswordComplexityPolicySequence(event *models.Event) error {
return v.saveCurrentSequence(passwordComplexityPolicyTable, event)
}
func (v *View) UpdatePasswordComplexityPolicySpoolerRunTimestamp() error {

View File

@ -2,10 +2,10 @@ package view
import (
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/iam/repository/view"
"github.com/caos/zitadel/internal/iam/repository/view/model"
global_view "github.com/caos/zitadel/internal/view/repository"
"time"
)
const (
@ -16,28 +16,28 @@ func (v *View) PasswordLockoutPolicyByAggregateID(aggregateID string) (*model.Pa
return view.GetPasswordLockoutPolicyByAggregateID(v.Db, passwordLockoutPolicyTable, aggregateID)
}
func (v *View) PutPasswordLockoutPolicy(policy *model.PasswordLockoutPolicyView, sequence uint64, eventTimestamp time.Time) error {
func (v *View) PutPasswordLockoutPolicy(policy *model.PasswordLockoutPolicyView, event *models.Event) error {
err := view.PutPasswordLockoutPolicy(v.Db, passwordLockoutPolicyTable, policy)
if err != nil {
return err
}
return v.ProcessedPasswordLockoutPolicySequence(sequence, eventTimestamp)
return v.ProcessedPasswordLockoutPolicySequence(event)
}
func (v *View) DeletePasswordLockoutPolicy(aggregateID string, eventSequence uint64, eventTimestamp time.Time) error {
func (v *View) DeletePasswordLockoutPolicy(aggregateID string, event *models.Event) error {
err := view.DeletePasswordLockoutPolicy(v.Db, passwordLockoutPolicyTable, aggregateID)
if err != nil && !errors.IsNotFound(err) {
return err
}
return v.ProcessedPasswordLockoutPolicySequence(eventSequence, eventTimestamp)
return v.ProcessedPasswordLockoutPolicySequence(event)
}
func (v *View) GetLatestPasswordLockoutPolicySequence() (*global_view.CurrentSequence, error) {
return v.latestSequence(passwordLockoutPolicyTable)
func (v *View) GetLatestPasswordLockoutPolicySequence(aggregateType string) (*global_view.CurrentSequence, error) {
return v.latestSequence(passwordLockoutPolicyTable, aggregateType)
}
func (v *View) ProcessedPasswordLockoutPolicySequence(eventSequence uint64, eventTimestamp time.Time) error {
return v.saveCurrentSequence(passwordLockoutPolicyTable, eventSequence, eventTimestamp)
func (v *View) ProcessedPasswordLockoutPolicySequence(event *models.Event) error {
return v.saveCurrentSequence(passwordLockoutPolicyTable, event)
}
func (v *View) UpdatePasswordLockoutPolicySpoolerRunTimestamp() error {

View File

@ -1,20 +1,22 @@
package view
import (
"github.com/caos/zitadel/internal/view/repository"
"time"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/view/repository"
)
const (
sequencesTable = "adminapi.current_sequences"
)
func (v *View) saveCurrentSequence(viewName string, sequence uint64, eventTimeStamp time.Time) error {
return repository.SaveCurrentSequence(v.Db, sequencesTable, viewName, sequence, eventTimeStamp)
func (v *View) saveCurrentSequence(viewName string, event *models.Event) error {
return repository.SaveCurrentSequence(v.Db, sequencesTable, viewName, string(event.AggregateType), event.Sequence, event.CreationDate)
}
func (v *View) latestSequence(viewName string) (*repository.CurrentSequence, error) {
return repository.LatestSequence(v.Db, sequencesTable, viewName)
func (v *View) latestSequence(viewName, aggregateType string) (*repository.CurrentSequence, error) {
return repository.LatestSequence(v.Db, sequencesTable, viewName, aggregateType)
}
func (v *View) AllCurrentSequences(db string) ([]*repository.CurrentSequence, error) {
@ -22,7 +24,7 @@ func (v *View) AllCurrentSequences(db string) ([]*repository.CurrentSequence, er
}
func (v *View) updateSpoolerRunSequence(viewName string) error {
currentSequence, err := repository.LatestSequence(v.Db, sequencesTable, viewName)
currentSequence, err := repository.LatestSequence(v.Db, sequencesTable, viewName, "")
if err != nil {
return err
}
@ -30,13 +32,16 @@ func (v *View) updateSpoolerRunSequence(viewName string) error {
currentSequence.ViewName = viewName
}
currentSequence.LastSuccessfulSpoolerRun = time.Now()
//update all aggregate types
//TODO: not sure if all scenarios work as expected
currentSequence.AggregateType = ""
return repository.UpdateCurrentSequence(v.Db, sequencesTable, currentSequence)
}
func (v *View) GetCurrentSequence(db, viewName string) (*repository.CurrentSequence, error) {
func (v *View) GetCurrentSequence(db, viewName, aggregateType string) (*repository.CurrentSequence, error) {
sequenceTable := db + ".current_sequences"
fullView := db + "." + viewName
return repository.LatestSequence(v.Db, sequenceTable, fullView)
return repository.LatestSequence(v.Db, sequenceTable, fullView, aggregateType)
}
func (v *View) ClearView(db, viewName string) error {

View File

@ -1,11 +1,12 @@
package view
import (
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
usr_model "github.com/caos/zitadel/internal/user/model"
"github.com/caos/zitadel/internal/user/repository/view"
"github.com/caos/zitadel/internal/user/repository/view/model"
"github.com/caos/zitadel/internal/view/repository"
"time"
)
const (
@ -40,39 +41,36 @@ func (v *View) UserMFAs(userID string) ([]*usr_model.MultiFactor, error) {
return view.UserMFAs(v.Db, userTable, userID)
}
func (v *View) PutUsers(user []*model.UserView, sequence uint64, eventTimestamp time.Time) error {
func (v *View) PutUsers(user []*model.UserView, event *models.Event) error {
err := view.PutUsers(v.Db, userTable, user...)
if err != nil {
return err
}
return v.ProcessedUserSequence(sequence, eventTimestamp)
return v.ProcessedUserSequence(event)
}
func (v *View) PutUser(user *model.UserView, sequence uint64, eventTimestamp time.Time) error {
func (v *View) PutUser(user *model.UserView, event *models.Event) error {
err := view.PutUser(v.Db, userTable, user)
if err != nil {
return err
}
if sequence != 0 {
return v.ProcessedUserSequence(sequence, eventTimestamp)
}
return nil
return v.ProcessedUserSequence(event)
}
func (v *View) DeleteUser(userID string, eventSequence uint64, eventTimestamp time.Time) error {
func (v *View) DeleteUser(userID string, event *models.Event) error {
err := view.DeleteUser(v.Db, userTable, userID)
if err != nil {
return nil
if err != nil && !errors.IsNotFound(err) {
return err
}
return v.ProcessedUserSequence(eventSequence, eventTimestamp)
return v.ProcessedUserSequence(event)
}
func (v *View) GetLatestUserSequence() (*repository.CurrentSequence, error) {
return v.latestSequence(userTable)
func (v *View) GetLatestUserSequence(aggregateType string) (*repository.CurrentSequence, error) {
return v.latestSequence(userTable, aggregateType)
}
func (v *View) ProcessedUserSequence(eventSequence uint64, eventTimestamp time.Time) error {
return v.saveCurrentSequence(userTable, eventSequence, eventTimestamp)
func (v *View) ProcessedUserSequence(event *models.Event) error {
return v.saveCurrentSequence(userTable, event)
}
func (v *View) UpdateUserSpoolerRunTimestamp() error {

View File

@ -14,33 +14,39 @@ const (
authenticated = "authenticated"
)
func CheckUserAuthorization(ctx context.Context, req interface{}, token, orgID string, verifier *TokenVerifier, authConfig Config, requiredAuthOption Option, method string) (_ context.Context, err error) {
func CheckUserAuthorization(ctx context.Context, req interface{}, token, orgID string, verifier *TokenVerifier, authConfig Config, requiredAuthOption Option, method string) (ctxSetter func(context.Context) context.Context, err error) {
ctx, span := tracing.NewServerInterceptorSpan(ctx)
defer func() { span.EndWithError(err) }()
ctx, err = VerifyTokenAndWriteCtxData(ctx, token, orgID, verifier, method)
ctxData, err := VerifyTokenAndCreateCtxData(ctx, token, orgID, verifier, method)
if err != nil {
return nil, err
}
var perms []string
if requiredAuthOption.Permission == authenticated {
return ctx, nil
return func(parent context.Context) context.Context {
return context.WithValue(parent, dataKey, ctxData)
}, nil
}
ctx, perms, err = getUserMethodPermissions(ctx, verifier, requiredAuthOption.Permission, authConfig)
requestedPermissions, allPermissions, err := getUserMethodPermissions(ctx, verifier, requiredAuthOption.Permission, authConfig, ctxData)
if err != nil {
return nil, err
}
ctx, userPermissionSpan := tracing.NewNamedSpan(ctx, "checkUserPermissions")
err = checkUserPermissions(req, perms, requiredAuthOption)
err = checkUserPermissions(req, requestedPermissions, requiredAuthOption)
userPermissionSpan.EndWithError(err)
if err != nil {
return nil, err
}
return ctx, nil
return func(parent context.Context) context.Context {
parent = context.WithValue(parent, dataKey, ctxData)
parent = context.WithValue(parent, allPermissionsKey, allPermissions)
parent = context.WithValue(parent, requestPermissionsKey, requestedPermissions)
return parent
}, nil
}
func checkUserPermissions(req interface{}, userPerms []string, authOpt Option) error {

View File

@ -36,29 +36,36 @@ type Grant struct {
Roles []string
}
func VerifyTokenAndWriteCtxData(ctx context.Context, token, orgID string, t *TokenVerifier, method string) (_ context.Context, err error) {
func VerifyTokenAndCreateCtxData(ctx context.Context, token, orgID string, t *TokenVerifier, method string) (_ CtxData, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
if orgID != "" {
err = t.ExistsOrg(ctx, orgID)
if err != nil {
return nil, errors.ThrowPermissionDenied(nil, "AUTH-Bs7Ds", "Organisation doesn't exist")
return CtxData{}, errors.ThrowPermissionDenied(nil, "AUTH-Bs7Ds", "Organisation doesn't exist")
}
}
userID, clientID, agentID, prefLang, err := verifyAccessToken(ctx, token, t, method)
if err != nil {
return nil, err
return CtxData{}, err
}
projectID, origins, err := t.ProjectIDAndOriginsByClientID(ctx, clientID)
if err != nil {
return nil, errors.ThrowPermissionDenied(err, "AUTH-GHpw2", "could not read projectid by clientid")
return CtxData{}, errors.ThrowPermissionDenied(err, "AUTH-GHpw2", "could not read projectid by clientid")
}
if err := checkOrigin(ctx, origins); err != nil {
return nil, err
return CtxData{}, err
}
return context.WithValue(ctx, dataKey, CtxData{UserID: userID, OrgID: orgID, ProjectID: projectID, AgentID: agentID, PreferredLanguage: prefLang}), nil
return CtxData{
UserID: userID,
OrgID: orgID,
ProjectID: projectID,
AgentID: agentID,
PreferredLanguage: prefLang,
}, nil
}
func SetCtxData(ctx context.Context, ctxData CtxData) context.Context {

View File

@ -7,29 +7,29 @@ import (
"github.com/caos/zitadel/internal/telemetry/tracing"
)
func getUserMethodPermissions(ctx context.Context, t *TokenVerifier, requiredPerm string, authConfig Config) (_ context.Context, _ []string, err error) {
func getUserMethodPermissions(ctx context.Context, t *TokenVerifier, requiredPerm string, authConfig Config, ctxData CtxData) (requestedPermissions, allPermissions []string, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
ctxData := GetCtxData(ctx)
if ctxData.IsZero() {
return nil, nil, errors.ThrowUnauthenticated(nil, "AUTH-rKLWEH", "context missing")
}
ctx = context.WithValue(ctx, dataKey, ctxData)
grant, err := t.ResolveGrant(ctx)
if err != nil {
return nil, nil, err
}
if grant == nil {
return context.WithValue(ctx, requestPermissionsKey, []string{}), []string{}, nil
return requestedPermissions, nil, nil
}
requestPermissions, allPermissions := mapGrantToPermissions(requiredPerm, grant, authConfig)
ctx = context.WithValue(ctx, allPermissionsKey, allPermissions)
return context.WithValue(ctx, requestPermissionsKey, requestPermissions), requestPermissions, nil
requestedPermissions, allPermissions = mapGrantToPermissions(requiredPerm, grant, authConfig)
return requestedPermissions, allPermissions, nil
}
func mapGrantToPermissions(requiredPerm string, grant *Grant, authConfig Config) ([]string, []string) {
requestPermissions := make([]string, 0)
allPermissions := make([]string, 0)
func mapGrantToPermissions(requiredPerm string, grant *Grant, authConfig Config) (requestPermissions, allPermissions []string) {
requestPermissions = make([]string, 0)
allPermissions = make([]string, 0)
for _, role := range grant.Roles {
requestPermissions, allPermissions = mapRoleToPerm(requiredPerm, role, authConfig, requestPermissions, allPermissions)
}

View File

@ -49,7 +49,7 @@ func equalStringArray(a, b []string) bool {
func Test_GetUserMethodPermissions(t *testing.T) {
type args struct {
ctx context.Context
ctxData CtxData
verifier *TokenVerifier
requiredPerm string
authConfig Config
@ -64,7 +64,7 @@ func Test_GetUserMethodPermissions(t *testing.T) {
{
name: "Empty Context",
args: args{
ctx: getTestCtx("", ""),
ctxData: CtxData{},
verifier: Start(&testVerifier{grant: &Grant{
Roles: []string{"ORG_OWNER"},
}}),
@ -89,7 +89,7 @@ func Test_GetUserMethodPermissions(t *testing.T) {
{
name: "No Grants",
args: args{
ctx: getTestCtx("", ""),
ctxData: CtxData{},
verifier: Start(&testVerifier{grant: &Grant{}}),
requiredPerm: "project.read",
authConfig: Config{
@ -110,9 +110,9 @@ func Test_GetUserMethodPermissions(t *testing.T) {
{
name: "Get Permissions",
args: args{
ctx: getTestCtx("userID", "orgID"),
ctxData: CtxData{UserID: "userID", OrgID: "orgID"},
verifier: Start(&testVerifier{grant: &Grant{
Roles: []string{"ORG_OWNER"},
Roles: []string{"IAM_OWNER"},
}}),
requiredPerm: "project.read",
authConfig: Config{
@ -133,7 +133,7 @@ func Test_GetUserMethodPermissions(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, perms, err := getUserMethodPermissions(tt.args.ctx, tt.args.verifier, tt.args.requiredPerm, tt.args.authConfig)
_, perms, err := getUserMethodPermissions(context.Background(), tt.args.verifier, tt.args.requiredPerm, tt.args.authConfig, tt.args.ctxData)
if tt.wantErr && err == nil {
t.Errorf("got wrong result, should get err: actual: %v ", err)

View File

@ -28,8 +28,8 @@ func failedEventsFromModel(failedEvents []*view_model.FailedEvent) []*admin.Fail
func viewFromModel(view *view_model.View) *admin.View {
eventTimestamp, err := ptypes.TimestampProto(view.EventTimestamp)
logging.Log("GRPC-KSo03").OnError(err).Debug("unable to parse timestamp")
lastSpool, err := ptypes.TimestampProto(view.EventTimestamp)
logging.Log("GRPC-KSo03").OnError(err).Debug("unable to parse timestamp")
lastSpool, err := ptypes.TimestampProto(view.LastSuccessfulSpoolerRun)
logging.Log("GRPC-0oP87").OnError(err).Debug("unable to parse timestamp")
return &admin.View{
Database: view.Database,

View File

@ -2,6 +2,7 @@ package auth
import (
"context"
"github.com/golang/protobuf/ptypes/empty"
"github.com/caos/zitadel/pkg/grpc/auth"
@ -162,6 +163,9 @@ func (s *Server) RemoveMfaOTP(ctx context.Context, _ *empty.Empty) (_ *empty.Emp
func (s *Server) AddMyMfaU2F(ctx context.Context, _ *empty.Empty) (_ *auth.WebAuthNResponse, err error) {
u2f, err := s.repo.AddMyMFAU2F(ctx)
if err != nil {
return nil, err
}
return verifyWebAuthNFromModel(u2f), err
}
@ -175,8 +179,19 @@ func (s *Server) RemoveMyMfaU2F(ctx context.Context, id *auth.WebAuthNTokenID) (
return &empty.Empty{}, err
}
func (s *Server) GetMyPasswordless(ctx context.Context, _ *empty.Empty) (_ *auth.WebAuthNTokens, err error) {
tokens, err := s.repo.GetMyPasswordless(ctx)
if err != nil {
return nil, err
}
return webAuthNTokensFromModel(tokens), err
}
func (s *Server) AddMyPasswordless(ctx context.Context, _ *empty.Empty) (_ *auth.WebAuthNResponse, err error) {
u2f, err := s.repo.AddMyPasswordless(ctx)
if err != nil {
return nil, err
}
return verifyWebAuthNFromModel(u2f), err
}

View File

@ -436,3 +436,19 @@ func verifyWebAuthNFromModel(u2f *usr_model.WebAuthNToken) *auth.WebAuthNRespons
State: mfaStateFromModel(u2f.State),
}
}
func webAuthNTokensFromModel(tokens []*usr_model.WebAuthNToken) *auth.WebAuthNTokens {
result := make([]*auth.WebAuthNToken, len(tokens))
for i, token := range tokens {
result[i] = webAuthNTokenFromModel(token)
}
return &auth.WebAuthNTokens{Tokens: result}
}
func webAuthNTokenFromModel(token *usr_model.WebAuthNToken) *auth.WebAuthNToken {
return &auth.WebAuthNToken{
Id: token.WebAuthNTokenID,
Name: token.WebAuthNTokenName,
State: mfaStateFromModel(token.State),
}
}

View File

@ -2,9 +2,11 @@ package management
import (
"context"
"github.com/golang/protobuf/ptypes/empty"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/pkg/grpc/management"
"github.com/golang/protobuf/ptypes/empty"
)
func (s *Server) GetUserByID(ctx context.Context, id *management.UserID) (*management.UserView, error) {
@ -226,6 +228,24 @@ func (s *Server) RemoveMfaOTP(ctx context.Context, userID *management.UserID) (*
return &empty.Empty{}, err
}
func (s *Server) RemoveMfaU2F(ctx context.Context, webAuthNTokenID *management.WebAuthNTokenID) (*empty.Empty, error) {
err := s.user.RemoveU2F(ctx, webAuthNTokenID.UserId, webAuthNTokenID.Id)
return &empty.Empty{}, err
}
func (s *Server) GetPasswordless(ctx context.Context, userID *management.UserID) (_ *management.WebAuthNTokens, err error) {
tokens, err := s.user.GetPasswordless(ctx, userID.Id)
if err != nil {
return nil, err
}
return webAuthNTokensFromModel(tokens), err
}
func (s *Server) RemovePasswordless(ctx context.Context, id *management.WebAuthNTokenID) (*empty.Empty, error) {
err := s.user.RemovePasswordless(ctx, id.UserId, id.Id)
return &empty.Empty{}, err
}
func (s *Server) SearchUserMemberships(ctx context.Context, in *management.UserMembershipSearchRequest) (*management.UserMembershipSearchResponse, error) {
request := userMembershipSearchRequestsToModel(in)
request.AppendUserIDQuery(in.UserId)

View File

@ -2,14 +2,15 @@ package management
import (
"encoding/json"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/model"
"github.com/golang/protobuf/ptypes"
"golang.org/x/text/language"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/types/known/structpb"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/model"
usr_model "github.com/caos/zitadel/internal/user/model"
"github.com/caos/zitadel/pkg/grpc/management"
"github.com/caos/zitadel/pkg/grpc/message"
@ -504,6 +505,7 @@ func mfaFromModel(mfa *usr_model.MultiFactor) *management.UserMultiFactor {
State: mfaStateFromModel(mfa.State),
Type: mfaTypeFromModel(mfa.Type),
Attribute: mfa.Attribute,
Id: mfa.ID,
}
}
@ -627,3 +629,19 @@ func userChangesToMgtAPI(changes *usr_model.UserChanges) (_ []*management.Change
return result
}
func webAuthNTokensFromModel(tokens []*usr_model.WebAuthNToken) *management.WebAuthNTokens {
result := make([]*management.WebAuthNToken, len(tokens))
for i, token := range tokens {
result[i] = webAuthNTokenFromModel(token)
}
return &management.WebAuthNTokens{Tokens: result}
}
func webAuthNTokenFromModel(token *usr_model.WebAuthNToken) *management.WebAuthNToken {
return &management.WebAuthNToken{
Id: token.WebAuthNTokenID,
Name: token.WebAuthNTokenName,
State: mfaStateFromModel(token.State),
}
}

View File

@ -25,20 +25,20 @@ func authorize(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo,
return handler(ctx, req)
}
ctx, span := tracing.NewServerInterceptorSpan(ctx)
authCtx, span := tracing.NewServerInterceptorSpan(ctx)
defer func() { span.EndWithError(err) }()
authToken := grpc_util.GetAuthorizationHeader(ctx)
authToken := grpc_util.GetAuthorizationHeader(authCtx)
if authToken == "" {
return nil, status.Error(codes.Unauthenticated, "auth header missing")
}
orgID := grpc_util.GetHeader(ctx, http.ZitadelOrgID)
orgID := grpc_util.GetHeader(authCtx, http.ZitadelOrgID)
ctx, err = authz.CheckUserAuthorization(ctx, req, authToken, orgID, verifier, authConfig, authOpt, info.FullMethod)
ctxSetter, err := authz.CheckUserAuthorization(authCtx, req, authToken, orgID, verifier, authConfig, authOpt, info.FullMethod)
if err != nil {
return nil, err
}
span.End()
return handler(ctx, req)
return handler(ctxSetter(ctx), req)
}

View File

@ -2,17 +2,16 @@ package oidc
import (
"context"
"github.com/caos/zitadel/internal/auth_request/model"
"strings"
"golang.org/x/text/language"
"gopkg.in/square/go-jose.v2"
"github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/pkg/op"
"golang.org/x/text/language"
"gopkg.in/square/go-jose.v2"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/api/http"
"github.com/caos/zitadel/internal/auth_request/model"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/errors"
proj_model "github.com/caos/zitadel/internal/project/model"
@ -155,7 +154,7 @@ func (o *OPStorage) GetUserinfoFromScopes(ctx context.Context, userID, applicati
roles = append(roles, strings.TrimPrefix(scope, ScopeProjectRolePrefix))
}
if strings.HasPrefix(scope, model.OrgDomainPrimaryScope) {
userInfo.AppendClaims(model.OrgDomainPrimaryScope, strings.TrimPrefix(scope, model.OrgDomainPrimaryScope))
userInfo.AppendClaims(model.OrgDomainPrimaryClaim, strings.TrimPrefix(scope, model.OrgDomainPrimaryScope))
}
}
}
@ -180,7 +179,7 @@ func (o *OPStorage) GetPrivateClaimsFromScopes(ctx context.Context, userID, clie
if strings.HasPrefix(scope, ScopeProjectRolePrefix) {
roles = append(roles, strings.TrimPrefix(scope, ScopeProjectRolePrefix))
} else if strings.HasPrefix(scope, model.OrgDomainPrimaryScope) {
claims = map[string]interface{}{model.OrgDomainPrimaryScope: strings.TrimPrefix(scope, model.OrgDomainPrimaryScope)}
claims = appendClaim(claims, model.OrgDomainPrimaryClaim, strings.TrimPrefix(scope, model.OrgDomainPrimaryScope))
}
}
if len(roles) == 0 || clientID == "" {
@ -191,7 +190,7 @@ func (o *OPStorage) GetPrivateClaimsFromScopes(ctx context.Context, userID, clie
return nil, err
}
if len(projectRoles) > 0 {
claims = map[string]interface{}{ClaimProjectRoles: projectRoles}
claims = appendClaim(claims, ClaimProjectRoles, projectRoles)
}
return claims, err
}
@ -240,3 +239,11 @@ func getGender(gender user_model.Gender) string {
}
return ""
}
func appendClaim(claims map[string]interface{}, claim string, value interface{}) map[string]interface{} {
if claims == nil {
claims = make(map[string]interface{})
}
claims[claim] = value
return claims
}

View File

@ -30,6 +30,4 @@ type AuthRequestRepository interface {
LinkExternalUsers(ctx context.Context, authReqID, userAgentID string, info *model.BrowserInfo) error
AutoRegisterExternalUser(ctx context.Context, user *user_model.User, externalIDP *user_model.ExternalIDP, member *org_model.OrgMember, authReqID, userAgentID, resourceOwner string, info *model.BrowserInfo) error
ResetLinkingUsers(ctx context.Context, authReqID, userAgentID string) error
GetOrgByPrimaryDomain(primaryDomain string) (*org_model.OrgView, error)
}

View File

@ -110,6 +110,9 @@ func (repo *AuthRequestRepo) CreateAuthRequest(ctx context.Context, request *mod
}
request.Audience = appIDs
request.AppendAudIfNotExisting(app.ProjectID)
if err := setOrgID(repo.OrgViewProvider, request); err != nil {
return nil, err
}
if request.LoginHint != "" {
err = repo.checkLoginName(ctx, request, request.LoginHint)
logging.LogWithFields("EVENT-aG311", "login name", request.LoginHint, "id", request.ID, "applicationID", request.ApplicationID, "traceID", tracing.TraceIDFromCtx(ctx)).OnError(err).Debug("login hint invalid")
@ -238,6 +241,9 @@ func (repo *AuthRequestRepo) SelectUser(ctx context.Context, id, userID, userAge
if err != nil {
return err
}
if request.RequestedOrgID != "" && request.RequestedOrgID != user.ResourceOwner {
return errors.ThrowPreconditionFailed(nil, "EVENT-fJe2a", "Errors.User.NotAllowedOrg")
}
request.SetUserInfo(user.ID, user.PreferredLoginName, user.DisplayName, user.ResourceOwner)
return repo.AuthRequests.UpdateAuthRequest(ctx, request)
}
@ -442,16 +448,9 @@ func (repo *AuthRequestRepo) getLoginPolicyAndIDPProviders(ctx context.Context,
}
func (repo *AuthRequestRepo) fillLoginPolicy(ctx context.Context, request *model.AuthRequest) error {
orgID := request.UserOrgID
orgID := request.RequestedOrgID
if orgID == "" {
primaryDomain := request.GetScopeOrgPrimaryDomain()
if primaryDomain != "" {
org, err := repo.GetOrgByPrimaryDomain(primaryDomain)
if err != nil {
return err
}
orgID = org.ID
}
orgID = request.UserOrgID
}
if orgID == "" {
orgID = repo.IAMID
@ -469,19 +468,9 @@ func (repo *AuthRequestRepo) fillLoginPolicy(ctx context.Context, request *model
}
func (repo *AuthRequestRepo) checkLoginName(ctx context.Context, request *model.AuthRequest, loginName string) (err error) {
primaryDomain := request.GetScopeOrgPrimaryDomain()
orgID := ""
if primaryDomain != "" {
org, err := repo.GetOrgByPrimaryDomain(primaryDomain)
if err != nil {
return err
}
orgID = org.ID
}
user := new(user_view_model.UserView)
if orgID != "" {
user, err = repo.View.UserByLoginNameAndResourceOwner(loginName, orgID)
if request.RequestedOrgID != "" {
user, err = repo.View.UserByLoginNameAndResourceOwner(loginName, request.RequestedOrgID)
} else {
user, err = repo.View.UserByLoginName(loginName)
if err == nil {
@ -499,14 +488,6 @@ func (repo *AuthRequestRepo) checkLoginName(ctx context.Context, request *model.
return nil
}
func (repo AuthRequestRepo) GetOrgByPrimaryDomain(primaryDomain string) (*org_model.OrgView, error) {
org, err := repo.OrgViewProvider.OrgByPrimaryDomain(primaryDomain)
if err != nil {
return nil, err
}
return org_view_model.OrgToModel(org), nil
}
func (repo AuthRequestRepo) checkLoginPolicyWithResourceOwner(ctx context.Context, request *model.AuthRequest, user *user_view_model.UserView) error {
loginPolicy, idpProviders, err := repo.getLoginPolicyAndIDPProviders(ctx, user.ResourceOwner)
if err != nil {
@ -537,15 +518,9 @@ func (repo *AuthRequestRepo) checkSelectedExternalIDP(request *model.AuthRequest
}
func (repo *AuthRequestRepo) checkExternalUserLogin(request *model.AuthRequest, idpConfigID, externalUserID string) (err error) {
primaryDomain := request.GetScopeOrgPrimaryDomain()
externalIDP := new(user_view_model.ExternalIDPView)
org := new(org_model.OrgView)
if primaryDomain != "" {
org, err = repo.GetOrgByPrimaryDomain(primaryDomain)
if err != nil {
return err
}
externalIDP, err = repo.View.ExternalIDPByExternalUserIDAndIDPConfigIDAndResourceOwner(externalUserID, idpConfigID, org.ID)
if request.RequestedOrgID != "" {
externalIDP, err = repo.View.ExternalIDPByExternalUserIDAndIDPConfigIDAndResourceOwner(externalUserID, idpConfigID, request.RequestedOrgID)
} else {
externalIDP, err = repo.View.ExternalIDPByExternalUserIDAndIDPConfigID(externalUserID, idpConfigID)
}
@ -653,10 +628,11 @@ func (repo *AuthRequestRepo) usersForUserSelection(request *model.AuthRequest) (
users := make([]model.UserSelection, len(userSessions))
for i, session := range userSessions {
users[i] = model.UserSelection{
UserID: session.UserID,
DisplayName: session.DisplayName,
LoginName: session.LoginName,
UserSessionState: session.State,
UserID: session.UserID,
DisplayName: session.DisplayName,
LoginName: session.LoginName,
UserSessionState: session.State,
SelectionPossible: request.RequestedOrgID == "" || request.RequestedOrgID == session.ResourceOwner,
}
}
return users, nil
@ -667,24 +643,28 @@ func (repo *AuthRequestRepo) firstFactorChecked(request *model.AuthRequest, user
return &model.InitUserStep{PasswordSet: user.PasswordSet}
}
if user.IsPasswordlessReady() {
if !checkVerificationTime(userSession.PasswordlessVerification, repo.MultiFactorCheckLifeTime) {
return &model.PasswordlessStep{}
var step model.NextStep
if request.LoginPolicy.PasswordlessType != iam_model.PasswordlessTypeNotAllowed && user.IsPasswordlessReady() {
if checkVerificationTime(userSession.PasswordlessVerification, repo.MultiFactorCheckLifeTime) {
request.AuthTime = userSession.PasswordlessVerification
return nil
}
request.AuthTime = userSession.PasswordlessVerification
return nil
step = &model.PasswordlessStep{}
}
if !user.PasswordSet {
return &model.InitPasswordStep{}
}
if !checkVerificationTime(userSession.PasswordVerification, repo.PasswordCheckLifeTime) {
return &model.PasswordStep{}
if checkVerificationTime(userSession.PasswordVerification, repo.PasswordCheckLifeTime) {
request.PasswordVerified = true
request.AuthTime = userSession.PasswordVerification
return nil
}
request.PasswordVerified = true
request.AuthTime = userSession.PasswordVerification
return nil
if step != nil {
return step
}
return &model.PasswordStep{}
}
func (repo *AuthRequestRepo) mfaChecked(userSession *user_model.UserSessionView, request *model.AuthRequest, user *user_model.UserView) (model.NextStep, bool, error) {
@ -753,6 +733,21 @@ func (repo *AuthRequestRepo) getLoginPolicy(ctx context.Context, orgID string) (
return iam_es_model.LoginPolicyViewToModel(policy), err
}
func setOrgID(orgViewProvider orgViewProvider, request *model.AuthRequest) error {
primaryDomain := request.GetScopeOrgPrimaryDomain()
if primaryDomain == "" {
return nil
}
org, err := orgViewProvider.OrgByPrimaryDomain(primaryDomain)
if err != nil {
return err
}
request.RequestedOrgID = org.ID
request.RequestedOrgName = org.Name
return nil
}
func getLoginPolicyIDPProviders(provider idpProviderViewProvider, iamID, orgID string, defaultPolicy bool) ([]*iam_model.IDPProviderView, error) {
if defaultPolicy {
idpProviders, err := provider.IDPProvidersByAggregateIDAndState(iamID, iam_model.IDPConfigStateActive)
@ -824,9 +819,8 @@ func userSessionByIDs(ctx context.Context, provider userSessionViewProvider, eve
case es_model.UserRemoved:
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dG2fe", "Errors.User.NotActive")
}
if err := sessionCopy.AppendEvent(event); err != nil {
return user_view_model.UserSessionToModel(&sessionCopy), nil
}
err := sessionCopy.AppendEvent(event)
logging.Log("EVENT-qbhj3").OnError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Warn("error appending event")
}
return user_view_model.UserSessionToModel(&sessionCopy), nil
}

View File

@ -56,8 +56,9 @@ type mockViewUserSession struct {
}
type mockUser struct {
UserID string
LoginName string
UserID string
LoginName string
ResourceOwner string
}
func (m *mockViewUserSession) UserSessionByIDs(string, string) (*user_view_model.UserSessionView, error) {
@ -74,8 +75,9 @@ func (m *mockViewUserSession) UserSessionsByAgentID(string) ([]*user_view_model.
sessions := make([]*user_view_model.UserSessionView, len(m.Users))
for i, user := range m.Users {
sessions[i] = &user_view_model.UserSessionView{
UserID: user.UserID,
LoginName: user.LoginName,
UserID: user.UserID,
LoginName: user.LoginName,
ResourceOwner: user.ResourceOwner,
}
}
return sessions, nil
@ -270,10 +272,12 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
{
"id1",
"loginname1",
"orgID1",
},
{
"id2",
"loginname2",
"orgID2",
},
},
},
@ -285,12 +289,52 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
&model.SelectUserStep{
Users: []model.UserSelection{
{
UserID: "id1",
LoginName: "loginname1",
UserID: "id1",
LoginName: "loginname1",
SelectionPossible: true,
},
{
UserID: "id2",
LoginName: "loginname2",
UserID: "id2",
LoginName: "loginname2",
SelectionPossible: true,
},
},
}},
nil,
},
{
"user not set, primary domain set, prompt select account, login and select account steps",
fields{
userSessionViewProvider: &mockViewUserSession{
Users: []mockUser{
{
"id1",
"loginname1",
"orgID1",
},
{
"id2",
"loginname2",
"orgID2",
},
},
},
userEventProvider: &mockEventUser{},
},
args{&model.AuthRequest{Prompt: model.PromptSelectAccount, RequestedOrgID: "orgID1"}, false},
[]model.NextStep{
&model.LoginStep{},
&model.SelectUserStep{
Users: []model.UserSelection{
{
UserID: "id1",
LoginName: "loginname1",
SelectionPossible: true,
},
{
UserID: "id2",
LoginName: "loginname2",
SelectionPossible: false,
},
},
}},
@ -386,7 +430,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
},
args{&model.AuthRequest{UserID: "UserID"}, false},
args{&model.AuthRequest{UserID: "UserID", LoginPolicy: &iam_model.LoginPolicyView{}}, false},
[]model.NextStep{&model.PasswordStep{}},
nil,
},
@ -431,7 +475,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
MultiFactorCheckLifeTime: 10 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID"}, false},
args{&model.AuthRequest{UserID: "UserID", LoginPolicy: &iam_model.LoginPolicyView{PasswordlessType: iam_model.PasswordlessTypeAllowed}}, false},
[]model.NextStep{&model.PasswordlessStep{}},
nil,
},
@ -456,7 +500,8 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
args{&model.AuthRequest{
UserID: "UserID",
LoginPolicy: &iam_model.LoginPolicyView{
MultiFactors: []iam_model.MultiFactorType{iam_model.MultiFactorTypeU2FWithPIN},
PasswordlessType: iam_model.PasswordlessTypeAllowed,
MultiFactors: []iam_model.MultiFactorType{iam_model.MultiFactorTypeU2FWithPIN},
},
}, false},
[]model.NextStep{&model.VerifyEMailStep{}},
@ -470,7 +515,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
},
args{&model.AuthRequest{UserID: "UserID"}, false},
args{&model.AuthRequest{UserID: "UserID", LoginPolicy: &iam_model.LoginPolicyView{}}, false},
[]model.NextStep{&model.InitPasswordStep{}},
nil,
},
@ -534,7 +579,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID"}, false},
args{&model.AuthRequest{UserID: "UserID", LoginPolicy: &iam_model.LoginPolicyView{}}, false},
[]model.NextStep{&model.PasswordStep{}},
nil,
},
@ -566,6 +611,35 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
[]model.NextStep{&model.RedirectToCallbackStep{}},
nil,
},
{
"password verified, passwordless set up, mfa not verified, mfa check step",
fields{
userSessionViewProvider: &mockViewUserSession{
PasswordVerification: time.Now().UTC().Add(-5 * time.Minute),
},
userViewProvider: &mockViewUser{
PasswordSet: true,
PasswordlessTokens: user_view_model.WebAuthNTokens{&user_view_model.WebAuthNView{ID: "id", State: int32(user_model.MFAStateReady)}},
OTPState: int32(user_model.MFAStateReady),
MFAMaxSetUp: int32(model.MFALevelMultiFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
args{
&model.AuthRequest{
UserID: "UserID",
LoginPolicy: &iam_model.LoginPolicyView{
SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP},
},
}, false},
[]model.NextStep{&model.MFAVerificationStep{
MFAProviders: []model.MFAType{model.MFATypeOTP},
}},
nil,
},
{
"mfa not verified, mfa check step",
fields{
@ -843,6 +917,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
args{
&model.AuthRequest{
UserID: "UserID",
LoginPolicy: &iam_model.LoginPolicyView{},
SelectedIDPConfigID: "IDPConfigID",
LinkingUsers: []*model.ExternalUser{{IDPConfigID: "IDPConfigID", ExternalUserID: "UserID", DisplayName: "DisplayName"}},
}, false},

View File

@ -36,7 +36,7 @@ type OrgRepository struct {
func (repo *OrgRepository) SearchOrgs(ctx context.Context, request *org_model.OrgSearchRequest) (*org_model.OrgSearchResult, error) {
request.EnsureLimit(repo.SearchLimit)
sequence, err := repo.View.GetLatestOrgSequence()
sequence, err := repo.View.GetLatestOrgSequence("")
logging.Log("EVENT-7Udhz").OnError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Warn("could not read latest org sequence")
members, count, err := repo.View.SearchOrgs(request)
if err != nil {

Some files were not shown because too many files have changed in this diff Show More