mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 20:57:24 +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",
|
||||
"@angular/common/locales/de",
|
||||
"codemirror/mode/javascript/javascript",
|
||||
"codemirror/mode/xml/xml",
|
||||
"src/app/proto/generated/zitadel/admin_pb",
|
||||
"src/app/proto/generated/zitadel/org_pb",
|
||||
"src/app/proto/generated/zitadel/management_pb",
|
||||
|
@ -46,6 +46,7 @@
|
||||
color="primary"
|
||||
type="submit"
|
||||
mat-raised-button
|
||||
data-e2e="save-button"
|
||||
>
|
||||
{{ 'ACTIONS.SAVE' | translate }}
|
||||
</button>
|
||||
|
@ -2,7 +2,12 @@ import { Component, OnInit } from '@angular/core';
|
||||
import { AbstractControl, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
||||
import { Duration } from 'google-protobuf/google/protobuf/duration_pb';
|
||||
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 { AdminService } from 'src/app/services/admin.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 {
|
||||
public oidcSettings!: OIDCSettings.AsObject;
|
||||
private settingsSet: boolean = false;
|
||||
|
||||
public loading: boolean = false;
|
||||
public form!: UntypedFormGroup;
|
||||
@ -25,10 +31,10 @@ export class OIDCConfigurationComponent implements OnInit {
|
||||
private authService: GrpcAuthService,
|
||||
) {
|
||||
this.form = this.fb.group({
|
||||
accessTokenLifetime: [{ disabled: true, value: 12 }, [Validators.required]],
|
||||
idTokenLifetime: [{ disabled: true, value: 12 }, [Validators.required]],
|
||||
refreshTokenExpiration: [{ disabled: true, value: 30 }, [Validators.required]],
|
||||
refreshTokenIdleExpiration: [{ disabled: true, value: 90 }, [Validators.required]],
|
||||
accessTokenLifetime: [{ disabled: true }, [Validators.required]],
|
||||
idTokenLifetime: [{ disabled: true }, [Validators.required]],
|
||||
refreshTokenExpiration: [{ disabled: true }, [Validators.required]],
|
||||
refreshTokenIdleExpiration: [{ disabled: true }, [Validators.required]],
|
||||
});
|
||||
}
|
||||
|
||||
@ -50,26 +56,27 @@ export class OIDCConfigurationComponent implements OnInit {
|
||||
.then((oidcConfiguration) => {
|
||||
if (oidcConfiguration.settings) {
|
||||
this.oidcSettings = oidcConfiguration.settings;
|
||||
this.settingsSet = true;
|
||||
|
||||
this.accessTokenLifetime?.setValue(
|
||||
oidcConfiguration.settings.accessTokenLifetime?.seconds
|
||||
? oidcConfiguration.settings.accessTokenLifetime?.seconds / 60 / 60
|
||||
: 12,
|
||||
: 0,
|
||||
);
|
||||
this.idTokenLifetime?.setValue(
|
||||
oidcConfiguration.settings.idTokenLifetime?.seconds
|
||||
? oidcConfiguration.settings.idTokenLifetime?.seconds / 60 / 60
|
||||
: 12,
|
||||
: 0,
|
||||
);
|
||||
this.refreshTokenExpiration?.setValue(
|
||||
oidcConfiguration.settings.refreshTokenExpiration?.seconds
|
||||
? oidcConfiguration.settings.refreshTokenExpiration?.seconds / 60 / 60 / 24
|
||||
: 30,
|
||||
: 0,
|
||||
);
|
||||
this.refreshTokenIdleExpiration?.setValue(
|
||||
oidcConfiguration.settings.refreshTokenIdleExpiration?.seconds
|
||||
? 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 accessToken = new Duration().setSeconds((this.accessTokenLifetime?.value ?? 12) * 60 * 60);
|
||||
const accessToken = new Duration().setSeconds((this.accessTokenLifetime?.value ?? 0) * 60 * 60);
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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 {
|
||||
const prom = this.updateData();
|
||||
if (prom) {
|
||||
prom
|
||||
if (this.settingsSet) {
|
||||
this.updateData()
|
||||
.then(() => {
|
||||
this.toast.showInfo('SETTING.SMTP.SAVED', true);
|
||||
setTimeout(() => {
|
||||
this.fetchData();
|
||||
}, 2000);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
} else {
|
||||
this.addData()
|
||||
.then(() => {
|
||||
this.toast.showInfo('SETTING.SMTP.SAVED', true);
|
||||
this.loading = true;
|
||||
setTimeout(() => {
|
||||
this.fetchData();
|
||||
}, 2000);
|
||||
|
@ -180,6 +180,8 @@ import {
|
||||
UpdateLockoutPolicyResponse,
|
||||
UpdateLoginPolicyRequest,
|
||||
UpdateLoginPolicyResponse,
|
||||
AddOIDCSettingsRequest,
|
||||
AddOIDCSettingsResponse,
|
||||
UpdateOIDCSettingsRequest,
|
||||
UpdateOIDCSettingsResponse,
|
||||
UpdatePasswordAgePolicyRequest,
|
||||
@ -623,6 +625,10 @@ export class AdminService {
|
||||
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 */
|
||||
|
||||
public getLogNotificationProvider(): Promise<GetLogNotificationProviderResponse.AsObject> {
|
||||
|
@ -284,6 +284,18 @@ Get OIDC settings (e.g token lifetimes, etc.)
|
||||
GET: /settings/oidc
|
||||
|
||||
|
||||
### AddOIDCSettings
|
||||
|
||||
> **rpc** AddOIDCSettings([AddOIDCSettingsRequest](#addoidcsettingsrequest))
|
||||
[AddOIDCSettingsResponse](#addoidcsettingsresponse)
|
||||
|
||||
Add oidc settings (e.g token lifetimes, etc)
|
||||
|
||||
|
||||
|
||||
POST: /settings/oidc
|
||||
|
||||
|
||||
### UpdateOIDCSettings
|
||||
|
||||
> **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
|
||||
|
||||
|
||||
|
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 {
|
||||
authHeader: string;
|
||||
mgntBaseURL: string;
|
||||
adminBaseURL: string;
|
||||
}
|
||||
|
||||
export function apiAuth(): Cypress.Chainable<apiCallProperties> {
|
||||
@ -10,6 +11,7 @@ export function apiAuth(): Cypress.Chainable<apiCallProperties> {
|
||||
return <apiCallProperties>{
|
||||
authHeader: `Bearer ${token}`,
|
||||
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(
|
||||
api: apiCallProperties,
|
||||
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(
|
||||
trials: number,
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
result, err := s.command.ChangeOIDCSettings(ctx, UpdateOIDCConfigToConfig(req))
|
||||
if err != nil {
|
||||
|
@ -1,12 +1,13 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
|
||||
obj_grpc "github.com/zitadel/zitadel/internal/api/grpc/object"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
|
||||
settings_pb "github.com/zitadel/zitadel/pkg/grpc/settings"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
)
|
||||
|
||||
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 {
|
||||
return &domain.OIDCSettings{
|
||||
AccessTokenLifetime: req.AccessTokenLifetime.AsDuration(),
|
||||
|
@ -88,7 +88,13 @@ func (o *OPStorage) CreateAccessToken(ctx context.Context, req op.TokenRequest)
|
||||
applicationID = authReq.ApplicationID
|
||||
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 {
|
||||
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 {
|
||||
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(),
|
||||
refreshToken, req.GetAudience(), scopes, authMethodsReferences, o.defaultAccessTokenLifetime,
|
||||
o.defaultRefreshTokenIdleExpiration, o.defaultRefreshTokenExpiration, authTime) //PLANNED: lifetime from client
|
||||
refreshToken, req.GetAudience(), scopes, authMethodsReferences, accessTokenLifetime,
|
||||
refreshTokenIdleExpiration, refreshTokenExpiration, authTime) //PLANNED: lifetime from client
|
||||
if err != nil {
|
||||
if errors.IsErrorInvalidArgument(err) {
|
||||
err = oidc.ErrInvalidGrant().WithParent(err)
|
||||
@ -248,3 +260,15 @@ func setContextUserSystem(ctx context.Context) context.Context {
|
||||
}
|
||||
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 {
|
||||
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) {
|
||||
|
@ -104,6 +104,12 @@ type InstanceSetup struct {
|
||||
EmailTemplate []byte
|
||||
MessageTexts []*domain.CustomMessageText
|
||||
SMTPConfiguration *smtp.EmailConfig
|
||||
OIDCSettings *struct {
|
||||
AccessTokenLifetime time.Duration
|
||||
IdTokenLifetime time.Duration
|
||||
RefreshTokenIdleExpiration time.Duration
|
||||
RefreshTokenExpiration time.Duration
|
||||
}
|
||||
}
|
||||
|
||||
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...)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
|
@ -2,78 +2,131 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/command/preparation"
|
||||
"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"
|
||||
)
|
||||
|
||||
func (c *Commands) AddOIDCSettings(ctx context.Context, settings *domain.OIDCSettings) (*domain.ObjectDetails, error) {
|
||||
oidcSettingWriteModel, err := c.getOIDCSettings(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if oidcSettingWriteModel.State.Exists() {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(oidcSettingWriteModel, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&oidcSettingWriteModel.WriteModel), nil
|
||||
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")
|
||||
}
|
||||
|
||||
func (c *Commands) ChangeOIDCSettings(ctx context.Context, settings *domain.OIDCSettings) (*domain.ObjectDetails, error) {
|
||||
oidcSettingWriteModel, err := c.getOIDCSettings(ctx)
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
writeModel, err := c.getOIDCSettingsWriteModel(ctx, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !oidcSettingWriteModel.State.Exists() {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-8snEr", "Errors.OIDCSettings.NotFound")
|
||||
if writeModel.State == domain.OIDCSettingsStateActive {
|
||||
return nil, errors.ThrowAlreadyExists(nil, "INST-0aaj1o", "Errors.OIDCSettings.AlreadyExists")
|
||||
}
|
||||
instanceAgg := InstanceAggregateFromWriteModel(&oidcSettingWriteModel.WriteModel)
|
||||
|
||||
changedEvent, hasChanged, err := oidcSettingWriteModel.NewChangedEvent(
|
||||
return []eventstore.Command{
|
||||
instance.NewOIDCSettingsAddedEvent(
|
||||
ctx,
|
||||
instanceAgg,
|
||||
settings.AccessTokenLifetime,
|
||||
settings.IdTokenLifetime,
|
||||
settings.RefreshTokenIdleExpiration,
|
||||
settings.RefreshTokenExpiration)
|
||||
&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, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-398uF", "Errors.NoChangesFound")
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "COMMAND-0pk2nu", "Errors.NoChangesFound")
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, changedEvent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return []eventstore.Command{
|
||||
changedEvent,
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
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) AddOIDCSettings(ctx context.Context, settings *domain.OIDCSettings) (*domain.ObjectDetails, error) {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
events, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &domain.ObjectDetails{
|
||||
Sequence: events[len(events)-1].Sequence(),
|
||||
EventDate: events[len(events)-1].CreationDate(),
|
||||
ResourceOwner: events[len(events)-1].Aggregate().InstanceID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Commands) ChangeOIDCSettings(ctx context.Context, settings *domain.OIDCSettings) (*domain.ObjectDetails, error) {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
events, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &domain.ObjectDetails{
|
||||
Sequence: events[len(events)-1].Sequence(),
|
||||
EventDate: events[len(events)-1].CreationDate(),
|
||||
ResourceOwner: events[len(events)-1].Aggregate().InstanceID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Commands) getOIDCSettingsWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer) (_ *InstanceOIDCSettingsWriteModel, err error) {
|
||||
writeModel := NewInstanceOIDCSettingsWriteModel(ctx)
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
||||
events, err := filter(ctx, writeModel.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(events) == 0 {
|
||||
return writeModel, nil
|
||||
}
|
||||
writeModel.AppendEvents(events...)
|
||||
err = writeModel.Reduce()
|
||||
return writeModel, err
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
@ -34,7 +35,7 @@ func TestCommandSide_AddOIDCConfig(t *testing.T) {
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "oidc config, error already exists",
|
||||
name: "oidc settings, error already exists",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
@ -52,7 +53,7 @@ func TestCommandSide_AddOIDCConfig(t *testing.T) {
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||
oidcConfig: &domain.OIDCSettings{
|
||||
AccessTokenLifetime: 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{
|
||||
eventstore: eventstoreExpect(
|
||||
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 {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@ -141,7 +222,7 @@ func TestCommandSide_ChangeOIDCConfig(t *testing.T) {
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "oidc config not existing, not found error",
|
||||
name: "oidc settings not existing, not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
@ -150,11 +231,97 @@ func TestCommandSide_ChangeOIDCConfig(t *testing.T) {
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
oidcConfig: &domain.OIDCSettings{
|
||||
AccessTokenLifetime: 1 * time.Hour,
|
||||
IdTokenLifetime: 1 * time.Hour,
|
||||
RefreshTokenIdleExpiration: 1 * time.Hour,
|
||||
RefreshTokenExpiration: 1 * time.Hour,
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
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",
|
||||
fields: fields{
|
||||
@ -175,7 +342,7 @@ func TestCommandSide_ChangeOIDCConfig(t *testing.T) {
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||
oidcConfig: &domain.OIDCSettings{
|
||||
AccessTokenLifetime: 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{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
@ -206,8 +373,9 @@ func TestCommandSide_ChangeOIDCConfig(t *testing.T) {
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
newOIDCConfigChangedEvent(context.Background(),
|
||||
eventFromEventPusherWithInstanceID("INSTANCE",
|
||||
newOIDCConfigChangedEvent(
|
||||
context.Background(),
|
||||
time.Hour*2,
|
||||
time.Hour*2,
|
||||
time.Hour*2,
|
||||
@ -218,7 +386,7 @@ func TestCommandSide_ChangeOIDCConfig(t *testing.T) {
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||
oidcConfig: &domain.OIDCSettings{
|
||||
AccessTokenLifetime: 2 * time.Hour,
|
||||
IdTokenLifetime: 2 * time.Hour,
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"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/notification/channels/smtp"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
@ -58,7 +57,7 @@ func (c *Commands) ChangeSMTPConfigPassword(ctx context.Context, password string
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
if password != "" {
|
||||
@ -180,7 +179,7 @@ func (c *Commands) prepareChangeSMTPConfig(a *instance.Aggregate, from, name, ho
|
||||
return nil, err
|
||||
}
|
||||
if !hasChanged {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-m0o3f", "Errors.NoChangesFound")
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "COMMAND-m0o3f", "Errors.NoChangesFound")
|
||||
}
|
||||
return []eventstore.Command{
|
||||
changedEvent,
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/eventstore/handler"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/handler/crdb"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
"github.com/zitadel/zitadel/internal/repository/project"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -43,7 +42,6 @@ func newOIDCSettingsProjection(ctx context.Context, config crdb.StatementHandler
|
||||
crdb.NewColumn(OIDCSettingsColumnInstanceID, crdb.ColumnTypeText),
|
||||
crdb.NewColumn(OIDCSettingsColumnSequence, crdb.ColumnTypeInt64),
|
||||
crdb.NewColumn(OIDCSettingsColumnAccessTokenLifetime, crdb.ColumnTypeInt64),
|
||||
crdb.NewColumn(ExternalLoginCheckLifetimeCol, crdb.ColumnTypeInt64),
|
||||
crdb.NewColumn(OIDCSettingsColumnIdTokenLifetime, crdb.ColumnTypeInt64),
|
||||
crdb.NewColumn(OIDCSettingsColumnRefreshTokenIdleExpiration, 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 {
|
||||
return []handler.AggregateReducer{
|
||||
{
|
||||
Aggregate: project.AggregateType,
|
||||
Aggregate: instance.AggregateType,
|
||||
EventRedusers: []handler.EventReducer{
|
||||
{
|
||||
Event: instance.OIDCSettingsAddedEventType,
|
||||
|
@ -14,7 +14,6 @@ const (
|
||||
oidcSettingsPrefix = "oidc.settings."
|
||||
OIDCSettingsAddedEventType = instanceEventTypePrefix + oidcSettingsPrefix + "added"
|
||||
OIDCSettingsChangedEventType = instanceEventTypePrefix + oidcSettingsPrefix + "changed"
|
||||
OIDCSettingsRemovedEventType = instanceEventTypePrefix + oidcSettingsPrefix + "removed"
|
||||
)
|
||||
|
||||
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)
|
||||
rpc UpdateOIDCSettings(UpdateOIDCSettingsRequest) returns (UpdateOIDCSettingsResponse) {
|
||||
option (google.api.http) = {
|
||||
@ -2895,6 +2907,17 @@ message GetOIDCSettingsResponse {
|
||||
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 {
|
||||
google.protobuf.Duration access_token_lifetime = 1;
|
||||
google.protobuf.Duration id_token_lifetime = 2;
|
||||
|
Loading…
x
Reference in New Issue
Block a user