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:
Stefan Benz 2022-09-27 11:53:49 +01:00 committed by GitHub
parent b32c02a39b
commit 2957407b5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 654 additions and 93 deletions

File diff suppressed because one or more lines are too long

View File

@ -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",

View File

@ -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>

View File

@ -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);

View File

@ -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> {

View File

@ -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

View 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');
});
});

View File

@ -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/`,
}; };
}); });
} }

View File

@ -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);
}
});
}

View 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);
}

View File

@ -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 {

View File

@ -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(),

View File

@ -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
}

View File

@ -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) {

View File

@ -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

View File

@ -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
} }

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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 {

View File

@ -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;