From 2e8fa82261509af0202f0794af3bf9d7ffc10848 Mon Sep 17 00:00:00 2001 From: Livio Amstutz Date: Wed, 19 May 2021 09:17:38 +0200 Subject: [PATCH] feat: add additional origins on applications (#1691) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add additional origins on applications * app additional redirects * chore(deps-dev): bump @angular/cli from 11.2.8 to 11.2.11 in /console (#1706) * fix: show org with regex (#1688) * fix: flag mapping (#1699) * chore(deps-dev): bump @angular/cli from 11.2.8 to 11.2.11 in /console Bumps [@angular/cli](https://github.com/angular/angular-cli) from 11.2.8 to 11.2.11. - [Release notes](https://github.com/angular/angular-cli/releases) - [Commits](https://github.com/angular/angular-cli/compare/v11.2.8...v11.2.11) Signed-off-by: dependabot[bot] Co-authored-by: Max Peintner Co-authored-by: Silvan Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump stylelint from 13.10.0 to 13.13.1 in /console (#1703) * fix: show org with regex (#1688) * fix: flag mapping (#1699) * chore(deps-dev): bump stylelint from 13.10.0 to 13.13.1 in /console Bumps [stylelint](https://github.com/stylelint/stylelint) from 13.10.0 to 13.13.1. - [Release notes](https://github.com/stylelint/stylelint/releases) - [Changelog](https://github.com/stylelint/stylelint/blob/master/CHANGELOG.md) - [Commits](https://github.com/stylelint/stylelint/compare/13.10.0...13.13.1) Signed-off-by: dependabot[bot] Co-authored-by: Max Peintner Co-authored-by: Silvan Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @types/node from 14.14.37 to 15.0.1 in /console (#1702) * fix: show org with regex (#1688) * fix: flag mapping (#1699) * chore(deps-dev): bump @types/node from 14.14.37 to 15.0.1 in /console Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 14.14.37 to 15.0.1. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Signed-off-by: dependabot[bot] Co-authored-by: Max Peintner Co-authored-by: Silvan Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump ts-protoc-gen from 0.14.0 to 0.15.0 in /console (#1701) * fix: show org with regex (#1688) * fix: flag mapping (#1699) * chore(deps): bump ts-protoc-gen from 0.14.0 to 0.15.0 in /console Bumps [ts-protoc-gen](https://github.com/improbable-eng/ts-protoc-gen) from 0.14.0 to 0.15.0. - [Release notes](https://github.com/improbable-eng/ts-protoc-gen/releases) - [Changelog](https://github.com/improbable-eng/ts-protoc-gen/blob/master/CHANGELOG.md) - [Commits](https://github.com/improbable-eng/ts-protoc-gen/compare/0.14.0...0.15.0) Signed-off-by: dependabot[bot] Co-authored-by: Max Peintner Co-authored-by: Silvan Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @types/jasmine from 3.6.9 to 3.6.10 in /console (#1682) Bumps [@types/jasmine](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jasmine) from 3.6.9 to 3.6.10. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jasmine) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump @types/google-protobuf in /console (#1681) Bumps [@types/google-protobuf](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/google-protobuf) from 3.7.4 to 3.15.2. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/google-protobuf) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump grpc from 1.24.5 to 1.24.7 in /console (#1666) Bumps [grpc](https://github.com/grpc/grpc-node) from 1.24.5 to 1.24.7. - [Release notes](https://github.com/grpc/grpc-node/releases) - [Commits](https://github.com/grpc/grpc-node/compare/grpc@1.24.5...grpc@1.24.7) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * lock * chore(deps-dev): bump @angular/language-service from 11.2.9 to 11.2.12 in /console (#1704) * fix: show org with regex (#1688) * fix: flag mapping (#1699) * chore(deps-dev): bump @angular/language-service in /console Bumps [@angular/language-service](https://github.com/angular/angular/tree/HEAD/packages/language-service) from 11.2.9 to 11.2.12. - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/master/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/11.2.12/packages/language-service) Signed-off-by: dependabot[bot] Co-authored-by: Max Peintner Co-authored-by: Silvan Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * package lock * downgrade grpc * downgrade protobuf types * revert npm packs 🥸 Co-authored-by: Max Peintner Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Silvan --- console/package.json | 2 +- .../additional-origins.component.html | 28 +++++++++ .../additional-origins.component.scss | 62 +++++++++++++++++++ .../additional-origins.component.spec.ts | 25 ++++++++ .../additional-origins.component.ts | 56 +++++++++++++++++ .../apps/app-detail/app-detail.component.html | 22 ++++++- .../apps/app-detail/app-detail.component.scss | 20 ++++++ .../apps/app-detail/app-detail.component.ts | 7 +++ .../app/pages/projects/apps/apps.module.ts | 13 ++-- .../pipes/origin-pipe/origin-pipe.module.ts | 18 ++++++ .../src/app/pipes/origin-pipe/origin.pipe.ts | 10 +++ console/src/assets/i18n/de.json | 6 ++ console/src/assets/i18n/en.json | 6 ++ docs/docs/apis/proto/app.md | 2 + docs/docs/apis/proto/management.md | 2 + .../project_application_converter.go | 2 + internal/api/grpc/project/application.go | 2 + internal/api/http/origin.go | 9 +++ internal/command/project_application_oidc.go | 6 +- .../command/project_application_oidc_model.go | 9 +++ .../command/project_application_oidc_test.go | 18 ++++-- internal/command/project_converter.go | 1 + internal/domain/application_oidc.go | 13 +++- internal/domain/application_oidc_test.go | 17 ++++- internal/project/model/application_view.go | 1 + .../repository/view/model/application.go | 7 +++ internal/repository/project/oidc_config.go | 12 +++- .../cockroach/V1.43__additional_origins.sql | 3 + proto/zitadel/app.proto | 16 ++++- proto/zitadel/management.proto | 2 + 30 files changed, 380 insertions(+), 17 deletions(-) create mode 100644 console/src/app/pages/projects/apps/additional-origins/additional-origins.component.html create mode 100644 console/src/app/pages/projects/apps/additional-origins/additional-origins.component.scss create mode 100644 console/src/app/pages/projects/apps/additional-origins/additional-origins.component.spec.ts create mode 100644 console/src/app/pages/projects/apps/additional-origins/additional-origins.component.ts create mode 100644 console/src/app/pipes/origin-pipe/origin-pipe.module.ts create mode 100644 console/src/app/pipes/origin-pipe/origin.pipe.ts create mode 100644 migrations/cockroach/V1.43__additional_origins.sql diff --git a/console/package.json b/console/package.json index 260b9aee74..947de2dda7 100644 --- a/console/package.json +++ b/console/package.json @@ -69,4 +69,4 @@ "tslint": "~6.1.3", "typescript": "^4.0.7" } -} +} \ No newline at end of file diff --git a/console/src/app/pages/projects/apps/additional-origins/additional-origins.component.html b/console/src/app/pages/projects/apps/additional-origins/additional-origins.component.html new file mode 100644 index 0000000000..448b4f4eb8 --- /dev/null +++ b/console/src/app/pages/projects/apps/additional-origins/additional-origins.component.html @@ -0,0 +1,28 @@ +
+ + {{ title }} + + + + +
+ +
+
+ {{uri}} + + + + + +
+
+ +

+ {{'APP.OIDC.REDIRECTNOTVALID' | translate}}

\ No newline at end of file diff --git a/console/src/app/pages/projects/apps/additional-origins/additional-origins.component.scss b/console/src/app/pages/projects/apps/additional-origins/additional-origins.component.scss new file mode 100644 index 0000000000..fa3bade765 --- /dev/null +++ b/console/src/app/pages/projects/apps/additional-origins/additional-origins.component.scss @@ -0,0 +1,62 @@ +.form { + display: flex; + align-items: flex-end; + min-width: 320px; + + .formfield { + flex: 1; + } + + button { + margin-bottom: 14px; + margin-right: -.5rem; + } +} + +.uri-list { + margin: 0 .5rem; + width: 100%; + + .uri-line { + display: flex; + align-items: center; + + .uri { + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + font-size: 14px; + } + + .fill-space { + flex: 1; + } + + i.green { + font-size: 1rem; + line-height: 35px; + height: 30px; + } + + i.red { + font-size: 1.2rem; + } + + .icon-button { + height: 30px; + line-height: 30px; + + .icon { + font-size: 1rem !important; + } + + &:not(:hover) { + color: var(--grey); + } + } + } +} + +.error { + font-size: 13px; + color: #f44336; + margin: 0 .5rem 1.5rem .5rem; +} diff --git a/console/src/app/pages/projects/apps/additional-origins/additional-origins.component.spec.ts b/console/src/app/pages/projects/apps/additional-origins/additional-origins.component.spec.ts new file mode 100644 index 0000000000..ae36ab6fdb --- /dev/null +++ b/console/src/app/pages/projects/apps/additional-origins/additional-origins.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AdditionalOriginsComponent } from './additional-origins.component'; + +describe('AdditionalOriginsComponent', () => { + let component: AdditionalOriginsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [AdditionalOriginsComponent], + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AdditionalOriginsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/console/src/app/pages/projects/apps/additional-origins/additional-origins.component.ts b/console/src/app/pages/projects/apps/additional-origins/additional-origins.component.ts new file mode 100644 index 0000000000..8fcd38c992 --- /dev/null +++ b/console/src/app/pages/projects/apps/additional-origins/additional-origins.component.ts @@ -0,0 +1,56 @@ +import { Component, EventEmitter, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { FormControl } from '@angular/forms'; +import { Observable, Subscription } from 'rxjs'; + +@Component({ + selector: 'cnsl-additional-origins', + templateUrl: './additional-origins.component.html', + styleUrls: ['./additional-origins.component.scss'], +}) +export class AdditionalOriginsComponent implements OnInit, OnDestroy { + @Input() title: string = ''; + @Input() canWrite: boolean = false; + @Input() public urisList: string[] = []; + @Input() public redirectControl: FormControl = new FormControl({ value: '', disabled: true }); + @Input() public changedUris: EventEmitter = new EventEmitter(); + @Input() public getValues: Observable = new Observable(); + public placeholder: string = ' "://" [ ":" ]'; + + @ViewChild('originInput') input!: any; + private sub: Subscription = new Subscription(); + + constructor() { } + + ngOnInit(): void { + if (this.canWrite) { + this.redirectControl.enable(); + } + + this.sub = this.getValues.subscribe(() => { + this.add(this.input.nativeElement); + }); + } + + ngOnDestroy(): void { + this.sub.unsubscribe(); + } + + public add(input: any): void { + if (this.redirectControl.valid) { + if (input.value !== '' && input.value !== ' ' && input.value !== '/') { + this.urisList.push(input.value); + } + if (input) { + input.value = ''; + } + } + } + + public remove(redirect: any): void { + const index = this.urisList.indexOf(redirect); + + if (index >= 0) { + this.urisList.splice(index, 1); + } + } +} diff --git a/console/src/app/pages/projects/apps/app-detail/app-detail.component.html b/console/src/app/pages/projects/apps/app-detail/app-detail.component.html index af0556d90d..a6251c47a3 100644 --- a/console/src/app/pages/projects/apps/app-detail/app-detail.component.html +++ b/console/src/app/pages/projects/apps/app-detail/app-detail.component.html @@ -118,7 +118,6 @@ {{'APP.OIDC.REDIRECTDESCRIPTIONWEB' | translate}} -
+ +
+ +
+

{{'APP.ADDITIONALORIGINS' | translate}} + +

+ +

{{'APP.ADDITIONALORIGINSDESC' | translate}}

+ + +
+
+ +
duration.lte.nanos: 0
duration.gte.seconds: 0
duration.gte.nanos: 0
| +| additional_origins | repeated string | - | | @@ -5194,6 +5195,7 @@ This is an empty request | id_token_role_assertion | bool | - | | | id_token_userinfo_assertion | bool | - | | | clock_skew | google.protobuf.Duration | - | duration.lte.seconds: 5
duration.lte.nanos: 0
duration.gte.seconds: 0
duration.gte.nanos: 0
| +| additional_origins | repeated string | - | | diff --git a/internal/api/grpc/management/project_application_converter.go b/internal/api/grpc/management/project_application_converter.go index e38ee5157f..516bae58ee 100644 --- a/internal/api/grpc/management/project_application_converter.go +++ b/internal/api/grpc/management/project_application_converter.go @@ -52,6 +52,7 @@ func AddOIDCAppRequestToDomain(req *mgmt_pb.AddOIDCAppRequest) *domain.OIDCApp { IDTokenRoleAssertion: req.IdTokenRoleAssertion, IDTokenUserinfoAssertion: req.IdTokenUserinfoAssertion, ClockSkew: req.ClockSkew.AsDuration(), + AdditionalOrigins: req.AdditionalOrigins, } } @@ -90,6 +91,7 @@ func UpdateOIDCAppConfigRequestToDomain(app *mgmt_pb.UpdateOIDCAppConfigRequest) IDTokenRoleAssertion: app.IdTokenRoleAssertion, IDTokenUserinfoAssertion: app.IdTokenUserinfoAssertion, ClockSkew: app.ClockSkew.AsDuration(), + AdditionalOrigins: app.AdditionalOrigins, } } diff --git a/internal/api/grpc/project/application.go b/internal/api/grpc/project/application.go index 07f1b60f00..efcf57689b 100644 --- a/internal/api/grpc/project/application.go +++ b/internal/api/grpc/project/application.go @@ -55,6 +55,8 @@ func AppOIDCConfigToPb(app *proj_model.ApplicationView) *app_pb.App_OidcConfig { IdTokenRoleAssertion: app.IDTokenRoleAssertion, IdTokenUserinfoAssertion: app.IDTokenUserinfoAssertion, ClockSkew: durationpb.New(app.ClockSkew), + AdditionalOrigins: app.AdditionalOrigins, + AllowedOrigins: app.OriginAllowList, }, } } diff --git a/internal/api/http/origin.go b/internal/api/http/origin.go index 44f2227900..62c6967bd0 100644 --- a/internal/api/http/origin.go +++ b/internal/api/http/origin.go @@ -21,3 +21,12 @@ func IsOriginAllowed(allowList []string, origin string) bool { } return false } + +//IsOrigin checks if provided string is an origin (scheme://hostname[:port]) without path, query or fragment +func IsOrigin(rawOrigin string) bool { + parsedUrl, err := url.Parse(rawOrigin) + if err != nil { + return false + } + return parsedUrl.Scheme != "" && parsedUrl.Host != "" && parsedUrl.Path == "" && len(parsedUrl.Query()) == 0 && parsedUrl.Fragment == "" +} diff --git a/internal/command/project_application_oidc.go b/internal/command/project_application_oidc.go index 540c69eea0..1453401715 100644 --- a/internal/command/project_application_oidc.go +++ b/internal/command/project_application_oidc.go @@ -81,7 +81,8 @@ func (c *Commands) addOIDCApplication(ctx context.Context, projectAgg *eventstor oidcApp.AccessTokenRoleAssertion, oidcApp.IDTokenRoleAssertion, oidcApp.IDTokenUserinfoAssertion, - oidcApp.ClockSkew)) + oidcApp.ClockSkew, + oidcApp.AdditionalOrigins)) return events, stringPw, nil } @@ -115,7 +116,8 @@ func (c *Commands) ChangeOIDCApplication(ctx context.Context, oidc *domain.OIDCA oidc.AccessTokenRoleAssertion, oidc.IDTokenRoleAssertion, oidc.IDTokenUserinfoAssertion, - oidc.ClockSkew) + oidc.ClockSkew, + oidc.AdditionalOrigins) if err != nil { return nil, err } diff --git a/internal/command/project_application_oidc_model.go b/internal/command/project_application_oidc_model.go index 2a39778c98..7b9ac23df2 100644 --- a/internal/command/project_application_oidc_model.go +++ b/internal/command/project_application_oidc_model.go @@ -33,6 +33,7 @@ type OIDCApplicationWriteModel struct { IDTokenUserinfoAssertion bool ClockSkew time.Duration State domain.AppState + AdditionalOrigins []string } func NewOIDCApplicationWriteModelWithAppID(projectID, appID, resourceOwner string) *OIDCApplicationWriteModel { @@ -151,6 +152,7 @@ func (wm *OIDCApplicationWriteModel) appendAddOIDCEvent(e *project.OIDCConfigAdd wm.IDTokenRoleAssertion = e.IDTokenRoleAssertion wm.IDTokenUserinfoAssertion = e.IDTokenUserinfoAssertion wm.ClockSkew = e.ClockSkew + wm.AdditionalOrigins = e.AdditionalOrigins } func (wm *OIDCApplicationWriteModel) appendChangeOIDCEvent(e *project.OIDCConfigChangedEvent) { @@ -193,6 +195,9 @@ func (wm *OIDCApplicationWriteModel) appendChangeOIDCEvent(e *project.OIDCConfig if e.ClockSkew != nil { wm.ClockSkew = *e.ClockSkew } + if e.AdditionalOrigins != nil { + wm.AdditionalOrigins = *e.AdditionalOrigins + } } func (wm *OIDCApplicationWriteModel) Query() *eventstore.SearchQueryBuilder { @@ -229,6 +234,7 @@ func (wm *OIDCApplicationWriteModel) NewChangedEvent( idTokenRoleAssertion, idTokenUserinfoAssertion bool, clockSkew time.Duration, + additionalOrigins []string, ) (*project.OIDCConfigChangedEvent, bool, error) { changes := make([]project.OIDCConfigChanges, 0) var err error @@ -272,6 +278,9 @@ func (wm *OIDCApplicationWriteModel) NewChangedEvent( if wm.ClockSkew != clockSkew { changes = append(changes, project.ChangeClockSkew(clockSkew)) } + if !reflect.DeepEqual(wm.AdditionalOrigins, additionalOrigins) { + changes = append(changes, project.ChangeAdditionalOrigins(additionalOrigins)) + } if len(changes) == 0 { return nil, false, nil } diff --git a/internal/command/project_application_oidc_test.go b/internal/command/project_application_oidc_test.go index f4079603b6..b590cf6cb4 100644 --- a/internal/command/project_application_oidc_test.go +++ b/internal/command/project_application_oidc_test.go @@ -151,7 +151,8 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { true, true, true, - time.Second*1), + time.Second*1, + []string{"https://sub.test.ch"}), ), }, uniqueConstraintsFromEventConstraint(project.NewAddApplicationUniqueConstraint("app", "project1")), @@ -180,6 +181,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { IDTokenRoleAssertion: true, IDTokenUserinfoAssertion: true, ClockSkew: time.Second * 1, + AdditionalOrigins: []string{"https://sub.test.ch"}, }, resourceOwner: "org1", }, @@ -206,6 +208,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { IDTokenRoleAssertion: true, IDTokenUserinfoAssertion: true, ClockSkew: time.Second * 1, + AdditionalOrigins: []string{"https://sub.test.ch"}, State: domain.AppStateActive, Compliance: &domain.Compliance{}, }, @@ -382,7 +385,8 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) { true, true, true, - time.Second*1), + time.Second*1, + []string{"https://sub.test.ch"}), ), ), ), @@ -408,6 +412,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) { IDTokenRoleAssertion: true, IDTokenUserinfoAssertion: true, ClockSkew: time.Second * 1, + AdditionalOrigins: []string{"https://sub.test.ch"}, }, resourceOwner: "org1", }, @@ -451,7 +456,8 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) { true, true, true, - time.Second*1), + time.Second*1, + []string{"https://sub.test.ch"}), ), ), expectPush( @@ -487,6 +493,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) { IDTokenRoleAssertion: false, IDTokenUserinfoAssertion: false, ClockSkew: time.Second * 2, + AdditionalOrigins: []string{"https://sub.test.ch"}, }, resourceOwner: "org1", }, @@ -512,6 +519,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) { IDTokenRoleAssertion: false, IDTokenUserinfoAssertion: false, ClockSkew: time.Second * 2, + AdditionalOrigins: []string{"https://sub.test.ch"}, Compliance: &domain.Compliance{}, State: domain.AppStateActive, }, @@ -645,7 +653,8 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) { true, true, true, - time.Second*1), + time.Second*1, + []string{"https://sub.test.ch"}), ), ), expectPush( @@ -695,6 +704,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) { IDTokenRoleAssertion: true, IDTokenUserinfoAssertion: true, ClockSkew: time.Second * 1, + AdditionalOrigins: []string{"https://sub.test.ch"}, State: domain.AppStateActive, }, }, diff --git a/internal/command/project_converter.go b/internal/command/project_converter.go index 220911c501..2aee9bb5ce 100644 --- a/internal/command/project_converter.go +++ b/internal/command/project_converter.go @@ -51,6 +51,7 @@ func oidcWriteModelToOIDCConfig(writeModel *OIDCApplicationWriteModel) *domain.O IDTokenRoleAssertion: writeModel.IDTokenRoleAssertion, IDTokenUserinfoAssertion: writeModel.IDTokenUserinfoAssertion, ClockSkew: writeModel.ClockSkew, + AdditionalOrigins: writeModel.AdditionalOrigins, } } diff --git a/internal/domain/application_oidc.go b/internal/domain/application_oidc.go index dc3b144709..fd67b9c6f6 100644 --- a/internal/domain/application_oidc.go +++ b/internal/domain/application_oidc.go @@ -4,6 +4,7 @@ import ( "strings" "time" + http_util "github.com/caos/zitadel/internal/api/http" "github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/eventstore/v1/models" ) @@ -43,6 +44,7 @@ type OIDCApp struct { IDTokenRoleAssertion bool IDTokenUserinfoAssertion bool ClockSkew time.Duration + AdditionalOrigins []string State AppState } @@ -119,7 +121,7 @@ const ( ) func (a *OIDCApp) IsValid() bool { - if a.ClockSkew > time.Second*5 || a.ClockSkew < time.Second*0 { + if a.ClockSkew > time.Second*5 || a.ClockSkew < time.Second*0 || !a.OriginsValid() { return false } grantTypes := a.getRequiredGrantTypes() @@ -135,6 +137,15 @@ func (a *OIDCApp) IsValid() bool { return true } +func (a *OIDCApp) OriginsValid() bool { + for _, origin := range a.AdditionalOrigins { + if !http_util.IsOrigin(origin) { + return false + } + } + return true +} + func (a *OIDCApp) getRequiredGrantTypes() []OIDCGrantType { grantTypes := make([]OIDCGrantType, 0) implicit := false diff --git a/internal/domain/application_oidc_test.go b/internal/domain/application_oidc_test.go index b2897764b2..63aee9159a 100644 --- a/internal/domain/application_oidc_test.go +++ b/internal/domain/application_oidc_test.go @@ -1,9 +1,10 @@ package domain import ( - "github.com/caos/zitadel/internal/eventstore/v1/models" "testing" "time" + + "github.com/caos/zitadel/internal/eventstore/v1/models" ) func TestApplicationValid(t *testing.T) { @@ -160,6 +161,20 @@ func TestApplicationValid(t *testing.T) { }, result: true, }, + { + name: "invalid oidc application: invalid origin", + args: args{ + app: &OIDCApp{ + ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"}, + AppID: "AppID", + AppName: "Name", + ResponseTypes: []OIDCResponseType{OIDCResponseTypeCode, OIDCResponseTypeIDToken, OIDCResponseTypeIDTokenToken}, + GrantTypes: []OIDCGrantType{OIDCGrantTypeAuthorizationCode, OIDCGrantTypeImplicit}, + AdditionalOrigins: []string{"https://test.com/test"}, + }, + }, + result: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/project/model/application_view.go b/internal/project/model/application_view.go index 37db1ebc2e..20bfc20624 100644 --- a/internal/project/model/application_view.go +++ b/internal/project/model/application_view.go @@ -30,6 +30,7 @@ type ApplicationView struct { ComplianceProblems []string DevMode bool OriginAllowList []string + AdditionalOrigins []string AccessTokenType OIDCTokenType IDTokenRoleAssertion bool AccessTokenRoleAssertion bool diff --git a/internal/project/repository/view/model/application.go b/internal/project/repository/view/model/application.go index 5a5a3a836e..4440c10ec1 100644 --- a/internal/project/repository/view/model/application.go +++ b/internal/project/repository/view/model/application.go @@ -45,6 +45,7 @@ type ApplicationView struct { ComplianceProblems pq.StringArray `json:"-" gorm:"column:compliance_problems"` DevMode bool `json:"devMode" gorm:"column:dev_mode"` OriginAllowList pq.StringArray `json:"-" gorm:"column:origin_allow_list"` + AdditionalOrigins pq.StringArray `json:"additionalOrigins" gorm:"column:additional_origins"` AccessTokenType int32 `json:"accessTokenType" gorm:"column:access_token_type"` AccessTokenRoleAssertion bool `json:"accessTokenRoleAssertion" gorm:"column:access_token_role_assertion"` IDTokenRoleAssertion bool `json:"idTokenRoleAssertion" gorm:"column:id_token_role_assertion"` @@ -79,6 +80,7 @@ func ApplicationViewToModel(app *ApplicationView) *model.ApplicationView { ComplianceProblems: app.ComplianceProblems, DevMode: app.DevMode, OriginAllowList: app.OriginAllowList, + AdditionalOrigins: app.AdditionalOrigins, AccessTokenType: model.OIDCTokenType(app.AccessTokenType), AccessTokenRoleAssertion: app.AccessTokenRoleAssertion, IDTokenRoleAssertion: app.IDTokenRoleAssertion, @@ -213,6 +215,11 @@ func (a *ApplicationView) setOriginAllowList() error { allowList = append(allowList, origin) } } + for _, origin := range a.AdditionalOrigins { + if !http_util.IsOriginAllowed(allowList, origin) { + allowList = append(allowList, origin) + } + } a.OriginAllowList = allowList return nil } diff --git a/internal/repository/project/oidc_config.go b/internal/repository/project/oidc_config.go index 2c2996799b..acf67ddcbe 100644 --- a/internal/repository/project/oidc_config.go +++ b/internal/repository/project/oidc_config.go @@ -3,12 +3,12 @@ package project import ( "context" "encoding/json" - "github.com/caos/zitadel/internal/eventstore" "time" "github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/eventstore/repository" ) @@ -39,6 +39,7 @@ type OIDCConfigAddedEvent struct { IDTokenRoleAssertion bool `json:"idTokenRoleAssertion,omitempty"` IDTokenUserinfoAssertion bool `json:"idTokenUserinfoAssertion,omitempty"` ClockSkew time.Duration `json:"clockSkew,omitempty"` + AdditionalOrigins []string `json:"additionalOrigins,omitempty"` } func (e *OIDCConfigAddedEvent) Data() interface{} { @@ -68,6 +69,7 @@ func NewOIDCConfigAddedEvent( idTokenRoleAssertion bool, idTokenUserinfoAssertion bool, clockSkew time.Duration, + additionalOrigins []string, ) *OIDCConfigAddedEvent { return &OIDCConfigAddedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -91,6 +93,7 @@ func NewOIDCConfigAddedEvent( IDTokenRoleAssertion: idTokenRoleAssertion, IDTokenUserinfoAssertion: idTokenUserinfoAssertion, ClockSkew: clockSkew, + AdditionalOrigins: additionalOrigins, } } @@ -124,6 +127,7 @@ type OIDCConfigChangedEvent struct { IDTokenRoleAssertion *bool `json:"idTokenRoleAssertion,omitempty"` IDTokenUserinfoAssertion *bool `json:"idTokenUserinfoAssertion,omitempty"` ClockSkew *time.Duration `json:"clockSkew,omitempty"` + AdditionalOrigins *[]string `json:"additionalOrigins,omitempty"` } func (e *OIDCConfigChangedEvent) Data() interface{} { @@ -238,6 +242,12 @@ func ChangeClockSkew(clockSkew time.Duration) func(event *OIDCConfigChangedEvent } } +func ChangeAdditionalOrigins(additionalOrigins []string) func(event *OIDCConfigChangedEvent) { + return func(e *OIDCConfigChangedEvent) { + e.AdditionalOrigins = &additionalOrigins + } +} + func OIDCConfigChangedEventMapper(event *repository.Event) (eventstore.EventReader, error) { e := &OIDCConfigChangedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(event), diff --git a/migrations/cockroach/V1.43__additional_origins.sql b/migrations/cockroach/V1.43__additional_origins.sql new file mode 100644 index 0000000000..0aac4c4749 --- /dev/null +++ b/migrations/cockroach/V1.43__additional_origins.sql @@ -0,0 +1,3 @@ +ALTER TABLE auth.applications ADD COLUMN additional_origins TEXT ARRAY; +ALTER TABLE authz.applications ADD COLUMN additional_origins TEXT ARRAY; +ALTER TABLE management.applications ADD COLUMN additional_origins TEXT ARRAY; \ No newline at end of file diff --git a/proto/zitadel/app.proto b/proto/zitadel/app.proto index 20d9093a43..312d3adbdb 100644 --- a/proto/zitadel/app.proto +++ b/proto/zitadel/app.proto @@ -65,7 +65,7 @@ message AppNameQuery { message OIDCConfig { repeated string redirect_uris = 1 [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { - example: "[\"console.zitadel.ch/authorized\"]"; + example: "[\"https://console.zitadel.ch/auth/callback\"]"; description: "Callback URI of the authorization request where the code or tokens will be sent to"; } ]; @@ -103,7 +103,7 @@ message OIDCConfig { ]; repeated string post_logout_redirect_uris = 8 [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { - example: "[\"console.zitadel.ch/logout\"]"; + example: "[\"https://console.zitadel.ch/logout\"]"; description: "ZITADEL will redirect to this link after a successful logout"; } ]; @@ -154,6 +154,18 @@ message OIDCConfig { // max: "5s"; } ]; + repeated string additional_origins = 18 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "[\"https://console.zitadel.ch/auth/callback\"]"; + description: "additional origins (other than the redirect_uris) from where the api can be used"; + } + ]; + repeated string allowed_origins = 19 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "[\"https://console.zitadel.ch/auth/callback\"]"; + description: "all allowed origins from where the api can be used"; + } + ]; } enum OIDCResponseType { diff --git a/proto/zitadel/management.proto b/proto/zitadel/management.proto index 83f7c470f9..f8d241298a 100644 --- a/proto/zitadel/management.proto +++ b/proto/zitadel/management.proto @@ -2953,6 +2953,7 @@ message AddOIDCAppRequest { bool id_token_role_assertion = 13; bool id_token_userinfo_assertion = 14; google.protobuf.Duration clock_skew = 15 [(validate.rules).duration = {gte: {}, lte: {seconds: 5}}]; + repeated string additional_origins = 16; } message AddOIDCAppResponse { @@ -3023,6 +3024,7 @@ message UpdateOIDCAppConfigRequest { bool id_token_role_assertion = 12; bool id_token_userinfo_assertion = 13; google.protobuf.Duration clock_skew = 14 [(validate.rules).duration = {gte: {}, lte: {seconds: 5}}]; + repeated string additional_origins = 15; } message UpdateOIDCAppConfigResponse {