mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 21:27:22 +00:00
fix: correct oidcsettings management (#4413)
* fix(oidcsettings): corrected projection, unittests and added the add endpoint * fix(oidcsettings): corrected default handling and instance setup * fix: set oidc settings correctly in console * cleanup * e2e test * improve e2e test * lint e2e Co-authored-by: Livio Spring <livio.a@gmail.com> Co-authored-by: Fabi <38692350+hifabienne@users.noreply.github.com>
This commit is contained in:
parent
b32c02a39b
commit
2957407b5b
File diff suppressed because one or more lines are too long
@ -32,6 +32,7 @@
|
|||||||
"grpc-web",
|
"grpc-web",
|
||||||
"@angular/common/locales/de",
|
"@angular/common/locales/de",
|
||||||
"codemirror/mode/javascript/javascript",
|
"codemirror/mode/javascript/javascript",
|
||||||
|
"codemirror/mode/xml/xml",
|
||||||
"src/app/proto/generated/zitadel/admin_pb",
|
"src/app/proto/generated/zitadel/admin_pb",
|
||||||
"src/app/proto/generated/zitadel/org_pb",
|
"src/app/proto/generated/zitadel/org_pb",
|
||||||
"src/app/proto/generated/zitadel/management_pb",
|
"src/app/proto/generated/zitadel/management_pb",
|
||||||
|
@ -46,6 +46,7 @@
|
|||||||
color="primary"
|
color="primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
mat-raised-button
|
mat-raised-button
|
||||||
|
data-e2e="save-button"
|
||||||
>
|
>
|
||||||
{{ 'ACTIONS.SAVE' | translate }}
|
{{ 'ACTIONS.SAVE' | translate }}
|
||||||
</button>
|
</button>
|
||||||
|
@ -2,7 +2,12 @@ import { Component, OnInit } from '@angular/core';
|
|||||||
import { AbstractControl, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
import { AbstractControl, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
||||||
import { Duration } from 'google-protobuf/google/protobuf/duration_pb';
|
import { Duration } from 'google-protobuf/google/protobuf/duration_pb';
|
||||||
import { take } from 'rxjs';
|
import { take } from 'rxjs';
|
||||||
import { SetDefaultLanguageResponse, UpdateOIDCSettingsRequest } from 'src/app/proto/generated/zitadel/admin_pb';
|
import {
|
||||||
|
AddOIDCSettingsRequest,
|
||||||
|
AddOIDCSettingsResponse,
|
||||||
|
UpdateOIDCSettingsRequest,
|
||||||
|
UpdateOIDCSettingsResponse,
|
||||||
|
} from 'src/app/proto/generated/zitadel/admin_pb';
|
||||||
import { OIDCSettings } from 'src/app/proto/generated/zitadel/settings_pb';
|
import { OIDCSettings } from 'src/app/proto/generated/zitadel/settings_pb';
|
||||||
import { AdminService } from 'src/app/services/admin.service';
|
import { AdminService } from 'src/app/services/admin.service';
|
||||||
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||||
@ -15,6 +20,7 @@ import { ToastService } from 'src/app/services/toast.service';
|
|||||||
})
|
})
|
||||||
export class OIDCConfigurationComponent implements OnInit {
|
export class OIDCConfigurationComponent implements OnInit {
|
||||||
public oidcSettings!: OIDCSettings.AsObject;
|
public oidcSettings!: OIDCSettings.AsObject;
|
||||||
|
private settingsSet: boolean = false;
|
||||||
|
|
||||||
public loading: boolean = false;
|
public loading: boolean = false;
|
||||||
public form!: UntypedFormGroup;
|
public form!: UntypedFormGroup;
|
||||||
@ -25,10 +31,10 @@ export class OIDCConfigurationComponent implements OnInit {
|
|||||||
private authService: GrpcAuthService,
|
private authService: GrpcAuthService,
|
||||||
) {
|
) {
|
||||||
this.form = this.fb.group({
|
this.form = this.fb.group({
|
||||||
accessTokenLifetime: [{ disabled: true, value: 12 }, [Validators.required]],
|
accessTokenLifetime: [{ disabled: true }, [Validators.required]],
|
||||||
idTokenLifetime: [{ disabled: true, value: 12 }, [Validators.required]],
|
idTokenLifetime: [{ disabled: true }, [Validators.required]],
|
||||||
refreshTokenExpiration: [{ disabled: true, value: 30 }, [Validators.required]],
|
refreshTokenExpiration: [{ disabled: true }, [Validators.required]],
|
||||||
refreshTokenIdleExpiration: [{ disabled: true, value: 90 }, [Validators.required]],
|
refreshTokenIdleExpiration: [{ disabled: true }, [Validators.required]],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,26 +56,27 @@ export class OIDCConfigurationComponent implements OnInit {
|
|||||||
.then((oidcConfiguration) => {
|
.then((oidcConfiguration) => {
|
||||||
if (oidcConfiguration.settings) {
|
if (oidcConfiguration.settings) {
|
||||||
this.oidcSettings = oidcConfiguration.settings;
|
this.oidcSettings = oidcConfiguration.settings;
|
||||||
|
this.settingsSet = true;
|
||||||
|
|
||||||
this.accessTokenLifetime?.setValue(
|
this.accessTokenLifetime?.setValue(
|
||||||
oidcConfiguration.settings.accessTokenLifetime?.seconds
|
oidcConfiguration.settings.accessTokenLifetime?.seconds
|
||||||
? oidcConfiguration.settings.accessTokenLifetime?.seconds / 60 / 60
|
? oidcConfiguration.settings.accessTokenLifetime?.seconds / 60 / 60
|
||||||
: 12,
|
: 0,
|
||||||
);
|
);
|
||||||
this.idTokenLifetime?.setValue(
|
this.idTokenLifetime?.setValue(
|
||||||
oidcConfiguration.settings.idTokenLifetime?.seconds
|
oidcConfiguration.settings.idTokenLifetime?.seconds
|
||||||
? oidcConfiguration.settings.idTokenLifetime?.seconds / 60 / 60
|
? oidcConfiguration.settings.idTokenLifetime?.seconds / 60 / 60
|
||||||
: 12,
|
: 0,
|
||||||
);
|
);
|
||||||
this.refreshTokenExpiration?.setValue(
|
this.refreshTokenExpiration?.setValue(
|
||||||
oidcConfiguration.settings.refreshTokenExpiration?.seconds
|
oidcConfiguration.settings.refreshTokenExpiration?.seconds
|
||||||
? oidcConfiguration.settings.refreshTokenExpiration?.seconds / 60 / 60 / 24
|
? oidcConfiguration.settings.refreshTokenExpiration?.seconds / 60 / 60 / 24
|
||||||
: 30,
|
: 0,
|
||||||
);
|
);
|
||||||
this.refreshTokenIdleExpiration?.setValue(
|
this.refreshTokenIdleExpiration?.setValue(
|
||||||
oidcConfiguration.settings.refreshTokenIdleExpiration?.seconds
|
oidcConfiguration.settings.refreshTokenIdleExpiration?.seconds
|
||||||
? oidcConfiguration.settings.refreshTokenIdleExpiration?.seconds / 60 / 60 / 24
|
? oidcConfiguration.settings.refreshTokenIdleExpiration?.seconds / 60 / 60 / 24
|
||||||
: 90,
|
: 0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -78,31 +85,58 @@ export class OIDCConfigurationComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateData(): Promise<SetDefaultLanguageResponse.AsObject> {
|
private updateData(): Promise<UpdateOIDCSettingsResponse.AsObject> {
|
||||||
const req = new UpdateOIDCSettingsRequest();
|
const req = new UpdateOIDCSettingsRequest();
|
||||||
|
|
||||||
const accessToken = new Duration().setSeconds((this.accessTokenLifetime?.value ?? 12) * 60 * 60);
|
const accessToken = new Duration().setSeconds((this.accessTokenLifetime?.value ?? 0) * 60 * 60);
|
||||||
req.setAccessTokenLifetime(accessToken);
|
req.setAccessTokenLifetime(accessToken);
|
||||||
|
|
||||||
const idToken = new Duration().setSeconds((this.idTokenLifetime?.value ?? 12) * 60 * 60);
|
const idToken = new Duration().setSeconds((this.idTokenLifetime?.value ?? 0) * 60 * 60);
|
||||||
req.setIdTokenLifetime(idToken);
|
req.setIdTokenLifetime(idToken);
|
||||||
|
|
||||||
const refreshToken = new Duration().setSeconds((this.refreshTokenExpiration?.value ?? 30) * 60 * 60 * 24);
|
const refreshToken = new Duration().setSeconds((this.refreshTokenExpiration?.value ?? 0) * 60 * 60 * 24);
|
||||||
req.setRefreshTokenExpiration(refreshToken);
|
req.setRefreshTokenExpiration(refreshToken);
|
||||||
|
|
||||||
const refreshIdleToken = new Duration().setSeconds((this.refreshTokenIdleExpiration?.value ?? 90) * 60 * 60 * 24);
|
const refreshIdleToken = new Duration().setSeconds((this.refreshTokenIdleExpiration?.value ?? 0) * 60 * 60 * 24);
|
||||||
req.setRefreshTokenIdleExpiration(refreshIdleToken);
|
req.setRefreshTokenIdleExpiration(refreshIdleToken);
|
||||||
|
|
||||||
return (this.service as AdminService).updateOIDCSettings(req);
|
return (this.service as AdminService).updateOIDCSettings(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private addData(): Promise<AddOIDCSettingsResponse.AsObject> {
|
||||||
|
const req = new AddOIDCSettingsRequest();
|
||||||
|
|
||||||
|
const accessToken = new Duration().setSeconds((this.accessTokenLifetime?.value ?? 0) * 60 * 60);
|
||||||
|
req.setAccessTokenLifetime(accessToken);
|
||||||
|
|
||||||
|
const idToken = new Duration().setSeconds((this.idTokenLifetime?.value ?? 0) * 60 * 60);
|
||||||
|
req.setIdTokenLifetime(idToken);
|
||||||
|
|
||||||
|
const refreshToken = new Duration().setSeconds((this.refreshTokenExpiration?.value ?? 0) * 60 * 60 * 24);
|
||||||
|
req.setRefreshTokenExpiration(refreshToken);
|
||||||
|
|
||||||
|
const refreshIdleToken = new Duration().setSeconds((this.refreshTokenIdleExpiration?.value ?? 0) * 60 * 60 * 24);
|
||||||
|
req.setRefreshTokenIdleExpiration(refreshIdleToken);
|
||||||
|
|
||||||
|
return (this.service as AdminService).addOIDCSettings(req);
|
||||||
|
}
|
||||||
|
|
||||||
public savePolicy(): void {
|
public savePolicy(): void {
|
||||||
const prom = this.updateData();
|
if (this.settingsSet) {
|
||||||
if (prom) {
|
this.updateData()
|
||||||
prom
|
.then(() => {
|
||||||
|
this.toast.showInfo('SETTING.SMTP.SAVED', true);
|
||||||
|
setTimeout(() => {
|
||||||
|
this.fetchData();
|
||||||
|
}, 2000);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.addData()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.toast.showInfo('SETTING.SMTP.SAVED', true);
|
this.toast.showInfo('SETTING.SMTP.SAVED', true);
|
||||||
this.loading = true;
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.fetchData();
|
this.fetchData();
|
||||||
}, 2000);
|
}, 2000);
|
||||||
|
@ -180,6 +180,8 @@ import {
|
|||||||
UpdateLockoutPolicyResponse,
|
UpdateLockoutPolicyResponse,
|
||||||
UpdateLoginPolicyRequest,
|
UpdateLoginPolicyRequest,
|
||||||
UpdateLoginPolicyResponse,
|
UpdateLoginPolicyResponse,
|
||||||
|
AddOIDCSettingsRequest,
|
||||||
|
AddOIDCSettingsResponse,
|
||||||
UpdateOIDCSettingsRequest,
|
UpdateOIDCSettingsRequest,
|
||||||
UpdateOIDCSettingsResponse,
|
UpdateOIDCSettingsResponse,
|
||||||
UpdatePasswordAgePolicyRequest,
|
UpdatePasswordAgePolicyRequest,
|
||||||
@ -623,6 +625,10 @@ export class AdminService {
|
|||||||
return this.grpcService.admin.updateOIDCSettings(req, null).then((resp) => resp.toObject());
|
return this.grpcService.admin.updateOIDCSettings(req, null).then((resp) => resp.toObject());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public addOIDCSettings(req: AddOIDCSettingsRequest): Promise<AddOIDCSettingsResponse.AsObject> {
|
||||||
|
return this.grpcService.admin.addOIDCSettings(req, null).then((resp) => resp.toObject());
|
||||||
|
}
|
||||||
|
|
||||||
/* LOG and FILE Notifications */
|
/* LOG and FILE Notifications */
|
||||||
|
|
||||||
public getLogNotificationProvider(): Promise<GetLogNotificationProviderResponse.AsObject> {
|
public getLogNotificationProvider(): Promise<GetLogNotificationProviderResponse.AsObject> {
|
||||||
|
@ -284,6 +284,18 @@ Get OIDC settings (e.g token lifetimes, etc.)
|
|||||||
GET: /settings/oidc
|
GET: /settings/oidc
|
||||||
|
|
||||||
|
|
||||||
|
### AddOIDCSettings
|
||||||
|
|
||||||
|
> **rpc** AddOIDCSettings([AddOIDCSettingsRequest](#addoidcsettingsrequest))
|
||||||
|
[AddOIDCSettingsResponse](#addoidcsettingsresponse)
|
||||||
|
|
||||||
|
Add oidc settings (e.g token lifetimes, etc)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
POST: /settings/oidc
|
||||||
|
|
||||||
|
|
||||||
### UpdateOIDCSettings
|
### UpdateOIDCSettings
|
||||||
|
|
||||||
> **rpc** UpdateOIDCSettings([UpdateOIDCSettingsRequest](#updateoidcsettingsrequest))
|
> **rpc** UpdateOIDCSettings([UpdateOIDCSettingsRequest](#updateoidcsettingsrequest))
|
||||||
@ -1739,6 +1751,31 @@ This is an empty request
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### AddOIDCSettingsRequest
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Field | Type | Description | Validation |
|
||||||
|
| ----- | ---- | ----------- | ----------- |
|
||||||
|
| access_token_lifetime | google.protobuf.Duration | - | |
|
||||||
|
| id_token_lifetime | google.protobuf.Duration | - | |
|
||||||
|
| refresh_token_idle_expiration | google.protobuf.Duration | - | |
|
||||||
|
| refresh_token_expiration | google.protobuf.Duration | - | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### AddOIDCSettingsResponse
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Field | Type | Description | Validation |
|
||||||
|
| ----- | ---- | ----------- | ----------- |
|
||||||
|
| details | zitadel.v1.ObjectDetails | - | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### AddSMSProviderTwilioRequest
|
### AddSMSProviderTwilioRequest
|
||||||
|
|
||||||
|
|
||||||
|
38
e2e/cypress/e2e/settings/oidc-settings.cy.ts
Normal file
38
e2e/cypress/e2e/settings/oidc-settings.cy.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { apiAuth } from '../../support/api/apiauth';
|
||||||
|
import { ensureOIDCSettingsSet } from '../../support/api/oidc-settings';
|
||||||
|
|
||||||
|
describe('oidc settings', () => {
|
||||||
|
const oidcSettingsPath = `/settings?id=oidc`;
|
||||||
|
const accessTokenPrecondition = 1;
|
||||||
|
const idTokenPrecondition = 2;
|
||||||
|
const refreshTokenExpirationPrecondition = 7;
|
||||||
|
const refreshTokenIdleExpirationPrecondition = 2;
|
||||||
|
|
||||||
|
before(`ensure they are set`, () => {
|
||||||
|
apiAuth().then((apiCallProperties) => {
|
||||||
|
ensureOIDCSettingsSet(
|
||||||
|
apiCallProperties,
|
||||||
|
accessTokenPrecondition,
|
||||||
|
idTokenPrecondition,
|
||||||
|
refreshTokenExpirationPrecondition,
|
||||||
|
refreshTokenIdleExpirationPrecondition,
|
||||||
|
);
|
||||||
|
cy.visit(oidcSettingsPath);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should update oidc settings`, () => {
|
||||||
|
cy.get('[formcontrolname="accessTokenLifetime"]').should('value', accessTokenPrecondition).clear().type('2');
|
||||||
|
cy.get('[formcontrolname="idTokenLifetime"]').should('value', idTokenPrecondition).clear().type('24');
|
||||||
|
cy.get('[formcontrolname="refreshTokenExpiration"]')
|
||||||
|
.should('value', refreshTokenExpirationPrecondition)
|
||||||
|
.clear()
|
||||||
|
.type('30');
|
||||||
|
cy.get('[formcontrolname="refreshTokenIdleExpiration"]')
|
||||||
|
.should('value', refreshTokenIdleExpirationPrecondition)
|
||||||
|
.clear()
|
||||||
|
.type('7');
|
||||||
|
cy.get('[data-e2e="save-button"]').click();
|
||||||
|
cy.get('.data-e2e-success');
|
||||||
|
});
|
||||||
|
});
|
@ -3,6 +3,7 @@ import { login, User } from 'support/login/users';
|
|||||||
export interface apiCallProperties {
|
export interface apiCallProperties {
|
||||||
authHeader: string;
|
authHeader: string;
|
||||||
mgntBaseURL: string;
|
mgntBaseURL: string;
|
||||||
|
adminBaseURL: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function apiAuth(): Cypress.Chainable<apiCallProperties> {
|
export function apiAuth(): Cypress.Chainable<apiCallProperties> {
|
||||||
@ -10,6 +11,7 @@ export function apiAuth(): Cypress.Chainable<apiCallProperties> {
|
|||||||
return <apiCallProperties>{
|
return <apiCallProperties>{
|
||||||
authHeader: `Bearer ${token}`,
|
authHeader: `Bearer ${token}`,
|
||||||
mgntBaseURL: `${Cypress.env('BACKEND_URL')}/management/v1/`,
|
mgntBaseURL: `${Cypress.env('BACKEND_URL')}/management/v1/`,
|
||||||
|
adminBaseURL: `${Cypress.env('BACKEND_URL')}/admin/v1/`,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,46 @@ export function ensureSomethingExists(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ensureSomethingIsSet(
|
||||||
|
api: apiCallProperties,
|
||||||
|
path: string,
|
||||||
|
find: (entity: any) => SearchResult,
|
||||||
|
createPath: string,
|
||||||
|
body: any,
|
||||||
|
): Cypress.Chainable<number> {
|
||||||
|
return getSomething(api, path, find)
|
||||||
|
.then((sRes) => {
|
||||||
|
if (sRes.entity) {
|
||||||
|
return cy.wrap({
|
||||||
|
id: sRes.entity.id,
|
||||||
|
initialSequence: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return cy
|
||||||
|
.request({
|
||||||
|
method: 'PUT',
|
||||||
|
url: createPath,
|
||||||
|
headers: {
|
||||||
|
Authorization: api.authHeader,
|
||||||
|
},
|
||||||
|
body: body,
|
||||||
|
failOnStatusCode: false,
|
||||||
|
followRedirect: false,
|
||||||
|
})
|
||||||
|
.then((cRes) => {
|
||||||
|
expect(cRes.status).to.equal(200);
|
||||||
|
return {
|
||||||
|
id: cRes.body.id,
|
||||||
|
initialSequence: sRes.sequence,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
awaitDesiredById(90, (entity) => !!entity, data.initialSequence, api, path, find);
|
||||||
|
return cy.wrap<number>(data.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function ensureSomethingDoesntExist(
|
export function ensureSomethingDoesntExist(
|
||||||
api: apiCallProperties,
|
api: apiCallProperties,
|
||||||
searchPath: string,
|
searchPath: string,
|
||||||
@ -97,6 +137,24 @@ function searchSomething(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSomething(
|
||||||
|
api: apiCallProperties,
|
||||||
|
searchPath: string,
|
||||||
|
find: (entity: any) => SearchResult,
|
||||||
|
): Cypress.Chainable<SearchResult> {
|
||||||
|
return cy
|
||||||
|
.request({
|
||||||
|
method: 'GET',
|
||||||
|
url: searchPath,
|
||||||
|
headers: {
|
||||||
|
Authorization: api.authHeader,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
return find(res.body);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function awaitDesired(
|
function awaitDesired(
|
||||||
trials: number,
|
trials: number,
|
||||||
expectEntity: (entity: any) => boolean,
|
expectEntity: (entity: any) => boolean,
|
||||||
@ -116,3 +174,23 @@ function awaitDesired(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function awaitDesiredById(
|
||||||
|
trials: number,
|
||||||
|
expectEntity: (entity: any) => boolean,
|
||||||
|
initialSequence: number,
|
||||||
|
api: apiCallProperties,
|
||||||
|
path: string,
|
||||||
|
find: (entity: any) => SearchResult,
|
||||||
|
) {
|
||||||
|
getSomething(api, path, find).then((resp) => {
|
||||||
|
const foundExpectedEntity = expectEntity(resp.entity);
|
||||||
|
const foundExpectedSequence = resp.sequence > initialSequence;
|
||||||
|
|
||||||
|
if (!foundExpectedEntity || !foundExpectedSequence) {
|
||||||
|
expect(trials, `trying ${trials} more times`).to.be.greaterThan(0);
|
||||||
|
cy.wait(1000);
|
||||||
|
awaitDesiredById(trials - 1, expectEntity, initialSequence, api, path, find);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
44
e2e/cypress/support/api/oidc-settings.ts
Normal file
44
e2e/cypress/support/api/oidc-settings.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { apiCallProperties } from './apiauth';
|
||||||
|
import { ensureSomethingIsSet } from './ensure';
|
||||||
|
|
||||||
|
export function ensureOIDCSettingsSet(
|
||||||
|
api: apiCallProperties,
|
||||||
|
accessTokenLifetime,
|
||||||
|
idTokenLifetime,
|
||||||
|
refreshTokenExpiration,
|
||||||
|
refreshTokenIdleExpiration: number,
|
||||||
|
): Cypress.Chainable<number> {
|
||||||
|
return ensureSomethingIsSet(
|
||||||
|
api,
|
||||||
|
`${api.adminBaseURL}settings/oidc`,
|
||||||
|
(settings: any) => {
|
||||||
|
let entity = null;
|
||||||
|
if (
|
||||||
|
settings.settings?.accessTokenLifetime === hoursToDuration(accessTokenLifetime) &&
|
||||||
|
settings.settings?.idTokenLifetime === hoursToDuration(idTokenLifetime) &&
|
||||||
|
settings.settings?.refreshTokenExpiration === daysToDuration(refreshTokenExpiration) &&
|
||||||
|
settings.settings?.refreshTokenIdleExpiration === daysToDuration(refreshTokenIdleExpiration)
|
||||||
|
) {
|
||||||
|
entity = settings.settings;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
entity: entity,
|
||||||
|
sequence: settings.settings?.details?.sequence,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
`${api.adminBaseURL}settings/oidc`,
|
||||||
|
{
|
||||||
|
accessTokenLifetime: hoursToDuration(accessTokenLifetime),
|
||||||
|
idTokenLifetime: hoursToDuration(idTokenLifetime),
|
||||||
|
refreshTokenExpiration: daysToDuration(refreshTokenExpiration),
|
||||||
|
refreshTokenIdleExpiration: daysToDuration(refreshTokenIdleExpiration),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hoursToDuration(hours: number): string {
|
||||||
|
return (hours * 3600).toString() + 's';
|
||||||
|
}
|
||||||
|
function daysToDuration(days: number): string {
|
||||||
|
return hoursToDuration(24 * days);
|
||||||
|
}
|
@ -18,6 +18,16 @@ func (s *Server) GetOIDCSettings(ctx context.Context, _ *admin_pb.GetOIDCSetting
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) AddOIDCSettings(ctx context.Context, req *admin_pb.AddOIDCSettingsRequest) (*admin_pb.AddOIDCSettingsResponse, error) {
|
||||||
|
result, err := s.command.AddOIDCSettings(ctx, AddOIDCConfigToConfig(req))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &admin_pb.AddOIDCSettingsResponse{
|
||||||
|
Details: object.DomainToChangeDetailsPb(result),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) UpdateOIDCSettings(ctx context.Context, req *admin_pb.UpdateOIDCSettingsRequest) (*admin_pb.UpdateOIDCSettingsResponse, error) {
|
func (s *Server) UpdateOIDCSettings(ctx context.Context, req *admin_pb.UpdateOIDCSettingsRequest) (*admin_pb.UpdateOIDCSettingsResponse, error) {
|
||||||
result, err := s.command.ChangeOIDCSettings(ctx, UpdateOIDCConfigToConfig(req))
|
result, err := s.command.ChangeOIDCSettings(ctx, UpdateOIDCConfigToConfig(req))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"google.golang.org/protobuf/types/known/durationpb"
|
||||||
|
|
||||||
obj_grpc "github.com/zitadel/zitadel/internal/api/grpc/object"
|
obj_grpc "github.com/zitadel/zitadel/internal/api/grpc/object"
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/query"
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
|
admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
|
||||||
settings_pb "github.com/zitadel/zitadel/pkg/grpc/settings"
|
settings_pb "github.com/zitadel/zitadel/pkg/grpc/settings"
|
||||||
"google.golang.org/protobuf/types/known/durationpb"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func OIDCSettingsToPb(config *query.OIDCSettings) *settings_pb.OIDCSettings {
|
func OIDCSettingsToPb(config *query.OIDCSettings) *settings_pb.OIDCSettings {
|
||||||
@ -19,6 +20,15 @@ func OIDCSettingsToPb(config *query.OIDCSettings) *settings_pb.OIDCSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AddOIDCConfigToConfig(req *admin_pb.AddOIDCSettingsRequest) *domain.OIDCSettings {
|
||||||
|
return &domain.OIDCSettings{
|
||||||
|
AccessTokenLifetime: req.AccessTokenLifetime.AsDuration(),
|
||||||
|
IdTokenLifetime: req.IdTokenLifetime.AsDuration(),
|
||||||
|
RefreshTokenIdleExpiration: req.RefreshTokenIdleExpiration.AsDuration(),
|
||||||
|
RefreshTokenExpiration: req.RefreshTokenExpiration.AsDuration(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func UpdateOIDCConfigToConfig(req *admin_pb.UpdateOIDCSettingsRequest) *domain.OIDCSettings {
|
func UpdateOIDCConfigToConfig(req *admin_pb.UpdateOIDCSettingsRequest) *domain.OIDCSettings {
|
||||||
return &domain.OIDCSettings{
|
return &domain.OIDCSettings{
|
||||||
AccessTokenLifetime: req.AccessTokenLifetime.AsDuration(),
|
AccessTokenLifetime: req.AccessTokenLifetime.AsDuration(),
|
||||||
|
@ -88,7 +88,13 @@ func (o *OPStorage) CreateAccessToken(ctx context.Context, req op.TokenRequest)
|
|||||||
applicationID = authReq.ApplicationID
|
applicationID = authReq.ApplicationID
|
||||||
userOrgID = authReq.UserOrgID
|
userOrgID = authReq.UserOrgID
|
||||||
}
|
}
|
||||||
resp, err := o.command.AddUserToken(setContextUserSystem(ctx), userOrgID, userAgentID, applicationID, req.GetSubject(), req.GetAudience(), req.GetScopes(), o.defaultAccessTokenLifetime) //PLANNED: lifetime from client
|
|
||||||
|
accessTokenLifetime, _, _, _, err := o.getOIDCSettings(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", time.Time{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := o.command.AddUserToken(setContextUserSystem(ctx), userOrgID, userAgentID, applicationID, req.GetSubject(), req.GetAudience(), req.GetScopes(), accessTokenLifetime) //PLANNED: lifetime from client
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", time.Time{}, err
|
return "", time.Time{}, err
|
||||||
}
|
}
|
||||||
@ -106,9 +112,15 @@ func (o *OPStorage) CreateAccessAndRefreshTokens(ctx context.Context, req op.Tok
|
|||||||
if request, ok := req.(op.RefreshTokenRequest); ok {
|
if request, ok := req.(op.RefreshTokenRequest); ok {
|
||||||
request.SetCurrentScopes(scopes)
|
request.SetCurrentScopes(scopes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accessTokenLifetime, _, refreshTokenIdleExpiration, refreshTokenExpiration, err := o.getOIDCSettings(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", time.Time{}, err
|
||||||
|
}
|
||||||
|
|
||||||
resp, token, err := o.command.AddAccessAndRefreshToken(setContextUserSystem(ctx), userOrgID, userAgentID, applicationID, req.GetSubject(),
|
resp, token, err := o.command.AddAccessAndRefreshToken(setContextUserSystem(ctx), userOrgID, userAgentID, applicationID, req.GetSubject(),
|
||||||
refreshToken, req.GetAudience(), scopes, authMethodsReferences, o.defaultAccessTokenLifetime,
|
refreshToken, req.GetAudience(), scopes, authMethodsReferences, accessTokenLifetime,
|
||||||
o.defaultRefreshTokenIdleExpiration, o.defaultRefreshTokenExpiration, authTime) //PLANNED: lifetime from client
|
refreshTokenIdleExpiration, refreshTokenExpiration, authTime) //PLANNED: lifetime from client
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.IsErrorInvalidArgument(err) {
|
if errors.IsErrorInvalidArgument(err) {
|
||||||
err = oidc.ErrInvalidGrant().WithParent(err)
|
err = oidc.ErrInvalidGrant().WithParent(err)
|
||||||
@ -248,3 +260,15 @@ func setContextUserSystem(ctx context.Context) context.Context {
|
|||||||
}
|
}
|
||||||
return authz.SetCtxData(ctx, data)
|
return authz.SetCtxData(ctx, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *OPStorage) getOIDCSettings(ctx context.Context) (accessTokenLifetime, idTokenLifetime, refreshTokenIdleExpiration, refreshTokenExpiration time.Duration, _ error) {
|
||||||
|
oidcSettings, err := o.query.OIDCSettingsByAggID(ctx, authz.GetInstance(ctx).InstanceID())
|
||||||
|
if err != nil && !errors.IsNotFound(err) {
|
||||||
|
return time.Duration(0), time.Duration(0), time.Duration(0), time.Duration(0), err
|
||||||
|
}
|
||||||
|
|
||||||
|
if oidcSettings != nil {
|
||||||
|
return oidcSettings.AccessTokenLifetime, oidcSettings.IdTokenLifetime, oidcSettings.RefreshTokenIdleExpiration, oidcSettings.RefreshTokenExpiration, nil
|
||||||
|
}
|
||||||
|
return o.defaultAccessTokenLifetime, o.defaultIdTokenLifetime, o.defaultRefreshTokenIdleExpiration, o.defaultRefreshTokenExpiration, nil
|
||||||
|
}
|
||||||
|
@ -51,7 +51,13 @@ func (o *OPStorage) GetClientByClientID(ctx context.Context, id string) (_ op.Cl
|
|||||||
for i, role := range projectRoles.ProjectRoles {
|
for i, role := range projectRoles.ProjectRoles {
|
||||||
allowedScopes[i] = ScopeProjectRolePrefix + role.Key
|
allowedScopes[i] = ScopeProjectRolePrefix + role.Key
|
||||||
}
|
}
|
||||||
return ClientFromBusiness(client, o.defaultLoginURL, o.defaultAccessTokenLifetime, o.defaultIdTokenLifetime, allowedScopes)
|
|
||||||
|
accessTokenLifetime, idTokenLifetime, _, _, err := o.getOIDCSettings(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ClientFromBusiness(client, o.defaultLoginURL, accessTokenLifetime, idTokenLifetime, allowedScopes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OPStorage) GetKeyByIDAndUserID(ctx context.Context, keyID, userID string) (_ *jose.JSONWebKey, err error) {
|
func (o *OPStorage) GetKeyByIDAndUserID(ctx context.Context, keyID, userID string) (_ *jose.JSONWebKey, err error) {
|
||||||
|
@ -104,6 +104,12 @@ type InstanceSetup struct {
|
|||||||
EmailTemplate []byte
|
EmailTemplate []byte
|
||||||
MessageTexts []*domain.CustomMessageText
|
MessageTexts []*domain.CustomMessageText
|
||||||
SMTPConfiguration *smtp.EmailConfig
|
SMTPConfiguration *smtp.EmailConfig
|
||||||
|
OIDCSettings *struct {
|
||||||
|
AccessTokenLifetime time.Duration
|
||||||
|
IdTokenLifetime time.Duration
|
||||||
|
RefreshTokenIdleExpiration time.Duration
|
||||||
|
RefreshTokenExpiration time.Duration
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ZitadelConfig struct {
|
type ZitadelConfig struct {
|
||||||
@ -346,6 +352,18 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if setup.OIDCSettings != nil {
|
||||||
|
validations = append(validations,
|
||||||
|
c.prepareAddOIDCSettings(
|
||||||
|
instanceAgg,
|
||||||
|
setup.OIDCSettings.AccessTokenLifetime,
|
||||||
|
setup.OIDCSettings.IdTokenLifetime,
|
||||||
|
setup.OIDCSettings.RefreshTokenIdleExpiration,
|
||||||
|
setup.OIDCSettings.RefreshTokenExpiration,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validations...)
|
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validations...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
|
@ -2,78 +2,131 @@ package command
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
|
"github.com/zitadel/zitadel/internal/command/preparation"
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
caos_errs "github.com/zitadel/zitadel/internal/errors"
|
"github.com/zitadel/zitadel/internal/errors"
|
||||||
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (c *Commands) prepareAddOIDCSettings(a *instance.Aggregate, accessTokenLifetime, idTokenLifetime, refreshTokenIdleExpiration, refreshTokenExpiration time.Duration) preparation.Validation {
|
||||||
|
return func() (preparation.CreateCommands, error) {
|
||||||
|
if accessTokenLifetime == time.Duration(0) ||
|
||||||
|
idTokenLifetime == time.Duration(0) ||
|
||||||
|
refreshTokenIdleExpiration == time.Duration(0) ||
|
||||||
|
refreshTokenExpiration == time.Duration(0) {
|
||||||
|
return nil, errors.ThrowInvalidArgument(nil, "INST-10s82j", "Errors.Invalid.Argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||||
|
writeModel, err := c.getOIDCSettingsWriteModel(ctx, filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if writeModel.State == domain.OIDCSettingsStateActive {
|
||||||
|
return nil, errors.ThrowAlreadyExists(nil, "INST-0aaj1o", "Errors.OIDCSettings.AlreadyExists")
|
||||||
|
}
|
||||||
|
return []eventstore.Command{
|
||||||
|
instance.NewOIDCSettingsAddedEvent(
|
||||||
|
ctx,
|
||||||
|
&a.Aggregate,
|
||||||
|
accessTokenLifetime,
|
||||||
|
idTokenLifetime,
|
||||||
|
refreshTokenIdleExpiration,
|
||||||
|
refreshTokenExpiration,
|
||||||
|
),
|
||||||
|
}, nil
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Commands) prepareUpdateOIDCSettings(a *instance.Aggregate, accessTokenLifetime, idTokenLifetime, refreshTokenIdleExpiration, refreshTokenExpiration time.Duration) preparation.Validation {
|
||||||
|
return func() (preparation.CreateCommands, error) {
|
||||||
|
if accessTokenLifetime == time.Duration(0) ||
|
||||||
|
idTokenLifetime == time.Duration(0) ||
|
||||||
|
refreshTokenIdleExpiration == time.Duration(0) ||
|
||||||
|
refreshTokenExpiration == time.Duration(0) {
|
||||||
|
return nil, errors.ThrowInvalidArgument(nil, "INST-10sxks", "Errors.Invalid.Argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||||
|
writeModel, err := c.getOIDCSettingsWriteModel(ctx, filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if writeModel.State != domain.OIDCSettingsStateActive {
|
||||||
|
return nil, errors.ThrowNotFound(nil, "INST-90s32oj", "Errors.OIDCSettings.NotFound")
|
||||||
|
}
|
||||||
|
changedEvent, hasChanged, err := writeModel.NewChangedEvent(
|
||||||
|
ctx,
|
||||||
|
&a.Aggregate,
|
||||||
|
accessTokenLifetime,
|
||||||
|
idTokenLifetime,
|
||||||
|
refreshTokenIdleExpiration,
|
||||||
|
refreshTokenExpiration,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !hasChanged {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(nil, "COMMAND-0pk2nu", "Errors.NoChangesFound")
|
||||||
|
}
|
||||||
|
return []eventstore.Command{
|
||||||
|
changedEvent,
|
||||||
|
}, nil
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Commands) AddOIDCSettings(ctx context.Context, settings *domain.OIDCSettings) (*domain.ObjectDetails, error) {
|
func (c *Commands) AddOIDCSettings(ctx context.Context, settings *domain.OIDCSettings) (*domain.ObjectDetails, error) {
|
||||||
oidcSettingWriteModel, err := c.getOIDCSettings(ctx)
|
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
|
||||||
|
validation := c.prepareAddOIDCSettings(instanceAgg, settings.AccessTokenLifetime, settings.IdTokenLifetime, settings.RefreshTokenIdleExpiration, settings.RefreshTokenExpiration)
|
||||||
|
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if oidcSettingWriteModel.State.Exists() {
|
events, err := c.eventstore.Push(ctx, cmds...)
|
||||||
return nil, caos_errs.ThrowAlreadyExists(nil, "COMMAND-d9nlw", "Errors.OIDCSettings.AlreadyExists")
|
|
||||||
}
|
|
||||||
instanceAgg := InstanceAggregateFromWriteModel(&oidcSettingWriteModel.WriteModel)
|
|
||||||
pushedEvents, err := c.eventstore.Push(ctx, instance.NewOIDCSettingsAddedEvent(
|
|
||||||
ctx,
|
|
||||||
instanceAgg,
|
|
||||||
settings.AccessTokenLifetime,
|
|
||||||
settings.IdTokenLifetime,
|
|
||||||
settings.RefreshTokenIdleExpiration,
|
|
||||||
settings.RefreshTokenExpiration))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = AppendAndReduce(oidcSettingWriteModel, pushedEvents...)
|
return &domain.ObjectDetails{
|
||||||
if err != nil {
|
Sequence: events[len(events)-1].Sequence(),
|
||||||
return nil, err
|
EventDate: events[len(events)-1].CreationDate(),
|
||||||
}
|
ResourceOwner: events[len(events)-1].Aggregate().InstanceID,
|
||||||
return writeModelToObjectDetails(&oidcSettingWriteModel.WriteModel), nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) ChangeOIDCSettings(ctx context.Context, settings *domain.OIDCSettings) (*domain.ObjectDetails, error) {
|
func (c *Commands) ChangeOIDCSettings(ctx context.Context, settings *domain.OIDCSettings) (*domain.ObjectDetails, error) {
|
||||||
oidcSettingWriteModel, err := c.getOIDCSettings(ctx)
|
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
|
||||||
|
validation := c.prepareUpdateOIDCSettings(instanceAgg, settings.AccessTokenLifetime, settings.IdTokenLifetime, settings.RefreshTokenIdleExpiration, settings.RefreshTokenExpiration)
|
||||||
|
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !oidcSettingWriteModel.State.Exists() {
|
events, err := c.eventstore.Push(ctx, cmds...)
|
||||||
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-8snEr", "Errors.OIDCSettings.NotFound")
|
|
||||||
}
|
|
||||||
instanceAgg := InstanceAggregateFromWriteModel(&oidcSettingWriteModel.WriteModel)
|
|
||||||
|
|
||||||
changedEvent, hasChanged, err := oidcSettingWriteModel.NewChangedEvent(
|
|
||||||
ctx,
|
|
||||||
instanceAgg,
|
|
||||||
settings.AccessTokenLifetime,
|
|
||||||
settings.IdTokenLifetime,
|
|
||||||
settings.RefreshTokenIdleExpiration,
|
|
||||||
settings.RefreshTokenExpiration)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !hasChanged {
|
return &domain.ObjectDetails{
|
||||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-398uF", "Errors.NoChangesFound")
|
Sequence: events[len(events)-1].Sequence(),
|
||||||
}
|
EventDate: events[len(events)-1].CreationDate(),
|
||||||
pushedEvents, err := c.eventstore.Push(ctx, changedEvent)
|
ResourceOwner: events[len(events)-1].Aggregate().InstanceID,
|
||||||
if err != nil {
|
}, nil
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = AppendAndReduce(oidcSettingWriteModel, pushedEvents...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return writeModelToObjectDetails(&oidcSettingWriteModel.WriteModel), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) getOIDCSettings(ctx context.Context) (_ *InstanceOIDCSettingsWriteModel, err error) {
|
func (c *Commands) getOIDCSettingsWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer) (_ *InstanceOIDCSettingsWriteModel, err error) {
|
||||||
writeModel := NewInstanceOIDCSettingsWriteModel(ctx)
|
writeModel := NewInstanceOIDCSettingsWriteModel(ctx)
|
||||||
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
events, err := filter(ctx, writeModel.Query())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if len(events) == 0 {
|
||||||
return writeModel, nil
|
return writeModel, nil
|
||||||
|
}
|
||||||
|
writeModel.AppendEvents(events...)
|
||||||
|
err = writeModel.Reduce()
|
||||||
|
return writeModel, err
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/api/authz"
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
@ -34,7 +35,7 @@ func TestCommandSide_AddOIDCConfig(t *testing.T) {
|
|||||||
res res
|
res res
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "oidc config, error already exists",
|
name: "oidc settings, error already exists",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: eventstoreExpect(
|
||||||
t,
|
t,
|
||||||
@ -52,7 +53,7 @@ func TestCommandSide_AddOIDCConfig(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||||
oidcConfig: &domain.OIDCSettings{
|
oidcConfig: &domain.OIDCSettings{
|
||||||
AccessTokenLifetime: 1 * time.Hour,
|
AccessTokenLifetime: 1 * time.Hour,
|
||||||
IdTokenLifetime: 1 * time.Hour,
|
IdTokenLifetime: 1 * time.Hour,
|
||||||
@ -65,7 +66,7 @@ func TestCommandSide_AddOIDCConfig(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "add secret generator, ok",
|
name: "add oidc settings, ok",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: eventstoreExpect(
|
||||||
t,
|
t,
|
||||||
@ -102,6 +103,86 @@ func TestCommandSide_AddOIDCConfig(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "add oidc settings, invalid argument 1",
|
||||||
|
fields: fields{
|
||||||
|
eventstore: eventstoreExpect(
|
||||||
|
t,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||||
|
oidcConfig: &domain.OIDCSettings{
|
||||||
|
AccessTokenLifetime: 0 * time.Hour,
|
||||||
|
IdTokenLifetime: 1 * time.Hour,
|
||||||
|
RefreshTokenIdleExpiration: 1 * time.Hour,
|
||||||
|
RefreshTokenExpiration: 1 * time.Hour,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
err: caos_errs.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "add oidc settings, invalid argument 2",
|
||||||
|
fields: fields{
|
||||||
|
eventstore: eventstoreExpect(
|
||||||
|
t,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||||
|
oidcConfig: &domain.OIDCSettings{
|
||||||
|
AccessTokenLifetime: 1 * time.Hour,
|
||||||
|
IdTokenLifetime: 0 * time.Hour,
|
||||||
|
RefreshTokenIdleExpiration: 1 * time.Hour,
|
||||||
|
RefreshTokenExpiration: 1 * time.Hour,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
err: caos_errs.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "add oidc settings, invalid argument 3",
|
||||||
|
fields: fields{
|
||||||
|
eventstore: eventstoreExpect(
|
||||||
|
t,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||||
|
oidcConfig: &domain.OIDCSettings{
|
||||||
|
AccessTokenLifetime: 1 * time.Hour,
|
||||||
|
IdTokenLifetime: 1 * time.Hour,
|
||||||
|
RefreshTokenIdleExpiration: 0 * time.Hour,
|
||||||
|
RefreshTokenExpiration: 1 * time.Hour,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
err: caos_errs.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "add oidc settings, invalid argument 4",
|
||||||
|
fields: fields{
|
||||||
|
eventstore: eventstoreExpect(
|
||||||
|
t,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||||
|
oidcConfig: &domain.OIDCSettings{
|
||||||
|
AccessTokenLifetime: 1 * time.Hour,
|
||||||
|
IdTokenLifetime: 1 * time.Hour,
|
||||||
|
RefreshTokenIdleExpiration: 1 * time.Hour,
|
||||||
|
RefreshTokenExpiration: 0 * time.Hour,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
err: caos_errs.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
@ -141,7 +222,7 @@ func TestCommandSide_ChangeOIDCConfig(t *testing.T) {
|
|||||||
res res
|
res res
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "oidc config not existing, not found error",
|
name: "oidc settings not existing, not found error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: eventstoreExpect(
|
||||||
t,
|
t,
|
||||||
@ -150,11 +231,97 @@ func TestCommandSide_ChangeOIDCConfig(t *testing.T) {
|
|||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
|
oidcConfig: &domain.OIDCSettings{
|
||||||
|
AccessTokenLifetime: 1 * time.Hour,
|
||||||
|
IdTokenLifetime: 1 * time.Hour,
|
||||||
|
RefreshTokenIdleExpiration: 1 * time.Hour,
|
||||||
|
RefreshTokenExpiration: 1 * time.Hour,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsNotFound,
|
err: caos_errs.IsNotFound,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "no changes, invalid argument error 1",
|
||||||
|
fields: fields{
|
||||||
|
eventstore: eventstoreExpect(
|
||||||
|
t,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||||
|
oidcConfig: &domain.OIDCSettings{
|
||||||
|
AccessTokenLifetime: 0 * time.Hour,
|
||||||
|
IdTokenLifetime: 1 * time.Hour,
|
||||||
|
RefreshTokenIdleExpiration: 1 * time.Hour,
|
||||||
|
RefreshTokenExpiration: 1 * time.Hour,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
err: caos_errs.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no changes, invalid argument error 2",
|
||||||
|
fields: fields{
|
||||||
|
eventstore: eventstoreExpect(
|
||||||
|
t,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||||
|
oidcConfig: &domain.OIDCSettings{
|
||||||
|
AccessTokenLifetime: 1 * time.Hour,
|
||||||
|
IdTokenLifetime: 0 * time.Hour,
|
||||||
|
RefreshTokenIdleExpiration: 1 * time.Hour,
|
||||||
|
RefreshTokenExpiration: 1 * time.Hour,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
err: caos_errs.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no changes, invalid argument error 3",
|
||||||
|
fields: fields{
|
||||||
|
eventstore: eventstoreExpect(
|
||||||
|
t,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||||
|
oidcConfig: &domain.OIDCSettings{
|
||||||
|
AccessTokenLifetime: 1 * time.Hour,
|
||||||
|
IdTokenLifetime: 1 * time.Hour,
|
||||||
|
RefreshTokenIdleExpiration: 0 * time.Hour,
|
||||||
|
RefreshTokenExpiration: 1 * time.Hour,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
err: caos_errs.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no changes, invalid argument error 4",
|
||||||
|
fields: fields{
|
||||||
|
eventstore: eventstoreExpect(
|
||||||
|
t,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||||
|
oidcConfig: &domain.OIDCSettings{
|
||||||
|
AccessTokenLifetime: 1 * time.Hour,
|
||||||
|
IdTokenLifetime: 1 * time.Hour,
|
||||||
|
RefreshTokenIdleExpiration: 1 * time.Hour,
|
||||||
|
RefreshTokenExpiration: 0 * time.Hour,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
err: caos_errs.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "no changes, precondition error",
|
name: "no changes, precondition error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
@ -175,7 +342,7 @@ func TestCommandSide_ChangeOIDCConfig(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||||
oidcConfig: &domain.OIDCSettings{
|
oidcConfig: &domain.OIDCSettings{
|
||||||
AccessTokenLifetime: 1 * time.Hour,
|
AccessTokenLifetime: 1 * time.Hour,
|
||||||
IdTokenLifetime: 1 * time.Hour,
|
IdTokenLifetime: 1 * time.Hour,
|
||||||
@ -188,7 +355,7 @@ func TestCommandSide_ChangeOIDCConfig(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "secret generator change, ok",
|
name: "oidc settings change, ok",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: eventstoreExpect(
|
||||||
t,
|
t,
|
||||||
@ -206,8 +373,9 @@ func TestCommandSide_ChangeOIDCConfig(t *testing.T) {
|
|||||||
),
|
),
|
||||||
expectPush(
|
expectPush(
|
||||||
[]*repository.Event{
|
[]*repository.Event{
|
||||||
eventFromEventPusher(
|
eventFromEventPusherWithInstanceID("INSTANCE",
|
||||||
newOIDCConfigChangedEvent(context.Background(),
|
newOIDCConfigChangedEvent(
|
||||||
|
context.Background(),
|
||||||
time.Hour*2,
|
time.Hour*2,
|
||||||
time.Hour*2,
|
time.Hour*2,
|
||||||
time.Hour*2,
|
time.Hour*2,
|
||||||
@ -218,7 +386,7 @@ func TestCommandSide_ChangeOIDCConfig(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||||
oidcConfig: &domain.OIDCSettings{
|
oidcConfig: &domain.OIDCSettings{
|
||||||
AccessTokenLifetime: 2 * time.Hour,
|
AccessTokenLifetime: 2 * time.Hour,
|
||||||
IdTokenLifetime: 2 * time.Hour,
|
IdTokenLifetime: 2 * time.Hour,
|
||||||
|
@ -9,7 +9,6 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/crypto"
|
"github.com/zitadel/zitadel/internal/crypto"
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/errors"
|
"github.com/zitadel/zitadel/internal/errors"
|
||||||
caos_errs "github.com/zitadel/zitadel/internal/errors"
|
|
||||||
"github.com/zitadel/zitadel/internal/eventstore"
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
"github.com/zitadel/zitadel/internal/notification/channels/smtp"
|
"github.com/zitadel/zitadel/internal/notification/channels/smtp"
|
||||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||||
@ -58,7 +57,7 @@ func (c *Commands) ChangeSMTPConfigPassword(ctx context.Context, password string
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if smtpConfigWriteModel.State != domain.SMTPConfigStateActive {
|
if smtpConfigWriteModel.State != domain.SMTPConfigStateActive {
|
||||||
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-3n9ls", "Errors.SMTPConfig.NotFound")
|
return nil, errors.ThrowNotFound(nil, "COMMAND-3n9ls", "Errors.SMTPConfig.NotFound")
|
||||||
}
|
}
|
||||||
var smtpPassword *crypto.CryptoValue
|
var smtpPassword *crypto.CryptoValue
|
||||||
if password != "" {
|
if password != "" {
|
||||||
@ -180,7 +179,7 @@ func (c *Commands) prepareChangeSMTPConfig(a *instance.Aggregate, from, name, ho
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !hasChanged {
|
if !hasChanged {
|
||||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-m0o3f", "Errors.NoChangesFound")
|
return nil, errors.ThrowPreconditionFailed(nil, "COMMAND-m0o3f", "Errors.NoChangesFound")
|
||||||
}
|
}
|
||||||
return []eventstore.Command{
|
return []eventstore.Command{
|
||||||
changedEvent,
|
changedEvent,
|
||||||
|
@ -8,7 +8,6 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/eventstore/handler"
|
"github.com/zitadel/zitadel/internal/eventstore/handler"
|
||||||
"github.com/zitadel/zitadel/internal/eventstore/handler/crdb"
|
"github.com/zitadel/zitadel/internal/eventstore/handler/crdb"
|
||||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||||
"github.com/zitadel/zitadel/internal/repository/project"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -43,7 +42,6 @@ func newOIDCSettingsProjection(ctx context.Context, config crdb.StatementHandler
|
|||||||
crdb.NewColumn(OIDCSettingsColumnInstanceID, crdb.ColumnTypeText),
|
crdb.NewColumn(OIDCSettingsColumnInstanceID, crdb.ColumnTypeText),
|
||||||
crdb.NewColumn(OIDCSettingsColumnSequence, crdb.ColumnTypeInt64),
|
crdb.NewColumn(OIDCSettingsColumnSequence, crdb.ColumnTypeInt64),
|
||||||
crdb.NewColumn(OIDCSettingsColumnAccessTokenLifetime, crdb.ColumnTypeInt64),
|
crdb.NewColumn(OIDCSettingsColumnAccessTokenLifetime, crdb.ColumnTypeInt64),
|
||||||
crdb.NewColumn(ExternalLoginCheckLifetimeCol, crdb.ColumnTypeInt64),
|
|
||||||
crdb.NewColumn(OIDCSettingsColumnIdTokenLifetime, crdb.ColumnTypeInt64),
|
crdb.NewColumn(OIDCSettingsColumnIdTokenLifetime, crdb.ColumnTypeInt64),
|
||||||
crdb.NewColumn(OIDCSettingsColumnRefreshTokenIdleExpiration, crdb.ColumnTypeInt64),
|
crdb.NewColumn(OIDCSettingsColumnRefreshTokenIdleExpiration, crdb.ColumnTypeInt64),
|
||||||
crdb.NewColumn(OIDCSettingsColumnRefreshTokenExpiration, crdb.ColumnTypeInt64),
|
crdb.NewColumn(OIDCSettingsColumnRefreshTokenExpiration, crdb.ColumnTypeInt64),
|
||||||
@ -58,7 +56,7 @@ func newOIDCSettingsProjection(ctx context.Context, config crdb.StatementHandler
|
|||||||
func (p *oidcSettingsProjection) reducers() []handler.AggregateReducer {
|
func (p *oidcSettingsProjection) reducers() []handler.AggregateReducer {
|
||||||
return []handler.AggregateReducer{
|
return []handler.AggregateReducer{
|
||||||
{
|
{
|
||||||
Aggregate: project.AggregateType,
|
Aggregate: instance.AggregateType,
|
||||||
EventRedusers: []handler.EventReducer{
|
EventRedusers: []handler.EventReducer{
|
||||||
{
|
{
|
||||||
Event: instance.OIDCSettingsAddedEventType,
|
Event: instance.OIDCSettingsAddedEventType,
|
||||||
|
@ -14,7 +14,6 @@ const (
|
|||||||
oidcSettingsPrefix = "oidc.settings."
|
oidcSettingsPrefix = "oidc.settings."
|
||||||
OIDCSettingsAddedEventType = instanceEventTypePrefix + oidcSettingsPrefix + "added"
|
OIDCSettingsAddedEventType = instanceEventTypePrefix + oidcSettingsPrefix + "added"
|
||||||
OIDCSettingsChangedEventType = instanceEventTypePrefix + oidcSettingsPrefix + "changed"
|
OIDCSettingsChangedEventType = instanceEventTypePrefix + oidcSettingsPrefix + "changed"
|
||||||
OIDCSettingsRemovedEventType = instanceEventTypePrefix + oidcSettingsPrefix + "removed"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type OIDCSettingsAddedEvent struct {
|
type OIDCSettingsAddedEvent struct {
|
||||||
|
@ -402,6 +402,18 @@ service AdminService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add oidc settings (e.g token lifetimes, etc)
|
||||||
|
rpc AddOIDCSettings(AddOIDCSettingsRequest) returns (AddOIDCSettingsResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/settings/oidc";
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.v1.auth_option) = {
|
||||||
|
permission: "iam.write";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Update oidc settings (e.g token lifetimes, etc)
|
// Update oidc settings (e.g token lifetimes, etc)
|
||||||
rpc UpdateOIDCSettings(UpdateOIDCSettingsRequest) returns (UpdateOIDCSettingsResponse) {
|
rpc UpdateOIDCSettings(UpdateOIDCSettingsRequest) returns (UpdateOIDCSettingsResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
@ -2895,6 +2907,17 @@ message GetOIDCSettingsResponse {
|
|||||||
zitadel.settings.v1.OIDCSettings settings = 1;
|
zitadel.settings.v1.OIDCSettings settings = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message AddOIDCSettingsRequest {
|
||||||
|
google.protobuf.Duration access_token_lifetime = 1;
|
||||||
|
google.protobuf.Duration id_token_lifetime = 2;
|
||||||
|
google.protobuf.Duration refresh_token_idle_expiration = 3;
|
||||||
|
google.protobuf.Duration refresh_token_expiration = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AddOIDCSettingsResponse {
|
||||||
|
zitadel.v1.ObjectDetails details = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message UpdateOIDCSettingsRequest {
|
message UpdateOIDCSettingsRequest {
|
||||||
google.protobuf.Duration access_token_lifetime = 1;
|
google.protobuf.Duration access_token_lifetime = 1;
|
||||||
google.protobuf.Duration id_token_lifetime = 2;
|
google.protobuf.Duration id_token_lifetime = 2;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user