mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-04 23:45:07 +00:00
feat(actions): add token customization flow and extend functionally with modules (#4337)
* fix: potential memory leak * feat(actions): possibility to parse json feat(actions): possibility to perform http calls * add query call * feat(api): list flow and trigger types fix(api): switch flow and trigger types to dynamic objects * fix(translations): add action translations * use `domain.FlowType` * localizers * localization * trigger types * options on `query.Action` * add functions for actions * feat: management api: add list flow and trigger (#4352) * console changes * cleanup * fix: wrong localization Co-authored-by: Max Peintner <max@caos.ch> * id token works * check if claims not nil * feat(actions): metadata api * refactor(actions): modules * fix: allow prerelease * fix: test * feat(actions): deny list for http hosts * feat(actions): deny list for http hosts * refactor: actions * fix: different error ids * fix: rename statusCode to status * Actions objects as options (#4418) * fix: rename statusCode to status * fix(actions): objects as options * fix(actions): objects as options * fix(actions): set fields * add http client to old actions * fix(actions): add log module * fix(actions): add user to context where possible * fix(actions): add user to ctx in external authorization/pre creation * fix(actions): query correct flow in claims * test: actions * fix(id-generator): panic if no machine id * tests * maybe this? * fix linting * refactor: improve code * fix: metadata and usergrant usage in actions * fix: appendUserGrant * fix: allowedToFail and timeout in action execution * fix: allowed to fail in token complement flow * docs: add action log claim * Update defaults.yaml * fix log claim * remove prerelease build Co-authored-by: Max Peintner <max@caos.ch> Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
parent
bffb10a4b4
commit
43fb3fd1a6
@ -307,6 +307,13 @@ SystemDefaults:
|
||||
PublicKeyLifetime: 30h
|
||||
CertificateLifetime: 8766h
|
||||
|
||||
Actions:
|
||||
HTTP:
|
||||
# wildcard sub domains are currently unsupported
|
||||
DenyList:
|
||||
- localhost
|
||||
- '127.0.0.1'
|
||||
|
||||
DefaultInstance:
|
||||
InstanceName:
|
||||
DefaultLanguage: en
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"github.com/zitadel/logging"
|
||||
"github.com/zitadel/zitadel/internal/api/saml"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/actions"
|
||||
admin_es "github.com/zitadel/zitadel/internal/admin/repository/eventsourcing"
|
||||
internal_authz "github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/http/middleware"
|
||||
@ -58,6 +59,7 @@ type Config struct {
|
||||
SystemAPIUsers map[string]*internal_authz.SystemAPIUser
|
||||
CustomerPortal string
|
||||
Machine *id.Config
|
||||
Actions *actions.Config
|
||||
}
|
||||
|
||||
func MustNewConfig(v *viper.Viper) *Config {
|
||||
@ -70,6 +72,7 @@ func MustNewConfig(v *viper.Viper) *Config {
|
||||
mapstructure.StringToTimeDurationHookFunc(),
|
||||
mapstructure.StringToSliceHookFunc(","),
|
||||
database.DecodeHook,
|
||||
actions.HTTPConfigDecodeHook,
|
||||
)),
|
||||
)
|
||||
logging.OnError(err).Fatal("unable to read config")
|
||||
@ -84,6 +87,7 @@ func MustNewConfig(v *viper.Viper) *Config {
|
||||
logging.OnError(err).Fatal("unable to set meter")
|
||||
|
||||
id.Configure(config.Machine)
|
||||
actions.SetHTTPConfig(&config.Actions.HTTP)
|
||||
|
||||
return config
|
||||
}
|
||||
|
@ -21,12 +21,12 @@
|
||||
<p class="desc cnsl-secondary-text">{{ 'FLOWS.FLOWSDESCRIPTION' | translate }}</p>
|
||||
|
||||
<ng-template cnslHasRole [hasRole]="['org.flow.read']">
|
||||
<div *ngIf="flow" class="flow">
|
||||
<div class="flow">
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'FLOWS.FLOWTYPE' | translate }}</cnsl-label>
|
||||
<mat-select [formControl]="typeControl">
|
||||
<mat-option *ngFor="let type of typesForSelection" [value]="type">
|
||||
{{ 'FLOWS.TYPES.' + type | translate }}
|
||||
{{ type.name?.localizedMessage }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</cnsl-form-field>
|
||||
@ -36,8 +36,14 @@
|
||||
|
||||
<div class="flow-type">
|
||||
<i class="type-icon las la-dot-circle"></i>
|
||||
<span>{{ 'FLOWS.TYPES.' + flow.type | translate }}</span>
|
||||
<button matTooltip="{{ 'ACTIONS.CLEAR' | translate }}" mat-icon-button color="warn" (click)="clearFlow()">
|
||||
<span>{{ flow.type?.name?.localizedMessage }}</span>
|
||||
<button
|
||||
*ngIf="flow.type && (flow.triggerActionsList?.length ?? 0) > 0"
|
||||
matTooltip="{{ 'ACTIONS.CLEAR' | translate }}"
|
||||
mat-icon-button
|
||||
color="warn"
|
||||
(click)="clearFlow(flow.type.id)"
|
||||
>
|
||||
<i class="type-button-icon las la-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
@ -45,7 +51,11 @@
|
||||
<cnsl-card *ngFor="let trigger of flow.triggerActionsList; index as i" class="trigger">
|
||||
<div class="trigger-top">
|
||||
<mat-icon svgIcon="mdi_arrow_right_bottom" class="icon"></mat-icon>
|
||||
<span>{{ 'FLOWS.TRIGGERTYPES.' + trigger.triggerType | translate }}</span>
|
||||
<span>{{ trigger.triggerType?.name?.localizedMessage }}</span>
|
||||
<span class="fill-space"></span>
|
||||
<button color="warn" mat-icon-button (click)="removeTriggerActionsList(i)">
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
<span class="fill-space"></span>
|
||||
<div class="flow-action-wrapper" cdkDropList (cdkDropListDropped)="drop(i, trigger.actionsList, $event)">
|
||||
@ -72,7 +82,13 @@
|
||||
</div>
|
||||
</cnsl-card>
|
||||
|
||||
<button class="add-btn cnsl-action-button" mat-raised-button color="primary" (click)="openAddTrigger()">
|
||||
<button
|
||||
*ngIf="flow.type"
|
||||
class="add-btn cnsl-action-button"
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
(click)="openAddTrigger(flow.type)"
|
||||
>
|
||||
<mat-icon>add</mat-icon>
|
||||
<span>{{ 'FLOWS.ADDTRIGGER' | translate }}</span>
|
||||
<span *ngIf="selection && selection.length"> ({{ selection.length }})</span>
|
||||
|
@ -68,6 +68,10 @@ h1 {
|
||||
align-items: center;
|
||||
margin-bottom: 0.5rem;
|
||||
padding-left: 7px;
|
||||
|
||||
.fill-space {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { UntypedFormControl } from '@angular/forms';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { ActionKeysType } from 'src/app/modules/action-keys/action-keys.component';
|
||||
import { InfoSectionType } from 'src/app/modules/info-section/info-section.component';
|
||||
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
|
||||
@ -18,13 +19,12 @@ import { AddFlowDialogComponent } from './add-flow-dialog/add-flow-dialog.compon
|
||||
templateUrl: './actions.component.html',
|
||||
styleUrls: ['./actions.component.scss'],
|
||||
})
|
||||
export class ActionsComponent {
|
||||
export class ActionsComponent implements OnDestroy {
|
||||
public flow!: Flow.AsObject;
|
||||
public flowType: FlowType = FlowType.FLOW_TYPE_EXTERNAL_AUTHENTICATION;
|
||||
|
||||
public typeControl: UntypedFormControl = new UntypedFormControl(FlowType.FLOW_TYPE_EXTERNAL_AUTHENTICATION);
|
||||
public typeControl: UntypedFormControl = new UntypedFormControl();
|
||||
|
||||
public typesForSelection: FlowType[] = [FlowType.FLOW_TYPE_EXTERNAL_AUTHENTICATION];
|
||||
public typesForSelection: FlowType.AsObject[] = [];
|
||||
|
||||
public selection: Action.AsObject[] = [];
|
||||
public InfoSectionType: any = InfoSectionType;
|
||||
@ -32,7 +32,7 @@ export class ActionsComponent {
|
||||
|
||||
public maxActions: number | null = null;
|
||||
public ActionState: any = ActionState;
|
||||
|
||||
private destroy$: Subject<void> = new Subject();
|
||||
constructor(
|
||||
private mgmtService: ManagementService,
|
||||
breadcrumbService: BreadcrumbService,
|
||||
@ -45,16 +45,42 @@ export class ActionsComponent {
|
||||
};
|
||||
breadcrumbService.setBreadcrumb([bread]);
|
||||
|
||||
this.loadFlow();
|
||||
}
|
||||
this.getFlowTypes();
|
||||
|
||||
private loadFlow() {
|
||||
this.mgmtService.getFlow(this.flowType).then((flowResponse) => {
|
||||
if (flowResponse.flow) this.flow = flowResponse.flow;
|
||||
this.typeControl.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((value) => {
|
||||
this.loadFlow((value as FlowType.AsObject).id);
|
||||
});
|
||||
}
|
||||
|
||||
public clearFlow(): void {
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
private getFlowTypes(): Promise<void> {
|
||||
return this.mgmtService
|
||||
.listFlowTypes()
|
||||
.then((resp) => {
|
||||
this.typesForSelection = resp.resultList;
|
||||
if (!this.flow && resp.resultList[0]) {
|
||||
const type = resp.resultList[0];
|
||||
this.typeControl.setValue(type);
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
|
||||
private loadFlow(id: string) {
|
||||
this.mgmtService.getFlow(id).then((flowResponse) => {
|
||||
if (flowResponse.flow) {
|
||||
this.flow = flowResponse.flow;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public clearFlow(id: string): void {
|
||||
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
||||
data: {
|
||||
confirmKey: 'ACTIONS.CLEAR',
|
||||
@ -68,10 +94,10 @@ export class ActionsComponent {
|
||||
dialogRef.afterClosed().subscribe((resp) => {
|
||||
if (resp) {
|
||||
this.mgmtService
|
||||
.clearFlow(this.flowType)
|
||||
.then((resp) => {
|
||||
.clearFlow(id)
|
||||
.then(() => {
|
||||
this.toast.showInfo('FLOWS.FLOWCLEARED', true);
|
||||
this.loadFlow();
|
||||
this.loadFlow(id);
|
||||
})
|
||||
.catch((error: any) => {
|
||||
this.toast.showError(error);
|
||||
@ -80,11 +106,10 @@ export class ActionsComponent {
|
||||
});
|
||||
}
|
||||
|
||||
public openAddTrigger(): void {
|
||||
public openAddTrigger(flow: FlowType.AsObject, trigger?: TriggerType.AsObject): void {
|
||||
const dialogRef = this.dialog.open(AddFlowDialogComponent, {
|
||||
data: {
|
||||
flowType: this.flowType,
|
||||
triggerType: TriggerType.TRIGGER_TYPE_POST_AUTHENTICATION,
|
||||
flowType: flow,
|
||||
actions: this.selection && this.selection.length ? this.selection : [],
|
||||
},
|
||||
width: '400px',
|
||||
@ -96,7 +121,7 @@ export class ActionsComponent {
|
||||
.setTriggerActions(req.getActionIdsList(), req.getFlowType(), req.getTriggerType())
|
||||
.then((resp) => {
|
||||
this.toast.showInfo('FLOWS.FLOWCHANGED', true);
|
||||
this.loadFlow();
|
||||
this.loadFlow(flow.id);
|
||||
})
|
||||
.catch((error: any) => {
|
||||
this.toast.showError(error);
|
||||
@ -111,17 +136,52 @@ export class ActionsComponent {
|
||||
}
|
||||
|
||||
saveFlow(index: number) {
|
||||
this.mgmtService
|
||||
.setTriggerActions(
|
||||
this.flow.triggerActionsList[index].actionsList.map((action) => action.id),
|
||||
this.flowType,
|
||||
this.flow.triggerActionsList[index].triggerType,
|
||||
)
|
||||
.then((updateResponse) => {
|
||||
this.toast.showInfo('FLOWS.TOAST.ACTIONSSET', true);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
if (
|
||||
this.flow.type &&
|
||||
this.flow.triggerActionsList &&
|
||||
this.flow.triggerActionsList[index] &&
|
||||
this.flow.triggerActionsList[index]?.triggerType
|
||||
) {
|
||||
this.mgmtService
|
||||
.setTriggerActions(
|
||||
this.flow.triggerActionsList[index].actionsList.map((action) => action.id),
|
||||
this.flow.type.id,
|
||||
this.flow.triggerActionsList[index].triggerType?.id ?? '',
|
||||
)
|
||||
.then(() => {
|
||||
this.toast.showInfo('FLOWS.TOAST.ACTIONSSET', true);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public removeTriggerActionsList(index: number) {
|
||||
if (this.flow.type && this.flow.triggerActionsList && this.flow.triggerActionsList[index]) {
|
||||
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
||||
data: {
|
||||
confirmKey: 'ACTIONS.CLEAR',
|
||||
cancelKey: 'ACTIONS.CANCEL',
|
||||
titleKey: 'FLOWS.DIALOG.REMOVEACTIONSLIST.TITLE',
|
||||
descriptionKey: 'FLOWS.DIALOG.REMOVEACTIONSLIST.DESCRIPTION',
|
||||
},
|
||||
width: '400px',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((resp) => {
|
||||
if (resp) {
|
||||
this.mgmtService
|
||||
.setTriggerActions([], this.flow?.type?.id ?? '', this.flow.triggerActionsList[index].triggerType?.id ?? '')
|
||||
.then(() => {
|
||||
this.toast.showInfo('FLOWS.TOAST.ACTIONSSET', true);
|
||||
this.loadFlow(this.flow?.type?.id ?? '');
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,20 +2,11 @@
|
||||
|
||||
<div mat-dialog-content>
|
||||
<form *ngIf="form" [formGroup]="form">
|
||||
<cnsl-form-field class="form-field">
|
||||
<cnsl-label>{{ 'FLOWS.FLOWTYPE' | translate }}</cnsl-label>
|
||||
<mat-select formControlName="flowType">
|
||||
<mat-option *ngFor="let type of typesForSelection" [value]="type">
|
||||
{{ 'FLOWS.TYPES.' + type | translate }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</cnsl-form-field>
|
||||
|
||||
<cnsl-form-field class="form-field">
|
||||
<cnsl-label>{{ 'FLOWS.TRIGGERTYPE' | translate }}</cnsl-label>
|
||||
<mat-select formControlName="triggerType" name="triggerType">
|
||||
<mat-option *ngFor="let type of triggerTypesForSelection" [value]="type">
|
||||
{{ 'FLOWS.TRIGGERTYPES.' + type | translate }}
|
||||
{{ type.name?.localizedMessage }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</cnsl-form-field>
|
||||
|
@ -12,13 +12,10 @@ import { ToastService } from 'src/app/services/toast.service';
|
||||
styleUrls: ['./add-flow-dialog.component.scss'],
|
||||
})
|
||||
export class AddFlowDialogComponent {
|
||||
public flowType?: FlowType.AsObject;
|
||||
public actions: Action.AsObject[] = [];
|
||||
public typesForSelection: FlowType[] = [FlowType.FLOW_TYPE_EXTERNAL_AUTHENTICATION];
|
||||
public triggerTypesForSelection: TriggerType[] = [
|
||||
TriggerType.TRIGGER_TYPE_POST_AUTHENTICATION,
|
||||
TriggerType.TRIGGER_TYPE_POST_CREATION,
|
||||
TriggerType.TRIGGER_TYPE_PRE_CREATION,
|
||||
];
|
||||
public typesForSelection: FlowType.AsObject[] = [];
|
||||
public triggerTypesForSelection: TriggerType.AsObject[] = [];
|
||||
|
||||
public form!: UntypedFormGroup;
|
||||
constructor(
|
||||
@ -28,14 +25,30 @@ export class AddFlowDialogComponent {
|
||||
public dialogRef: MatDialogRef<AddFlowDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any,
|
||||
) {
|
||||
if (data.flowType) {
|
||||
this.flowType = data.flowType as FlowType.AsObject;
|
||||
this.getTriggerTypes((data.flowType as FlowType.AsObject).id);
|
||||
}
|
||||
|
||||
this.form = this.fb.group({
|
||||
flowType: [data.flowType ? data.flowType : '', [Validators.required]],
|
||||
triggerType: [data.triggerType ? data.triggerType : '', [Validators.required]],
|
||||
actionIdsList: [data.actions ? (data.actions as Action.AsObject[]).map((a) => a.id) : [], [Validators.required]],
|
||||
});
|
||||
|
||||
this.getActionIds();
|
||||
}
|
||||
|
||||
private getTriggerTypes(id: string): Promise<void> {
|
||||
return this.mgmtService
|
||||
.listFlowTriggerTypes(id)
|
||||
.then((resp) => {
|
||||
this.triggerTypesForSelection = resp.resultList;
|
||||
})
|
||||
.catch((error: any) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
|
||||
private getActionIds(): Promise<void> {
|
||||
return this.mgmtService
|
||||
.listActions()
|
||||
@ -52,11 +65,13 @@ export class AddFlowDialogComponent {
|
||||
}
|
||||
|
||||
public closeDialogWithSuccess(): void {
|
||||
const req = new SetTriggerActionsRequest();
|
||||
req.setActionIdsList(this.form.get('actionIdsList')?.value);
|
||||
req.setFlowType(this.form.get('flowType')?.value);
|
||||
req.setTriggerType(this.form.get('triggerType')?.value);
|
||||
if (this.flowType) {
|
||||
const req = new SetTriggerActionsRequest();
|
||||
req.setActionIdsList(this.form.get('actionIdsList')?.value);
|
||||
req.setFlowType(this.flowType.id);
|
||||
req.setTriggerType((this.form.get('triggerType')?.value as TriggerType.AsObject).id);
|
||||
|
||||
this.dialogRef.close(req);
|
||||
this.dialogRef.close(req);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -192,6 +192,10 @@ import {
|
||||
ListAppKeysResponse,
|
||||
ListAppsRequest,
|
||||
ListAppsResponse,
|
||||
ListFlowTriggerTypesRequest,
|
||||
ListFlowTriggerTypesResponse,
|
||||
ListFlowTypesRequest,
|
||||
ListFlowTypesResponse,
|
||||
ListGrantedProjectRolesRequest,
|
||||
ListGrantedProjectRolesResponse,
|
||||
ListGrantedProjectsRequest,
|
||||
@ -982,13 +986,24 @@ export class ManagementService {
|
||||
return this.grpcService.mgmt.listActions(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public getFlow(type: FlowType): Promise<GetFlowResponse.AsObject> {
|
||||
public listFlowTypes(): Promise<ListFlowTypesResponse.AsObject> {
|
||||
const req = new ListFlowTypesRequest();
|
||||
return this.grpcService.mgmt.listFlowTypes(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public listFlowTriggerTypes(type: string): Promise<ListFlowTriggerTypesResponse.AsObject> {
|
||||
const req = new ListFlowTriggerTypesRequest();
|
||||
req.setType(type);
|
||||
return this.grpcService.mgmt.listFlowTriggerTypes(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public getFlow(type: string): Promise<GetFlowResponse.AsObject> {
|
||||
const req = new GetFlowRequest();
|
||||
req.setType(type);
|
||||
return this.grpcService.mgmt.getFlow(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public clearFlow(type: FlowType): Promise<ClearFlowResponse.AsObject> {
|
||||
public clearFlow(type: string): Promise<ClearFlowResponse.AsObject> {
|
||||
const req = new ClearFlowRequest();
|
||||
req.setType(type);
|
||||
return this.grpcService.mgmt.clearFlow(req, null).then((resp) => resp.toObject());
|
||||
@ -996,8 +1011,8 @@ export class ManagementService {
|
||||
|
||||
public setTriggerActions(
|
||||
actionIdsList: string[],
|
||||
type: FlowType,
|
||||
triggerType: TriggerType,
|
||||
type: string,
|
||||
triggerType: string,
|
||||
): Promise<SetTriggerActionsResponse.AsObject> {
|
||||
const req = new SetTriggerActionsRequest();
|
||||
req.setActionIdsList(actionIdsList);
|
||||
|
@ -654,15 +654,6 @@
|
||||
"1": "inaktiv",
|
||||
"2": "aktiv"
|
||||
},
|
||||
"TYPES": {
|
||||
"0": "Unspezifisch",
|
||||
"1": "Externe Authentifizierung"
|
||||
},
|
||||
"TRIGGERTYPES": {
|
||||
"1": "Post Authentication",
|
||||
"2": "Pre Creation",
|
||||
"3": "Post Creation"
|
||||
},
|
||||
"ADDTRIGGER": "Trigger hinzufügen",
|
||||
"FLOWCHANGED": "Der Ablauf wurde geändert",
|
||||
"FLOWCLEARED": "Der Ablauf wurde zurückgesetzt",
|
||||
@ -689,6 +680,10 @@
|
||||
"CLEAR": {
|
||||
"TITLE": "Flow zurücksetzen?",
|
||||
"DESCRIPTION": "Sie sind im Begriff den Flow mitsamt seinen Triggern und Aktionen zurückzusetzen. Diese Änderung kann nicht wiederhergestellt werden."
|
||||
},
|
||||
"REMOVEACTIONSLIST": {
|
||||
"TITLE": "Ausgewählte Aktionen löschen?",
|
||||
"DESCRIPTION": "Wollen Sie die gewählten Aktionen wirklich löschen?"
|
||||
}
|
||||
},
|
||||
"TOAST": {
|
||||
|
@ -654,15 +654,6 @@
|
||||
"1": "inactive",
|
||||
"2": "active"
|
||||
},
|
||||
"TYPES": {
|
||||
"0": "Unspecified Type",
|
||||
"1": "External Authentication"
|
||||
},
|
||||
"TRIGGERTYPES": {
|
||||
"1": "Post Authentication",
|
||||
"2": "Pre Creation",
|
||||
"3": "Post Creation"
|
||||
},
|
||||
"ADDTRIGGER": "Add trigger",
|
||||
"FLOWCHANGED": "The flow was changed successfully",
|
||||
"FLOWCLEARED": "The flow was reset successfully",
|
||||
@ -689,6 +680,10 @@
|
||||
"CLEAR": {
|
||||
"TITLE": "Clear flow?",
|
||||
"DESCRIPTION": "You are about to reset the flow along with its triggers and actions. This change cannot be restored. Are you sure?"
|
||||
},
|
||||
"REMOVEACTIONSLIST": {
|
||||
"TITLE": "Delete selected Actions?",
|
||||
"DESCRIPTION": "Are you sure you want to delete the selected actions from the flow?"
|
||||
}
|
||||
},
|
||||
"TOAST": {
|
||||
|
@ -654,15 +654,6 @@
|
||||
"1": "inactif",
|
||||
"2": "actif"
|
||||
},
|
||||
"TYPES": {
|
||||
"0": "Type non spécifié",
|
||||
"1": "Authentification externe"
|
||||
},
|
||||
"TRIGGERTYPES": {
|
||||
"1": "Post Authentification",
|
||||
"2": "Pré création",
|
||||
"3": "Post création"
|
||||
},
|
||||
"ADDTRIGGER": "Ajouter un déclencheur",
|
||||
"FLOWCHANGED": "Le processus a été modifié",
|
||||
"FLOWCLEARED": "Le processus a été supprimé avec succès",
|
||||
@ -689,6 +680,10 @@
|
||||
"CLEAR": {
|
||||
"TITLE": "Effacer le flux ?",
|
||||
"DESCRIPTION": "Vous êtes sur le point de réinitialiser le flux ainsi que ses déclencheurs et actions. Ce changement ne peut pas être restauré. Etes-vous sûr ?"
|
||||
},
|
||||
"REMOVEACTIONSLIST": {
|
||||
"TITLE": "Supprimer les actions sélectionnées ?",
|
||||
"DESCRIPTION": "Voulez-vous vraiment supprimer les actions sélectionnées du flux ?"
|
||||
}
|
||||
},
|
||||
"TOAST": {
|
||||
|
@ -654,15 +654,6 @@
|
||||
"1": "inattivo",
|
||||
"2": "attivo"
|
||||
},
|
||||
"TYPES": {
|
||||
"0": "Non specifico",
|
||||
"1": "Autenticazione esterna"
|
||||
},
|
||||
"TRIGGERTYPES": {
|
||||
"1": "Post autenticazione",
|
||||
"2": "Pre creazione",
|
||||
"3": "Post creazione"
|
||||
},
|
||||
"ADDTRIGGER": "Aggiungi trigger",
|
||||
"FLOWCHANGED": "Il processo è stato modificato con successo.",
|
||||
"FLOWCLEARED": "Il processo è stato eliminato con successo.",
|
||||
@ -689,6 +680,10 @@
|
||||
"CLEAR": {
|
||||
"TITLE": "Elimina processo",
|
||||
"DESCRIPTION": "Stai per eliminare un processo. Questa azione non può essere annullata. Vuoi continuare?"
|
||||
},
|
||||
"REMOVEACTIONSLIST": {
|
||||
"TITLE": "Elimina le azioni selezionate?",
|
||||
"DESCRIPTION": "Sei sicuro di voler eliminare le azioni selezionate dal processo?"
|
||||
}
|
||||
},
|
||||
"TOAST": {
|
||||
|
@ -654,15 +654,6 @@
|
||||
"1": "停用",
|
||||
"2": "激活"
|
||||
},
|
||||
"TYPES": {
|
||||
"0": "未指定类型",
|
||||
"1": "外部认证"
|
||||
},
|
||||
"TRIGGERTYPES": {
|
||||
"1": "身份验证后",
|
||||
"2": "创建之前",
|
||||
"3": "创建时"
|
||||
},
|
||||
"ADDTRIGGER": "添加触发器",
|
||||
"FLOWCHANGED": "流量已成功更改",
|
||||
"FLOWCLEARED": "流已成功删除",
|
||||
@ -689,6 +680,10 @@
|
||||
"CLEAR": {
|
||||
"TITLE": "清理流程?",
|
||||
"DESCRIPTION": "您即将重置流程及其触发器和动作。此更改无法恢复。你确定吗?"
|
||||
},
|
||||
"REMOVEACTIONSLIST": {
|
||||
"TITLE": "删除选定的操作?",
|
||||
"DESCRIPTION": "您确定要从流中删除选定的操作吗?"
|
||||
}
|
||||
},
|
||||
"TOAST": {
|
||||
|
@ -91,9 +91,9 @@ ZITADEL supports only the external authentication flow at the moment.
|
||||
|
||||
```ts
|
||||
{
|
||||
ProjectID: string,
|
||||
ProjectGrantID: string,
|
||||
Roles: Array<string>,
|
||||
projectID: string,
|
||||
projectGrantID: string,
|
||||
roles: Array<string>,
|
||||
}
|
||||
```
|
||||
|
||||
@ -103,4 +103,4 @@ ZITADEL supports only the external authentication flow at the moment.
|
||||
## Further reading
|
||||
|
||||
- [Actions concept](../concepts/features/actions)
|
||||
- [Actions guide](../guides/manage/customize/behavior)
|
||||
- [Actions guide](../guides/manage/customize/behavior)
|
||||
|
@ -39,31 +39,31 @@ Please check below the matrix for an overview where which scope is asserted.
|
||||
|
||||
## Standard Claims
|
||||
|
||||
| Claims | Example | Description |
|
||||
|:-------------------|:-----------------------------------------|-----------------------------------------------------------------------------------------------|
|
||||
| acr | TBA | TBA |
|
||||
| address | `Teufener Strasse 19, 9000 St. Gallen` | TBA |
|
||||
| Claims | Example | Description |
|
||||
|:-------------------|:-----------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| acr | TBA | TBA |
|
||||
| address | `Teufener Strasse 19, 9000 St. Gallen` | TBA |
|
||||
| amr | `pwd mfa` | Authentication Method References as defined in [RFC8176](https://tools.ietf.org/html/rfc8176) <br/> `password` value is deprecated, please check `pwd` |
|
||||
| aud | `69234237810729019` | The audience of the token, by default all client id's and the project id are included |
|
||||
| auth_time | `1311280969` | Unix time of the authentication |
|
||||
| azp | `69234237810729234` | Client id of the client who requested the token |
|
||||
| email | `road.runner@acme.ch` | Email Address of the subject |
|
||||
| email_verified | `true` | Boolean if the email was verified by ZITADEL |
|
||||
| exp | `1311281970` | Time the token expires (as unix time) |
|
||||
| family_name | `Runner` | The subjects family name |
|
||||
| gender | `other` | Gender of the subject |
|
||||
| given_name | `Road` | Given name of the subject |
|
||||
| iat | `1311280970` | Time of the token was issued at (as unix time) |
|
||||
| iss | `{your_domain}` | Issuing domain of a token |
|
||||
| jti | `69234237813329048` | Unique id of the token |
|
||||
| locale | `en` | Language from the subject |
|
||||
| name | `Road Runner` | The subjects full name |
|
||||
| nbf | `1311280970` | Time the token must not be used before (as unix time) |
|
||||
| nonce | `blQtVEJHNTF0WHhFQmhqZ0RqeHJsdzdkd2d...` | The nonce provided by the client |
|
||||
| phone | `+41 79 XXX XX XX` | Phone number provided by the user |
|
||||
| phone_verified | `true` | Boolean if the phone was verified by ZITADEL |
|
||||
| preferred_username | `road.runner@acme.caos.ch` | ZITADEL's login name of the user. Consist of `username@primarydomain` |
|
||||
| sub | `77776025198584418` | Subject ID of the user |
|
||||
| aud | `69234237810729019` | The audience of the token, by default all client id's and the project id are included |
|
||||
| auth_time | `1311280969` | Unix time of the authentication |
|
||||
| azp | `69234237810729234` | Client id of the client who requested the token |
|
||||
| email | `road.runner@acme.ch` | Email Address of the subject |
|
||||
| email_verified | `true` | Boolean if the email was verified by ZITADEL |
|
||||
| exp | `1311281970` | Time the token expires (as unix time) |
|
||||
| family_name | `Runner` | The subjects family name |
|
||||
| gender | `other` | Gender of the subject |
|
||||
| given_name | `Road` | Given name of the subject |
|
||||
| iat | `1311280970` | Time of the token was issued at (as unix time) |
|
||||
| iss | `{your_domain}` | Issuing domain of a token |
|
||||
| jti | `69234237813329048` | Unique id of the token |
|
||||
| locale | `en` | Language from the subject |
|
||||
| name | `Road Runner` | The subjects full name |
|
||||
| nbf | `1311280970` | Time the token must not be used before (as unix time) |
|
||||
| nonce | `blQtVEJHNTF0WHhFQmhqZ0RqeHJsdzdkd2d...` | The nonce provided by the client |
|
||||
| phone | `+41 79 XXX XX XX` | Phone number provided by the user |
|
||||
| phone_verified | `true` | Boolean if the phone was verified by ZITADEL |
|
||||
| preferred_username | `road.runner@acme.caos.ch` | ZITADEL's login name of the user. Consist of `username@primarydomain` |
|
||||
| sub | `77776025198584418` | Subject ID of the user |
|
||||
|
||||
## Custom Claims
|
||||
|
||||
@ -75,6 +75,7 @@ ZITADEL reserves some claims to assert certain data. Please check out the [reser
|
||||
|
||||
| Claims | Example | Description |
|
||||
|:--------------------------------------------------|:-----------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| urn:zitadel:iam:action:{actionname}:log | `{"urn:zitadel:iam:action:appendCustomClaims:log": ["test log", "another test log"]}` | This claim is set during Actions as a log, e.g. if two custom claims with the same keys are set. |
|
||||
| urn:zitadel:iam:org:domain:primary:{domainname} | `{"urn:zitadel:iam:org:domain:primary": "acme.ch"}` | This claim represents the primary domain of the organization the user belongs to. |
|
||||
| urn:zitadel:iam:org:project:roles | `{"urn:zitadel:iam:org:project:roles": [ {"user": {"id1": "acme.zitade.ch", "id2": "caos.ch"} } ] }` | When roles are asserted, ZITADEL does this by providing the `id` and `primaryDomain` below the role. This gives you the option to check in which organization a user has the role. |
|
||||
| urn:zitadel:iam:roles:{rolename} | TBA | TBA |
|
||||
|
@ -66,7 +66,7 @@ ActionStateQuery is always equals
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| type | FlowType | - | |
|
||||
| type | FlowType | id of the flow type | |
|
||||
| details | zitadel.v1.ObjectDetails | - | |
|
||||
| state | FlowState | - | |
|
||||
| trigger_actions | repeated TriggerAction | - | |
|
||||
@ -74,24 +74,14 @@ ActionStateQuery is always equals
|
||||
|
||||
|
||||
|
||||
### FlowStateQuery
|
||||
FlowStateQuery is always equals
|
||||
### FlowType
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| state | FlowState | - | enum.defined_only: true<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### FlowTypeQuery
|
||||
FlowTypeQuery is always equals
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| state | FlowType | - | enum.defined_only: true<br /> |
|
||||
| id | string | identifier of the type | |
|
||||
| name | zitadel.v1.LocalizedMessage | key and name of the type | |
|
||||
|
||||
|
||||
|
||||
@ -102,12 +92,24 @@ FlowTypeQuery is always equals
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| trigger_type | TriggerType | - | |
|
||||
| trigger_type | TriggerType | id of the trigger type | |
|
||||
| actions | repeated Action | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### TriggerType
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| id | string | identifier of the type | |
|
||||
| name | zitadel.v1.LocalizedMessage | key and name of the type | |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Enums
|
||||
@ -138,18 +140,6 @@ FlowTypeQuery is always equals
|
||||
|
||||
|
||||
|
||||
### FlowFieldName {#flowfieldname}
|
||||
|
||||
|
||||
| Name | Number | Description |
|
||||
| ---- | ------ | ----------- |
|
||||
| FLOW_FIELD_NAME_UNSPECIFIED | 0 | - |
|
||||
| FLOW_FIELD_NAME_TYPE | 1 | - |
|
||||
| FLOW_FIELD_NAME_STATE | 2 | - |
|
||||
|
||||
|
||||
|
||||
|
||||
### FlowState {#flowstate}
|
||||
|
||||
|
||||
@ -162,27 +152,3 @@ FlowTypeQuery is always equals
|
||||
|
||||
|
||||
|
||||
### FlowType {#flowtype}
|
||||
|
||||
|
||||
| Name | Number | Description |
|
||||
| ---- | ------ | ----------- |
|
||||
| FLOW_TYPE_UNSPECIFIED | 0 | - |
|
||||
| FLOW_TYPE_EXTERNAL_AUTHENTICATION | 1 | - |
|
||||
|
||||
|
||||
|
||||
|
||||
### TriggerType {#triggertype}
|
||||
|
||||
|
||||
| Name | Number | Description |
|
||||
| ---- | ------ | ----------- |
|
||||
| TRIGGER_TYPE_UNSPECIFIED | 0 | - |
|
||||
| TRIGGER_TYPE_POST_AUTHENTICATION | 1 | - |
|
||||
| TRIGGER_TYPE_PRE_CREATION | 2 | - |
|
||||
| TRIGGER_TYPE_POST_CREATION | 3 | - |
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -2956,6 +2956,30 @@ Change JWT identity provider configuration of the organisation
|
||||
DELETE: /actions/{id}
|
||||
|
||||
|
||||
### ListFlowTypes
|
||||
|
||||
> **rpc** ListFlowTypes([ListFlowTypesRequest](#listflowtypesrequest))
|
||||
[ListFlowTypesResponse](#listflowtypesresponse)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
POST: /flows/types/_search
|
||||
|
||||
|
||||
### ListFlowTriggerTypes
|
||||
|
||||
> **rpc** ListFlowTriggerTypes([ListFlowTriggerTypesRequest](#listflowtriggertypesrequest))
|
||||
[ListFlowTriggerTypesResponse](#listflowtriggertypesresponse)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
POST: /flows/{type}/triggers/_search
|
||||
|
||||
|
||||
### GetFlow
|
||||
|
||||
> **rpc** GetFlow([GetFlowRequest](#getflowrequest))
|
||||
@ -4015,7 +4039,7 @@ This is an empty request
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| type | zitadel.action.v1.FlowType | - | |
|
||||
| type | string | id of the flow | |
|
||||
|
||||
|
||||
|
||||
@ -4774,7 +4798,7 @@ This is an empty request
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| type | zitadel.action.v1.FlowType | - | |
|
||||
| type | string | id of the flow | |
|
||||
|
||||
|
||||
|
||||
@ -5611,6 +5635,45 @@ This is an empty response
|
||||
|
||||
|
||||
|
||||
### ListFlowTriggerTypesRequest
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| type | string | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### ListFlowTriggerTypesResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| result | repeated zitadel.action.v1.TriggerType | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### ListFlowTypesRequest
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### ListFlowTypesResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| result | repeated zitadel.action.v1.FlowType | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### ListGrantedProjectRolesRequest
|
||||
|
||||
|
||||
@ -7896,8 +7959,8 @@ This is an empty request
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| flow_type | zitadel.action.v1.FlowType | - | |
|
||||
| trigger_type | zitadel.action.v1.TriggerType | - | |
|
||||
| flow_type | string | id of the flow type | |
|
||||
| trigger_type | string | id of the trigger type | |
|
||||
| action_ids | repeated string | - | |
|
||||
|
||||
|
||||
|
22
go.mod
22
go.mod
@ -14,10 +14,10 @@ require (
|
||||
github.com/allegro/bigcache v1.2.1
|
||||
github.com/boombuler/barcode v1.0.1
|
||||
github.com/cockroachdb/cockroach-go/v2 v2.2.4
|
||||
github.com/dop251/goja v0.0.0-20211129110639-4739a1d10a51
|
||||
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d
|
||||
github.com/dop251/goja v0.0.0-20220815083517-0c74f9139fd6
|
||||
github.com/dop251/goja_nodejs v0.0.0-20220905124449-678b33ca5009
|
||||
github.com/duo-labs/webauthn v0.0.0-20211216225436-9a12cd078b8a
|
||||
github.com/envoyproxy/protoc-gen-validate v0.6.2
|
||||
github.com/envoyproxy/protoc-gen-validate v0.6.7
|
||||
github.com/golang/glog v1.0.0
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/golang/protobuf v1.5.2
|
||||
@ -27,7 +27,7 @@ require (
|
||||
github.com/gorilla/securecookie v1.1.1
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.1
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.10.1
|
||||
github.com/improbable-eng/grpc-web v0.15.0
|
||||
github.com/jackc/pgconn v1.12.1
|
||||
github.com/jackc/pgtype v1.11.0
|
||||
@ -66,14 +66,14 @@ require (
|
||||
go.opentelemetry.io/otel/sdk/metric v0.25.0
|
||||
go.opentelemetry.io/otel/trace v1.2.0
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
|
||||
golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd
|
||||
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/text v0.3.7
|
||||
golang.org/x/tools v0.1.11
|
||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa
|
||||
google.golang.org/grpc v1.43.0
|
||||
google.golang.org/protobuf v1.27.1
|
||||
google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd
|
||||
google.golang.org/grpc v1.46.2
|
||||
google.golang.org/protobuf v1.28.0
|
||||
gopkg.in/square/go-jose.v2 v2.6.0
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
)
|
||||
@ -94,7 +94,7 @@ require (
|
||||
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
|
||||
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect
|
||||
github.com/dlclark/regexp2 v1.7.0 // indirect
|
||||
github.com/drone/envsubst v1.0.3
|
||||
github.com/dsoprea/go-exif v0.0.0-20210131231135-d154f10435cc // indirect
|
||||
github.com/dsoprea/go-exif/v2 v2.0.0-20200604193436-ca8584a0e1c4 // indirect
|
||||
@ -105,7 +105,7 @@ require (
|
||||
github.com/dsoprea/go-png-image-structure v0.0.0-20200807080309-a98d4e94ac82 // indirect
|
||||
github.com/dsoprea/go-utility v0.0.0-20200512094054-1abbbc781176 // indirect
|
||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||
github.com/envoyproxy/go-control-plane v0.10.1 // indirect
|
||||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.2 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.2.0 // indirect
|
||||
|
45
go.sum
45
go.sum
@ -182,14 +182,16 @@ github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xb
|
||||
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I=
|
||||
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E=
|
||||
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
|
||||
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
|
||||
github.com/dop251/goja v0.0.0-20211129110639-4739a1d10a51 h1:HQgSQ8RIZIhMwjefT7S2jtq99fnGHSN+BF0nkZ+myiI=
|
||||
github.com/dop251/goja v0.0.0-20211129110639-4739a1d10a51/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
|
||||
github.com/dop251/goja v0.0.0-20220815083517-0c74f9139fd6 h1:xHdUVG+c8SWJnct16Z3QJOVlaYo3OwoJyamo6kR6OL0=
|
||||
github.com/dop251/goja v0.0.0-20220815083517-0c74f9139fd6/go.mod h1:yRkwfj0CBpOGre+TwBsqPV0IH0Pk73e4PXJOeNDboGs=
|
||||
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
|
||||
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d h1:W1n4DvpzZGOISgp7wWNtraLcHtnmnTwBlJidqtMIuwQ=
|
||||
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM=
|
||||
github.com/dop251/goja_nodejs v0.0.0-20220905124449-678b33ca5009 h1:HIFRhi3kCQY7NMLX8WaoO0WNBLOKWrkWiQpxtvVwbS4=
|
||||
github.com/dop251/goja_nodejs v0.0.0-20220905124449-678b33ca5009/go.mod h1:+CJy9V5cGycP5qwp6RM5jLg+TFEMyGtD7A9xUbU/BOQ=
|
||||
github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g=
|
||||
github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g=
|
||||
github.com/dsoprea/go-exif v0.0.0-20210131231135-d154f10435cc h1:AuzYp98IFVOi0NU/WcZyGDQ6vAh/zkCjxGD3kt8aLzA=
|
||||
@ -228,11 +230,13 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||
github.com/envoyproxy/go-control-plane v0.10.1 h1:cgDRLG7bs59Zd+apAWuzLQL95obVYAymNJek76W3mgw=
|
||||
github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ=
|
||||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1 h1:xvqufLtNVwAhN8NMyWklVgxnWohi+wtMGQMhtxexlm0=
|
||||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.6.2 h1:JiO+kJTpmYGjEodY7O1Zk8oZcNz1+f30UtwtXoFUPzE=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.6.7 h1:qcZcULcd/abmQg6dwigimCNEyi4gg31M/xaciQlDml8=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
@ -432,8 +436,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.1 h1:p5m7GOEGXyoq6QWl4/RRMsQ6tWbTpbQmAnkxXgWSprY=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.1/go.mod h1:8ZeZajTed/blCOHBbj8Fss8bPHiFKcmJJzuIbUtFCAo=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.10.1 h1:Y7pyy1viWfoKMUVxmjfI5X6fVLlen75kdYjeIwl9CKc=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.10.1/go.mod h1:chrfS3YoLAlKTRE5cFWvCbt8uGAjshktT4PveTUpsFQ=
|
||||
github.com/h2non/filetype v1.1.1 h1:xvOwnXKAckvtLWsN398qS9QhlxlnVXBjXBydK2/UFB4=
|
||||
github.com/h2non/filetype v1.1.1/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
|
||||
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
|
||||
@ -636,6 +640,7 @@ github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
|
||||
github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
|
||||
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
|
||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
@ -901,10 +906,6 @@ github.com/zitadel/logging v0.3.4 h1:9hZsTjMMTE3X2LUi0xcF9Q9EdLo+FAezeu52ireBbHM
|
||||
github.com/zitadel/logging v0.3.4/go.mod h1:aPpLQhE+v6ocNK0TWrBrd363hZ95KcI17Q1ixAQwZF0=
|
||||
github.com/zitadel/oidc/v2 v2.0.0-dynamic-issuer.5 h1:dP+6SheVtpF4T/oql6mJoqou8jlW3J/9NCTYnEpKgpM=
|
||||
github.com/zitadel/oidc/v2 v2.0.0-dynamic-issuer.5/go.mod h1:uoJw5Xc6HXfnQbNZiLbld9dED0/8UMu0M4gOipTRZBA=
|
||||
github.com/zitadel/saml v0.0.4 h1:xz97KyKD3mrQcIEKi0aUPyNX834xm3p1ToO9HK/vVeY=
|
||||
github.com/zitadel/saml v0.0.4/go.mod h1:DIy/ln32rNYv/bIBA8uOB6Y2JmxjZldDYBeMNn7YyeQ=
|
||||
github.com/zitadel/saml v0.0.5 h1:ufLE0MeWe2SLGGkSWbY3J20xxYtAL57IddeDsyMqCuM=
|
||||
github.com/zitadel/saml v0.0.5/go.mod h1:DIy/ln32rNYv/bIBA8uOB6Y2JmxjZldDYBeMNn7YyeQ=
|
||||
github.com/zitadel/saml v0.0.6 h1:avcOanSNd0x4jkn/+MsXIGQpJOvwqkk+slGJVIhqk84=
|
||||
github.com/zitadel/saml v0.0.6/go.mod h1:DIy/ln32rNYv/bIBA8uOB6Y2JmxjZldDYBeMNn7YyeQ=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
@ -1093,8 +1094,8 @@ golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20210716203947-853a461950ff/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba h1:6u6sik+bn/y7vILcYkK3iwTBWN7WtBvB0+SZswQnbf8=
|
||||
golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@ -1111,8 +1112,9 @@ golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ
|
||||
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg=
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 h1:OSnWWcOd/CtWQC2cYSBgbTSJv3ciqd8r54ySIW2y3RE=
|
||||
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -1208,11 +1210,13 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@ -1415,8 +1419,9 @@ google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ6
|
||||
google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa h1:I0YcKz0I7OAhddo7ya8kMnvprhcWM045PmkBdMO9zN0=
|
||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd h1:e0TwkXOdbnH/1x5rc5MZ/VYyiZ4v+RdVfrGMqEwT68I=
|
||||
google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
|
||||
@ -1451,8 +1456,9 @@ google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9K
|
||||
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
|
||||
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM=
|
||||
google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.46.2 h1:u+MLGgVf7vRdjEYZ8wDFhAVNmhkbJ5hmrA1LMWK1CAQ=
|
||||
google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
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=
|
||||
@ -1466,8 +1472,9 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
@ -1,112 +1,113 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
"fmt"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"github.com/dop251/goja_nodejs/console"
|
||||
"github.com/dop251/goja_nodejs/require"
|
||||
"github.com/zitadel/logging"
|
||||
z_errs "github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
)
|
||||
|
||||
var ErrHalt = errors.New("interrupt")
|
||||
type Config struct {
|
||||
HTTP HTTPConfig
|
||||
}
|
||||
|
||||
type jsAction func(*Context, *API) error
|
||||
var (
|
||||
ErrHalt = errors.New("interrupt")
|
||||
)
|
||||
|
||||
func Run(ctx *Context, api *API, script, name string, timeout time.Duration, allowedToFail bool) error {
|
||||
if timeout <= 0 || timeout > 20 {
|
||||
timeout = 20 * time.Second
|
||||
}
|
||||
prepareTimeout := timeout
|
||||
if prepareTimeout > 5 {
|
||||
prepareTimeout = 5 * time.Second
|
||||
}
|
||||
vm, err := prepareRun(script, prepareTimeout)
|
||||
type jsAction func(fields, fields) error
|
||||
|
||||
func Run(ctx context.Context, ctxParam contextFields, apiParam apiFields, script, name string, opts ...Option) error {
|
||||
config, err := prepareRun(ctx, ctxParam, apiParam, script, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var fn jsAction
|
||||
jsFn := vm.Get(name)
|
||||
jsFn := config.vm.Get(name)
|
||||
if jsFn == nil {
|
||||
return errors.New("function not found")
|
||||
}
|
||||
err = vm.ExportTo(jsFn, &fn)
|
||||
err = config.vm.ExportTo(jsFn, &fn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t := setInterrupt(vm, timeout)
|
||||
|
||||
t := config.Start()
|
||||
defer func() {
|
||||
t.Stop()
|
||||
}()
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r != nil && !allowedToFail {
|
||||
err, ok := r.(error)
|
||||
if !ok {
|
||||
e, ok := r.(string)
|
||||
if ok {
|
||||
err = errors.New(e)
|
||||
}
|
||||
}
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
}()
|
||||
err = fn(ctx, api)
|
||||
if err != nil && !allowedToFail {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
errCh <- nil
|
||||
}()
|
||||
return <-errCh
|
||||
|
||||
return executeFn(config, fn)
|
||||
}
|
||||
|
||||
func newRuntime() *goja.Runtime {
|
||||
vm := goja.New()
|
||||
func prepareRun(ctx context.Context, ctxParam contextFields, apiParam apiFields, script string, opts []Option) (config *runConfig, err error) {
|
||||
config = newRunConfig(ctx, opts...)
|
||||
if config.timeout == 0 {
|
||||
return nil, z_errs.ThrowInternal(nil, "ACTIO-uCpCx", "Errrors.Internal")
|
||||
}
|
||||
t := config.Prepare()
|
||||
defer func() {
|
||||
t.Stop()
|
||||
}()
|
||||
|
||||
if ctxParam != nil {
|
||||
ctxParam(config.ctxParam)
|
||||
}
|
||||
if apiParam != nil {
|
||||
apiParam(config.apiParam)
|
||||
}
|
||||
|
||||
printer := console.PrinterFunc(func(s string) {
|
||||
logging.Log("ACTIONS-dfgg2").Debug(s)
|
||||
})
|
||||
registry := new(require.Registry)
|
||||
registry.Enable(vm)
|
||||
registry.RegisterNativeModule("console", console.RequireWithPrinter(printer))
|
||||
console.Enable(vm)
|
||||
registry.Enable(config.vm)
|
||||
|
||||
return vm
|
||||
}
|
||||
for name, loader := range config.modules {
|
||||
registry.RegisterNativeModule(name, loader)
|
||||
}
|
||||
|
||||
func prepareRun(script string, timeout time.Duration) (*goja.Runtime, error) {
|
||||
vm := newRuntime()
|
||||
t := setInterrupt(vm, timeout)
|
||||
// overload error if function panics
|
||||
defer func() {
|
||||
t.Stop()
|
||||
}()
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r != nil {
|
||||
errCh <- r.(error)
|
||||
return
|
||||
}
|
||||
}()
|
||||
_, err := vm.RunString(script)
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
r := recover()
|
||||
if r != nil {
|
||||
err = r.(error)
|
||||
return
|
||||
}
|
||||
errCh <- nil
|
||||
}()
|
||||
return vm, <-errCh
|
||||
_, err = config.vm.RunString(script)
|
||||
return config, err
|
||||
}
|
||||
|
||||
func setInterrupt(vm *goja.Runtime, timeout time.Duration) *time.Timer {
|
||||
vm.ClearInterrupt()
|
||||
return time.AfterFunc(timeout, func() {
|
||||
vm.Interrupt(ErrHalt)
|
||||
})
|
||||
func executeFn(config *runConfig, fn jsAction) (err error) {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r != nil && !config.allowedToFail {
|
||||
var ok bool
|
||||
if err, ok = r.(error); ok {
|
||||
return
|
||||
}
|
||||
|
||||
e, ok := r.(string)
|
||||
if ok {
|
||||
err = errors.New(e)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("unknown error occured: %v", r)
|
||||
}
|
||||
}()
|
||||
err = fn(config.ctxParam.fields, config.apiParam.fields)
|
||||
if err != nil && !config.allowedToFail {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ActionToOptions(a *query.Action) []Option {
|
||||
opts := make([]Option, 0, 1)
|
||||
if a.AllowedToFail {
|
||||
opts = append(opts, WithAllowedToFail())
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
69
internal/actions/actions_test.go
Normal file
69
internal/actions/actions_test.go
Normal file
@ -0,0 +1,69 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
)
|
||||
|
||||
func TestRun(t *testing.T) {
|
||||
type args struct {
|
||||
timeout time.Duration
|
||||
api apiFields
|
||||
ctx contextFields
|
||||
script string
|
||||
name string
|
||||
opts []Option
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr func(error) bool
|
||||
}{
|
||||
{
|
||||
name: "simple script",
|
||||
args: args{
|
||||
api: nil,
|
||||
script: `
|
||||
function testFunc() {
|
||||
for (i = 0; i < 10; i++) {}
|
||||
}`,
|
||||
name: "testFunc",
|
||||
opts: []Option{},
|
||||
},
|
||||
wantErr: func(err error) bool { return err == nil },
|
||||
},
|
||||
{
|
||||
name: "throw error",
|
||||
args: args{
|
||||
api: nil,
|
||||
script: "function testFunc() {throw 'some error'}",
|
||||
name: "testFunc",
|
||||
opts: []Option{},
|
||||
},
|
||||
wantErr: func(err error) bool {
|
||||
gojaErr := new(goja.Exception)
|
||||
if errors.As(err, &gojaErr) {
|
||||
return gojaErr.Value().String() == "some error"
|
||||
}
|
||||
return false
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.args.timeout == 0 {
|
||||
tt.args.timeout = 10 * time.Second
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), tt.args.timeout)
|
||||
if err := Run(ctx, tt.args.ctx, tt.args.api, tt.args.script, tt.args.name, tt.args.opts...); !tt.wantErr(err) {
|
||||
t.Errorf("Run() unexpected error = (%[1]T) %[1]v", err)
|
||||
}
|
||||
cancel()
|
||||
})
|
||||
}
|
||||
}
|
@ -1,105 +1,19 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
type API map[string]interface{}
|
||||
|
||||
func (a API) set(name string, value interface{}) {
|
||||
map[string]interface{}(a)[name] = value
|
||||
type apiConfig struct {
|
||||
FieldConfig
|
||||
}
|
||||
|
||||
func (a *API) SetHuman(human *domain.Human) *API {
|
||||
a.set("setFirstName", func(firstName string) {
|
||||
human.FirstName = firstName
|
||||
})
|
||||
a.set("setLastName", func(lastName string) {
|
||||
human.LastName = lastName
|
||||
})
|
||||
a.set("setNickName", func(nickName string) {
|
||||
human.NickName = nickName
|
||||
})
|
||||
a.set("setDisplayName", func(displayName string) {
|
||||
human.DisplayName = displayName
|
||||
})
|
||||
a.set("setPreferredLanguage", func(preferredLanguage string) {
|
||||
human.PreferredLanguage = language.Make(preferredLanguage)
|
||||
})
|
||||
a.set("setGender", func(gender domain.Gender) {
|
||||
human.Gender = gender
|
||||
})
|
||||
a.set("setUsername", func(username string) {
|
||||
human.Username = username
|
||||
})
|
||||
a.set("setEmail", func(email string) {
|
||||
if human.Email == nil {
|
||||
human.Email = &domain.Email{}
|
||||
type apiFields func(*apiConfig)
|
||||
|
||||
func WithAPIFields(opts ...FieldOption) apiFields {
|
||||
return func(p *apiConfig) {
|
||||
if p.fields == nil {
|
||||
p.fields = fields{}
|
||||
}
|
||||
human.Email.EmailAddress = email
|
||||
})
|
||||
a.set("setEmailVerified", func(verified bool) {
|
||||
if human.Email == nil {
|
||||
return
|
||||
}
|
||||
human.Email.IsEmailVerified = verified
|
||||
})
|
||||
a.set("setPhone", func(email string) {
|
||||
if human.Phone == nil {
|
||||
human.Phone = &domain.Phone{}
|
||||
}
|
||||
human.Phone.PhoneNumber = email
|
||||
})
|
||||
a.set("setPhoneVerified", func(verified bool) {
|
||||
if human.Phone == nil {
|
||||
return
|
||||
}
|
||||
human.Phone.IsPhoneVerified = verified
|
||||
})
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *API) SetExternalUser(user *domain.ExternalUser) *API {
|
||||
a.set("setFirstName", func(firstName string) {
|
||||
user.FirstName = firstName
|
||||
})
|
||||
a.set("setLastName", func(lastName string) {
|
||||
user.LastName = lastName
|
||||
})
|
||||
a.set("setNickName", func(nickName string) {
|
||||
user.NickName = nickName
|
||||
})
|
||||
a.set("setDisplayName", func(displayName string) {
|
||||
user.DisplayName = displayName
|
||||
})
|
||||
a.set("setPreferredLanguage", func(preferredLanguage string) {
|
||||
user.PreferredLanguage = language.Make(preferredLanguage)
|
||||
})
|
||||
a.set("setPreferredUsername", func(username string) {
|
||||
user.PreferredUsername = username
|
||||
})
|
||||
a.set("setEmail", func(email string) {
|
||||
user.Email = email
|
||||
})
|
||||
a.set("setEmailVerified", func(verified bool) {
|
||||
user.IsEmailVerified = verified
|
||||
})
|
||||
a.set("setPhone", func(phone string) {
|
||||
user.Phone = phone
|
||||
})
|
||||
a.set("setPhoneVerified", func(verified bool) {
|
||||
user.IsPhoneVerified = verified
|
||||
})
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *API) SetMetadata(metadata *[]*domain.Metadata) *API {
|
||||
a.set("metadata", metadata)
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *API) SetUserGrants(usergrants *[]UserGrant) *API {
|
||||
a.set("userGrants", usergrants)
|
||||
return a
|
||||
for _, opt := range opts {
|
||||
opt(&p.FieldConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
86
internal/actions/config.go
Normal file
86
internal/actions/config.go
Normal file
@ -0,0 +1,86 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"github.com/dop251/goja_nodejs/require"
|
||||
"github.com/zitadel/logging"
|
||||
)
|
||||
|
||||
const (
|
||||
maxPrepareTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
type Option func(*runConfig)
|
||||
|
||||
func WithAllowedToFail() Option {
|
||||
return func(c *runConfig) {
|
||||
c.allowedToFail = true
|
||||
}
|
||||
}
|
||||
|
||||
type runConfig struct {
|
||||
allowedToFail bool
|
||||
timeout,
|
||||
prepareTimeout time.Duration
|
||||
modules map[string]require.ModuleLoader
|
||||
|
||||
vm *goja.Runtime
|
||||
ctxParam *ctxConfig
|
||||
apiParam *apiConfig
|
||||
}
|
||||
|
||||
func newRunConfig(ctx context.Context, opts ...Option) *runConfig {
|
||||
deadline, ok := ctx.Deadline()
|
||||
if !ok {
|
||||
logging.Warn("no timeout set on action run")
|
||||
}
|
||||
|
||||
vm := goja.New()
|
||||
vm.SetFieldNameMapper(goja.UncapFieldNameMapper())
|
||||
|
||||
config := &runConfig{
|
||||
timeout: time.Until(deadline),
|
||||
prepareTimeout: maxPrepareTimeout,
|
||||
modules: map[string]require.ModuleLoader{},
|
||||
vm: vm,
|
||||
ctxParam: &ctxConfig{
|
||||
FieldConfig: FieldConfig{
|
||||
Runtime: vm,
|
||||
fields: fields{},
|
||||
},
|
||||
},
|
||||
apiParam: &apiConfig{
|
||||
FieldConfig: FieldConfig{
|
||||
Runtime: vm,
|
||||
fields: fields{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(config)
|
||||
}
|
||||
|
||||
if config.prepareTimeout > config.timeout {
|
||||
config.prepareTimeout = config.timeout
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func (c *runConfig) Start() *time.Timer {
|
||||
c.vm.ClearInterrupt()
|
||||
return time.AfterFunc(c.timeout, func() {
|
||||
c.vm.Interrupt(ErrHalt)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *runConfig) Prepare() *time.Timer {
|
||||
c.vm.ClearInterrupt()
|
||||
return time.AfterFunc(c.prepareTimeout, func() {
|
||||
c.vm.Interrupt(ErrHalt)
|
||||
})
|
||||
}
|
@ -1,36 +1,19 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
)
|
||||
|
||||
type Context map[string]interface{}
|
||||
|
||||
func (c Context) set(name string, value interface{}) {
|
||||
map[string]interface{}(c)[name] = value
|
||||
type ctxConfig struct {
|
||||
FieldConfig
|
||||
}
|
||||
|
||||
func (c *Context) SetToken(t *oidc.Tokens) *Context {
|
||||
if t == nil {
|
||||
return c
|
||||
type contextFields func(*ctxConfig)
|
||||
|
||||
func SetContextFields(opts ...FieldOption) contextFields {
|
||||
return func(p *ctxConfig) {
|
||||
if p.fields == nil {
|
||||
p.fields = fields{}
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(&p.FieldConfig)
|
||||
}
|
||||
}
|
||||
if t.Token != nil && t.Token.AccessToken != "" {
|
||||
c.set("accessToken", t.AccessToken)
|
||||
}
|
||||
if t.IDToken != "" {
|
||||
c.set("idToken", t.IDToken)
|
||||
}
|
||||
if t.IDTokenClaims != nil {
|
||||
c.set("getClaim", func(claim string) interface{} { return t.IDTokenClaims.GetClaim(claim) })
|
||||
c.set("claimsJSON", func() (string, error) {
|
||||
c, err := json.Marshal(t.IDTokenClaims)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(c), nil
|
||||
})
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
71
internal/actions/fields.go
Normal file
71
internal/actions/fields.go
Normal file
@ -0,0 +1,71 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"github.com/dop251/goja"
|
||||
"github.com/zitadel/logging"
|
||||
)
|
||||
|
||||
type fields map[string]interface{}
|
||||
|
||||
type FieldOption func(*FieldConfig)
|
||||
|
||||
type FieldConfig struct {
|
||||
fields
|
||||
|
||||
Runtime *goja.Runtime
|
||||
}
|
||||
|
||||
func SetFields(name string, values ...interface{}) FieldOption {
|
||||
return func(p *FieldConfig) {
|
||||
if len(values) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for _, value := range values {
|
||||
val, ok := value.(FieldOption)
|
||||
// is the lowest field and can be set without further checks
|
||||
|
||||
if !ok {
|
||||
// {
|
||||
// "value": "some value"
|
||||
// }
|
||||
p.set(name, value)
|
||||
continue
|
||||
}
|
||||
|
||||
var field fields
|
||||
if f, ok := p.fields[name]; ok {
|
||||
// check if the found field is an object
|
||||
if field, ok = f.(fields); !ok {
|
||||
// panic because overwriting fields is not allowed
|
||||
logging.WithFields("sub", name).Warn("sub is not an object")
|
||||
panic("unable to prepare parameter")
|
||||
}
|
||||
} else {
|
||||
// field does not exist so far.
|
||||
// sub object for field can be created
|
||||
field = fields{}
|
||||
p.fields[name] = field
|
||||
}
|
||||
|
||||
fieldParam := FieldConfig{
|
||||
Runtime: p.Runtime,
|
||||
fields: field,
|
||||
}
|
||||
val(&fieldParam)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FieldConfig) set(name string, value interface{}) {
|
||||
if _, ok := f.fields[name]; ok {
|
||||
logging.WithFields("name", name).Error("tried to overwrite field")
|
||||
panic("tried to overwrite field")
|
||||
}
|
||||
v, ok := value.(func(*FieldConfig) interface{})
|
||||
if ok {
|
||||
f.fields[name] = v(f)
|
||||
return
|
||||
}
|
||||
f.fields[name] = value
|
||||
}
|
186
internal/actions/fields_test.go
Normal file
186
internal/actions/fields_test.go
Normal file
@ -0,0 +1,186 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
)
|
||||
|
||||
func TestSetFields(t *testing.T) {
|
||||
primitveFn := func(a string) { fmt.Println(a) }
|
||||
complexFn := func(*FieldConfig) interface{} {
|
||||
return primitveFn
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
setFields FieldOption
|
||||
want fields
|
||||
shouldPanic bool
|
||||
}{
|
||||
{
|
||||
name: "field is simple value",
|
||||
setFields: SetFields("value", 5),
|
||||
want: fields{
|
||||
"value": 5,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "field is method",
|
||||
setFields: SetFields("value", primitveFn),
|
||||
want: fields{
|
||||
"value": primitveFn,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "field is complex method",
|
||||
setFields: SetFields("value", complexFn),
|
||||
want: fields{
|
||||
"value": primitveFn,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "field without value",
|
||||
setFields: SetFields("value"),
|
||||
want: fields{},
|
||||
},
|
||||
{
|
||||
name: "field with empty value",
|
||||
setFields: SetFields("value", ""),
|
||||
want: fields{
|
||||
"value": "",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nested simple value",
|
||||
setFields: SetFields(
|
||||
"field",
|
||||
SetFields("sub", 5),
|
||||
),
|
||||
want: fields{
|
||||
"field": fields{
|
||||
"sub": 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nested multiple fields",
|
||||
setFields: SetFields(
|
||||
"field",
|
||||
SetFields("sub1", 5),
|
||||
SetFields("sub2", "asdf"),
|
||||
SetFields("sub3", primitveFn),
|
||||
),
|
||||
want: fields{
|
||||
"field": fields{
|
||||
"sub1": 5,
|
||||
"sub2": "asdf",
|
||||
"sub3": primitveFn,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "try to overwrite field primitives",
|
||||
setFields: SetFields(
|
||||
"field",
|
||||
SetFields("sub", 5),
|
||||
SetFields("sub", primitveFn),
|
||||
),
|
||||
shouldPanic: true,
|
||||
},
|
||||
{
|
||||
name: "try to overwrite primitives with fields",
|
||||
setFields: SetFields(
|
||||
"field",
|
||||
SetFields("sub", 5),
|
||||
SetFields("sub", SetFields("please", "panic")),
|
||||
),
|
||||
shouldPanic: true,
|
||||
},
|
||||
{
|
||||
name: "try to overwrite fields with primitive",
|
||||
setFields: SetFields(
|
||||
"field",
|
||||
SetFields("sub", SetFields("please", "panic")),
|
||||
SetFields("sub", 5),
|
||||
),
|
||||
shouldPanic: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
panicked := false
|
||||
if tt.shouldPanic {
|
||||
defer func() {
|
||||
if panicked != tt.shouldPanic {
|
||||
t.Errorf("wanted panic: %v got %v", tt.shouldPanic, panicked)
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
recover()
|
||||
panicked = true
|
||||
}()
|
||||
}
|
||||
config := &FieldConfig{
|
||||
Runtime: goja.New(),
|
||||
fields: fields{},
|
||||
}
|
||||
tt.setFields(config)
|
||||
if !tt.shouldPanic && fmt.Sprint(config.fields) != fmt.Sprint(tt.want) {
|
||||
t.Errorf("SetFields() = %v, want %v", fmt.Sprint(config.fields), fmt.Sprint(tt.want))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetFieldsExecuteMethods(t *testing.T) {
|
||||
primitveFn := func(a string) { fmt.Println(a) }
|
||||
complexFn := func(*FieldConfig) interface{} {
|
||||
return primitveFn
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
setFields FieldOption
|
||||
want fields
|
||||
shouldPanic bool
|
||||
}{
|
||||
{
|
||||
name: "field is method",
|
||||
setFields: SetFields("value", primitveFn),
|
||||
want: fields{
|
||||
"value": primitveFn,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "field is complex method",
|
||||
setFields: SetFields("value", complexFn),
|
||||
want: fields{
|
||||
"value": primitveFn,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
panicked := false
|
||||
if tt.shouldPanic {
|
||||
defer func() {
|
||||
if panicked != tt.shouldPanic {
|
||||
t.Errorf("wanted panic: %v got %v", tt.shouldPanic, panicked)
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
recover()
|
||||
panicked = true
|
||||
}()
|
||||
}
|
||||
config := &FieldConfig{
|
||||
Runtime: goja.New(),
|
||||
fields: fields{},
|
||||
}
|
||||
tt.setFields(config)
|
||||
if !tt.shouldPanic && fmt.Sprint(config.fields) != fmt.Sprint(tt.want) {
|
||||
t.Errorf("SetFields() = %v, want %v", fmt.Sprint(config.fields), fmt.Sprint(tt.want))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
196
internal/actions/http_module.go
Normal file
196
internal/actions/http_module.go
Normal file
@ -0,0 +1,196 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
z_errs "github.com/zitadel/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func WithHTTP(ctx context.Context) Option {
|
||||
return func(c *runConfig) {
|
||||
c.modules["zitadel/http"] = func(runtime *goja.Runtime, module *goja.Object) {
|
||||
requireHTTP(ctx, &http.Client{Transport: new(transport)}, runtime, module)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type HTTP struct {
|
||||
runtime *goja.Runtime
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func requireHTTP(ctx context.Context, client *http.Client, runtime *goja.Runtime, module *goja.Object) {
|
||||
c := &HTTP{
|
||||
client: client,
|
||||
runtime: runtime,
|
||||
}
|
||||
o := module.Get("exports").(*goja.Object)
|
||||
logging.OnError(o.Set("fetch", c.fetch(ctx))).Warn("unable to set module")
|
||||
}
|
||||
|
||||
type fetchConfig struct {
|
||||
Method string
|
||||
Headers http.Header
|
||||
Body io.Reader
|
||||
}
|
||||
|
||||
var defaultFetchConfig = fetchConfig{
|
||||
Method: http.MethodGet,
|
||||
Headers: http.Header{
|
||||
"Content-Type": []string{"application/json"},
|
||||
"Accept": []string{"application/json"},
|
||||
},
|
||||
}
|
||||
|
||||
func (c *HTTP) fetchConfigFromArg(arg *goja.Object, config *fetchConfig) (err error) {
|
||||
for _, key := range arg.Keys() {
|
||||
switch key {
|
||||
case "headers":
|
||||
config.Headers = parseHeaders(arg.Get(key).ToObject(c.runtime))
|
||||
case "method":
|
||||
config.Method = arg.Get(key).String()
|
||||
case "body":
|
||||
body, err := arg.Get(key).ToObject(c.runtime).MarshalJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.Body = bytes.NewReader(body)
|
||||
default:
|
||||
return z_errs.ThrowInvalidArgument(nil, "ACTIO-OfUeA", "key is invalid")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type response struct {
|
||||
Body string
|
||||
Status int
|
||||
Headers map[string][]string
|
||||
runtime *goja.Runtime
|
||||
}
|
||||
|
||||
func (r *response) Json() goja.Value {
|
||||
var val interface{}
|
||||
|
||||
if err := json.Unmarshal([]byte(r.Body), &val); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return r.runtime.ToValue(val)
|
||||
}
|
||||
|
||||
func (r *response) Text() goja.Value {
|
||||
return r.runtime.ToValue(r.Body)
|
||||
}
|
||||
|
||||
func (c *HTTP) fetch(ctx context.Context) func(call goja.FunctionCall) goja.Value {
|
||||
return func(call goja.FunctionCall) goja.Value {
|
||||
req := c.buildHTTPRequest(ctx, call.Arguments)
|
||||
if deadline, ok := ctx.Deadline(); ok {
|
||||
c.client.Timeout = time.Until(deadline)
|
||||
}
|
||||
|
||||
res, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
logging.WithError(err).Debug("call failed")
|
||||
panic(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
logging.WithError(err).Warn("unable to parse body")
|
||||
panic("unable to read response body")
|
||||
}
|
||||
return c.runtime.ToValue(&response{Status: res.StatusCode, Body: string(body), runtime: c.runtime})
|
||||
}
|
||||
}
|
||||
|
||||
// the first argument has to be a string and is required
|
||||
// the second agrument is optional and an object with the following fields possible:
|
||||
// - `Headers`: map with string key and value of type string or string array
|
||||
// - `Body`: json body of the request
|
||||
// - `Method`: http method type
|
||||
func (c *HTTP) buildHTTPRequest(ctx context.Context, args []goja.Value) (req *http.Request) {
|
||||
if len(args) > 2 {
|
||||
logging.WithFields("count", len(args)).Debug("more than 2 args provided")
|
||||
panic("too many args")
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
panic("no url provided")
|
||||
}
|
||||
|
||||
config := defaultFetchConfig
|
||||
var err error
|
||||
if len(args) == 2 {
|
||||
if err = c.fetchConfigFromArg(args[1].ToObject(c.runtime), &config); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
req, err = http.NewRequestWithContext(ctx, config.Method, args[0].Export().(string), config.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
req.Header = config.Headers
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
func parseHeaders(headers *goja.Object) http.Header {
|
||||
h := make(http.Header, len(headers.Keys()))
|
||||
for _, k := range headers.Keys() {
|
||||
header := headers.Get(k).Export()
|
||||
var values []string
|
||||
|
||||
switch headerValue := header.(type) {
|
||||
case string:
|
||||
values = strings.Split(headerValue, ",")
|
||||
case []any:
|
||||
for _, v := range headerValue {
|
||||
values = append(values, v.(string))
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range values {
|
||||
h.Add(k, strings.TrimSpace(v))
|
||||
}
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
type transport struct{}
|
||||
|
||||
func (*transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
if httpConfig == nil {
|
||||
return http.DefaultTransport.RoundTrip(req)
|
||||
}
|
||||
if isHostBlocked(httpConfig.DenyList, req.URL) {
|
||||
return nil, z_errs.ThrowInvalidArgument(nil, "ACTIO-N72d0", "host is denied")
|
||||
}
|
||||
return http.DefaultTransport.RoundTrip(req)
|
||||
}
|
||||
|
||||
func isHostBlocked(denyList []AddressChecker, address *url.URL) bool {
|
||||
for _, blocked := range denyList {
|
||||
if blocked.Matches(address.Hostname()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type AddressChecker interface {
|
||||
Matches(string) bool
|
||||
}
|
98
internal/actions/http_module_config.go
Normal file
98
internal/actions/http_module_config.go
Normal file
@ -0,0 +1,98 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
z_errs "github.com/zitadel/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func SetHTTPConfig(config *HTTPConfig) {
|
||||
httpConfig = config
|
||||
}
|
||||
|
||||
var httpConfig *HTTPConfig
|
||||
|
||||
type HTTPConfig struct {
|
||||
DenyList []AddressChecker
|
||||
}
|
||||
|
||||
func HTTPConfigDecodeHook(from, to reflect.Value) (interface{}, error) {
|
||||
if to.Type() != reflect.TypeOf(HTTPConfig{}) {
|
||||
return from.Interface(), nil
|
||||
}
|
||||
|
||||
config := struct {
|
||||
DenyList []string
|
||||
}{}
|
||||
|
||||
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
|
||||
WeaklyTypedInput: true,
|
||||
Result: &config,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = decoder.Decode(from.Interface()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := HTTPConfig{
|
||||
DenyList: make([]AddressChecker, len(config.DenyList)),
|
||||
}
|
||||
|
||||
for i, entry := range config.DenyList {
|
||||
if c.DenyList[i], err = parseDenyListEntry(entry); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func parseDenyListEntry(entry string) (AddressChecker, error) {
|
||||
if checker, err := NewIPChecker(entry); err == nil {
|
||||
return checker, nil
|
||||
}
|
||||
return &DomainChecker{Domain: entry}, nil
|
||||
}
|
||||
|
||||
func NewIPChecker(i string) (AddressChecker, error) {
|
||||
_, network, err := net.ParseCIDR(i)
|
||||
if err == nil {
|
||||
return &IPChecker{Net: network}, nil
|
||||
}
|
||||
if ip := net.ParseIP(i); ip != nil {
|
||||
return &IPChecker{IP: ip}, nil
|
||||
}
|
||||
return nil, z_errs.ThrowInvalidArgument(nil, "ACTIO-ddJ7h", "invalid ip")
|
||||
}
|
||||
|
||||
type IPChecker struct {
|
||||
Net *net.IPNet
|
||||
IP net.IP
|
||||
}
|
||||
|
||||
func (c *IPChecker) Matches(address string) bool {
|
||||
ip := net.ParseIP(address)
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if c.IP != nil {
|
||||
return c.IP.Equal(ip)
|
||||
}
|
||||
return c.Net.Contains(ip)
|
||||
}
|
||||
|
||||
type DomainChecker struct {
|
||||
Domain string
|
||||
}
|
||||
|
||||
func (c *DomainChecker) Matches(domain string) bool {
|
||||
//TODO: allow wild cards
|
||||
return c.Domain == domain
|
||||
}
|
433
internal/actions/http_module_test.go
Normal file
433
internal/actions/http_module_test.go
Normal file
@ -0,0 +1,433 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func Test_isHostBlocked(t *testing.T) {
|
||||
var denyList = []AddressChecker{
|
||||
mustNewIPChecker(t, "192.168.5.0/24"),
|
||||
mustNewIPChecker(t, "127.0.0.1"),
|
||||
&DomainChecker{Domain: "test.com"},
|
||||
}
|
||||
type args struct {
|
||||
address *url.URL
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "in range",
|
||||
args: args{
|
||||
address: mustNewURL(t, "https://192.168.5.4/hodor"),
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "exact ip",
|
||||
args: args{
|
||||
address: mustNewURL(t, "http://127.0.0.1:8080/hodor"),
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "address match",
|
||||
args: args{
|
||||
address: mustNewURL(t, "https://test.com:42/hodor"),
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "address not match",
|
||||
args: args{
|
||||
address: mustNewURL(t, "https://test2.com/hodor"),
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := isHostBlocked(denyList, tt.args.address); got != tt.want {
|
||||
t.Errorf("isHostBlocked() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func mustNewIPChecker(t *testing.T, ip string) AddressChecker {
|
||||
t.Helper()
|
||||
checker, err := NewIPChecker(ip)
|
||||
if err != nil {
|
||||
t.Errorf("unable to parse cidr of %q because: %v", ip, err)
|
||||
t.FailNow()
|
||||
}
|
||||
return checker
|
||||
}
|
||||
|
||||
func mustNewURL(t *testing.T, raw string) *url.URL {
|
||||
u, err := url.Parse(raw)
|
||||
if err != nil {
|
||||
t.Errorf("unable to parse address of %q because: %v", raw, err)
|
||||
t.FailNow()
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
func TestHTTP_fetchConfigFromArg(t *testing.T) {
|
||||
runtime := goja.New()
|
||||
runtime.SetFieldNameMapper(goja.UncapFieldNameMapper())
|
||||
|
||||
type args struct {
|
||||
arg *goja.Object
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantConfig fetchConfig
|
||||
wantErr func(error) bool
|
||||
}{
|
||||
{
|
||||
name: "no fetch option provided",
|
||||
args: args{
|
||||
arg: runtime.ToValue(
|
||||
struct{}{},
|
||||
).ToObject(runtime),
|
||||
},
|
||||
wantConfig: fetchConfig{},
|
||||
wantErr: func(err error) bool {
|
||||
return err == nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "header set as string",
|
||||
args: args{
|
||||
arg: runtime.ToValue(
|
||||
&struct {
|
||||
Headers map[string]string
|
||||
}{
|
||||
Headers: map[string]string{
|
||||
"Authorization": "Bearer token",
|
||||
},
|
||||
},
|
||||
).ToObject(runtime),
|
||||
},
|
||||
wantConfig: fetchConfig{
|
||||
Headers: http.Header{
|
||||
"Authorization": {"Bearer token"},
|
||||
},
|
||||
},
|
||||
wantErr: func(err error) bool {
|
||||
return err == nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "header set as list",
|
||||
args: args{
|
||||
arg: runtime.ToValue(
|
||||
&struct {
|
||||
Headers map[string][]any
|
||||
}{
|
||||
Headers: map[string][]any{
|
||||
"Authorization": {"Bearer token"},
|
||||
},
|
||||
},
|
||||
).ToObject(runtime),
|
||||
},
|
||||
wantConfig: fetchConfig{
|
||||
Headers: http.Header{
|
||||
"Authorization": {"Bearer token"},
|
||||
},
|
||||
},
|
||||
wantErr: func(err error) bool {
|
||||
return err == nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "method set",
|
||||
args: args{
|
||||
arg: runtime.ToValue(
|
||||
&struct {
|
||||
Method string
|
||||
}{
|
||||
Method: http.MethodPost,
|
||||
},
|
||||
).ToObject(runtime),
|
||||
},
|
||||
wantConfig: fetchConfig{
|
||||
Method: http.MethodPost,
|
||||
},
|
||||
wantErr: func(err error) bool {
|
||||
return err == nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "body set",
|
||||
args: args{
|
||||
arg: runtime.ToValue(
|
||||
&struct {
|
||||
Body struct{ Id string }
|
||||
}{
|
||||
Body: struct{ Id string }{
|
||||
Id: "asdf123",
|
||||
},
|
||||
},
|
||||
).ToObject(runtime),
|
||||
},
|
||||
wantConfig: fetchConfig{
|
||||
Body: bytes.NewReader([]byte(`{"id":"asdf123"}`)),
|
||||
},
|
||||
wantErr: func(err error) bool {
|
||||
return err == nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid header",
|
||||
args: args{
|
||||
arg: runtime.ToValue(
|
||||
&struct {
|
||||
NotExists struct{}
|
||||
}{
|
||||
NotExists: struct{}{},
|
||||
},
|
||||
).ToObject(runtime),
|
||||
},
|
||||
wantConfig: fetchConfig{},
|
||||
wantErr: func(err error) bool {
|
||||
return errors.IsErrorInvalidArgument(err)
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &HTTP{
|
||||
runtime: runtime,
|
||||
client: http.DefaultClient,
|
||||
}
|
||||
gotConfig := new(fetchConfig)
|
||||
|
||||
err := c.fetchConfigFromArg(tt.args.arg, gotConfig)
|
||||
if !tt.wantErr(err) {
|
||||
t.Errorf("HTTP.fetchConfigFromArg() error = %v", err)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(gotConfig.Headers, tt.wantConfig.Headers) {
|
||||
t.Errorf("config.Headers got = %#v, want %#v", gotConfig.Headers, tt.wantConfig.Headers)
|
||||
}
|
||||
if gotConfig.Method != tt.wantConfig.Method {
|
||||
t.Errorf("config.Method got = %#v, want %#v", gotConfig.Method, tt.wantConfig.Method)
|
||||
}
|
||||
|
||||
if tt.wantConfig.Body == nil {
|
||||
if gotConfig.Body != nil {
|
||||
t.Errorf("didn't expect a body")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
gotBody, _ := io.ReadAll(gotConfig.Body)
|
||||
wantBody, _ := io.ReadAll(tt.wantConfig.Body)
|
||||
|
||||
if !reflect.DeepEqual(gotBody, wantBody) {
|
||||
t.Errorf("config.Body got = %s, want %s", gotBody, wantBody)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTP_buildHTTPRequest(t *testing.T) {
|
||||
runtime := goja.New()
|
||||
runtime.SetFieldNameMapper(goja.UncapFieldNameMapper())
|
||||
|
||||
type args struct {
|
||||
args []goja.Value
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantReq *http.Request
|
||||
shouldPanic bool
|
||||
}{
|
||||
{
|
||||
name: "only url",
|
||||
args: args{
|
||||
args: []goja.Value{
|
||||
runtime.ToValue("http://my-url.ch"),
|
||||
},
|
||||
},
|
||||
wantReq: &http.Request{
|
||||
Method: http.MethodGet,
|
||||
URL: mustNewURL(t, "http://my-url.ch"),
|
||||
Header: defaultFetchConfig.Headers,
|
||||
Body: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no params",
|
||||
args: args{
|
||||
args: []goja.Value{
|
||||
runtime.ToValue("http://my-url.ch"),
|
||||
runtime.ToValue(&struct{}{}),
|
||||
},
|
||||
},
|
||||
wantReq: &http.Request{
|
||||
Method: http.MethodGet,
|
||||
URL: mustNewURL(t, "http://my-url.ch"),
|
||||
Header: defaultFetchConfig.Headers,
|
||||
Body: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "overwrite headers",
|
||||
args: args{
|
||||
args: []goja.Value{
|
||||
runtime.ToValue("http://my-url.ch"),
|
||||
runtime.ToValue(struct {
|
||||
Headers map[string][]interface{}
|
||||
}{
|
||||
Headers: map[string][]interface{}{"Authorization": {"some token"}},
|
||||
}),
|
||||
},
|
||||
},
|
||||
wantReq: &http.Request{
|
||||
Method: http.MethodGet,
|
||||
URL: mustNewURL(t, "http://my-url.ch"),
|
||||
Header: http.Header{
|
||||
"Authorization": []string{"some token"},
|
||||
},
|
||||
Body: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "post with body",
|
||||
args: args{
|
||||
args: []goja.Value{
|
||||
runtime.ToValue("http://my-url.ch"),
|
||||
runtime.ToValue(struct {
|
||||
Body struct{ MyData string }
|
||||
}{
|
||||
Body: struct{ MyData string }{MyData: "hello world"},
|
||||
}),
|
||||
},
|
||||
},
|
||||
wantReq: &http.Request{
|
||||
Method: http.MethodGet,
|
||||
URL: mustNewURL(t, "http://my-url.ch"),
|
||||
Header: defaultFetchConfig.Headers,
|
||||
Body: io.NopCloser(bytes.NewReader([]byte(`{"myData":"hello world"}`))),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "too many args",
|
||||
args: args{
|
||||
args: []goja.Value{
|
||||
runtime.ToValue("http://my-url.ch"),
|
||||
runtime.ToValue("http://my-url.ch"),
|
||||
runtime.ToValue("http://my-url.ch"),
|
||||
},
|
||||
},
|
||||
wantReq: nil,
|
||||
shouldPanic: true,
|
||||
},
|
||||
{
|
||||
name: "no args",
|
||||
args: args{
|
||||
args: []goja.Value{},
|
||||
},
|
||||
wantReq: nil,
|
||||
shouldPanic: true,
|
||||
},
|
||||
{
|
||||
name: "invalid config",
|
||||
args: args{
|
||||
args: []goja.Value{
|
||||
runtime.ToValue("http://my-url.ch"),
|
||||
runtime.ToValue(struct {
|
||||
Invalid bool
|
||||
}{
|
||||
Invalid: true,
|
||||
}),
|
||||
},
|
||||
},
|
||||
wantReq: nil,
|
||||
shouldPanic: true,
|
||||
},
|
||||
{
|
||||
name: "invalid method",
|
||||
args: args{
|
||||
args: []goja.Value{
|
||||
runtime.ToValue("http://my-url.ch"),
|
||||
runtime.ToValue(struct {
|
||||
Method string
|
||||
}{
|
||||
Method: " asdf asdf",
|
||||
}),
|
||||
},
|
||||
},
|
||||
wantReq: nil,
|
||||
shouldPanic: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
panicked := false
|
||||
if tt.shouldPanic {
|
||||
defer func() {
|
||||
if panicked != tt.shouldPanic {
|
||||
t.Errorf("wanted panic: %v got %v", tt.shouldPanic, panicked)
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
recover()
|
||||
panicked = true
|
||||
}()
|
||||
}
|
||||
|
||||
c := &HTTP{
|
||||
runtime: runtime,
|
||||
}
|
||||
|
||||
gotReq := c.buildHTTPRequest(context.Background(), tt.args.args)
|
||||
|
||||
if tt.shouldPanic {
|
||||
return
|
||||
}
|
||||
|
||||
if gotReq.URL.String() != tt.wantReq.URL.String() {
|
||||
t.Errorf("url = %s, want %s", gotReq.URL, tt.wantReq.URL)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(gotReq.Header, tt.wantReq.Header) {
|
||||
t.Errorf("headers = %v, want %v", gotReq.Header, tt.wantReq.Header)
|
||||
}
|
||||
|
||||
if gotReq.Method != tt.wantReq.Method {
|
||||
t.Errorf("method = %s, want %s", gotReq.Method, tt.wantReq.Method)
|
||||
}
|
||||
|
||||
if tt.wantReq.Body == nil {
|
||||
if gotReq.Body != nil {
|
||||
t.Errorf("didn't expect a body")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
gotBody, _ := io.ReadAll(gotReq.Body)
|
||||
wantBody, _ := io.ReadAll(tt.wantReq.Body)
|
||||
|
||||
if !reflect.DeepEqual(gotBody, wantBody) {
|
||||
t.Errorf("config.Body got = %s, want %s", gotBody, wantBody)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
30
internal/actions/log_module.go
Normal file
30
internal/actions/log_module.go
Normal file
@ -0,0 +1,30 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"github.com/dop251/goja_nodejs/console"
|
||||
)
|
||||
|
||||
var ServerLog *logrus
|
||||
|
||||
type logrus struct{}
|
||||
|
||||
func (*logrus) Log(s string) {
|
||||
logging.WithFields("message", s).Info("log from action")
|
||||
}
|
||||
func (*logrus) Warn(s string) {
|
||||
logging.WithFields("message", s).Info("warn from action")
|
||||
}
|
||||
func (*logrus) Error(s string) {
|
||||
logging.WithFields("message", s).Info("error from action")
|
||||
}
|
||||
|
||||
func WithLogger(logger console.Printer) Option {
|
||||
return func(c *runConfig) {
|
||||
c.modules["zitadel/log"] = func(runtime *goja.Runtime, module *goja.Object) {
|
||||
console.RequireWithPrinter(logger)(runtime, module)
|
||||
}
|
||||
}
|
||||
}
|
56
internal/actions/object/metadata.go
Normal file
56
internal/actions/object/metadata.go
Normal file
@ -0,0 +1,56 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/actions"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
)
|
||||
|
||||
func UserMetadataListFromQuery(c *actions.FieldConfig, metadata *query.UserMetadataList) goja.Value {
|
||||
result := &userMetadataList{
|
||||
Count: metadata.Count,
|
||||
Sequence: metadata.Sequence,
|
||||
Timestamp: metadata.Timestamp,
|
||||
Metadata: make([]*userMetadata, len(metadata.Metadata)),
|
||||
}
|
||||
|
||||
for i, md := range metadata.Metadata {
|
||||
var value interface{}
|
||||
err := json.Unmarshal(md.Value, &value)
|
||||
if err != nil {
|
||||
logging.WithError(err).Debug("unable to unmarshal into map")
|
||||
panic(err)
|
||||
}
|
||||
result.Metadata[i] = &userMetadata{
|
||||
CreationDate: md.CreationDate,
|
||||
ChangeDate: md.ChangeDate,
|
||||
ResourceOwner: md.ResourceOwner,
|
||||
Sequence: md.Sequence,
|
||||
Key: md.Key,
|
||||
Value: c.Runtime.ToValue(value),
|
||||
}
|
||||
}
|
||||
|
||||
return c.Runtime.ToValue(result)
|
||||
}
|
||||
|
||||
type userMetadataList struct {
|
||||
Count uint64
|
||||
Sequence uint64
|
||||
Timestamp time.Time
|
||||
Metadata []*userMetadata
|
||||
}
|
||||
|
||||
type userMetadata struct {
|
||||
CreationDate time.Time
|
||||
ChangeDate time.Time
|
||||
ResourceOwner string
|
||||
Sequence uint64
|
||||
Key string
|
||||
Value goja.Value
|
||||
}
|
165
internal/actions/object/user.go
Normal file
165
internal/actions/object/user.go
Normal file
@ -0,0 +1,165 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"github.com/zitadel/zitadel/internal/actions"
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
)
|
||||
|
||||
func UserFromExternalUser(c *actions.FieldConfig, user *domain.ExternalUser) goja.Value {
|
||||
return c.Runtime.ToValue(&externalUser{
|
||||
ExternalId: user.ExternalUserID,
|
||||
ExternalIdpId: user.ExternalUserID,
|
||||
Human: human{
|
||||
FirstName: user.FirstName,
|
||||
LastName: user.LastName,
|
||||
NickName: user.NickName,
|
||||
DisplayName: user.DisplayName,
|
||||
PreferredLanguage: user.PreferredLanguage.String(),
|
||||
Email: user.Email,
|
||||
IsEmailVerified: user.IsEmailVerified,
|
||||
Phone: user.Phone,
|
||||
IsPhoneVerified: user.IsPhoneVerified,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func UserFromHuman(c *actions.FieldConfig, user *domain.Human) goja.Value {
|
||||
u := &humanUser{
|
||||
Id: user.AggregateID,
|
||||
CreationDate: user.CreationDate,
|
||||
ChangeDate: user.ChangeDate,
|
||||
ResourceOwner: user.ResourceOwner,
|
||||
Sequence: user.Sequence,
|
||||
State: user.State,
|
||||
Username: user.Username,
|
||||
LoginNames: user.LoginNames,
|
||||
PreferredLoginName: user.PreferredLoginName,
|
||||
}
|
||||
|
||||
if user.Profile != nil {
|
||||
u.Human.FirstName = user.Profile.FirstName
|
||||
u.Human.LastName = user.Profile.LastName
|
||||
u.Human.NickName = user.Profile.NickName
|
||||
u.Human.DisplayName = user.Profile.DisplayName
|
||||
u.Human.PreferredLanguage = user.Profile.PreferredLanguage.String()
|
||||
}
|
||||
|
||||
if user.Email != nil {
|
||||
u.Human.Email = user.Email.EmailAddress
|
||||
u.Human.IsEmailVerified = user.Email.IsEmailVerified
|
||||
}
|
||||
|
||||
if user.Phone != nil {
|
||||
u.Human.Phone = user.Phone.PhoneNumber
|
||||
u.Human.IsPhoneVerified = user.Phone.IsPhoneVerified
|
||||
}
|
||||
|
||||
return c.Runtime.ToValue(u)
|
||||
}
|
||||
|
||||
func UserFromQuery(c *actions.FieldConfig, user *query.User) goja.Value {
|
||||
if user.Human != nil {
|
||||
return humanFromQuery(c, user)
|
||||
}
|
||||
return machineFromQuery(c, user)
|
||||
}
|
||||
|
||||
func humanFromQuery(c *actions.FieldConfig, user *query.User) goja.Value {
|
||||
return c.Runtime.ToValue(&humanUser{
|
||||
Id: user.ID,
|
||||
CreationDate: user.CreationDate,
|
||||
ChangeDate: user.ChangeDate,
|
||||
ResourceOwner: user.ResourceOwner,
|
||||
Sequence: user.Sequence,
|
||||
State: user.State,
|
||||
Username: user.Username,
|
||||
LoginNames: user.LoginNames,
|
||||
PreferredLoginName: user.PreferredLoginName,
|
||||
Human: human{
|
||||
FirstName: user.Human.FirstName,
|
||||
LastName: user.Human.LastName,
|
||||
NickName: user.Human.NickName,
|
||||
DisplayName: user.Human.DisplayName,
|
||||
AvatarKey: user.Human.AvatarKey,
|
||||
PreferredLanguage: user.Human.PreferredLanguage.String(),
|
||||
Gender: user.Human.Gender,
|
||||
Email: user.Human.Email,
|
||||
IsEmailVerified: user.Human.IsEmailVerified,
|
||||
Phone: user.Human.Phone,
|
||||
IsPhoneVerified: user.Human.IsPhoneVerified,
|
||||
},
|
||||
})
|
||||
}
|
||||
func machineFromQuery(c *actions.FieldConfig, user *query.User) goja.Value {
|
||||
return c.Runtime.ToValue(&machineUser{
|
||||
Id: user.ID,
|
||||
CreationDate: user.CreationDate,
|
||||
ChangeDate: user.ChangeDate,
|
||||
ResourceOwner: user.ResourceOwner,
|
||||
Sequence: user.Sequence,
|
||||
State: user.State,
|
||||
Username: user.Username,
|
||||
LoginNames: user.LoginNames,
|
||||
PreferredLoginName: user.PreferredLoginName,
|
||||
Machine: machine{
|
||||
Name: user.Machine.Name,
|
||||
Description: user.Machine.Description,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type externalUser struct {
|
||||
ExternalId string
|
||||
ExternalIdpId string
|
||||
Human human
|
||||
}
|
||||
|
||||
type humanUser struct {
|
||||
Id string
|
||||
CreationDate time.Time
|
||||
ChangeDate time.Time
|
||||
ResourceOwner string
|
||||
Sequence uint64
|
||||
State domain.UserState
|
||||
Username string
|
||||
LoginNames database.StringArray
|
||||
PreferredLoginName string
|
||||
Human human
|
||||
}
|
||||
|
||||
type human struct {
|
||||
FirstName string
|
||||
LastName string
|
||||
NickName string
|
||||
DisplayName string
|
||||
AvatarKey string
|
||||
PreferredLanguage string
|
||||
Gender domain.Gender
|
||||
Email string
|
||||
IsEmailVerified bool
|
||||
Phone string
|
||||
IsPhoneVerified bool
|
||||
}
|
||||
|
||||
type machineUser struct {
|
||||
Id string
|
||||
CreationDate time.Time
|
||||
ChangeDate time.Time
|
||||
ResourceOwner string
|
||||
Sequence uint64
|
||||
State domain.UserState
|
||||
Username string
|
||||
LoginNames database.StringArray
|
||||
PreferredLoginName string
|
||||
Machine machine
|
||||
}
|
||||
|
||||
type machine struct {
|
||||
Name string
|
||||
Description string
|
||||
}
|
@ -1,55 +1,7 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
)
|
||||
|
||||
type UserGrant struct {
|
||||
ProjectID string
|
||||
ProjectGrantID string
|
||||
Roles []string
|
||||
}
|
||||
|
||||
func appendUserGrant(list *[]UserGrant) func(goja.FunctionCall) goja.Value {
|
||||
return func(call goja.FunctionCall) goja.Value {
|
||||
userGrantMap := call.Argument(0).Export()
|
||||
userGrant, _ := userGrantFromMap(userGrantMap)
|
||||
*list = append(*list, userGrant)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func userGrantFromMap(grantMap interface{}) (UserGrant, error) {
|
||||
m, ok := grantMap.(map[string]interface{})
|
||||
if !ok {
|
||||
return UserGrant{}, errors.New("invalid")
|
||||
}
|
||||
projectID, ok := m["projectID"].(string)
|
||||
if !ok {
|
||||
return UserGrant{}, errors.New("invalid")
|
||||
}
|
||||
var projectGrantID string
|
||||
if id, ok := m["projectGrantID"]; ok {
|
||||
projectGrantID, ok = id.(string)
|
||||
if !ok {
|
||||
return UserGrant{}, errors.New("invalid")
|
||||
}
|
||||
}
|
||||
var roles []string
|
||||
if r := m["roles"]; r != nil {
|
||||
rs, ok := r.([]interface{})
|
||||
if !ok {
|
||||
return UserGrant{}, errors.New("invalid")
|
||||
}
|
||||
for _, role := range rs {
|
||||
roles = append(roles, role.(string))
|
||||
}
|
||||
}
|
||||
return UserGrant{
|
||||
ProjectID: projectID,
|
||||
ProjectGrantID: projectGrantID,
|
||||
Roles: roles,
|
||||
}, nil
|
||||
}
|
||||
|
@ -7,30 +7,66 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
action_pb "github.com/zitadel/zitadel/pkg/grpc/action"
|
||||
message_pb "github.com/zitadel/zitadel/pkg/grpc/message"
|
||||
)
|
||||
|
||||
func FlowTypeToDomain(flowType action_pb.FlowType) domain.FlowType {
|
||||
// for backward compatability: old enum identifiers are mapped as well
|
||||
func FlowTypeToDomain(flowType string) domain.FlowType {
|
||||
switch flowType {
|
||||
case action_pb.FlowType_FLOW_TYPE_EXTERNAL_AUTHENTICATION:
|
||||
case "FLOW_TYPE_EXTERNAL_AUTHENTICATION", domain.FlowTypeExternalAuthentication.ID():
|
||||
return domain.FlowTypeExternalAuthentication
|
||||
case domain.FlowTypeCustomiseToken.ID():
|
||||
return domain.FlowTypeCustomiseToken
|
||||
default:
|
||||
return domain.FlowTypeUnspecified
|
||||
}
|
||||
}
|
||||
|
||||
func TriggerTypeToDomain(triggerType action_pb.TriggerType) domain.TriggerType {
|
||||
func FlowTypeToPb(typ domain.FlowType) *action_pb.FlowType {
|
||||
return &action_pb.FlowType{
|
||||
Id: typ.ID(),
|
||||
Name: &message_pb.LocalizedMessage{
|
||||
Key: typ.LocalizationKey(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TriggerTypeToDomain maps the pb type to domain
|
||||
// for backward compatability: old enum identifiers are mapped as well
|
||||
func TriggerTypeToDomain(triggerType string) domain.TriggerType {
|
||||
switch triggerType {
|
||||
case action_pb.TriggerType_TRIGGER_TYPE_POST_AUTHENTICATION:
|
||||
case "TRIGGER_TYPE_POST_AUTHENTICATION", domain.TriggerTypePostAuthentication.ID():
|
||||
return domain.TriggerTypePostAuthentication
|
||||
case action_pb.TriggerType_TRIGGER_TYPE_PRE_CREATION:
|
||||
case "TRIGGER_TYPE_PRE_CREATION", domain.TriggerTypePreCreation.ID():
|
||||
return domain.TriggerTypePreCreation
|
||||
case action_pb.TriggerType_TRIGGER_TYPE_POST_CREATION:
|
||||
case "TRIGGER_TYPE_POST_CREATION", domain.TriggerTypePostCreation.ID():
|
||||
return domain.TriggerTypePostCreation
|
||||
case domain.TriggerTypePreAccessTokenCreation.ID():
|
||||
return domain.TriggerTypePreAccessTokenCreation
|
||||
case domain.TriggerTypePreUserinfoCreation.ID():
|
||||
return domain.TriggerTypePreUserinfoCreation
|
||||
default:
|
||||
return domain.TriggerTypeUnspecified
|
||||
}
|
||||
}
|
||||
|
||||
func TriggerTypesToPb(types []domain.TriggerType) []*action_pb.TriggerType {
|
||||
list := make([]*action_pb.TriggerType, len(types))
|
||||
for i, typ := range types {
|
||||
list[i] = TriggerTypeToPb(typ)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func TriggerTypeToPb(typ domain.TriggerType) *action_pb.TriggerType {
|
||||
return &action_pb.TriggerType{
|
||||
Id: typ.ID(),
|
||||
Name: &message_pb.LocalizedMessage{
|
||||
Key: typ.LocalizationKey(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func FlowToPb(flow *query.Flow) *action_pb.Flow {
|
||||
return &action_pb.Flow{
|
||||
Type: FlowTypeToPb(flow.Type),
|
||||
@ -47,28 +83,6 @@ func TriggerActionToPb(trigger domain.TriggerType, actions []*query.Action) *act
|
||||
}
|
||||
}
|
||||
|
||||
func FlowTypeToPb(flowType domain.FlowType) action_pb.FlowType {
|
||||
switch flowType {
|
||||
case domain.FlowTypeExternalAuthentication:
|
||||
return action_pb.FlowType_FLOW_TYPE_EXTERNAL_AUTHENTICATION
|
||||
default:
|
||||
return action_pb.FlowType_FLOW_TYPE_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
func TriggerTypeToPb(triggerType domain.TriggerType) action_pb.TriggerType {
|
||||
switch triggerType {
|
||||
case domain.TriggerTypePostAuthentication:
|
||||
return action_pb.TriggerType_TRIGGER_TYPE_POST_AUTHENTICATION
|
||||
case domain.TriggerTypePreCreation:
|
||||
return action_pb.TriggerType_TRIGGER_TYPE_PRE_CREATION
|
||||
case domain.TriggerTypePostCreation:
|
||||
return action_pb.TriggerType_TRIGGER_TYPE_POST_CREATION
|
||||
default:
|
||||
return action_pb.TriggerType_TRIGGER_TYPE_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
func TriggerActionsToPb(triggers map[domain.TriggerType][]*query.Action) []*action_pb.TriggerAction {
|
||||
list := make([]*action_pb.TriggerAction, 0)
|
||||
for trigger, actions := range triggers {
|
||||
@ -92,7 +106,7 @@ func ActionToPb(action *query.Action) *action_pb.Action {
|
||||
State: ActionStateToPb(action.State),
|
||||
Name: action.Name,
|
||||
Script: action.Script,
|
||||
Timeout: durationpb.New(action.Timeout),
|
||||
Timeout: durationpb.New(action.Timeout()),
|
||||
AllowedToFail: action.AllowedToFail,
|
||||
}
|
||||
}
|
||||
|
@ -2,12 +2,14 @@ package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
|
||||
text_grpc "github.com/zitadel/zitadel/internal/api/grpc/text"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
caos_errors "github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
action_pb "github.com/zitadel/zitadel/pkg/grpc/action"
|
||||
admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
|
||||
app_pb "github.com/zitadel/zitadel/pkg/grpc/app"
|
||||
idp_pb "github.com/zitadel/zitadel/pkg/grpc/idp"
|
||||
@ -17,7 +19,6 @@ import (
|
||||
project_pb "github.com/zitadel/zitadel/pkg/grpc/project"
|
||||
user_pb "github.com/zitadel/zitadel/pkg/grpc/user"
|
||||
v1_pb "github.com/zitadel/zitadel/pkg/grpc/v1"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
)
|
||||
|
||||
func (s *Server) ExportData(ctx context.Context, req *admin_pb.ExportDataRequest) (_ *admin_pb.ExportDataResponse, err error) {
|
||||
@ -639,8 +640,8 @@ func (s *Server) getTriggerActions(ctx context.Context, org string, processedAct
|
||||
}
|
||||
|
||||
triggerActions = append(triggerActions, &management_pb.SetTriggerActionsRequest{
|
||||
FlowType: action_pb.FlowType(flowType),
|
||||
TriggerType: action_pb.TriggerType(triggerType),
|
||||
FlowType: flowType.ID(),
|
||||
TriggerType: triggerType.ID(),
|
||||
ActionIds: actions,
|
||||
})
|
||||
}
|
||||
@ -662,7 +663,7 @@ func (s *Server) getActions(ctx context.Context, org string) ([]*v1_pb.DataActio
|
||||
return actions, nil
|
||||
}
|
||||
for i, action := range queriedActions.Actions {
|
||||
timeout := durationpb.New(action.Timeout)
|
||||
timeout := durationpb.New(action.Timeout())
|
||||
|
||||
actions[i] = &v1_pb.DataAction{
|
||||
ActionId: action.ID,
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
action_grpc "github.com/zitadel/zitadel/internal/api/grpc/action"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/management"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
@ -693,9 +694,9 @@ func (s *Server) importData(ctx context.Context, orgs []*admin_pb.DataOrg) (*adm
|
||||
|
||||
if org.TriggerActions != nil {
|
||||
for _, triggerAction := range org.GetTriggerActions() {
|
||||
_, err := s.command.SetTriggerActions(ctx, domain.FlowType(triggerAction.FlowType), domain.TriggerType(triggerAction.TriggerType), triggerAction.ActionIds, org.GetOrgId())
|
||||
_, err := s.command.SetTriggerActions(ctx, action_grpc.FlowTypeToDomain(triggerAction.FlowType), action_grpc.TriggerTypeToDomain(triggerAction.TriggerType), triggerAction.ActionIds, org.GetOrgId())
|
||||
if err != nil {
|
||||
errors = append(errors, &admin_pb.ImportDataError{Type: "trigger_action", Id: triggerAction.FlowType.String() + "_" + triggerAction.TriggerType.String(), Message: err.Error()})
|
||||
errors = append(errors, &admin_pb.ImportDataError{Type: "trigger_action", Id: triggerAction.FlowType + "_" + triggerAction.TriggerType, Message: err.Error()})
|
||||
continue
|
||||
}
|
||||
successOrg.TriggerActions = append(successOrg.TriggerActions, &management_pb.SetTriggerActionsRequest{FlowType: triggerAction.FlowType, TriggerType: triggerAction.TriggerType, ActionIds: triggerAction.GetActionIds()})
|
||||
|
@ -6,9 +6,31 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
action_grpc "github.com/zitadel/zitadel/internal/api/grpc/action"
|
||||
obj_grpc "github.com/zitadel/zitadel/internal/api/grpc/object"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
action_pb "github.com/zitadel/zitadel/pkg/grpc/action"
|
||||
mgmt_pb "github.com/zitadel/zitadel/pkg/grpc/management"
|
||||
)
|
||||
|
||||
func (s *Server) ListFlowTypes(ctx context.Context, _ *mgmt_pb.ListFlowTypesRequest) (*mgmt_pb.ListFlowTypesResponse, error) {
|
||||
return &mgmt_pb.ListFlowTypesResponse{
|
||||
Result: []*action_pb.FlowType{
|
||||
action_grpc.FlowTypeToPb(domain.FlowTypeExternalAuthentication),
|
||||
action_grpc.FlowTypeToPb(domain.FlowTypeCustomiseToken),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ListFlowTriggerTypes(ctx context.Context, req *mgmt_pb.ListFlowTriggerTypesRequest) (*mgmt_pb.ListFlowTriggerTypesResponse, error) {
|
||||
triggerTypes := action_grpc.FlowTypeToDomain(req.Type).TriggerTypes()
|
||||
if len(triggerTypes) == 0 {
|
||||
return nil, errors.ThrowNotFound(nil, "MANAG-P2OBk", "Errors.NotFound")
|
||||
}
|
||||
return &mgmt_pb.ListFlowTriggerTypesResponse{
|
||||
Result: action_grpc.TriggerTypesToPb(triggerTypes),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetFlow(ctx context.Context, req *mgmt_pb.GetFlowRequest) (*mgmt_pb.GetFlowResponse, error) {
|
||||
flow, err := s.query.GetFlow(ctx, action_grpc.FlowTypeToDomain(req.Type), authz.GetCtxData(ctx).OrgID)
|
||||
if err != nil {
|
||||
|
@ -3,14 +3,20 @@ package oidc
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"github.com/zitadel/logging"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v2/pkg/op"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/actions"
|
||||
"github.com/zitadel/zitadel/internal/actions/object"
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/http"
|
||||
api_http "github.com/zitadel/zitadel/internal/api/http"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
@ -25,6 +31,7 @@ const (
|
||||
ClaimUserMetaData = ScopeUserMetaData
|
||||
ScopeResourceOwner = "urn:zitadel:iam:user:resourceowner"
|
||||
ClaimResourceOwner = ScopeResourceOwner + ":"
|
||||
ClaimActionLogFormat = "urn:zitadel:iam:action:%s:log"
|
||||
|
||||
oidcCtx = "oidc"
|
||||
)
|
||||
@ -141,7 +148,7 @@ func (o *OPStorage) SetUserinfoFromToken(ctx context.Context, userInfo oidc.User
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if origin != "" && !http.IsOriginAllowed(app.OIDCConfig.AllowedOrigins, origin) {
|
||||
if origin != "" && !api_http.IsOriginAllowed(app.OIDCConfig.AllowedOrigins, origin) {
|
||||
return errors.ThrowPermissionDenied(nil, "OIDC-da1f3", "origin is not allowed")
|
||||
}
|
||||
}
|
||||
@ -276,8 +283,9 @@ func (o *OPStorage) setUserinfo(ctx context.Context, userInfo oidc.UserInfoSette
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(roles) == 0 || applicationID == "" {
|
||||
return nil
|
||||
return o.userinfoFlows(ctx, user.ResourceOwner, userInfo)
|
||||
}
|
||||
projectRoles, err := o.assertRoles(ctx, userID, applicationID, roles)
|
||||
if err != nil {
|
||||
@ -286,6 +294,106 @@ func (o *OPStorage) setUserinfo(ctx context.Context, userInfo oidc.UserInfoSette
|
||||
if len(projectRoles) > 0 {
|
||||
userInfo.AppendClaims(ClaimProjectRoles, projectRoles)
|
||||
}
|
||||
|
||||
return o.userinfoFlows(ctx, user.ResourceOwner, userInfo)
|
||||
}
|
||||
|
||||
func (o *OPStorage) userinfoFlows(ctx context.Context, resourceOwner string, userInfo oidc.UserInfoSetter) error {
|
||||
queriedActions, err := o.query.GetActiveActionsByFlowAndTriggerType(ctx, domain.FlowTypeCustomiseToken, domain.TriggerTypePreUserinfoCreation, resourceOwner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctxFields := actions.SetContextFields(
|
||||
actions.SetFields("v1",
|
||||
actions.SetFields("user",
|
||||
actions.SetFields("getMetadata", func(c *actions.FieldConfig) interface{} {
|
||||
return func(goja.FunctionCall) goja.Value {
|
||||
resourceOwnerQuery, err := query.NewUserMetadataResourceOwnerSearchQuery(resourceOwner)
|
||||
if err != nil {
|
||||
logging.WithError(err).Debug("unable to create search query")
|
||||
panic(err)
|
||||
}
|
||||
metadata, err := o.query.SearchUserMetadata(
|
||||
ctx,
|
||||
true,
|
||||
userInfo.GetSubject(),
|
||||
&query.UserMetadataSearchQueries{Queries: []query.SearchQuery{resourceOwnerQuery}},
|
||||
)
|
||||
if err != nil {
|
||||
logging.WithError(err).Info("unable to get md in action")
|
||||
panic(err)
|
||||
}
|
||||
return object.UserMetadataListFromQuery(c, metadata)
|
||||
}
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
for _, action := range queriedActions {
|
||||
actionCtx, cancel := context.WithTimeout(ctx, action.Timeout())
|
||||
claimLogs := []string{}
|
||||
|
||||
apiFields := actions.WithAPIFields(
|
||||
actions.SetFields("v1",
|
||||
actions.SetFields("userinfo",
|
||||
actions.SetFields("setClaim", func(key string, value interface{}) {
|
||||
if userInfo.GetClaim(key) == nil {
|
||||
userInfo.AppendClaims(key, value)
|
||||
return
|
||||
}
|
||||
claimLogs = append(claimLogs, fmt.Sprintf("key %q already exists", key))
|
||||
}),
|
||||
actions.SetFields("appendLogIntoClaims", func(entry string) {
|
||||
claimLogs = append(claimLogs, entry)
|
||||
}),
|
||||
),
|
||||
actions.SetFields("user",
|
||||
actions.SetFields("setMetadata", func(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) != 2 {
|
||||
panic("exactly 2 (key, value) arguments expected")
|
||||
}
|
||||
key := call.Arguments[0].Export().(string)
|
||||
val := call.Arguments[1].Export()
|
||||
|
||||
value, err := json.Marshal(val)
|
||||
if err != nil {
|
||||
logging.WithError(err).Debug("unable to marshal")
|
||||
panic(err)
|
||||
}
|
||||
|
||||
metadata := &domain.Metadata{
|
||||
Key: key,
|
||||
Value: value,
|
||||
}
|
||||
if _, err = o.command.SetUserMetadata(ctx, metadata, userInfo.GetSubject(), resourceOwner); err != nil {
|
||||
logging.WithError(err).Info("unable to set md in action")
|
||||
panic(err)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
err = actions.Run(
|
||||
actionCtx,
|
||||
ctxFields,
|
||||
apiFields,
|
||||
action.Script,
|
||||
action.Name,
|
||||
append(actions.ActionToOptions(action), actions.WithHTTP(actionCtx), actions.WithLogger(actions.ServerLog))...,
|
||||
)
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(claimLogs) > 0 {
|
||||
userInfo.AppendClaims(fmt.Sprintf(ClaimActionLogFormat, action.Name), claimLogs)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -327,8 +435,9 @@ func (o *OPStorage) GetPrivateClaimsFromScopes(ctx context.Context, userID, clie
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(roles) == 0 || clientID == "" {
|
||||
return claims, nil
|
||||
return o.privateClaimsFlows(ctx, userID, claims)
|
||||
}
|
||||
projectRoles, err := o.assertRoles(ctx, userID, clientID, roles)
|
||||
if err != nil {
|
||||
@ -337,7 +446,111 @@ func (o *OPStorage) GetPrivateClaimsFromScopes(ctx context.Context, userID, clie
|
||||
if len(projectRoles) > 0 {
|
||||
claims = appendClaim(claims, ClaimProjectRoles, projectRoles)
|
||||
}
|
||||
return claims, err
|
||||
|
||||
return o.privateClaimsFlows(ctx, userID, claims)
|
||||
}
|
||||
|
||||
func (o *OPStorage) privateClaimsFlows(ctx context.Context, userID string, claims map[string]interface{}) (map[string]interface{}, error) {
|
||||
user, err := o.query.GetUserByID(ctx, true, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queriedActions, err := o.query.GetActiveActionsByFlowAndTriggerType(ctx, domain.FlowTypeCustomiseToken, domain.TriggerTypePreAccessTokenCreation, user.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctxFields := actions.SetContextFields(
|
||||
actions.SetFields("v1",
|
||||
actions.SetFields("user",
|
||||
actions.SetFields("getMetadata", func(c *actions.FieldConfig) interface{} {
|
||||
return func(goja.FunctionCall) goja.Value {
|
||||
resourceOwnerQuery, err := query.NewUserMetadataResourceOwnerSearchQuery(user.ResourceOwner)
|
||||
if err != nil {
|
||||
logging.WithError(err).Debug("unable to create search query")
|
||||
panic(err)
|
||||
}
|
||||
metadata, err := o.query.SearchUserMetadata(
|
||||
ctx,
|
||||
true,
|
||||
userID,
|
||||
&query.UserMetadataSearchQueries{Queries: []query.SearchQuery{resourceOwnerQuery}},
|
||||
)
|
||||
if err != nil {
|
||||
logging.WithError(err).Info("unable to get md in action")
|
||||
panic(err)
|
||||
}
|
||||
return object.UserMetadataListFromQuery(c, metadata)
|
||||
}
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
for _, action := range queriedActions {
|
||||
claimLogs := []string{}
|
||||
actionCtx, cancel := context.WithTimeout(ctx, action.Timeout())
|
||||
|
||||
apiFields := actions.WithAPIFields(
|
||||
actions.SetFields("v1",
|
||||
actions.SetFields("claims",
|
||||
actions.SetFields("setClaim", func(key string, value interface{}) {
|
||||
if _, ok := claims[key]; !ok {
|
||||
claims[key] = value
|
||||
return
|
||||
}
|
||||
claimLogs = append(claimLogs, fmt.Sprintf("key %q already exists", key))
|
||||
}),
|
||||
actions.SetFields("appendLogIntoClaims", func(entry string) {
|
||||
claimLogs = append(claimLogs, entry)
|
||||
}),
|
||||
),
|
||||
actions.SetFields("user",
|
||||
actions.SetFields("setMetadata", func(call goja.FunctionCall) {
|
||||
if len(call.Arguments) != 2 {
|
||||
panic("exactly 2 (key, value) arguments expected")
|
||||
}
|
||||
key := call.Arguments[0].Export().(string)
|
||||
val := call.Arguments[1].Export()
|
||||
|
||||
value, err := json.Marshal(val)
|
||||
if err != nil {
|
||||
logging.WithError(err).Debug("unable to marshal")
|
||||
panic(err)
|
||||
}
|
||||
|
||||
metadata := &domain.Metadata{
|
||||
Key: key,
|
||||
Value: value,
|
||||
}
|
||||
if _, err = o.command.SetUserMetadata(ctx, metadata, userID, user.ResourceOwner); err != nil {
|
||||
logging.WithError(err).Info("unable to set md in action")
|
||||
panic(err)
|
||||
}
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
err = actions.Run(
|
||||
actionCtx,
|
||||
ctxFields,
|
||||
apiFields,
|
||||
action.Script,
|
||||
action.Name,
|
||||
append(actions.ActionToOptions(action), actions.WithHTTP(actionCtx), actions.WithLogger(actions.ServerLog))...,
|
||||
)
|
||||
cancel()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(claimLogs) > 0 {
|
||||
claims = appendClaim(claims, fmt.Sprintf(ClaimActionLogFormat, action.Name), claimLogs)
|
||||
claimLogs = nil
|
||||
}
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
func (o *OPStorage) assertRoles(ctx context.Context, userID, applicationID string, requestedRoles []string) (map[string]map[string]string, error) {
|
||||
|
@ -2,10 +2,15 @@ package login
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"github.com/zitadel/logging"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/actions"
|
||||
"github.com/zitadel/zitadel/internal/actions/object"
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
iam_model "github.com/zitadel/zitadel/internal/iam/model"
|
||||
@ -24,10 +29,95 @@ func (l *Login) customExternalUserMapping(ctx context.Context, user *domain.Exte
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
actionCtx := (&actions.Context{}).SetToken(tokens)
|
||||
api := (&actions.API{}).SetExternalUser(user).SetMetadata(&user.Metadatas)
|
||||
|
||||
ctxFields := actions.SetContextFields(
|
||||
actions.SetFields("accessToken", tokens.AccessToken),
|
||||
actions.SetFields("idToken", tokens.IDToken),
|
||||
actions.SetFields("getClaim", func(claim string) interface{} {
|
||||
return tokens.IDTokenClaims.GetClaim(claim)
|
||||
}),
|
||||
actions.SetFields("claimsJSON", func() (string, error) {
|
||||
c, err := json.Marshal(tokens.IDTokenClaims)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(c), nil
|
||||
}),
|
||||
actions.SetFields("v1",
|
||||
actions.SetFields("externalUser", func(c *actions.FieldConfig) interface{} {
|
||||
return object.UserFromExternalUser(c, user)
|
||||
}),
|
||||
),
|
||||
)
|
||||
apiFields := actions.WithAPIFields(
|
||||
actions.SetFields("setFirstName", func(firstName string) {
|
||||
user.FirstName = firstName
|
||||
}),
|
||||
actions.SetFields("setLastName", func(lastName string) {
|
||||
user.LastName = lastName
|
||||
}),
|
||||
actions.SetFields("setNickName", func(nickName string) {
|
||||
user.NickName = nickName
|
||||
}),
|
||||
actions.SetFields("setDisplayName", func(displayName string) {
|
||||
user.DisplayName = displayName
|
||||
}),
|
||||
actions.SetFields("setPreferredLanguage", func(preferredLanguage string) {
|
||||
user.PreferredLanguage = language.Make(preferredLanguage)
|
||||
}),
|
||||
actions.SetFields("setPreferredUsername", func(username string) {
|
||||
user.PreferredUsername = username
|
||||
}),
|
||||
actions.SetFields("setEmail", func(email string) {
|
||||
user.Email = email
|
||||
}),
|
||||
actions.SetFields("setEmailVerified", func(verified bool) {
|
||||
user.IsEmailVerified = verified
|
||||
}),
|
||||
actions.SetFields("setPhone", func(phone string) {
|
||||
user.Phone = phone
|
||||
}),
|
||||
actions.SetFields("setPhoneVerified", func(verified bool) {
|
||||
user.IsPhoneVerified = verified
|
||||
}),
|
||||
actions.SetFields("metadata", &user.Metadatas),
|
||||
actions.SetFields("v1",
|
||||
actions.SetFields("user",
|
||||
actions.SetFields("appendMetadata", func(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) != 2 {
|
||||
panic("exactly 2 (key, value) arguments expected")
|
||||
}
|
||||
key := call.Arguments[0].Export().(string)
|
||||
val := call.Arguments[1].Export()
|
||||
|
||||
value, err := json.Marshal(val)
|
||||
if err != nil {
|
||||
logging.WithError(err).Debug("unable to marshal")
|
||||
panic(err)
|
||||
}
|
||||
|
||||
user.Metadatas = append(user.Metadatas,
|
||||
&domain.Metadata{
|
||||
Key: key,
|
||||
Value: value,
|
||||
})
|
||||
return nil
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
for _, a := range triggerActions {
|
||||
err = actions.Run(actionCtx, api, a.Script, a.Name, a.Timeout, a.AllowedToFail)
|
||||
actionCtx, cancel := context.WithTimeout(ctx, a.Timeout())
|
||||
err = actions.Run(
|
||||
actionCtx,
|
||||
ctxFields,
|
||||
apiFields,
|
||||
a.Script,
|
||||
a.Name,
|
||||
append(actions.ActionToOptions(a), actions.WithHTTP(actionCtx), actions.WithLogger(actions.ServerLog))...,
|
||||
)
|
||||
cancel()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -40,10 +130,98 @@ func (l *Login) customExternalUserToLoginUserMapping(ctx context.Context, user *
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
actionCtx := (&actions.Context{}).SetToken(tokens)
|
||||
api := (&actions.API{}).SetHuman(user).SetMetadata(&metadata)
|
||||
|
||||
ctxOpts := actions.SetContextFields(
|
||||
actions.SetFields("v1",
|
||||
actions.SetFields("user", func(c *actions.FieldConfig) interface{} {
|
||||
return object.UserFromHuman(c, user)
|
||||
}),
|
||||
),
|
||||
)
|
||||
apiFields := actions.WithAPIFields(
|
||||
actions.SetFields("setFirstName", func(firstName string) {
|
||||
user.FirstName = firstName
|
||||
}),
|
||||
actions.SetFields("setLastName", func(lastName string) {
|
||||
user.LastName = lastName
|
||||
}),
|
||||
actions.SetFields("setNickName", func(nickName string) {
|
||||
user.NickName = nickName
|
||||
}),
|
||||
actions.SetFields("setDisplayName", func(displayName string) {
|
||||
user.DisplayName = displayName
|
||||
}),
|
||||
actions.SetFields("setPreferredLanguage", func(preferredLanguage string) {
|
||||
user.PreferredLanguage = language.Make(preferredLanguage)
|
||||
}),
|
||||
actions.SetFields("setGender", func(gender domain.Gender) {
|
||||
user.Gender = gender
|
||||
}),
|
||||
actions.SetFields("setUsername", func(username string) {
|
||||
user.Username = username
|
||||
}),
|
||||
actions.SetFields("setEmail", func(email string) {
|
||||
if user.Email == nil {
|
||||
user.Email = &domain.Email{}
|
||||
}
|
||||
user.Email.EmailAddress = email
|
||||
}),
|
||||
actions.SetFields("setEmailVerified", func(verified bool) {
|
||||
if user.Email == nil {
|
||||
return
|
||||
}
|
||||
user.Email.IsEmailVerified = verified
|
||||
}),
|
||||
actions.SetFields("setPhone", func(email string) {
|
||||
if user.Phone == nil {
|
||||
user.Phone = &domain.Phone{}
|
||||
}
|
||||
user.Phone.PhoneNumber = email
|
||||
}),
|
||||
actions.SetFields("setPhoneVerified", func(verified bool) {
|
||||
if user.Phone == nil {
|
||||
return
|
||||
}
|
||||
user.Phone.IsPhoneVerified = verified
|
||||
}),
|
||||
actions.SetFields("metadata", metadata),
|
||||
actions.SetFields("v1",
|
||||
actions.SetFields("user",
|
||||
actions.SetFields("appendMetadata", func(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) != 2 {
|
||||
panic("exactly 2 (key, value) arguments expected")
|
||||
}
|
||||
key := call.Arguments[0].Export().(string)
|
||||
val := call.Arguments[1].Export()
|
||||
|
||||
value, err := json.Marshal(val)
|
||||
if err != nil {
|
||||
logging.WithError(err).Debug("unable to marshal")
|
||||
panic(err)
|
||||
}
|
||||
|
||||
metadata = append(metadata,
|
||||
&domain.Metadata{
|
||||
Key: key,
|
||||
Value: value,
|
||||
})
|
||||
return nil
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
for _, a := range triggerActions {
|
||||
err = actions.Run(actionCtx, api, a.Script, a.Name, a.Timeout, a.AllowedToFail)
|
||||
actionCtx, cancel := context.WithTimeout(ctx, a.Timeout())
|
||||
err = actions.Run(
|
||||
actionCtx,
|
||||
ctxOpts,
|
||||
apiFields,
|
||||
a.Script,
|
||||
a.Name,
|
||||
append(actions.ActionToOptions(a), actions.WithHTTP(actionCtx), actions.WithLogger(actions.ServerLog))...,
|
||||
)
|
||||
cancel()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -56,11 +234,78 @@ func (l *Login) customGrants(ctx context.Context, userID string, tokens *oidc.To
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
actionCtx := (&actions.Context{}).SetToken(tokens)
|
||||
|
||||
actionUserGrants := make([]actions.UserGrant, 0)
|
||||
api := (&actions.API{}).SetUserGrants(&actionUserGrants)
|
||||
|
||||
apiFields := actions.WithAPIFields(
|
||||
actions.SetFields("userGrants", &actionUserGrants),
|
||||
actions.SetFields("v1",
|
||||
actions.SetFields("appendUserGrant", func(c *actions.FieldConfig) interface{} {
|
||||
return func(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) != 1 {
|
||||
panic("exactly one argument expected")
|
||||
}
|
||||
object := call.Arguments[0].ToObject(c.Runtime)
|
||||
if object == nil {
|
||||
panic("unable to unmarshal arg")
|
||||
}
|
||||
grant := actions.UserGrant{}
|
||||
|
||||
for _, key := range object.Keys() {
|
||||
switch key {
|
||||
case "projectId":
|
||||
grant.ProjectID = object.Get(key).String()
|
||||
case "projectGrantId":
|
||||
grant.ProjectGrantID = object.Get(key).String()
|
||||
case "roles":
|
||||
if roles, ok := object.Get(key).Export().([]interface{}); ok {
|
||||
for _, role := range roles {
|
||||
if r, ok := role.(string); ok {
|
||||
grant.Roles = append(grant.Roles, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if grant.ProjectID == "" {
|
||||
panic("projectId not set")
|
||||
}
|
||||
|
||||
actionUserGrants = append(actionUserGrants, grant)
|
||||
|
||||
return nil
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
for _, a := range triggerActions {
|
||||
err = actions.Run(actionCtx, api, a.Script, a.Name, a.Timeout, a.AllowedToFail)
|
||||
actionCtx, cancel := context.WithTimeout(ctx, a.Timeout())
|
||||
|
||||
ctxFields := actions.SetContextFields(
|
||||
actions.SetFields("v1",
|
||||
actions.SetFields("getUser", func(c *actions.FieldConfig) interface{} {
|
||||
return func(call goja.FunctionCall) goja.Value {
|
||||
user, err := l.query.GetUserByID(actionCtx, true, userID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return object.UserFromQuery(c, user)
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
err = actions.Run(
|
||||
actionCtx,
|
||||
ctxFields,
|
||||
apiFields,
|
||||
a.Script,
|
||||
a.Name,
|
||||
append(actions.ActionToOptions(a), actions.WithHTTP(actionCtx), actions.WithLogger(actions.ServerLog))...,
|
||||
)
|
||||
cancel()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
caos_errors "github.com/zitadel/zitadel/internal/errors"
|
||||
iam_model "github.com/zitadel/zitadel/internal/iam/model"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
)
|
||||
@ -121,7 +120,7 @@ func (l *Login) handleJWTAuthorize(w http.ResponseWriter, r *http.Request, authR
|
||||
q.Set(QueryAuthRequestID, authReq.ID)
|
||||
userAgentID, ok := http_mw.UserAgentIDFromCtx(r.Context())
|
||||
if !ok {
|
||||
l.renderLogin(w, r, authReq, caos_errors.ThrowPreconditionFailed(nil, "LOGIN-dsgg3", "Errors.AuthRequest.UserAgentNotFound"))
|
||||
l.renderLogin(w, r, authReq, errors.ThrowPreconditionFailed(nil, "LOGIN-dsgg3", "Errors.AuthRequest.UserAgentNotFound"))
|
||||
return
|
||||
}
|
||||
nonce, err := l.idpConfigAlg.Encrypt([]byte(userAgentID))
|
||||
@ -166,7 +165,7 @@ func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Reque
|
||||
l.handleExternalUserAuthenticated(w, r, authReq, idpConfig, userAgentID, tokens)
|
||||
return
|
||||
}
|
||||
l.renderError(w, r, authReq, caos_errors.ThrowPreconditionFailed(nil, "RP-asff2", "Errors.ExternalIDP.IDPTypeNotImplemented"))
|
||||
l.renderError(w, r, authReq, errors.ThrowPreconditionFailed(nil, "RP-asff2", "Errors.ExternalIDP.IDPTypeNotImplemented"))
|
||||
}
|
||||
|
||||
func (l *Login) getRPConfig(ctx context.Context, idpConfig *iam_model.IDPConfigView, callbackEndpoint string) (rp.RelyingParty, error) {
|
||||
@ -178,7 +177,7 @@ func (l *Login) getRPConfig(ctx context.Context, idpConfig *iam_model.IDPConfigV
|
||||
return rp.NewRelyingPartyOIDC(idpConfig.OIDCIssuer, idpConfig.OIDCClientID, oidcClientSecret, l.baseURL(ctx)+callbackEndpoint, idpConfig.OIDCScopes, rp.WithVerifierOpts(rp.WithIssuedAtOffset(3*time.Second)))
|
||||
}
|
||||
if idpConfig.OAuthAuthorizationEndpoint == "" || idpConfig.OAuthTokenEndpoint == "" {
|
||||
return nil, caos_errors.ThrowPreconditionFailed(nil, "RP-4n0fs", "Errors.IdentityProvider.InvalidConfig")
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "RP-4n0fs", "Errors.IdentityProvider.InvalidConfig")
|
||||
}
|
||||
oauth2Config := &oauth2.Config{
|
||||
ClientID: idpConfig.OIDCClientID,
|
||||
@ -361,7 +360,7 @@ func (l *Login) handleAutoRegister(w http.ResponseWriter, r *http.Request, authR
|
||||
|
||||
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
|
||||
if len(authReq.LinkingUsers) == 0 {
|
||||
l.renderError(w, r, authReq, caos_errors.ThrowPreconditionFailed(nil, "LOGIN-asfg3", "Errors.ExternalIDP.NoExternalUserData"))
|
||||
l.renderError(w, r, authReq, errors.ThrowPreconditionFailed(nil, "LOGIN-asfg3", "Errors.ExternalIDP.NoExternalUserData"))
|
||||
return
|
||||
}
|
||||
|
||||
@ -407,19 +406,19 @@ func (l *Login) handleAutoRegister(w http.ResponseWriter, r *http.Request, authR
|
||||
}
|
||||
|
||||
func (l *Login) mapExternalNotFoundOptionFormDataToLoginUser(formData *externalNotFoundOptionFormData) *domain.ExternalUser {
|
||||
isEmailVerified := formData.externalRegisterFormData.ExternalEmailVerified && formData.externalRegisterFormData.Email == formData.externalRegisterFormData.ExternalEmail
|
||||
isPhoneVerified := formData.externalRegisterFormData.ExternalPhoneVerified && formData.externalRegisterFormData.Phone == formData.externalRegisterFormData.ExternalPhone
|
||||
isEmailVerified := formData.ExternalEmailVerified && formData.Email == formData.ExternalEmail
|
||||
isPhoneVerified := formData.ExternalPhoneVerified && formData.Phone == formData.ExternalPhone
|
||||
return &domain.ExternalUser{
|
||||
IDPConfigID: formData.externalRegisterFormData.ExternalIDPConfigID,
|
||||
ExternalUserID: formData.externalRegisterFormData.ExternalIDPExtUserID,
|
||||
PreferredUsername: formData.externalRegisterFormData.Username,
|
||||
DisplayName: formData.externalRegisterFormData.Email,
|
||||
FirstName: formData.externalRegisterFormData.Firstname,
|
||||
LastName: formData.externalRegisterFormData.Lastname,
|
||||
NickName: formData.externalRegisterFormData.Nickname,
|
||||
Email: formData.externalRegisterFormData.Email,
|
||||
IDPConfigID: formData.ExternalIDPConfigID,
|
||||
ExternalUserID: formData.ExternalIDPExtUserID,
|
||||
PreferredUsername: formData.Username,
|
||||
DisplayName: formData.Email,
|
||||
FirstName: formData.Firstname,
|
||||
LastName: formData.Lastname,
|
||||
NickName: formData.Nickname,
|
||||
Email: formData.Email,
|
||||
IsEmailVerified: isEmailVerified,
|
||||
Phone: formData.externalRegisterFormData.Phone,
|
||||
Phone: formData.Phone,
|
||||
IsPhoneVerified: isPhoneVerified,
|
||||
}
|
||||
}
|
||||
|
@ -4,19 +4,17 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
caos_errs "github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
)
|
||||
|
||||
func (c *Commands) AddDefaultIDPConfig(ctx context.Context, config *domain.IDPConfig) (*domain.IDPConfig, error) {
|
||||
if config.OIDCConfig == nil && config.JWTConfig == nil {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "IDP-s8nn3", "Errors.IDPConfig.Invalid")
|
||||
return nil, errors.ThrowInvalidArgument(nil, "IDP-s8nn3", "Errors.IDPConfig.Invalid")
|
||||
}
|
||||
idpConfigID, err := c.idGenerator.Next()
|
||||
if err != nil {
|
||||
@ -86,13 +84,13 @@ func (c *Commands) ChangeDefaultIDPConfig(ctx context.Context, config *domain.ID
|
||||
return nil, err
|
||||
}
|
||||
if existingIDP.State == domain.IDPConfigStateRemoved || existingIDP.State == domain.IDPConfigStateUnspecified {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "INSTANCE-m0e3r", "Errors.IDPConfig.NotExisting")
|
||||
return nil, errors.ThrowNotFound(nil, "INSTANCE-m0e3r", "Errors.IDPConfig.NotExisting")
|
||||
}
|
||||
|
||||
instanceAgg := InstanceAggregateFromWriteModel(&existingIDP.WriteModel)
|
||||
changedEvent, hasChanged := existingIDP.NewChangedEvent(ctx, instanceAgg, config.IDPConfigID, config.Name, config.StylingType, config.AutoRegister)
|
||||
if !hasChanged {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "INSTANCE-3k0fs", "Errors.IAM.IDPConfig.NotChanged")
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "INSTANCE-3k0fs", "Errors.IAM.IDPConfig.NotChanged")
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, changedEvent)
|
||||
if err != nil {
|
||||
@ -111,7 +109,7 @@ func (c *Commands) DeactivateDefaultIDPConfig(ctx context.Context, idpID string)
|
||||
return nil, err
|
||||
}
|
||||
if existingIDP.State != domain.IDPConfigStateActive {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "INSTANCE-2n0fs", "Errors.IAM.IDPConfig.NotActive")
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "INSTANCE-2n0fs", "Errors.IAM.IDPConfig.NotActive")
|
||||
}
|
||||
instanceAgg := InstanceAggregateFromWriteModel(&existingIDP.WriteModel)
|
||||
pushedEvents, err := c.eventstore.Push(ctx, instance.NewIDPConfigDeactivatedEvent(ctx, instanceAgg, idpID))
|
||||
@ -131,7 +129,7 @@ func (c *Commands) ReactivateDefaultIDPConfig(ctx context.Context, idpID string)
|
||||
return nil, err
|
||||
}
|
||||
if existingIDP.State != domain.IDPConfigStateInactive {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "INSTANCE-5Mo0d", "Errors.IAM.IDPConfig.NotInactive")
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "INSTANCE-5Mo0d", "Errors.IAM.IDPConfig.NotInactive")
|
||||
}
|
||||
instanceAgg := InstanceAggregateFromWriteModel(&existingIDP.WriteModel)
|
||||
pushedEvents, err := c.eventstore.Push(ctx, instance.NewIDPConfigReactivatedEvent(ctx, instanceAgg, idpID))
|
||||
@ -151,7 +149,7 @@ func (c *Commands) RemoveDefaultIDPConfig(ctx context.Context, idpID string, idp
|
||||
return nil, err
|
||||
}
|
||||
if existingIDP.State == domain.IDPConfigStateRemoved || existingIDP.State == domain.IDPConfigStateUnspecified {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "INSTANCE-4M0xy", "Errors.IDPConfig.NotExisting")
|
||||
return nil, errors.ThrowNotFound(nil, "INSTANCE-4M0xy", "Errors.IDPConfig.NotExisting")
|
||||
}
|
||||
|
||||
instanceAgg := InstanceAggregateFromWriteModel(&existingIDP.WriteModel)
|
||||
@ -186,7 +184,7 @@ func (c *Commands) getInstanceIDPConfigByID(ctx context.Context, idpID string) (
|
||||
return nil, err
|
||||
}
|
||||
if !config.State.Exists() {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "INSTANCE-p0pFF", "Errors.IDPConfig.NotExisting")
|
||||
return nil, errors.ThrowNotFound(nil, "INSTANCE-p0pFF", "Errors.IDPConfig.NotExisting")
|
||||
}
|
||||
return writeModelToIDPConfig(&config.IDPConfigWriteModel), nil
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ func (c *Commands) SetUpOrg(ctx context.Context, o *OrgSetup, userIDs ...string)
|
||||
return c.setUpOrgWithIDs(ctx, o, orgID, userID, userIDs...)
|
||||
}
|
||||
|
||||
//AddOrgCommand defines the commands to create a new org,
|
||||
// AddOrgCommand defines the commands to create a new org,
|
||||
// this includes the verified default domain
|
||||
func AddOrgCommand(ctx context.Context, a *org.Aggregate, name string, userIDs ...string) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
@ -117,7 +117,7 @@ func (c *Commands) checkOrgExists(ctx context.Context, orgID string) error {
|
||||
return err
|
||||
}
|
||||
if orgWriteModel.State == domain.OrgStateUnspecified || orgWriteModel.State == domain.OrgStateRemoved {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M0fs", "Errors.Org.NotFound")
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-QXPGs", "Errors.Org.NotFound")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -165,7 +165,7 @@ func (c *Commands) checkProjectExists(ctx context.Context, projectID, resourceOw
|
||||
return err
|
||||
}
|
||||
if projectWriteModel.State == domain.ProjectStateUnspecified || projectWriteModel.State == domain.ProjectStateRemoved {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M0fs", "Errors.Project.NotFound")
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-EbFMN", "Errors.Project.NotFound")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -391,7 +391,7 @@ func (c *Commands) checkUserExists(ctx context.Context, userID, resourceOwner st
|
||||
return err
|
||||
}
|
||||
if !isUserStateExists(existingUser.UserState) {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M0fs", "Errors.User.NotFound")
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-uXHNj", "Errors.User.NotFound")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ func (c *Commands) AddUserGrant(ctx context.Context, usergrant *domain.UserGrant
|
||||
|
||||
func (c *Commands) addUserGrant(ctx context.Context, userGrant *domain.UserGrant, resourceOwner string) (command eventstore.Command, _ *UserGrantWriteModel, err error) {
|
||||
if !userGrant.IsValid() {
|
||||
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M0fs", "Errors.UserGrant.Invalid")
|
||||
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-kVfMa", "Errors.UserGrant.Invalid")
|
||||
}
|
||||
err = c.checkUserGrantPreCondition(ctx, userGrant, resourceOwner)
|
||||
if err != nil {
|
||||
|
@ -1,5 +1,7 @@
|
||||
package domain
|
||||
|
||||
import "strconv"
|
||||
|
||||
type FlowState int32
|
||||
|
||||
const (
|
||||
@ -17,6 +19,7 @@ type FlowType int32
|
||||
const (
|
||||
FlowTypeUnspecified FlowType = iota
|
||||
FlowTypeExternalAuthentication
|
||||
FlowTypeCustomiseToken
|
||||
flowTypeCount
|
||||
)
|
||||
|
||||
@ -25,15 +28,51 @@ func (s FlowType) Valid() bool {
|
||||
}
|
||||
|
||||
func (s FlowType) HasTrigger(triggerType TriggerType) bool {
|
||||
switch triggerType {
|
||||
case TriggerTypePostAuthentication:
|
||||
return s == FlowTypeExternalAuthentication
|
||||
case TriggerTypePreCreation:
|
||||
return s == FlowTypeExternalAuthentication
|
||||
case TriggerTypePostCreation:
|
||||
return s == FlowTypeExternalAuthentication
|
||||
for _, trigger := range s.TriggerTypes() {
|
||||
if trigger == triggerType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s FlowType) TriggerTypes() []TriggerType {
|
||||
switch s {
|
||||
case FlowTypeExternalAuthentication:
|
||||
return []TriggerType{
|
||||
TriggerTypePostAuthentication,
|
||||
TriggerTypePreCreation,
|
||||
TriggerTypePostCreation,
|
||||
}
|
||||
case FlowTypeCustomiseToken:
|
||||
return []TriggerType{
|
||||
TriggerTypePreUserinfoCreation,
|
||||
TriggerTypePreAccessTokenCreation,
|
||||
}
|
||||
default:
|
||||
return false
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s FlowType) ID() string {
|
||||
if s < 0 && s >= flowTypeCount {
|
||||
return FlowTypeUnspecified.ID()
|
||||
}
|
||||
return strconv.Itoa(int(s))
|
||||
}
|
||||
|
||||
func (s FlowType) LocalizationKey() string {
|
||||
if s < 0 && s >= flowTypeCount {
|
||||
return FlowTypeUnspecified.LocalizationKey()
|
||||
}
|
||||
|
||||
switch s {
|
||||
case FlowTypeExternalAuthentication:
|
||||
return "Action.Flow.Type.ExternalAuthentication"
|
||||
case FlowTypeCustomiseToken:
|
||||
return "Action.Flow.Type.CustomiseToken"
|
||||
default:
|
||||
return "Action.Flow.Type.Unspecified"
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,9 +83,39 @@ const (
|
||||
TriggerTypePostAuthentication
|
||||
TriggerTypePreCreation
|
||||
TriggerTypePostCreation
|
||||
TriggerTypePreUserinfoCreation
|
||||
TriggerTypePreAccessTokenCreation
|
||||
triggerTypeCount
|
||||
)
|
||||
|
||||
func (s TriggerType) Valid() bool {
|
||||
return s >= 0 && s < triggerTypeCount
|
||||
}
|
||||
|
||||
func (s TriggerType) ID() string {
|
||||
if !s.Valid() {
|
||||
return TriggerTypeUnspecified.ID()
|
||||
}
|
||||
return strconv.Itoa(int(s))
|
||||
}
|
||||
|
||||
func (s TriggerType) LocalizationKey() string {
|
||||
if !s.Valid() {
|
||||
return FlowTypeUnspecified.LocalizationKey()
|
||||
}
|
||||
|
||||
switch s {
|
||||
case TriggerTypePostAuthentication:
|
||||
return "Action.TriggerType.PostAuthentication"
|
||||
case TriggerTypePreCreation:
|
||||
return "Action.TriggerType.PreCreation"
|
||||
case TriggerTypePostCreation:
|
||||
return "Action.TriggerType.PostCreation"
|
||||
case TriggerTypePreUserinfoCreation:
|
||||
return "Action.TriggerType.PreUserinfoCreation"
|
||||
case TriggerTypePreAccessTokenCreation:
|
||||
return "Action.TriggerType.PreAccessTokenCreation"
|
||||
default:
|
||||
return "Action.TriggerType.Unspecified"
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"github.com/drone/envsubst"
|
||||
"github.com/jarcoal/jpath"
|
||||
"github.com/sony/sonyflake"
|
||||
"github.com/zitadel/logging"
|
||||
)
|
||||
|
||||
type sonyflakeGenerator struct {
|
||||
@ -31,10 +32,12 @@ func (s *sonyflakeGenerator) Next() (string, error) {
|
||||
}
|
||||
|
||||
var (
|
||||
GeneratorConfig *Config = nil
|
||||
sonyFlakeGenerator *Generator = nil
|
||||
GeneratorConfig *Config = nil
|
||||
sonyFlakeGenerator Generator = nil
|
||||
)
|
||||
|
||||
// SonyFlakeGenerator creates a new id generator
|
||||
// the function panics if the generator cannot be created
|
||||
func SonyFlakeGenerator() Generator {
|
||||
if sonyFlakeGenerator == nil {
|
||||
sfg := Generator(&sonyflakeGenerator{
|
||||
@ -44,10 +47,10 @@ func SonyFlakeGenerator() Generator {
|
||||
}),
|
||||
})
|
||||
|
||||
sonyFlakeGenerator = &sfg
|
||||
sonyFlakeGenerator = sfg
|
||||
}
|
||||
|
||||
return *sonyFlakeGenerator
|
||||
return sonyFlakeGenerator
|
||||
}
|
||||
|
||||
// the following is a copy of sonyflake (https://github.com/sony/sonyflake/blob/master/sonyflake.go)
|
||||
@ -88,40 +91,41 @@ func isPrivateIPv4(ip net.IP) bool {
|
||||
|
||||
func machineID() (uint16, error) {
|
||||
if GeneratorConfig == nil {
|
||||
return 0, errors.New("cannot create a unique id for the machine, generator has not been configured")
|
||||
logging.Panic("cannot create a unique id for the machine, generator has not been configured")
|
||||
}
|
||||
|
||||
errors := []string{}
|
||||
if GeneratorConfig.Identification.PrivateIp.Enabled {
|
||||
ip, ipErr := lower16BitPrivateIP()
|
||||
if ipErr == nil {
|
||||
ip, err := lower16BitPrivateIP()
|
||||
if err == nil {
|
||||
return ip, nil
|
||||
}
|
||||
errors = append(errors, fmt.Sprintf("failed to get Private IP address %s", ipErr))
|
||||
errors = append(errors, fmt.Sprintf("failed to get Private IP address %s", err))
|
||||
}
|
||||
|
||||
if GeneratorConfig.Identification.Hostname.Enabled {
|
||||
hn, hostErr := hostname()
|
||||
if hostErr == nil {
|
||||
hn, err := hostname()
|
||||
if err == nil {
|
||||
return hn, nil
|
||||
}
|
||||
errors = append(errors, fmt.Sprintf("failed to get Hostname %s", hostErr))
|
||||
errors = append(errors, fmt.Sprintf("failed to get Hostname %s", err))
|
||||
}
|
||||
|
||||
if GeneratorConfig.Identification.Webhook.Enabled {
|
||||
cid, cidErr := metadataWebhookID()
|
||||
if cidErr == nil {
|
||||
cid, err := metadataWebhookID()
|
||||
if err == nil {
|
||||
return cid, nil
|
||||
}
|
||||
|
||||
errors = append(errors, fmt.Sprintf("failed to query metadata webhook %s", cidErr))
|
||||
errors = append(errors, fmt.Sprintf("failed to query metadata webhook %s", err))
|
||||
}
|
||||
|
||||
if len(errors) == 0 {
|
||||
errors = append(errors, "No machine identification method enabled.")
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("none of the enabled methods for identifying the machine succeeded: %s", strings.Join(errors, ". "))
|
||||
logging.WithFields("errors", strings.Join(errors, ", ")).Panic("none of the enabled methods for identifying the machine succeeded")
|
||||
//this return will never happen because of panic one line before
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func lower16BitPrivateIP() (uint16, error) {
|
||||
|
@ -9,12 +9,15 @@ import (
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/query/projection"
|
||||
)
|
||||
|
||||
const (
|
||||
maxTimeout = 20 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
actionTable = table{
|
||||
name: projection.ActionTable,
|
||||
@ -80,10 +83,17 @@ type Action struct {
|
||||
|
||||
Name string
|
||||
Script string
|
||||
Timeout time.Duration
|
||||
timeout time.Duration
|
||||
AllowedToFail bool
|
||||
}
|
||||
|
||||
func (a *Action) Timeout() time.Duration {
|
||||
if a.timeout > 0 && a.timeout < maxTimeout {
|
||||
return a.timeout
|
||||
}
|
||||
return maxTimeout
|
||||
}
|
||||
|
||||
type ActionSearchQueries struct {
|
||||
SearchRequest
|
||||
Queries []SearchQuery
|
||||
@ -180,7 +190,7 @@ func prepareActionsQuery() (sq.SelectBuilder, func(rows *sql.Rows) (*Actions, er
|
||||
&action.State,
|
||||
&action.Name,
|
||||
&action.Script,
|
||||
&action.Timeout,
|
||||
&action.timeout,
|
||||
&action.AllowedToFail,
|
||||
&count,
|
||||
)
|
||||
@ -227,7 +237,7 @@ func prepareActionQuery() (sq.SelectBuilder, func(row *sql.Row) (*Action, error)
|
||||
&action.State,
|
||||
&action.Name,
|
||||
&action.Script,
|
||||
&action.Timeout,
|
||||
&action.timeout,
|
||||
&action.AllowedToFail,
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/query/projection"
|
||||
@ -155,6 +154,8 @@ func prepareTriggerActionsQuery() (sq.SelectBuilder, func(*sql.Rows) ([]*Action,
|
||||
ActionColumnSequence.identifier(),
|
||||
ActionColumnName.identifier(),
|
||||
ActionColumnScript.identifier(),
|
||||
ActionColumnAllowedToFail.identifier(),
|
||||
ActionColumnTimeout.identifier(),
|
||||
).
|
||||
From(flowsTriggersTable.name).
|
||||
LeftJoin(join(ActionColumnID, FlowsTriggersColumnActionID)).
|
||||
@ -172,6 +173,8 @@ func prepareTriggerActionsQuery() (sq.SelectBuilder, func(*sql.Rows) ([]*Action,
|
||||
&action.Sequence,
|
||||
&action.Name,
|
||||
&action.Script,
|
||||
&action.AllowedToFail,
|
||||
&action.timeout,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -197,6 +200,8 @@ func prepareFlowQuery(flowType domain.FlowType) (sq.SelectBuilder, func(*sql.Row
|
||||
ActionColumnSequence.identifier(),
|
||||
ActionColumnName.identifier(),
|
||||
ActionColumnScript.identifier(),
|
||||
ActionColumnAllowedToFail.identifier(),
|
||||
ActionColumnTimeout.identifier(),
|
||||
FlowsTriggersColumnTriggerType.identifier(),
|
||||
FlowsTriggersColumnTriggerSequence.identifier(),
|
||||
FlowsTriggersColumnFlowType.identifier(),
|
||||
@ -222,6 +227,8 @@ func prepareFlowQuery(flowType domain.FlowType) (sq.SelectBuilder, func(*sql.Row
|
||||
actionSequence sql.NullInt64
|
||||
actionName sql.NullString
|
||||
actionScript sql.NullString
|
||||
actionAllowedToFail sql.NullBool
|
||||
actionTimeout sql.NullInt64
|
||||
|
||||
triggerType domain.TriggerType
|
||||
triggerSequence int
|
||||
@ -235,6 +242,8 @@ func prepareFlowQuery(flowType domain.FlowType) (sq.SelectBuilder, func(*sql.Row
|
||||
&actionSequence,
|
||||
&actionName,
|
||||
&actionScript,
|
||||
&actionAllowedToFail,
|
||||
&actionTimeout,
|
||||
&triggerType,
|
||||
&triggerSequence,
|
||||
&flow.Type,
|
||||
@ -257,6 +266,8 @@ func prepareFlowQuery(flowType domain.FlowType) (sq.SelectBuilder, func(*sql.Row
|
||||
Sequence: uint64(actionSequence.Int64),
|
||||
Name: actionName.String,
|
||||
Script: actionScript.String,
|
||||
AllowedToFail: actionAllowedToFail.Bool,
|
||||
timeout: time.Duration(actionTimeout.Int64),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
|
||||
@ -39,6 +40,8 @@ func Test_FlowPrepares(t *testing.T) {
|
||||
` projections.actions2.sequence,`+
|
||||
` projections.actions2.name,`+
|
||||
` projections.actions2.script,`+
|
||||
` projections.actions2.allowed_to_fail,`+
|
||||
` projections.actions2.timeout,`+
|
||||
` projections.flows_triggers.trigger_type,`+
|
||||
` projections.flows_triggers.trigger_sequence,`+
|
||||
` projections.flows_triggers.flow_type,`+
|
||||
@ -71,6 +74,8 @@ func Test_FlowPrepares(t *testing.T) {
|
||||
` projections.actions2.sequence,`+
|
||||
` projections.actions2.name,`+
|
||||
` projections.actions2.script,`+
|
||||
` projections.actions2.allowed_to_fail,`+
|
||||
` projections.actions2.timeout,`+
|
||||
` projections.flows_triggers.trigger_type,`+
|
||||
` projections.flows_triggers.trigger_sequence,`+
|
||||
` projections.flows_triggers.flow_type,`+
|
||||
@ -88,6 +93,8 @@ func Test_FlowPrepares(t *testing.T) {
|
||||
"sequence",
|
||||
"name",
|
||||
"script",
|
||||
"allowed_to_fail",
|
||||
"timeout",
|
||||
//flow
|
||||
"trigger_type",
|
||||
"trigger_sequence",
|
||||
@ -106,6 +113,8 @@ func Test_FlowPrepares(t *testing.T) {
|
||||
uint64(20211115),
|
||||
"action-name",
|
||||
"script",
|
||||
true,
|
||||
10000000000,
|
||||
domain.TriggerTypePreCreation,
|
||||
uint64(20211109),
|
||||
domain.FlowTypeExternalAuthentication,
|
||||
@ -132,6 +141,8 @@ func Test_FlowPrepares(t *testing.T) {
|
||||
Sequence: 20211115,
|
||||
Name: "action-name",
|
||||
Script: "script",
|
||||
AllowedToFail: true,
|
||||
timeout: 10 * time.Second,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -152,6 +163,8 @@ func Test_FlowPrepares(t *testing.T) {
|
||||
` projections.actions2.sequence,`+
|
||||
` projections.actions2.name,`+
|
||||
` projections.actions2.script,`+
|
||||
` projections.actions2.allowed_to_fail,`+
|
||||
` projections.actions2.timeout,`+
|
||||
` projections.flows_triggers.trigger_type,`+
|
||||
` projections.flows_triggers.trigger_sequence,`+
|
||||
` projections.flows_triggers.flow_type,`+
|
||||
@ -169,6 +182,8 @@ func Test_FlowPrepares(t *testing.T) {
|
||||
"sequence",
|
||||
"name",
|
||||
"script",
|
||||
"allowed_to_fail",
|
||||
"timeout",
|
||||
//flow
|
||||
"trigger_type",
|
||||
"trigger_sequence",
|
||||
@ -187,6 +202,8 @@ func Test_FlowPrepares(t *testing.T) {
|
||||
uint64(20211115),
|
||||
"action-name-pre",
|
||||
"script",
|
||||
true,
|
||||
10000000000,
|
||||
domain.TriggerTypePreCreation,
|
||||
uint64(20211109),
|
||||
domain.FlowTypeExternalAuthentication,
|
||||
@ -203,6 +220,8 @@ func Test_FlowPrepares(t *testing.T) {
|
||||
uint64(20211115),
|
||||
"action-name-post",
|
||||
"script",
|
||||
false,
|
||||
5000000000,
|
||||
domain.TriggerTypePostCreation,
|
||||
uint64(20211109),
|
||||
domain.FlowTypeExternalAuthentication,
|
||||
@ -229,6 +248,8 @@ func Test_FlowPrepares(t *testing.T) {
|
||||
Sequence: 20211115,
|
||||
Name: "action-name-pre",
|
||||
Script: "script",
|
||||
AllowedToFail: true,
|
||||
timeout: 10 * time.Second,
|
||||
},
|
||||
},
|
||||
domain.TriggerTypePostCreation: {
|
||||
@ -241,6 +262,8 @@ func Test_FlowPrepares(t *testing.T) {
|
||||
Sequence: 20211115,
|
||||
Name: "action-name-post",
|
||||
Script: "script",
|
||||
AllowedToFail: false,
|
||||
timeout: 5 * time.Second,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -261,6 +284,8 @@ func Test_FlowPrepares(t *testing.T) {
|
||||
` projections.actions2.sequence,`+
|
||||
` projections.actions2.name,`+
|
||||
` projections.actions2.script,`+
|
||||
` projections.actions2.allowed_to_fail,`+
|
||||
` projections.actions2.timeout,`+
|
||||
` projections.flows_triggers.trigger_type,`+
|
||||
` projections.flows_triggers.trigger_sequence,`+
|
||||
` projections.flows_triggers.flow_type,`+
|
||||
@ -278,6 +303,8 @@ func Test_FlowPrepares(t *testing.T) {
|
||||
"sequence",
|
||||
"name",
|
||||
"script",
|
||||
"allowed_to_fail",
|
||||
"timeout",
|
||||
//flow
|
||||
"trigger_type",
|
||||
"trigger_sequence",
|
||||
@ -296,6 +323,8 @@ func Test_FlowPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
domain.TriggerTypePostCreation,
|
||||
uint64(20211109),
|
||||
domain.FlowTypeExternalAuthentication,
|
||||
@ -329,6 +358,8 @@ func Test_FlowPrepares(t *testing.T) {
|
||||
` projections.actions2.sequence,`+
|
||||
` projections.actions2.name,`+
|
||||
` projections.actions2.script,`+
|
||||
` projections.actions2.allowed_to_fail,`+
|
||||
` projections.actions2.timeout,`+
|
||||
` projections.flows_triggers.trigger_type,`+
|
||||
` projections.flows_triggers.trigger_sequence,`+
|
||||
` projections.flows_triggers.flow_type,`+
|
||||
@ -360,7 +391,9 @@ func Test_FlowPrepares(t *testing.T) {
|
||||
` projections.actions2.action_state,`+
|
||||
` projections.actions2.sequence,`+
|
||||
` projections.actions2.name,`+
|
||||
` projections.actions2.script`+
|
||||
` projections.actions2.script,`+
|
||||
` projections.actions2.allowed_to_fail,`+
|
||||
` projections.actions2.timeout`+
|
||||
` FROM projections.flows_triggers`+
|
||||
` LEFT JOIN projections.actions2 ON projections.flows_triggers.action_id = projections.actions2.id`),
|
||||
nil,
|
||||
@ -381,7 +414,9 @@ func Test_FlowPrepares(t *testing.T) {
|
||||
` projections.actions2.action_state,`+
|
||||
` projections.actions2.sequence,`+
|
||||
` projections.actions2.name,`+
|
||||
` projections.actions2.script`+
|
||||
` projections.actions2.script,`+
|
||||
` projections.actions2.allowed_to_fail,`+
|
||||
` projections.actions2.timeout`+
|
||||
` FROM projections.flows_triggers`+
|
||||
` LEFT JOIN projections.actions2 ON projections.flows_triggers.action_id = projections.actions2.id`),
|
||||
[]string{
|
||||
@ -393,6 +428,8 @@ func Test_FlowPrepares(t *testing.T) {
|
||||
"sequence",
|
||||
"name",
|
||||
"script",
|
||||
"allowed_to_fail",
|
||||
"timeout",
|
||||
},
|
||||
[][]driver.Value{
|
||||
{
|
||||
@ -404,6 +441,8 @@ func Test_FlowPrepares(t *testing.T) {
|
||||
uint64(20211115),
|
||||
"action-name",
|
||||
"script",
|
||||
true,
|
||||
10000000000,
|
||||
},
|
||||
},
|
||||
),
|
||||
@ -418,6 +457,8 @@ func Test_FlowPrepares(t *testing.T) {
|
||||
Sequence: 20211115,
|
||||
Name: "action-name",
|
||||
Script: "script",
|
||||
AllowedToFail: true,
|
||||
timeout: 10 * time.Second,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -433,7 +474,9 @@ func Test_FlowPrepares(t *testing.T) {
|
||||
` projections.actions2.action_state,`+
|
||||
` projections.actions2.sequence,`+
|
||||
` projections.actions2.name,`+
|
||||
` projections.actions2.script`+
|
||||
` projections.actions2.script,`+
|
||||
` projections.actions2.allowed_to_fail,`+
|
||||
` projections.actions2.timeout`+
|
||||
` FROM projections.flows_triggers`+
|
||||
` LEFT JOIN projections.actions2 ON projections.flows_triggers.action_id = projections.actions2.id`),
|
||||
[]string{
|
||||
@ -445,6 +488,8 @@ func Test_FlowPrepares(t *testing.T) {
|
||||
"sequence",
|
||||
"name",
|
||||
"script",
|
||||
"allowed_to_fail",
|
||||
"timeout",
|
||||
},
|
||||
[][]driver.Value{
|
||||
{
|
||||
@ -456,6 +501,8 @@ func Test_FlowPrepares(t *testing.T) {
|
||||
uint64(20211115),
|
||||
"action-name-1",
|
||||
"script",
|
||||
true,
|
||||
10000000000,
|
||||
},
|
||||
{
|
||||
"action-id-2",
|
||||
@ -466,6 +513,8 @@ func Test_FlowPrepares(t *testing.T) {
|
||||
uint64(20211115),
|
||||
"action-name-2",
|
||||
"script",
|
||||
false,
|
||||
5000000000,
|
||||
},
|
||||
},
|
||||
),
|
||||
@ -480,6 +529,8 @@ func Test_FlowPrepares(t *testing.T) {
|
||||
Sequence: 20211115,
|
||||
Name: "action-name-1",
|
||||
Script: "script",
|
||||
AllowedToFail: true,
|
||||
timeout: 10 * time.Second,
|
||||
},
|
||||
{
|
||||
ID: "action-id-2",
|
||||
@ -490,6 +541,8 @@ func Test_FlowPrepares(t *testing.T) {
|
||||
Sequence: 20211115,
|
||||
Name: "action-name-2",
|
||||
Script: "script",
|
||||
AllowedToFail: false,
|
||||
timeout: 5 * time.Second,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -505,7 +558,9 @@ func Test_FlowPrepares(t *testing.T) {
|
||||
` projections.actions2.action_state,`+
|
||||
` projections.actions2.sequence,`+
|
||||
` projections.actions2.name,`+
|
||||
` projections.actions2.script`+
|
||||
` projections.actions2.script,`+
|
||||
` projections.actions2.allowed_to_fail,`+
|
||||
` projections.actions2.timeout`+
|
||||
` FROM projections.flows_triggers`+
|
||||
` LEFT JOIN projections.actions2 ON projections.flows_triggers.action_id = projections.actions2.id`),
|
||||
sql.ErrConnDone,
|
||||
|
@ -107,7 +107,7 @@ func Test_ActionPrepares(t *testing.T) {
|
||||
Sequence: 20211109,
|
||||
Name: "action-name",
|
||||
Script: "script",
|
||||
Timeout: 1 * time.Second,
|
||||
timeout: 1 * time.Second,
|
||||
AllowedToFail: true,
|
||||
},
|
||||
},
|
||||
@ -185,7 +185,7 @@ func Test_ActionPrepares(t *testing.T) {
|
||||
Sequence: 20211109,
|
||||
Name: "action-name-1",
|
||||
Script: "script",
|
||||
Timeout: 1 * time.Second,
|
||||
timeout: 1 * time.Second,
|
||||
AllowedToFail: true,
|
||||
},
|
||||
{
|
||||
@ -197,7 +197,7 @@ func Test_ActionPrepares(t *testing.T) {
|
||||
Sequence: 20211109,
|
||||
Name: "action-name-2",
|
||||
Script: "script",
|
||||
Timeout: 1 * time.Second,
|
||||
timeout: 1 * time.Second,
|
||||
AllowedToFail: true,
|
||||
},
|
||||
},
|
||||
@ -310,7 +310,7 @@ func Test_ActionPrepares(t *testing.T) {
|
||||
Sequence: 20211109,
|
||||
Name: "action-name",
|
||||
Script: "script",
|
||||
Timeout: 1 * time.Second,
|
||||
timeout: 1 * time.Second,
|
||||
AllowedToFail: true,
|
||||
},
|
||||
},
|
||||
|
@ -10,11 +10,10 @@ import (
|
||||
"github.com/rakyll/statik/fs"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
sd "github.com/zitadel/zitadel/internal/config/systemdefaults"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
sd "github.com/zitadel/zitadel/internal/config/systemdefaults"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/query/projection"
|
||||
"github.com/zitadel/zitadel/internal/repository/action"
|
||||
|
@ -160,7 +160,7 @@ const (
|
||||
textCompareMax
|
||||
)
|
||||
|
||||
//Deprecated: Use TextComparison, will be removed as soon as all calls are changed to query
|
||||
// Deprecated: Use TextComparison, will be removed as soon as all calls are changed to query
|
||||
func TextComparisonFromMethod(m domain.SearchMethod) TextComparison {
|
||||
switch m {
|
||||
case domain.SearchMethodEquals:
|
||||
@ -244,7 +244,7 @@ const (
|
||||
numberCompareMax
|
||||
)
|
||||
|
||||
//Deprecated: Use NumberComparison, will be removed as soon as all calls are changed to query
|
||||
// Deprecated: Use NumberComparison, will be removed as soon as all calls are changed to query
|
||||
func NumberComparisonFromMethod(m domain.SearchMethod) NumberComparison {
|
||||
switch m {
|
||||
case domain.SearchMethodEquals:
|
||||
|
@ -911,3 +911,17 @@ Application:
|
||||
GrantType:
|
||||
Refresh:
|
||||
NoAuthCode: Refresh Token nur in Kombination mit Authorization Code erlaubt.
|
||||
|
||||
Action:
|
||||
Flow:
|
||||
Type:
|
||||
Unspecified: Unspezifiziert
|
||||
ExternalAuthentication: Externe Authentifizierung
|
||||
CustomiseToken: Token ergänzen
|
||||
TriggerType:
|
||||
Unspecified: Unspezifiziert
|
||||
PostAuthentication: Nach Authentifizierung
|
||||
PreCreation: Vor Erstellung
|
||||
PostCreation: Nach Erstellung
|
||||
PreUserinfoCreation: Vor Userinfo Erstellung
|
||||
PreAccessTokenCreation: Vor Access Token Erstellung
|
@ -911,3 +911,17 @@ Application:
|
||||
GrantType:
|
||||
Refresh:
|
||||
NoAuthCode: Refresh Token only allowed in combination with Authorization Code.
|
||||
|
||||
Action:
|
||||
Flow:
|
||||
Type:
|
||||
Unspecified: Unspecified
|
||||
ExternalAuthentication: External Authentication
|
||||
CustomiseToken: Complement Token
|
||||
TriggerType:
|
||||
Unspecified: Unspecified
|
||||
PostAuthentication: Post Authentication
|
||||
PreCreation: Pre Creation
|
||||
PostCreation: Post Creation
|
||||
PreUserinfoCreation: Pre Userinfo creation
|
||||
PreAccessTokenCreation: Pre access token creation
|
@ -911,3 +911,17 @@ Application:
|
||||
GrantType:
|
||||
Refresh:
|
||||
NoAuthCode: Le jeton de rafraîchissement n'est autorisé qu'en combinaison avec le code d'autorisation.
|
||||
|
||||
Action:
|
||||
Flow:
|
||||
Type:
|
||||
Unspecified: Non spécifié
|
||||
ExternalAuthentication: Authentification externe
|
||||
CustomiseToken: Compléter Token
|
||||
TriggerType:
|
||||
Unspecified: Non spécifié
|
||||
PostAuthentication: Authentification postérieure
|
||||
PreCreation: Pré création
|
||||
PostCreation: Post-création
|
||||
PreUserinfoCreation: Pré Userinfo création
|
||||
PreAccessTokenCreation: Pré access token création
|
@ -911,3 +911,17 @@ Application:
|
||||
GrantType:
|
||||
Refresh:
|
||||
NoAuthCode: Refresh Token consentito solo in combinazione con Authorization Code.
|
||||
|
||||
Action:
|
||||
Flow:
|
||||
Type:
|
||||
Unspecified: Non specificato
|
||||
ExternalAuthentication: Autenticazione esterna
|
||||
CustomiseToken: Completare Token
|
||||
TriggerType:
|
||||
Unspecified: Non specificato
|
||||
PostAuthentication: Post-autenticazione
|
||||
PreCreation: Pre-creazione
|
||||
PostCreation: Creazione successiva
|
||||
PreUserinfoCreation: Pre userinfo creazione
|
||||
PreAccessTokenCreation: Pre access token creazione
|
@ -905,3 +905,17 @@ Application:
|
||||
GrantType:
|
||||
Refresh:
|
||||
NoAuthCode: Refresh Token 仅允许与授权码(Authorization Code)模式一起使用。
|
||||
|
||||
Action:
|
||||
Flow:
|
||||
Type:
|
||||
Unspecified: 未指定的
|
||||
ExternalAuthentication: 外部认证
|
||||
CustomiseToken: Complement Token
|
||||
TriggerType:
|
||||
Unspecified: 未指定的
|
||||
PostAuthentication: 后期认证
|
||||
PreCreation: 创建前
|
||||
PostCreation: 创建后
|
||||
PreUserinfoCreation: Pre Userinfo creation
|
||||
PreAccessTokenCreation: Pre access token creation
|
41
pkg/grpc/action/action.go
Normal file
41
pkg/grpc/action/action.go
Normal file
@ -0,0 +1,41 @@
|
||||
package action
|
||||
|
||||
import "github.com/zitadel/zitadel/internal/api/grpc/server/middleware"
|
||||
|
||||
func (f *Flow) Localizers() []middleware.Localizer {
|
||||
if f == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
localizers := make([]middleware.Localizer, 0, len(f.TriggerActions)+1)
|
||||
localizers = append(localizers, f.Type.Localizers()...)
|
||||
for _, action := range f.TriggerActions {
|
||||
localizers = append(localizers, action.Localizers()...)
|
||||
}
|
||||
|
||||
return localizers
|
||||
}
|
||||
|
||||
func (t *FlowType) Localizers() []middleware.Localizer {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return []middleware.Localizer{t.Name}
|
||||
}
|
||||
|
||||
func (t *TriggerType) Localizers() []middleware.Localizer {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return []middleware.Localizer{t.Name}
|
||||
}
|
||||
|
||||
func (ta *TriggerAction) Localizers() []middleware.Localizer {
|
||||
if ta == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ta.TriggerType.Localizers()
|
||||
}
|
37
pkg/grpc/management/action.go
Normal file
37
pkg/grpc/management/action.go
Normal file
@ -0,0 +1,37 @@
|
||||
package management
|
||||
|
||||
import "github.com/zitadel/zitadel/internal/api/grpc/server/middleware"
|
||||
|
||||
func (r *ListFlowTypesResponse) Localizers() (localizers []middleware.Localizer) {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
localizers = make([]middleware.Localizer, 0, len(r.Result))
|
||||
for _, typ := range r.Result {
|
||||
localizers = append(localizers, typ.Localizers()...)
|
||||
}
|
||||
|
||||
return localizers
|
||||
}
|
||||
|
||||
func (r *ListFlowTriggerTypesResponse) Localizers() (localizers []middleware.Localizer) {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
localizers = make([]middleware.Localizer, 0, len(r.Result))
|
||||
for _, typ := range r.Result {
|
||||
localizers = append(localizers, typ.Localizers()...)
|
||||
}
|
||||
|
||||
return localizers
|
||||
}
|
||||
|
||||
func (r *GetFlowResponse) Localizers() []middleware.Localizer {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return r.Flow.Localizers()
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "zitadel/object.proto";
|
||||
import "zitadel/message.proto";
|
||||
import "validate/validate.proto";
|
||||
import "google/protobuf/duration.proto";
|
||||
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||
@ -91,6 +92,7 @@ enum ActionFieldName {
|
||||
}
|
||||
|
||||
message Flow {
|
||||
// id of the flow type
|
||||
FlowType type = 1 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "\"the type of the flow\"";
|
||||
@ -105,9 +107,11 @@ message Flow {
|
||||
repeated TriggerAction trigger_actions = 4;
|
||||
}
|
||||
|
||||
enum FlowType {
|
||||
FLOW_TYPE_UNSPECIFIED = 0;
|
||||
FLOW_TYPE_EXTERNAL_AUTHENTICATION = 1;
|
||||
message FlowType {
|
||||
// identifier of the type
|
||||
string id = 1;
|
||||
// key and name of the type
|
||||
zitadel.v1.LocalizedMessage name = 2;
|
||||
}
|
||||
|
||||
enum FlowState {
|
||||
@ -116,40 +120,15 @@ enum FlowState {
|
||||
FLOW_STATE_ACTIVE = 2;
|
||||
}
|
||||
|
||||
enum TriggerType {
|
||||
TRIGGER_TYPE_UNSPECIFIED = 0;
|
||||
TRIGGER_TYPE_POST_AUTHENTICATION = 1;
|
||||
TRIGGER_TYPE_PRE_CREATION = 2;
|
||||
TRIGGER_TYPE_POST_CREATION = 3;
|
||||
message TriggerType {
|
||||
// identifier of the type
|
||||
string id = 1;
|
||||
// key and name of the type
|
||||
zitadel.v1.LocalizedMessage name = 2;
|
||||
}
|
||||
|
||||
message TriggerAction {
|
||||
// id of the trigger type
|
||||
TriggerType trigger_type = 1;
|
||||
repeated Action actions = 2;
|
||||
}
|
||||
|
||||
enum FlowFieldName {
|
||||
FLOW_FIELD_NAME_UNSPECIFIED = 0;
|
||||
FLOW_FIELD_NAME_TYPE = 1;
|
||||
FLOW_FIELD_NAME_STATE = 2;
|
||||
}
|
||||
|
||||
//FlowTypeQuery is always equals
|
||||
message FlowTypeQuery {
|
||||
FlowType state = 1 [
|
||||
(validate.rules).enum.defined_only = true,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "type of the flow";
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
//FlowStateQuery is always equals
|
||||
message FlowStateQuery {
|
||||
FlowState state = 1 [
|
||||
(validate.rules).enum.defined_only = true,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "current state of the flow";
|
||||
}
|
||||
];
|
||||
}
|
||||
|
@ -2938,6 +2938,26 @@ service ManagementService {
|
||||
};
|
||||
}
|
||||
|
||||
rpc ListFlowTypes(ListFlowTypesRequest) returns (ListFlowTypesResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/flows/types/_search"
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "org.flow.read"
|
||||
};
|
||||
}
|
||||
|
||||
rpc ListFlowTriggerTypes(ListFlowTriggerTypesRequest) returns (ListFlowTriggerTypesResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/flows/{type}/triggers/_search"
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "org.flow.read"
|
||||
};
|
||||
}
|
||||
|
||||
rpc GetFlow(GetFlowRequest) returns (GetFlowResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/flows/{type}"
|
||||
@ -5725,6 +5745,20 @@ message DeleteActionRequest {
|
||||
|
||||
message DeleteActionResponse {}
|
||||
|
||||
message ListFlowTypesRequest {}
|
||||
|
||||
message ListFlowTypesResponse {
|
||||
repeated zitadel.action.v1.FlowType result = 1;
|
||||
}
|
||||
|
||||
message ListFlowTriggerTypesRequest {
|
||||
string type = 1;
|
||||
}
|
||||
|
||||
message ListFlowTriggerTypesResponse {
|
||||
repeated zitadel.action.v1.TriggerType result = 1;
|
||||
}
|
||||
|
||||
message DeactivateActionRequest {
|
||||
string id = 1;
|
||||
}
|
||||
@ -5742,7 +5776,8 @@ message ReactivateActionResponse {
|
||||
}
|
||||
|
||||
message GetFlowRequest {
|
||||
zitadel.action.v1.FlowType type = 1;
|
||||
// id of the flow
|
||||
string type = 1;
|
||||
}
|
||||
|
||||
message GetFlowResponse {
|
||||
@ -5750,7 +5785,8 @@ message GetFlowResponse {
|
||||
}
|
||||
|
||||
message ClearFlowRequest {
|
||||
zitadel.action.v1.FlowType type = 1;
|
||||
// id of the flow
|
||||
string type = 1;
|
||||
}
|
||||
|
||||
message ClearFlowResponse {
|
||||
@ -5758,8 +5794,10 @@ message ClearFlowResponse {
|
||||
}
|
||||
|
||||
message SetTriggerActionsRequest {
|
||||
zitadel.action.v1.FlowType flow_type = 1;
|
||||
zitadel.action.v1.TriggerType trigger_type = 2;
|
||||
// id of the flow type
|
||||
string flow_type = 1;
|
||||
// id of the trigger type
|
||||
string trigger_type = 2;
|
||||
repeated string action_ids = 3;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user