From dfc5e1a9341871a808afa4ad39fa6f3d21da32c7 Mon Sep 17 00:00:00 2001 From: Marcel Haag Date: Fri, 16 Apr 2021 13:35:48 +0200 Subject: [PATCH] feat: added Keycloak and WebsecurityConfig and improved script management --- c4po.sh | 35 +- docker-compose.yml | 24 - security-c4po-angular/package-lock.json | 34 + security-c4po-angular/package.json | 3 + .../src/app/app-routing.module.ts | 7 +- .../src/app/app.component.spec.ts | 4 + security-c4po-angular/src/app/app.module.ts | 43 +- .../app/dashboard/dashboard.component.spec.ts | 4 +- .../src/app/home/home.component.spec.ts | 4 + .../src/app/login/login.component.spec.ts | 2 + .../src/app/login/login.component.ts | 17 +- .../src/environments/environment.prod.ts | 10 +- .../src/environments/environment.ts | 7 + .../src/shared/guards/auth-guard.service.ts | 59 +- .../shared/guards/login-guard.service.spec.ts | 2 + .../src/shared/guards/login-guard.service.ts | 43 +- .../src/shared/interceptors/index.ts | 6 + .../shared/interceptors/token.interceptor.ts | 72 + .../src/shared/models/user.model.ts | 24 +- .../services/notification.service.spec.ts | 2 + .../shared/services/project.service.spec.ts | 5 +- .../src/shared/services/project.service.ts | 2 +- .../src/shared/services/user.service.spec.ts | 5 +- .../src/shared/services/user.service.ts | 36 +- .../session-state/session-state.actions.ts | 4 + .../session-state/session-state.spec.ts | 5 +- .../stores/session-state/session-state.ts | 15 +- security-c4po-api/build.gradle.kts | 3 +- .../security-c4po-api.postman_collection.json | 109 +- .../src/main/asciidoc/SecurityC4PO.adoc | 6 +- .../securityc4po/api/{v1 => }/BaseEntity.kt | 4 +- .../securityc4po/api/{v1 => }/ResponseBody.kt | 2 +- .../api/{v1 => }/SecurityC4POApplication.kt | 2 +- .../configuration/SpotBugsConstants.kt | 2 +- .../api/configuration/security/Appuser.kt | 47 + .../security/AppuserJwtAuthConverter.kt | 53 + .../configuration/security/ModelmapperCfg.kt | 17 + .../security/UserAccountDetailsService.kt | 15 + .../security/WebSecurityConfiguration.kt | 48 + .../api/{v1 => }/extensions/InlineFuntions.kt | 2 +- .../api/http/AddResponseHeaderFilter.kt | 23 + .../api/http/ApplicationHeaders.kt | 9 + .../api/http/RequestLogIntercepter.kt | 24 + .../api/{v1 => }/project/Project.kt | 4 +- .../api/{v1 => }/project/ProjectController.kt | 13 +- .../api/{v1 => }/project/ProjectEntity.kt | 6 +- .../api/{v1 => }/project/ProjectService.kt | 7 +- .../securityc4po/api/{v1 => }/user/User.kt | 4 +- .../api/{v1 => }/user/UserEntity.kt | 6 +- .../src/main/resources/application.properties | 7 +- .../SecurityC4POApplicationTests.kt | 13 - .../api}/BaseContainerizedTest.kt | 2 +- .../api}/BaseDocumentationIntTest.kt | 10 +- .../securityc4po/api}/BaseIntTest.kt | 2 +- .../ProjectControllerDocumentationTest.kt | 6 +- .../api}/project/ProjectControllerIntTest.kt | 8 +- .../api}/project/ProjectServiceTest.kt | 4 +- .../backend/docker-compose.backend.yml | 16 + security-c4po-cfg/cfg/c4po_realm_export.json | 1811 +++++++++++++++++ security-c4po-cfg/cfg/keycloak.env | 9 + security-c4po-cfg/cfg/keycloakdb.env | 4 + .../frontend/docker-compose.frontend.yml | 16 + .../kc/docker-compose.keycloak.yml | 24 + 63 files changed, 2647 insertions(+), 165 deletions(-) delete mode 100644 docker-compose.yml create mode 100644 security-c4po-angular/src/shared/interceptors/index.ts create mode 100644 security-c4po-angular/src/shared/interceptors/token.interceptor.ts rename security-c4po-api/src/main/kotlin/com/securityc4po/api/{v1 => }/BaseEntity.kt (62%) rename security-c4po-api/src/main/kotlin/com/securityc4po/api/{v1 => }/ResponseBody.kt (57%) rename security-c4po-api/src/main/kotlin/com/securityc4po/api/{v1 => }/SecurityC4POApplication.kt (88%) rename security-c4po-api/src/main/kotlin/com/securityc4po/api/{v1 => }/configuration/SpotBugsConstants.kt (95%) create mode 100644 security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/Appuser.kt create mode 100644 security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/AppuserJwtAuthConverter.kt create mode 100644 security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/ModelmapperCfg.kt create mode 100644 security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/UserAccountDetailsService.kt create mode 100644 security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/WebSecurityConfiguration.kt rename security-c4po-api/src/main/kotlin/com/securityc4po/api/{v1 => }/extensions/InlineFuntions.kt (62%) create mode 100644 security-c4po-api/src/main/kotlin/com/securityc4po/api/http/AddResponseHeaderFilter.kt create mode 100644 security-c4po-api/src/main/kotlin/com/securityc4po/api/http/ApplicationHeaders.kt create mode 100644 security-c4po-api/src/main/kotlin/com/securityc4po/api/http/RequestLogIntercepter.kt rename security-c4po-api/src/main/kotlin/com/securityc4po/api/{v1 => }/project/Project.kt (92%) rename security-c4po-api/src/main/kotlin/com/securityc4po/api/{v1 => }/project/ProjectController.kt (68%) rename security-c4po-api/src/main/kotlin/com/securityc4po/api/{v1 => }/project/ProjectEntity.kt (55%) rename security-c4po-api/src/main/kotlin/com/securityc4po/api/{v1 => }/project/ProjectService.kt (92%) rename security-c4po-api/src/main/kotlin/com/securityc4po/api/{v1 => }/user/User.kt (85%) rename security-c4po-api/src/main/kotlin/com/securityc4po/api/{v1 => }/user/UserEntity.kt (55%) delete mode 100644 security-c4po-api/src/test/kotlin/com.securityc4po.api.v1/SecurityC4POApplicationTests.kt rename security-c4po-api/src/test/kotlin/{com.securityc4po.api.v1 => com/securityc4po/api}/BaseContainerizedTest.kt (85%) rename security-c4po-api/src/test/kotlin/{com.securityc4po.api.v1 => com/securityc4po/api}/BaseDocumentationIntTest.kt (80%) rename security-c4po-api/src/test/kotlin/{com.securityc4po.api.v1 => com/securityc4po/api}/BaseIntTest.kt (80%) rename security-c4po-api/src/test/kotlin/{com.securityc4po.api.v1 => com/securityc4po/api}/project/ProjectControllerDocumentationTest.kt (96%) rename security-c4po-api/src/test/kotlin/{com.securityc4po.api.v1 => com/securityc4po/api}/project/ProjectControllerIntTest.kt (94%) rename security-c4po-api/src/test/kotlin/{com.securityc4po.api.v1 => com/securityc4po/api}/project/ProjectServiceTest.kt (82%) create mode 100644 security-c4po-cfg/backend/docker-compose.backend.yml create mode 100644 security-c4po-cfg/cfg/c4po_realm_export.json create mode 100644 security-c4po-cfg/cfg/keycloak.env create mode 100644 security-c4po-cfg/cfg/keycloakdb.env create mode 100644 security-c4po-cfg/frontend/docker-compose.frontend.yml create mode 100644 security-c4po-cfg/kc/docker-compose.keycloak.yml diff --git a/c4po.sh b/c4po.sh index 4abd45e..ad09377 100755 --- a/c4po.sh +++ b/c4po.sh @@ -1,6 +1,12 @@ #!/bin/bash -baseDir=$(pwd)"/" -echo" +docker_reg="c4po.io" +baseDir=$(pwd) + +composeKeycloak=$baseDir"/security-c4po-cfg/kc/docker-compose.keycloak.yml" +composeFrontend=$baseDir"/security-c4po-cfg/frontend/docker-compose.frontend.yml" +composeBackend=$baseDir"/security-c4po-cfg/backend/docker-compose.backend.yml" + +echo -e " _______ _______ _______ _ _ ______ _____ _______ __ __ |______ |______ | | | |_____/ | | \_/ ______| |______ |_____ |_____| | \_ __|__ | | _/_/_/ _/ _/ _/_/_/ _/_/ @@ -8,6 +14,25 @@ ______| |______ |_____ |_____| | \_ __|__ | | _/_/_/ _/ _/ _/_/_/_/ _/_/_/ _/ _/ _/ _/ _/ _/ _/ _/_/_/ _/ _/ _/_/ -" -#docker-compose up --build -docker-compose up +\n" + +echo "-------------CLEAN UP Container---------------" +echo -e "\n" +#docker rm -f security-c4po-keycloak +#docker rm -f security-c4po-postgres-keycloak +docker rm -f security-c4po-api +docker rm -f security-c4po-angular +echo -e "\n" + +echo "-----------------Start Build------------------" +echo -e "\n" +echo " - Backend: " +docker-compose -f ${composeBackend} build +echo -e "\n" +echo " - Frontend: " +docker-compose -f ${composeFrontend} build +echo -e "\n" + +echo "------------Start Docker Container------------" +echo -e "\n" +docker-compose -f ${composeKeycloak} -f ${composeBackend} -f ${composeFrontend} up diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index d3bb086..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,24 +0,0 @@ -version: '3.1' - -services: - api: - build: './security-c4po-api' - image: security-c4po-api:latest - container_name: security-c4po-api - deploy: - resources: - limits: - memory: "1G" - ports: - - '8443:8443' - - angular: - build: './security-c4po-angular' - image: security-c4po-angular:latest - container_name: security-c4po-angular - deploy: - resources: - limits: - memory: "1G" - ports: - - '4200:4200' diff --git a/security-c4po-angular/package-lock.json b/security-c4po-angular/package-lock.json index 07f9a09..f5a7e9c 100644 --- a/security-c4po-angular/package-lock.json +++ b/security-c4po-angular/package-lock.json @@ -8610,6 +8610,11 @@ "supports-color": "^7.0.0" } }, + "js-sha256": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", + "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -8782,6 +8787,11 @@ "set-immediate-shim": "~1.0.1" } }, + "jwt-decode": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", + "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" + }, "karma-source-map-support": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", @@ -8791,6 +8801,30 @@ "source-map-support": "^0.5.5" } }, + "keycloak-angular": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/keycloak-angular/-/keycloak-angular-8.1.0.tgz", + "integrity": "sha512-FNIZBVKI3QNw0ucHnSjDDe8859WT6NtVlsKtCvJzAS9mFiYCDFDT9cRWt9On2aFu39rGyBEBNbpsTE1Mso48NQ==", + "requires": { + "tslib": "^2.0.0" + } + }, + "keycloak-js": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-13.0.0.tgz", + "integrity": "sha512-XMbppXjkkFmt88vR8jrxH32dz/dmFETDObD6NzLAT3HgpC9Thi6LSEUm7XsROq4z+2i/qLwlWTHbgLXj6LxBrg==", + "requires": { + "base64-js": "1.3.1", + "js-sha256": "0.9.0" + }, + "dependencies": { + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + } + } + }, "killable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", diff --git a/security-c4po-angular/package.json b/security-c4po-angular/package.json index b873e4f..dda4d09 100644 --- a/security-c4po-angular/package.json +++ b/security-c4po-angular/package.json @@ -35,6 +35,9 @@ "@ngxs/store": "^3.7.0", "eva-icons": "^1.1.3", "i18n-iso-countries": "^6.2.2", + "jwt-decode": "^3.1.2", + "keycloak-angular": "^8.1.0", + "keycloak-js": "^13.0.0", "moment": "^2.29.1", "moment-timezone": "latest", "ngx-moment": "^5.0.0", diff --git a/security-c4po-angular/src/app/app-routing.module.ts b/security-c4po-angular/src/app/app-routing.module.ts index 0410dfc..b84f70a 100644 --- a/security-c4po-angular/src/app/app-routing.module.ts +++ b/security-c4po-angular/src/app/app-routing.module.ts @@ -2,10 +2,8 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import {HomeComponent} from './home/home.component'; import {AuthGuardService} from '../shared/guards/auth-guard.service'; -import {LoginGuardService} from '../shared/guards/login-guard.service'; export const START_PAGE = 'home'; -export const FALLBACK_PAGE = 'home'; const routes: Routes = [ { @@ -18,11 +16,12 @@ const routes: Routes = [ loadChildren: () => import('./dashboard').then(mod => mod.DashboardModule), canActivate: [AuthGuardService] }, - { + // ToDo: Exchange default Keycloak login with self made login + /*{ path: 'login', loadChildren: () => import('./login').then(mod => mod.LoginModule), canActivate: [LoginGuardService] - }, + },*/ {path: '**', redirectTo: START_PAGE}, {path: '', redirectTo: START_PAGE, pathMatch: 'full'}, ]; diff --git a/security-c4po-angular/src/app/app.component.spec.ts b/security-c4po-angular/src/app/app.component.spec.ts index ae1f120..889ebdf 100644 --- a/security-c4po-angular/src/app/app.component.spec.ts +++ b/security-c4po-angular/src/app/app.component.spec.ts @@ -11,6 +11,7 @@ import {HttpClientTestingModule} from '@angular/common/http/testing'; import {SessionState} from '../shared/stores/session-state/session-state'; import {NgxsModule} from '@ngxs/store'; import {HeaderModule} from './header/header.module'; +import {KeycloakService} from 'keycloak-angular'; describe('AppComponent', () => { beforeEach(async () => { @@ -32,6 +33,9 @@ describe('AppComponent', () => { NgxsModule.forRoot([SessionState]), HttpClientTestingModule ], + providers: [ + KeycloakService + ], declarations: [ AppComponent ], diff --git a/security-c4po-angular/src/app/app.module.ts b/security-c4po-angular/src/app/app.module.ts index 826ab11..2ce09d6 100644 --- a/security-c4po-angular/src/app/app.module.ts +++ b/security-c4po-angular/src/app/app.module.ts @@ -1,4 +1,4 @@ -import {NgModule} from '@angular/core'; +import {APP_INITIALIZER, NgModule} from '@angular/core'; import {BrowserModule} from '@angular/platform-browser'; import {AppRoutingModule} from './app-routing.module'; import {AppComponent} from './app.component'; @@ -23,6 +23,8 @@ import {NotificationService} from '../shared/services/notification.service'; import {ThemeModule} from '@assets/@theme/theme.module'; import {HeaderModule} from './header/header.module'; import {HomeModule} from './home/home.module'; +import {KeycloakService} from 'keycloak-angular'; +import {httpInterceptorProviders} from '../shared/interceptors'; @NgModule({ declarations: [ @@ -55,9 +57,19 @@ import {HomeModule} from './home/home.module'; ], providers: [ HttpClient, + { + provide: APP_INITIALIZER, + useFactory: initializer, + multi: true, + deps: [KeycloakService] + }, + KeycloakService, + httpInterceptorProviders, NotificationService ], - bootstrap: [AppComponent] + bootstrap: [ + AppComponent + ] }) export class AppModule { constructor(library: FaIconLibrary, faConfig: FaConfig) { @@ -65,3 +77,30 @@ export class AppModule { faConfig.defaultPrefix = 'fas'; } } + +export function initializer(keycloak: KeycloakService): () => Promise { + return async (): Promise => { + try { + await keycloak.init({ + config: { + url: environment.keycloakURL, + realm: environment.keycloakrealm, + clientId: environment.keycloakclientId + }, + initOptions: { + onLoad: 'login-required', + checkLoginIframe: false, + // flow: 'implicit' + }, + loadUserProfileAtStartUp: false, + enableBearerInterceptor: true, + bearerExcludedUrls: [ + '/assets', + '/clients/public' + ] + }); + } catch (error) { + // console.error(error); + } + }; +} diff --git a/security-c4po-angular/src/app/dashboard/dashboard.component.spec.ts b/security-c4po-angular/src/app/dashboard/dashboard.component.spec.ts index 5ec4ff8..a83926d 100644 --- a/security-c4po-angular/src/app/dashboard/dashboard.component.spec.ts +++ b/security-c4po-angular/src/app/dashboard/dashboard.component.spec.ts @@ -8,7 +8,9 @@ describe('DashboardComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ DashboardComponent ] + declarations: [ + DashboardComponent + ] }) .compileComponents(); }); diff --git a/security-c4po-angular/src/app/home/home.component.spec.ts b/security-c4po-angular/src/app/home/home.component.spec.ts index cd0c5be..05d1b59 100644 --- a/security-c4po-angular/src/app/home/home.component.spec.ts +++ b/security-c4po-angular/src/app/home/home.component.spec.ts @@ -3,6 +3,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { HomeComponent } from './home.component'; import {NbButtonModule, NbCardModule} from '@nebular/theme'; import {HttpClientTestingModule} from '@angular/common/http/testing'; +import {KeycloakService} from 'keycloak-angular'; describe('HomeComponent', () => { let component: HomeComponent; @@ -17,6 +18,9 @@ describe('HomeComponent', () => { HttpClientTestingModule, NbCardModule, NbButtonModule + ], + providers : [ + KeycloakService ] }) .compileComponents(); diff --git a/security-c4po-angular/src/app/login/login.component.spec.ts b/security-c4po-angular/src/app/login/login.component.spec.ts index ee976a0..fa2f99c 100644 --- a/security-c4po-angular/src/app/login/login.component.spec.ts +++ b/security-c4po-angular/src/app/login/login.component.spec.ts @@ -23,6 +23,7 @@ import {CommonModule} from '@angular/common'; import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; import {NotificationService} from '../../shared/services/notification.service'; import {NotificationServiceMock} from '../../shared/services/notification.service.mock'; +import {KeycloakService} from 'keycloak-angular'; const DESIRED_STORE_STATE_SESSION: SessionStateModel = { userAccount: { @@ -68,6 +69,7 @@ describe('LoginComponent', () => { LoginComponent ], providers: [ + KeycloakService, {provide: NotificationService, useValue: new NotificationServiceMock()} ] }) diff --git a/security-c4po-angular/src/app/login/login.component.ts b/security-c4po-angular/src/app/login/login.component.ts index 2ca866a..2faf7f3 100644 --- a/security-c4po-angular/src/app/login/login.component.ts +++ b/security-c4po-angular/src/app/login/login.component.ts @@ -10,12 +10,14 @@ import {UpdateIsAuthenticated, UpdateUser} from '../../shared/stores/session-sta import {GlobalTitlesVariables} from '../../shared/config/global-variables'; import {HttpClient} from '@angular/common/http'; import {FieldStatus} from '../../shared/models/form-field-status.model'; +import {KeycloakService} from 'keycloak-angular'; @Component({ selector: 'app-login', templateUrl: './login.component.html', styleUrls: ['./login.component.scss'] }) +// ToDo: Exchange default Keycloak login with self made login export class LoginComponent implements OnInit, OnDestroy { readonly MIN_LENGTH: number = 2; readonly SECURITYC4PO_TITLE = GlobalTitlesVariables.SECURITYC4PO_TITLE; @@ -41,7 +43,8 @@ export class LoginComponent implements OnInit, OnDestroy { private router: Router, private store: Store, private readonly httpClient: HttpClient, - private notificationService: NotificationService) { + private notificationService: NotificationService, + protected keycloakService: KeycloakService) { } ngOnInit(): void { @@ -65,8 +68,9 @@ export class LoginComponent implements OnInit, OnDestroy { login(): void { const username = this.loginUsernameCtrl.value; const password = this.loginPasswordCtrl.value; - if (username === DefaultUser.username - && password === DefaultUser.password) { + // ToDo: Should be handled in Guards + this.keycloakService.login({}); + if (true) { // ToDo: Should be handled in Guards this.store.dispatch(new UpdateIsAuthenticated(true)); this.store.dispatch(new UpdateUser(this.user, true)); @@ -120,10 +124,3 @@ export class LoginComponent implements OnInit, OnDestroy { export interface Version { version: string; } - -export enum DefaultUser { - username = 'ttt', - password = 'Test1234!' -} - - diff --git a/security-c4po-angular/src/environments/environment.prod.ts b/security-c4po-angular/src/environments/environment.prod.ts index 3612073..8eb2105 100644 --- a/security-c4po-angular/src/environments/environment.prod.ts +++ b/security-c4po-angular/src/environments/environment.prod.ts @@ -1,3 +1,11 @@ export const environment = { - production: true + production: true, + + // keycloak + keycloakURL: 'http://localhost:8888/auth', + keycloakrealm: 'c4po_realm_local', + keycloakclientId: 'c4po_local', + + // backend service + apiEndpoint: 'http://localhost:8443', }; diff --git a/security-c4po-angular/src/environments/environment.ts b/security-c4po-angular/src/environments/environment.ts index 9817ea7..ccae9d4 100644 --- a/security-c4po-angular/src/environments/environment.ts +++ b/security-c4po-angular/src/environments/environment.ts @@ -5,6 +5,13 @@ export const environment = { stage: 'n/a', production: false, + + // keycloak + keycloakURL: 'http://localhost:8888/auth', + keycloakrealm: 'c4po_realm_local', + keycloakclientId: 'c4po_local', + + // backend service apiEndpoint: 'http://localhost:8443', }; diff --git a/security-c4po-angular/src/shared/guards/auth-guard.service.ts b/security-c4po-angular/src/shared/guards/auth-guard.service.ts index 0757df1..c1f61b4 100644 --- a/security-c4po-angular/src/shared/guards/auth-guard.service.ts +++ b/security-c4po-angular/src/shared/guards/auth-guard.service.ts @@ -4,45 +4,42 @@ import {Store} from '@ngxs/store'; import {Observable, of} from 'rxjs'; import {SessionState} from '../stores/session-state/session-state'; import {catchError, map} from 'rxjs/operators'; +import {KeycloakAuthGuard, KeycloakService} from 'keycloak-angular'; +import {UpdateIsAuthenticated, UpdateUser} from '../stores/session-state/session-state.actions'; +import {User} from '../models/user.model'; @Injectable({ providedIn: 'root' }) -export class AuthGuardService implements CanActivate { +export class AuthGuardService extends KeycloakAuthGuard implements CanActivate { constructor( - private readonly router: Router, + public readonly router: Router, + protected keycloakService: KeycloakService, private readonly store: Store) { + super(router, keycloakService); } - canActivate( - next: ActivatedRouteSnapshot, - state: RouterStateSnapshot): Observable { - return this.isAuthenticated() - .pipe( - map((canAccess: boolean) => { - if (canAccess) { - return canAccess; - } else { - this.router.navigate(['/login']); - return false; - } - }) - ); - } + isAccessAllowed(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { + return new Promise((resolve, reject) => { + if (!this.authenticated) { + this.keycloakAngular.login() + .catch(e => console.error(e)); + return reject(false); + } - /** - * @return boolean - */ - private isAuthenticated(): Observable { - // ToDo: Should check from Authentication Provider - return of(this.store.selectSnapshot(SessionState.isAuthenticated)) - .pipe( - map((isLoggedIn: boolean) => { - return isLoggedIn; - }), - catchError(() => { - return of(false); - }) - ); + const requiredRoles: string[] = route.data.roles; + if (!requiredRoles || requiredRoles.length === 0) { + this.store.dispatch(new UpdateIsAuthenticated(true)); + this.store.dispatch(new UpdateUser(route.data.user, true)); + return resolve(true); + } else { + if (!this.roles || this.roles.length === 0) { + this.store.dispatch(new UpdateIsAuthenticated(false)); + this.store.dispatch(new UpdateUser(null, true)); + resolve(false); + } + resolve(requiredRoles.every(role => this.roles.indexOf(role) > -1)); + } + }); } } diff --git a/security-c4po-angular/src/shared/guards/login-guard.service.spec.ts b/security-c4po-angular/src/shared/guards/login-guard.service.spec.ts index cfaac7a..1f2767d 100644 --- a/security-c4po-angular/src/shared/guards/login-guard.service.spec.ts +++ b/security-c4po-angular/src/shared/guards/login-guard.service.spec.ts @@ -8,6 +8,7 @@ import {SessionState} from '../stores/session-state/session-state'; import {TranslateLoader, TranslateModule} from '@ngx-translate/core'; import {HttpLoaderFactory} from '../../app/common-app.module'; import {HttpClient} from '@angular/common/http'; +import {KeycloakService} from 'keycloak-angular'; describe('LoginGuardService', () => { let service: LoginGuardService; @@ -27,6 +28,7 @@ describe('LoginGuardService', () => { NgxsModule.forRoot([SessionState]) ], providers: [ + KeycloakService ] }); service = TestBed.inject(LoginGuardService); diff --git a/security-c4po-angular/src/shared/guards/login-guard.service.ts b/security-c4po-angular/src/shared/guards/login-guard.service.ts index cd4f138..831d852 100644 --- a/security-c4po-angular/src/shared/guards/login-guard.service.ts +++ b/security-c4po-angular/src/shared/guards/login-guard.service.ts @@ -4,18 +4,45 @@ import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from ' import {Observable, of} from 'rxjs'; import {catchError, map, tap} from 'rxjs/operators'; import {SessionState} from '../stores/session-state/session-state'; +import {KeycloakAuthGuard, KeycloakService} from 'keycloak-angular'; +import {UpdateIsAuthenticated, UpdateUser} from '../stores/session-state/session-state.actions'; @Injectable({ providedIn: 'root' }) -export class LoginGuardService implements CanActivate { - +export class LoginGuardService extends KeycloakAuthGuard implements CanActivate { constructor( - private router: Router, - private store: Store) { + public readonly router: Router, + protected keycloakAngular: KeycloakService, + private readonly store: Store) { + super(router, keycloakAngular); } - canActivate(routeSnapshot: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { + isAccessAllowed(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { + return new Promise((resolve, reject) => { + if (!this.authenticated) { + this.keycloakAngular.login() + .catch(e => console.error(e)); + return reject(false); + } + + const requiredRoles: string[] = route.data.roles; + if (!requiredRoles || requiredRoles.length === 0) { + this.store.dispatch(new UpdateIsAuthenticated(true)); + this.store.dispatch(new UpdateUser(route.data.user, true)); + return resolve(true); + } else { + if (!this.roles || this.roles.length === 0) { + this.store.dispatch(new UpdateIsAuthenticated(false)); + this.store.dispatch(new UpdateUser(null, true)); + resolve(false); + } + resolve(requiredRoles.every(role => this.roles.indexOf(role) > -1)); + } + }); + } + +/* canActivate(routeSnapshot: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { return this.isAuthenticated().pipe( tap((canAccess: boolean) => { if (canAccess) { @@ -27,9 +54,9 @@ export class LoginGuardService implements CanActivate { ); } - /** + /!** * @return state of authentication - */ + *!/ private isAuthenticated(): Observable { // ToDo: Should check from Authentication Provider return of(this.store.selectSnapshot(SessionState.isAuthenticated)) @@ -41,6 +68,6 @@ export class LoginGuardService implements CanActivate { return of(false); }) ); - } + }*/ } diff --git a/security-c4po-angular/src/shared/interceptors/index.ts b/security-c4po-angular/src/shared/interceptors/index.ts new file mode 100644 index 0000000..6628c42 --- /dev/null +++ b/security-c4po-angular/src/shared/interceptors/index.ts @@ -0,0 +1,6 @@ +import {HTTP_INTERCEPTORS} from '@angular/common/http'; +import {TokenInterceptor} from './token.interceptor'; + +export const httpInterceptorProviders = [ + {provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true}, +]; diff --git a/security-c4po-angular/src/shared/interceptors/token.interceptor.ts b/security-c4po-angular/src/shared/interceptors/token.interceptor.ts new file mode 100644 index 0000000..db5375e --- /dev/null +++ b/security-c4po-angular/src/shared/interceptors/token.interceptor.ts @@ -0,0 +1,72 @@ +import {Injectable} from '@angular/core'; +import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http'; +import {KeycloakService} from 'keycloak-angular'; +import {environment} from '../../environments/environment'; +import {Observable, Subscriber} from 'rxjs'; +import {mergeMap} from 'rxjs/operators'; + +@Injectable() +export class TokenInterceptor implements HttpInterceptor { + + constructor(private keycloakService: KeycloakService) { + } + + /** + * TODO: has to be edit every time a service is added and requires the keycloak token on HEADER + */ + private static listOfKeycloakRelevantHosts(): { origin: string }[] { + const relevantList = new Array<{ origin: string }>(); + relevantList.push({origin: getOriginByUrl(environment.apiEndpoint)}); + relevantList.push({origin: getOriginByUrl(environment.keycloakURL)}); + return relevantList; + + function getOriginByUrl(inputUrl: string): string { + return (new URL(inputUrl)).origin; + } + } + + private static requestTargetIsWhitelisted(requestTargetOrigin: string): boolean { + if (requestTargetOrigin) { + try { + const targetUrl: URL = new URL(requestTargetOrigin); + if (targetUrl && targetUrl.origin) { + const matchList = TokenInterceptor.listOfKeycloakRelevantHosts() + .map(value => value.origin) + .filter(value => (value === targetUrl.origin)); + return !!matchList.length; + } + } catch (e) { + // ignore e.g. local calls + } + } + return false; + } + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + const requestTargetHost = request.url || ''; + + if (TokenInterceptor.requestTargetIsWhitelisted(requestTargetHost)) { + const tokenObserver: Observable = new Observable((observer: Subscriber): void => { + this.keycloakService.getToken().then(token => { + observer.next(token); + observer.complete(); + }).catch(error => { + observer.error(error); + observer.complete(); + }); + }); + + return tokenObserver.pipe( + mergeMap((authToken: string) => { + request = request.clone({ + headers: request.headers.append('Authorization', `Bearer ${authToken}`) + }); + return next.handle(request); + } + )); + } else { + // Do nothing + return next.handle(request); + } + } +} diff --git a/security-c4po-angular/src/shared/models/user.model.ts b/security-c4po-angular/src/shared/models/user.model.ts index 4f467fd..d93a307 100644 --- a/security-c4po-angular/src/shared/models/user.model.ts +++ b/security-c4po-angular/src/shared/models/user.model.ts @@ -1,12 +1,12 @@ import { v4 as UUID } from 'uuid'; export class User { - id: string; - username: string; - firstName: string; - lastName: string; - mailAddress: string; - interfaceLang: string; + id?: string; + username?: string; + firstName?: string; + lastName?: string; + mailAddress?: string; + interfaceLang?: string; constructor(username?: string, firstName?: string, @@ -17,7 +17,15 @@ export class User { this.username = username; this.firstName = firstName; this.lastName = lastName; - this.mailAddress = email; - this.interfaceLang = interfaceLang; + if (email) { + this.mailAddress = email; + } else { + this.mailAddress = null; + } + if (interfaceLang) { + this.interfaceLang = interfaceLang; + } else { + this.interfaceLang = 'en-US'; + } } } diff --git a/security-c4po-angular/src/shared/services/notification.service.spec.ts b/security-c4po-angular/src/shared/services/notification.service.spec.ts index 5e20e7e..60e08dc 100644 --- a/security-c4po-angular/src/shared/services/notification.service.spec.ts +++ b/security-c4po-angular/src/shared/services/notification.service.spec.ts @@ -10,6 +10,7 @@ import {HttpLoaderFactory} from '../../app/common-app.module'; import {HttpClient} from '@angular/common/http'; import {NgxsModule} from '@ngxs/store'; import {SessionState} from '../stores/session-state/session-state'; +import {KeycloakService} from 'keycloak-angular'; describe('NotificationService', () => { let toastrServiceStub: Partial; @@ -45,6 +46,7 @@ describe('NotificationService', () => { ], providers: [ NotificationService, + KeycloakService, {provide: NbToastrService, useValue: toastrServiceStub}, {provide: TranslateService, useValue: translateServiceStub}] }); diff --git a/security-c4po-angular/src/shared/services/project.service.spec.ts b/security-c4po-angular/src/shared/services/project.service.spec.ts index 4f691b8..51c314c 100644 --- a/security-c4po-angular/src/shared/services/project.service.spec.ts +++ b/security-c4po-angular/src/shared/services/project.service.spec.ts @@ -3,6 +3,7 @@ import { TestBed } from '@angular/core/testing'; import { ProjectService } from './project.service'; import {HttpClientTestingModule} from '@angular/common/http/testing'; import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; +import {KeycloakService} from 'keycloak-angular'; describe('ProjectService', () => { let service: ProjectService; @@ -13,7 +14,9 @@ describe('ProjectService', () => { HttpClientTestingModule, BrowserAnimationsModule, ], - providers: [] + providers: [ + KeycloakService + ] }); service = TestBed.inject(ProjectService); }); diff --git a/security-c4po-angular/src/shared/services/project.service.ts b/security-c4po-angular/src/shared/services/project.service.ts index 39cccae..83ae315 100644 --- a/security-c4po-angular/src/shared/services/project.service.ts +++ b/security-c4po-angular/src/shared/services/project.service.ts @@ -9,7 +9,7 @@ import {Observable} from 'rxjs'; }) export class ProjectService { - private apiBaseURL = `${environment.apiEndpoint}/v1/projects`; + private apiBaseURL = `${environment.apiEndpoint}/projects`; constructor(private http: HttpClient) { } diff --git a/security-c4po-angular/src/shared/services/user.service.spec.ts b/security-c4po-angular/src/shared/services/user.service.spec.ts index 4558d8b..99109bc 100644 --- a/security-c4po-angular/src/shared/services/user.service.spec.ts +++ b/security-c4po-angular/src/shared/services/user.service.spec.ts @@ -8,6 +8,7 @@ import {HttpLoaderFactory} from '../../app/common-app.module'; import {HttpClient} from '@angular/common/http'; import {NgxsModule} from '@ngxs/store'; import {SessionState} from '../stores/session-state/session-state'; +import {KeycloakService} from 'keycloak-angular'; describe('UserService', () => { let service: UserService; @@ -26,7 +27,9 @@ describe('UserService', () => { } }), ], - providers: [] + providers: [ + KeycloakService + ] }); service = TestBed.inject(UserService); }); diff --git a/security-c4po-angular/src/shared/services/user.service.ts b/security-c4po-angular/src/shared/services/user.service.ts index 669643b..a72a3d9 100644 --- a/security-c4po-angular/src/shared/services/user.service.ts +++ b/security-c4po-angular/src/shared/services/user.service.ts @@ -1,9 +1,10 @@ import {Injectable} from '@angular/core'; import {HttpClient, HttpHeaders} from '@angular/common/http'; import {User} from '../models/user.model'; -import {Observable} from 'rxjs'; +import {from, Observable, Subscriber} from 'rxjs'; import {Store} from '@ngxs/store'; -import {SessionState} from '../stores/session-state/session-state'; +import {KeycloakService} from 'keycloak-angular'; +import {map} from 'rxjs/operators'; @Injectable({ providedIn: 'root' @@ -11,6 +12,7 @@ import {SessionState} from '../stores/session-state/session-state'; export class UserService { constructor(private http: HttpClient, + private keycloakService: KeycloakService, private store: Store) { } @@ -20,7 +22,33 @@ export class UserService { }); } - getCurrentAuthenticatedUser(): Observable { - return this.store.select(SessionState.userAccount); + private createHttpOptions(): Observable { + return this.getToken().pipe( + // create HttpHeaders + map((token: string): HttpHeaders => { + return UserService.createHttpHeadersWithContentType(token); + }), + // createHttpOptions + map((httpHeaders: HttpHeaders): { headers } => { + return {headers: httpHeaders}; + }) + ); + } + + public loadUserProfile(): Observable { + return from(this.keycloakService.loadUserProfile()) as Observable; + } + + private getToken(): Observable { + return new Observable((observer: Subscriber): void => { + this.keycloakService.getToken().then(token => { + console.warn(token); + observer.next(token); + observer.complete(); + }).catch(error => { + observer.error(error); + observer.complete(); + }); + }); } } diff --git a/security-c4po-angular/src/shared/stores/session-state/session-state.actions.ts b/security-c4po-angular/src/shared/stores/session-state/session-state.actions.ts index a008f46..a0facf4 100644 --- a/security-c4po-angular/src/shared/stores/session-state/session-state.actions.ts +++ b/security-c4po-angular/src/shared/stores/session-state/session-state.actions.ts @@ -9,6 +9,10 @@ export class ResetSession { static readonly type = '[Session] ResetSession'; } +export class FetchUser { + static readonly type = '[Session] FetchUser'; +} + export class UpdateUser { static readonly type = '[Session] UpdateUser'; diff --git a/security-c4po-angular/src/shared/stores/session-state/session-state.spec.ts b/security-c4po-angular/src/shared/stores/session-state/session-state.spec.ts index 22952e0..338abb4 100644 --- a/security-c4po-angular/src/shared/stores/session-state/session-state.spec.ts +++ b/security-c4po-angular/src/shared/stores/session-state/session-state.spec.ts @@ -7,6 +7,7 @@ import {HttpClient} from '@angular/common/http'; import {SESSION_STATE_NAME, SessionState, SessionStateModel} from './session-state'; import {User} from '../../models/user.model'; import {InitSession, UpdateUser} from './session-state.actions'; +import {KeycloakService} from 'keycloak-angular'; const INITIAL_STORE_STATE_SESSION: SessionStateModel = { userAccount: { @@ -40,7 +41,9 @@ describe('SessionState', () => { }), NgxsModule.forRoot([SessionState]), ], - providers: [] + providers: [ + KeycloakService + ] }); store = TestBed.inject(Store); store.reset({ diff --git a/security-c4po-angular/src/shared/stores/session-state/session-state.ts b/security-c4po-angular/src/shared/stores/session-state/session-state.ts index 9b15e2a..06e162e 100644 --- a/security-c4po-angular/src/shared/stores/session-state/session-state.ts +++ b/security-c4po-angular/src/shared/stores/session-state/session-state.ts @@ -2,9 +2,10 @@ import {User} from '../../models/user.model'; import {Inject, Injectable, LOCALE_ID} from '@angular/core'; import {Action, Selector, State, StateContext} from '@ngxs/store'; import {TranslateService} from '@ngx-translate/core'; -import {InitSession, ResetSession, UpdateIsAuthenticated, UpdateUser, UpdateUserSettings} from './session-state.actions'; +import {FetchUser, InitSession, ResetSession, UpdateIsAuthenticated, UpdateUser, UpdateUserSettings} from './session-state.actions'; import deepEqual from 'deep-equal'; import moment from 'moment'; +import {UserService} from '../../services/user.service'; export interface SessionStateModel { userAccount: User; @@ -24,6 +25,7 @@ export const SESSION_STORAGE_KEY_USER = 'user'; @Injectable() export class SessionState { constructor(@Inject(LOCALE_ID) private readonly localeId: string, + private readonly userService: UserService, private readonly translateService: TranslateService) { } @@ -51,6 +53,17 @@ export class SessionState { ctx.dispatch(new InitSession()); } + @Action(FetchUser) + fetchUser(ctx: StateContext): void { + this.userService.loadUserProfile().subscribe({ + next: (user: User): void => { + ctx.dispatch(new UpdateUser(user, true)); + }, + // TODO: add better error handling + error: (err) => console.error('Failed to load UserProfile', err) + }); + } + @Action(UpdateUser) updateUser(ctx: StateContext, {user, force}: UpdateUser): void { const state = ctx.getState(); diff --git a/security-c4po-api/build.gradle.kts b/security-c4po-api/build.gradle.kts index 0344ac2..24c2802 100644 --- a/security-c4po-api/build.gradle.kts +++ b/security-c4po-api/build.gradle.kts @@ -61,6 +61,7 @@ dependencies { implementation("com.fasterxml.jackson.datatype:jackson-datatype-joda:2.11.3") implementation("io.projectreactor.kotlin:reactor-kotlin-extensions:1.1.1") implementation("javax.websocket:javax.websocket-api:1.1") + implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server") implementation("org.springframework.boot:spring-boot-starter-webflux") implementation("org.springframework.boot:spring-boot-starter-actuator") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") @@ -72,7 +73,7 @@ dependencies { implementation("org.modelmapper:modelmapper:2.3.2") api("org.springframework.boot:spring-boot-starter-test") - /*api("org.springframework.security:spring-security-jwt:1.0.10.RELEASE")*/ + api("org.springframework.security:spring-security-jwt:1.1.1.RELEASE") testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.1.0") testImplementation("io.projectreactor:reactor-test") diff --git a/security-c4po-api/security-c4po-api.postman_collection.json b/security-c4po-api/security-c4po-api.postman_collection.json index f478261..f450858 100644 --- a/security-c4po-api/security-c4po-api.postman_collection.json +++ b/security-c4po-api/security-c4po-api.postman_collection.json @@ -11,17 +11,41 @@ { "name": "getProjects", "request": { + "auth": { + "type": "oauth2", + "oauth2": [ + { + "key": "tokenType", + "value": "", + "type": "string" + }, + { + "key": "accessToken", + "value": "", + "type": "string" + }, + { + "key": "grant_type", + "value": "authorization_code_with_pkce", + "type": "string" + }, + { + "key": "addTokenTo", + "value": "header", + "type": "string" + } + ] + }, "method": "GET", "header": [], "url": { - "raw": "http://localhost:8443/v1/projects", + "raw": "http://localhost:8443/projects", "protocol": "http", "host": [ "localhost" ], "port": "8443", "path": [ - "v1", "projects" ] } @@ -49,6 +73,87 @@ } }, "response": [] + }, + { + "name": "postKeycloakToken", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "urlencoded", + "urlencoded": [ + { + "key": "client_id", + "value": "c4po_local", + "type": "text" + }, + { + "key": "username", + "value": "ttt", + "type": "text" + }, + { + "key": "password", + "value": "Test1234!", + "type": "text" + }, + { + "key": "grant_type", + "value": "password", + "type": "text" + }, + { + "key": "token", + "value": "", + "type": "text", + "disabled": true + }, + { + "key": "client_secret", + "value": "secret", + "type": "text", + "disabled": true + } + ] + }, + "url": { + "raw": "http://localhost:8888/auth/realms/c4po_realm_local/protocol/openid-connect/token", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8888", + "path": [ + "auth", + "realms", + "c4po_realm_local", + "protocol", + "openid-connect", + "token" + ] + } + }, + "response": [] + }, + { + "name": "getASCIIDocumentation", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8443/docs/SecurityC4PO.html", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8443", + "path": [ + "docs", + "SecurityC4PO.html" + ] + } + }, + "response": [] } ] } \ No newline at end of file diff --git a/security-c4po-api/src/main/asciidoc/SecurityC4PO.adoc b/security-c4po-api/src/main/asciidoc/SecurityC4PO.adoc index 9cb526a..7f40413 100644 --- a/security-c4po-api/src/main/asciidoc/SecurityC4PO.adoc +++ b/security-c4po-api/src/main/asciidoc/SecurityC4PO.adoc @@ -25,15 +25,15 @@ When creating requests you must not follow the examples exactly, e.g. instead of === Get projects -To get projects, call the GET request /v1/projects +To get projects, call the GET request /projects ==== Request example -#include::{snippets}/getProjects/http-request.adoc[] +#include::{snippets}/getProjects/com.securityc4po.api.http-request.adoc[] ==== Response example -#include::{snippets}/getProjects/http-response.adoc[] +#include::{snippets}/getProjects/com.securityc4po.api.http-response.adoc[] ==== Response structure diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/BaseEntity.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/BaseEntity.kt similarity index 62% rename from security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/BaseEntity.kt rename to security-c4po-api/src/main/kotlin/com/securityc4po/api/BaseEntity.kt index e6d542e..c18583d 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/BaseEntity.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/BaseEntity.kt @@ -1,6 +1,6 @@ -package com.securityc4po.api.v1 +package com.securityc4po.api abstract class BaseEntity( var data: T ) { -} \ No newline at end of file +} diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/ResponseBody.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/ResponseBody.kt similarity index 57% rename from security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/ResponseBody.kt rename to security-c4po-api/src/main/kotlin/com/securityc4po/api/ResponseBody.kt index c7f5688..855c4d2 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/ResponseBody.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/ResponseBody.kt @@ -1,3 +1,3 @@ -package com.securityc4po.api.v1 +package com.securityc4po.api typealias ResponseBody = Map diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/SecurityC4POApplication.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/SecurityC4POApplication.kt similarity index 88% rename from security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/SecurityC4POApplication.kt rename to security-c4po-api/src/main/kotlin/com/securityc4po/api/SecurityC4POApplication.kt index de9930f..3d8d434 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/SecurityC4POApplication.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/SecurityC4POApplication.kt @@ -1,4 +1,4 @@ -package com.securityc4po.api.v1 +package com.securityc4po.api import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/configuration/SpotBugsConstants.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/SpotBugsConstants.kt similarity index 95% rename from security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/configuration/SpotBugsConstants.kt rename to security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/SpotBugsConstants.kt index c09443d..d11a99a 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/configuration/SpotBugsConstants.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/SpotBugsConstants.kt @@ -1,4 +1,4 @@ -package com.securityc4po.api.v1.configuration +package com.securityc4po.api.configuration // Constants for SpotBugs warning suppressions const val NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR = "NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR" diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/Appuser.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/Appuser.kt new file mode 100644 index 0000000..bb6ad3e --- /dev/null +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/Appuser.kt @@ -0,0 +1,47 @@ +package com.securityc4po.api.configuration.security + +import java.util.stream.Collectors +import org.springframework.security.core.authority.SimpleGrantedAuthority +import org.springframework.security.core.GrantedAuthority +import org.springframework.security.core.userdetails.UserDetails + +class Appuser : UserDetails { + + override fun getAuthorities(): Collection { + return listOf("user").stream().map { + it.toUpperCase() + }.map { + ROLE_PREFIX + it + }.map { + SimpleGrantedAuthority(it) + }.collect(Collectors.toList()) + } + + override fun getPassword(): String { + return "n/a" + } + + override fun getUsername(): String { + return "n/a" + } + + override fun isAccountNonExpired(): Boolean { + return true + } + + override fun isAccountNonLocked(): Boolean { + return true + } + + override fun isCredentialsNonExpired(): Boolean { + return true + } + + override fun isEnabled(): Boolean { + return true + } + + companion object { + private val ROLE_PREFIX = "ROLE_" + } +} diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/AppuserJwtAuthConverter.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/AppuserJwtAuthConverter.kt new file mode 100644 index 0000000..68bf153 --- /dev/null +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/AppuserJwtAuthConverter.kt @@ -0,0 +1,53 @@ +package com.securityc4po.api.configuration.security + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import org.springframework.core.convert.converter.Converter +import org.springframework.security.authentication.AbstractAuthenticationToken +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.core.GrantedAuthority +import org.springframework.security.core.authority.SimpleGrantedAuthority +import org.springframework.security.oauth2.jwt.Jwt +import reactor.core.publisher.Mono +import java.util.stream.Collectors + +/** JWT converter that takes the roles from 'groups' claim of JWT token. */ +class AppuserJwtAuthConverter( + private val appuserDetailsService: UserAccountDetailsService) : Converter> { + + override fun convert(jwt: Jwt): Mono { + val authorities = extractAuthorities(jwt) + return appuserDetailsService + .findByUsername(jwt.getClaimAsString("sub")) + .map { u -> + UsernamePasswordAuthenticationToken(u, "n/a", authorities); + } + } + + private fun extractAuthorities(jwt: Jwt): Collection { + return this.getScopes(jwt).stream().map { authority -> + ROLE_PREFIX + authority.toUpperCase() + }.map { + SimpleGrantedAuthority(it) + }.collect(Collectors.toList()) + } + + private fun getScopes(jwt: Jwt): Collection { + val mapper = ObjectMapper() + val scopes = jwt.getClaims().get(GROUPS_CLAIM).toString() + if (scopes != null) { + val roleStringValue = mapper.readTree(scopes).get("roles").toString() + val roles = mapper.readValue>(roleStringValue) + if (!roles.isEmpty()){ + return roles + } + } + + return emptyList() + } + + companion object { + private val GROUPS_CLAIM = "realm_access" + private val ROLE_PREFIX = "ROLE_" + } +} diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/ModelmapperCfg.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/ModelmapperCfg.kt new file mode 100644 index 0000000..70da72f --- /dev/null +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/ModelmapperCfg.kt @@ -0,0 +1,17 @@ +package com.securityc4po.api.configuration.security + +import org.modelmapper.ModelMapper +import org.modelmapper.convention.MatchingStrategies +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class ModelmapperCfg { + + @Bean + fun modelMapper(): ModelMapper { + val modelMapper = ModelMapper() + modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT) + return modelMapper + } +} diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/UserAccountDetailsService.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/UserAccountDetailsService.kt new file mode 100644 index 0000000..39618f2 --- /dev/null +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/UserAccountDetailsService.kt @@ -0,0 +1,15 @@ +package com.securityc4po.api.configuration.security + +import org.springframework.security.core.userdetails.ReactiveUserDetailsService +import org.springframework.security.core.userdetails.UserDetails +import org.springframework.stereotype.Service +import reactor.core.publisher.Mono +import reactor.kotlin.core.publisher.toMono + +@Service +class UserAccountDetailsService : ReactiveUserDetailsService { + + override fun findByUsername(username: String): Mono { + return Appuser().toMono() + } +} diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/WebSecurityConfiguration.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/WebSecurityConfiguration.kt new file mode 100644 index 0000000..fb358b7 --- /dev/null +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/WebSecurityConfiguration.kt @@ -0,0 +1,48 @@ +package com.securityc4po.api.configuration.security + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.http.HttpMethod +import org.springframework.web.cors.CorsConfiguration +import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity +import org.springframework.security.config.web.server.ServerHttpSecurity +import org.springframework.security.web.server.SecurityWebFilterChain + +@Configuration +@EnableWebFluxSecurity +@EnableReactiveMethodSecurity +class WebSecurityConfiguration(private val userAccountDetailsService: UserAccountDetailsService) { + + @Bean + fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { + http.cors().configurationSource { + CorsConfiguration().apply { + this.applyPermitDefaultValues() + this.addAllowedMethod(HttpMethod.DELETE) + this.addAllowedMethod(HttpMethod.PATCH) + this.addAllowedMethod(HttpMethod.POST) + this.addAllowedMethod(HttpMethod.GET) + this.addAllowedMethod(HttpMethod.PUT) + } + } + .and() + .csrf() + .disable() + .authorizeExchange() + .pathMatchers(HttpMethod.GET, "/v1/projects/**").authenticated() + .pathMatchers("/actuator/**").permitAll() + .pathMatchers("/docs/SecurityC4PO.html").permitAll() + .anyExchange().authenticated() + .and() + .oauth2ResourceServer() + .jwt() + .jwtAuthenticationConverter(appuserJwtAuthenticationConverter()) + return http.build() + } + + @Bean + fun appuserJwtAuthenticationConverter(): AppuserJwtAuthConverter { + return AppuserJwtAuthConverter(userAccountDetailsService) + } +} diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/extensions/InlineFuntions.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/extensions/InlineFuntions.kt similarity index 62% rename from security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/extensions/InlineFuntions.kt rename to security-c4po-api/src/main/kotlin/com/securityc4po/api/extensions/InlineFuntions.kt index e772753..ba25170 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/extensions/InlineFuntions.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/extensions/InlineFuntions.kt @@ -1,4 +1,4 @@ -package com.securityc4po.api.v1.extensions +package com.securityc4po.api.extensions import org.slf4j.LoggerFactory diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/http/AddResponseHeaderFilter.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/http/AddResponseHeaderFilter.kt new file mode 100644 index 0000000..eb5dfad --- /dev/null +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/http/AddResponseHeaderFilter.kt @@ -0,0 +1,23 @@ +package com.securityc4po.api.http + +import org.springframework.stereotype.Component +import org.springframework.web.server.ServerWebExchange +import org.springframework.web.server.WebFilter +import org.springframework.web.server.WebFilterChain +import reactor.core.publisher.Mono + +@Component +class AddResponseHeaderFilter: WebFilter { + + override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono { + val httpHeaders: HashMap = HashMap() + httpHeaders.put("Application-Name", ApplicationHeaders.APPLICATION_NAME) + httpHeaders.put("X-Version", ApplicationHeaders.XVERSION.toString()) + + return chain.filter( + exchange.apply { + response.headers.setAll(httpHeaders) + } + ) + } +} diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/http/ApplicationHeaders.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/http/ApplicationHeaders.kt new file mode 100644 index 0000000..255d8b2 --- /dev/null +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/http/ApplicationHeaders.kt @@ -0,0 +1,9 @@ +package com.securityc4po.api.http + +import org.springframework.http.HttpHeaders + +object ApplicationHeaders { + const val AUTHORIZATION = HttpHeaders.AUTHORIZATION + const val APPLICATION_NAME = "SecurityC4PO" + const val XVERSION = 1 +} \ No newline at end of file diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/http/RequestLogIntercepter.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/http/RequestLogIntercepter.kt new file mode 100644 index 0000000..8d439b7 --- /dev/null +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/http/RequestLogIntercepter.kt @@ -0,0 +1,24 @@ +package com.securityc4po.api.http + +import com.securityc4po.api.extensions.getLoggerFor +import org.springframework.context.annotation.Bean +import org.springframework.stereotype.Component +import org.springframework.web.server.WebFilter + +@Component +class RequestLogIntercepter { + + private val logger = getLoggerFor() + + @Bean + fun loggingFilter(): WebFilter = + WebFilter { exchange, chain -> + val request = exchange.request + if (request.headers.getFirst(ApplicationHeaders.AUTHORIZATION) == null) { + logger.warn("No Authorization header present for request: ${request.id}") + } + logger.info("Request recognized: [id: ${request.id}, method=${request.method}, path=${request.path.pathWithinApplication()}, params=[${request.queryParams}] }") + val result = chain.filter(exchange) + return@WebFilter result + } +} \ No newline at end of file diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/project/Project.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/Project.kt similarity index 92% rename from security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/project/Project.kt rename to security-c4po-api/src/main/kotlin/com/securityc4po/api/project/Project.kt index 8e8ed67..a98ef98 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/project/Project.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/Project.kt @@ -1,7 +1,7 @@ -package com.securityc4po.api.v1.project +package com.securityc4po.api.project import com.fasterxml.jackson.annotation.JsonFormat -import com.securityc4po.api.v1.ResponseBody +import com.securityc4po.api.ResponseBody import java.time.Instant import java.util.UUID diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/project/ProjectController.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectController.kt similarity index 68% rename from security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/project/ProjectController.kt rename to security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectController.kt index 7c247a8..fffde87 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/project/ProjectController.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectController.kt @@ -1,14 +1,15 @@ -package com.securityc4po.api.v1.project +package com.securityc4po.api.project -import com.securityc4po.api.v1.ResponseBody -import com.securityc4po.api.v1.configuration.BC_BAD_CAST_TO_ABSTRACT_COLLECTION -import com.securityc4po.api.v1.extensions.getLoggerFor +import com.securityc4po.api.configuration.BC_BAD_CAST_TO_ABSTRACT_COLLECTION +import com.securityc4po.api.extensions.getLoggerFor import edu.umd.cs.findbugs.annotations.SuppressFBWarnings +import com.securityc4po.api.ResponseBody +import org.springframework.http.HttpHeaders import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* @RestController -@RequestMapping("/v1/projects") +@RequestMapping("/projects") @CrossOrigin( origins = [], allowCredentials = "false", @@ -25,4 +26,4 @@ class ProjectController(private val projectService: ProjectService) { return projectService.getProjects() } -} \ No newline at end of file +} diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/project/ProjectEntity.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectEntity.kt similarity index 55% rename from security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/project/ProjectEntity.kt rename to security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectEntity.kt index d136e06..87787b4 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/project/ProjectEntity.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectEntity.kt @@ -1,6 +1,6 @@ -package com.securityc4po.api.v1.project +package com.securityc4po.api.project -import com.securityc4po.api.v1.BaseEntity +import com.securityc4po.api.BaseEntity /* * @Document(collection = "project") @@ -8,4 +8,4 @@ import com.securityc4po.api.v1.BaseEntity */ open class ProjectEntity( data: Project -) : BaseEntity(data) \ No newline at end of file +) : BaseEntity(data) diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/project/ProjectService.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectService.kt similarity index 92% rename from security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/project/ProjectService.kt rename to security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectService.kt index caafb31..5b10529 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/project/ProjectService.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectService.kt @@ -1,6 +1,6 @@ -package com.securityc4po.api.v1.project +package com.securityc4po.api.project -import com.securityc4po.api.v1.extensions.getLoggerFor +import com.securityc4po.api.extensions.getLoggerFor import org.junit.BeforeClass import org.springframework.stereotype.Service import reactor.core.publisher.Flux @@ -25,7 +25,6 @@ class ProjectService() { mapper.registerModule(JavaTimeModule()) } - /** * Get all [Project]s * @@ -37,4 +36,4 @@ class ProjectService() { /* After database integration the return should be Flux of ProjectEntity */ return jsonProjectList; } -} \ No newline at end of file +} diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/user/User.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/user/User.kt similarity index 85% rename from security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/user/User.kt rename to security-c4po-api/src/main/kotlin/com/securityc4po/api/user/User.kt index 0e0c92d..babd8ea 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/user/User.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/user/User.kt @@ -1,4 +1,4 @@ -package com.securityc4po.api.v1.user +package com.securityc4po.api.user data class User( val id: String, @@ -12,4 +12,4 @@ data class User( val email: String? = null, val interfaceLang: String? = null -) \ No newline at end of file +) diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/user/UserEntity.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/user/UserEntity.kt similarity index 55% rename from security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/user/UserEntity.kt rename to security-c4po-api/src/main/kotlin/com/securityc4po/api/user/UserEntity.kt index 7559c06..ab22d59 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/v1/user/UserEntity.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/user/UserEntity.kt @@ -1,6 +1,6 @@ -package com.securityc4po.api.v1.user +package com.securityc4po.api.user -import com.securityc4po.api.v1.BaseEntity +import com.securityc4po.api.BaseEntity /* * @Document(collection = "user") @@ -8,4 +8,4 @@ import com.securityc4po.api.v1.BaseEntity */ open class UserEntity( data: User -) : BaseEntity(data) \ No newline at end of file +) : BaseEntity(data) diff --git a/security-c4po-api/src/main/resources/application.properties b/security-c4po-api/src/main/resources/application.properties index 970f9f0..c467b52 100644 --- a/security-c4po-api/src/main/resources/application.properties +++ b/security-c4po-api/src/main/resources/application.properties @@ -15,4 +15,9 @@ management.endpoints.web.exposure.include=info, health, metrics # spring.data.mongodb.host=localhost # spring.data.mongodb.port=27017 # spring.main.allow-bean-definition-overriding=true -# spring.data.mongodb.auto-index-creation=true \ No newline at end of file +# spring.data.mongodb.auto-index-creation=true + +## IdentityProvider (Keycloak for tests) ## +spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8888/auth/realms/c4po_realm_local +keycloakhost=localhost +keycloak.client.url=http://localhost:8888/ \ No newline at end of file diff --git a/security-c4po-api/src/test/kotlin/com.securityc4po.api.v1/SecurityC4POApplicationTests.kt b/security-c4po-api/src/test/kotlin/com.securityc4po.api.v1/SecurityC4POApplicationTests.kt deleted file mode 100644 index 588044f..0000000 --- a/security-c4po-api/src/test/kotlin/com.securityc4po.api.v1/SecurityC4POApplicationTests.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.securityc4po.api.v1 - -import org.junit.jupiter.api.Test -import org.springframework.boot.test.context.SpringBootTest - -@SpringBootTest -class SecurityC4POApplicationTests { - - @Test - fun contextLoads() { - } - -} diff --git a/security-c4po-api/src/test/kotlin/com.securityc4po.api.v1/BaseContainerizedTest.kt b/security-c4po-api/src/test/kotlin/com/securityc4po/api/BaseContainerizedTest.kt similarity index 85% rename from security-c4po-api/src/test/kotlin/com.securityc4po.api.v1/BaseContainerizedTest.kt rename to security-c4po-api/src/test/kotlin/com/securityc4po/api/BaseContainerizedTest.kt index bcd4e65..a383f67 100644 --- a/security-c4po-api/src/test/kotlin/com.securityc4po.api.v1/BaseContainerizedTest.kt +++ b/security-c4po-api/src/test/kotlin/com/securityc4po/api/BaseContainerizedTest.kt @@ -1,4 +1,4 @@ -package com.securityc4po.api.v1 +package com.securityc4po.api import org.junit.jupiter.api.TestInstance import org.springframework.test.context.TestPropertySource diff --git a/security-c4po-api/src/test/kotlin/com.securityc4po.api.v1/BaseDocumentationIntTest.kt b/security-c4po-api/src/test/kotlin/com/securityc4po/api/BaseDocumentationIntTest.kt similarity index 80% rename from security-c4po-api/src/test/kotlin/com.securityc4po.api.v1/BaseDocumentationIntTest.kt rename to security-c4po-api/src/test/kotlin/com/securityc4po/api/BaseDocumentationIntTest.kt index 135e160..cff40b3 100644 --- a/security-c4po-api/src/test/kotlin/com.securityc4po.api.v1/BaseDocumentationIntTest.kt +++ b/security-c4po-api/src/test/kotlin/com/securityc4po/api/BaseDocumentationIntTest.kt @@ -1,8 +1,8 @@ -package com.securityc4po.api.v1 +package com.securityc4po.api -import com.securityc4po.api.v1.configuration.MESSAGE_NOT_INITIALIZED_REDUNDANT_NULLCHECK -import com.securityc4po.api.v1.configuration.NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR -import com.securityc4po.api.v1.configuration.RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE +import com.securityc4po.api.configuration.MESSAGE_NOT_INITIALIZED_REDUNDANT_NULLCHECK +import com.securityc4po.api.configuration.NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR +import com.securityc4po.api.configuration.RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE import edu.umd.cs.findbugs.annotations.SuppressFBWarnings import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.extension.ExtendWith @@ -28,7 +28,7 @@ abstract class BaseDocumentationIntTest : BaseContainerizedTest() { @BeforeEach fun setupDocs(restDocumentation: RestDocumentationContextProvider) { webTestClient = WebTestClient.bindToServer() - .baseUrl("http://localhost:$port") + .baseUrl("com.securityc4po.api.http://localhost:$port") .filter(documentationConfiguration(restDocumentation)) .responseTimeout(Duration.ofMillis(10000)) .build() diff --git a/security-c4po-api/src/test/kotlin/com.securityc4po.api.v1/BaseIntTest.kt b/security-c4po-api/src/test/kotlin/com/securityc4po/api/BaseIntTest.kt similarity index 80% rename from security-c4po-api/src/test/kotlin/com.securityc4po.api.v1/BaseIntTest.kt rename to security-c4po-api/src/test/kotlin/com/securityc4po/api/BaseIntTest.kt index c0f20fd..65e7b77 100644 --- a/security-c4po-api/src/test/kotlin/com.securityc4po.api.v1/BaseIntTest.kt +++ b/security-c4po-api/src/test/kotlin/com/securityc4po/api/BaseIntTest.kt @@ -1,4 +1,4 @@ -package com.securityc4po.api.v1 +package com.securityc4po.api import org.junit.jupiter.api.extension.ExtendWith import org.springframework.boot.test.context.SpringBootTest diff --git a/security-c4po-api/src/test/kotlin/com.securityc4po.api.v1/project/ProjectControllerDocumentationTest.kt b/security-c4po-api/src/test/kotlin/com/securityc4po/api/project/ProjectControllerDocumentationTest.kt similarity index 96% rename from security-c4po-api/src/test/kotlin/com.securityc4po.api.v1/project/ProjectControllerDocumentationTest.kt rename to security-c4po-api/src/test/kotlin/com/securityc4po/api/project/ProjectControllerDocumentationTest.kt index 829cd08..0bf18a8 100644 --- a/security-c4po-api/src/test/kotlin/com.securityc4po.api.v1/project/ProjectControllerDocumentationTest.kt +++ b/security-c4po-api/src/test/kotlin/com/securityc4po/api/project/ProjectControllerDocumentationTest.kt @@ -1,8 +1,8 @@ -package com.securityc4po.api.v1.project +package com.securityc4po.api.project import com.github.tomakehurst.wiremock.common.Json -import com.securityc4po.api.v1.BaseDocumentationIntTest -import com.securityc4po.api.v1.configuration.SIC_INNER_SHOULD_BE_STATIC +import com.securityc4po.api.BaseDocumentationIntTest +import com.securityc4po.api.configuration.SIC_INNER_SHOULD_BE_STATIC import edu.umd.cs.findbugs.annotations.SuppressFBWarnings import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Nested diff --git a/security-c4po-api/src/test/kotlin/com.securityc4po.api.v1/project/ProjectControllerIntTest.kt b/security-c4po-api/src/test/kotlin/com/securityc4po/api/project/ProjectControllerIntTest.kt similarity index 94% rename from security-c4po-api/src/test/kotlin/com.securityc4po.api.v1/project/ProjectControllerIntTest.kt rename to security-c4po-api/src/test/kotlin/com/securityc4po/api/project/ProjectControllerIntTest.kt index b3f1ebd..335a505 100644 --- a/security-c4po-api/src/test/kotlin/com.securityc4po.api.v1/project/ProjectControllerIntTest.kt +++ b/security-c4po-api/src/test/kotlin/com/securityc4po/api/project/ProjectControllerIntTest.kt @@ -1,9 +1,9 @@ -package com.securityc4po.api.v1.project +package com.securityc4po.api.project import com.github.tomakehurst.wiremock.common.Json -import com.securityc4po.api.v1.BaseIntTest -import com.securityc4po.api.v1.configuration.SIC_INNER_SHOULD_BE_STATIC -import com.securityc4po.api.v1.configuration.URF_UNREAD_FIELD +import com.securityc4po.api.BaseIntTest +import com.securityc4po.api.configuration.SIC_INNER_SHOULD_BE_STATIC +import com.securityc4po.api.configuration.URF_UNREAD_FIELD import edu.umd.cs.findbugs.annotations.SuppressFBWarnings import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Nested diff --git a/security-c4po-api/src/test/kotlin/com.securityc4po.api.v1/project/ProjectServiceTest.kt b/security-c4po-api/src/test/kotlin/com/securityc4po/api/project/ProjectServiceTest.kt similarity index 82% rename from security-c4po-api/src/test/kotlin/com.securityc4po.api.v1/project/ProjectServiceTest.kt rename to security-c4po-api/src/test/kotlin/com/securityc4po/api/project/ProjectServiceTest.kt index 80291e0..a2ccded 100644 --- a/security-c4po-api/src/test/kotlin/com.securityc4po.api.v1/project/ProjectServiceTest.kt +++ b/security-c4po-api/src/test/kotlin/com/securityc4po/api/project/ProjectServiceTest.kt @@ -1,7 +1,7 @@ -package com.securityc4po.api.v1.project +package com.securityc4po.api.project import com.nhaarman.mockitokotlin2.mock -import com.securityc4po.api.v1.configuration.SIC_INNER_SHOULD_BE_STATIC +import com.securityc4po.api.configuration.SIC_INNER_SHOULD_BE_STATIC import edu.umd.cs.findbugs.annotations.SuppressFBWarnings import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test diff --git a/security-c4po-cfg/backend/docker-compose.backend.yml b/security-c4po-cfg/backend/docker-compose.backend.yml new file mode 100644 index 0000000..005e749 --- /dev/null +++ b/security-c4po-cfg/backend/docker-compose.backend.yml @@ -0,0 +1,16 @@ +version: '3.1' + +services: + c4po-api: + build: '../../security-c4po-api' + image: security-c4po-api:latest + container_name: security-c4po-api + deploy: + resources: + limits: + memory: "1G" + ports: + - '8443:8443' + +networks: + c4po: diff --git a/security-c4po-cfg/cfg/c4po_realm_export.json b/security-c4po-cfg/cfg/c4po_realm_export.json new file mode 100644 index 0000000..d948248 --- /dev/null +++ b/security-c4po-cfg/cfg/c4po_realm_export.json @@ -0,0 +1,1811 @@ +{ + "id" : "c4po_realm_local", + "realm" : "c4po_realm_local", + "notBefore" : 0, + "revokeRefreshToken" : false, + "refreshTokenMaxReuse" : 0, + "accessTokenLifespan" : 300, + "accessTokenLifespanForImplicitFlow" : 900, + "ssoSessionIdleTimeout" : 1800, + "ssoSessionMaxLifespan" : 36000, + "ssoSessionIdleTimeoutRememberMe" : 0, + "ssoSessionMaxLifespanRememberMe" : 0, + "offlineSessionIdleTimeout" : 2592000, + "offlineSessionMaxLifespanEnabled" : false, + "offlineSessionMaxLifespan" : 5184000, + "clientSessionIdleTimeout" : 0, + "clientSessionMaxLifespan" : 0, + "clientOfflineSessionIdleTimeout" : 0, + "clientOfflineSessionMaxLifespan" : 0, + "accessCodeLifespan" : 60, + "accessCodeLifespanUserAction" : 300, + "accessCodeLifespanLogin" : 1800, + "actionTokenGeneratedByAdminLifespan" : 43200, + "actionTokenGeneratedByUserLifespan" : 300, + "enabled" : true, + "sslRequired" : "external", + "registrationAllowed" : false, + "registrationEmailAsUsername" : false, + "rememberMe" : false, + "verifyEmail" : false, + "loginWithEmailAllowed" : true, + "duplicateEmailsAllowed" : false, + "resetPasswordAllowed" : false, + "editUsernameAllowed" : false, + "bruteForceProtected" : false, + "permanentLockout" : false, + "maxFailureWaitSeconds" : 900, + "minimumQuickLoginWaitSeconds" : 60, + "waitIncrementSeconds" : 60, + "quickLoginCheckMilliSeconds" : 1000, + "maxDeltaTimeSeconds" : 43200, + "failureFactor" : 30, + "roles" : { + "realm" : [ { + "id" : "2faaa7e1-01d0-480d-b397-66155bf8a950", + "name" : "uma_authorization", + "description" : "${role_uma_authorization}", + "composite" : false, + "clientRole" : false, + "containerId" : "c4po_realm_local", + "attributes" : { } + }, { + "id" : "1fabc468-65bf-4651-8436-7d8d6a3a79e7", + "name" : "c4po_user", + "description" : "This is a normal c4po User role", + "composite" : true, + "composites" : { + "realm" : [ "offline_access", "uma_authorization" ] + }, + "clientRole" : false, + "containerId" : "c4po_realm_local", + "attributes" : { } + }, { + "id" : "9b6774c4-335d-44fb-82ba-d6e18dde814d", + "name" : "offline_access", + "description" : "${role_offline-access}", + "composite" : false, + "clientRole" : false, + "containerId" : "c4po_realm_local", + "attributes" : { } + }, { + "id" : "3dc67a08-dc0a-4bb1-8808-b49bbf4611b0", + "name" : "c4po_admin", + "description" : "This is an c4po admin role", + "composite" : true, + "composites" : { + "realm" : [ "c4po_user", "offline_access", "uma_authorization" ] + }, + "clientRole" : false, + "containerId" : "c4po_realm_local", + "attributes" : { } + } ], + "client" : { + "realm-management" : [ { + "id" : "72960cc0-cb99-4759-b342-7096bcd3c92a", + "name" : "create-client", + "description" : "${role_create-client}", + "composite" : false, + "clientRole" : true, + "containerId" : "fa74c4e8-a9c0-4fa9-bb21-2ad3535b08ef", + "attributes" : { } + }, { + "id" : "c90d908c-7e17-4ada-9f3b-aa623e449ef1", + "name" : "manage-clients", + "description" : "${role_manage-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "fa74c4e8-a9c0-4fa9-bb21-2ad3535b08ef", + "attributes" : { } + }, { + "id" : "dc9e7c69-4ed1-403d-ac42-55c507f3be40", + "name" : "manage-realm", + "description" : "${role_manage-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "fa74c4e8-a9c0-4fa9-bb21-2ad3535b08ef", + "attributes" : { } + }, { + "id" : "3e152bff-b1b3-491e-8b41-5824f417357e", + "name" : "query-groups", + "description" : "${role_query-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "fa74c4e8-a9c0-4fa9-bb21-2ad3535b08ef", + "attributes" : { } + }, { + "id" : "82f7b76d-b528-4fd5-aa9f-d89f1df9e1e1", + "name" : "impersonation", + "description" : "${role_impersonation}", + "composite" : false, + "clientRole" : true, + "containerId" : "fa74c4e8-a9c0-4fa9-bb21-2ad3535b08ef", + "attributes" : { } + }, { + "id" : "fa7c22da-a9ef-4895-ae56-57403f279631", + "name" : "view-authorization", + "description" : "${role_view-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "fa74c4e8-a9c0-4fa9-bb21-2ad3535b08ef", + "attributes" : { } + }, { + "id" : "d0610310-b6e1-46cc-90e3-64a9948f1e1d", + "name" : "view-clients", + "description" : "${role_view-clients}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-clients" ] + } + }, + "clientRole" : true, + "containerId" : "fa74c4e8-a9c0-4fa9-bb21-2ad3535b08ef", + "attributes" : { } + }, { + "id" : "c8fb89bc-00a8-4d6b-bb5c-d13cba12840d", + "name" : "manage-identity-providers", + "description" : "${role_manage-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "fa74c4e8-a9c0-4fa9-bb21-2ad3535b08ef", + "attributes" : { } + }, { + "id" : "84338fd5-5a70-4c6a-b580-adb7416cb8b6", + "name" : "view-events", + "description" : "${role_view-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "fa74c4e8-a9c0-4fa9-bb21-2ad3535b08ef", + "attributes" : { } + }, { + "id" : "f36d5b71-6f9e-433e-a549-5f8dab3fa39d", + "name" : "query-realms", + "description" : "${role_query-realms}", + "composite" : false, + "clientRole" : true, + "containerId" : "fa74c4e8-a9c0-4fa9-bb21-2ad3535b08ef", + "attributes" : { } + }, { + "id" : "d10b3abb-4120-4d28-a3a5-2bc2600502a6", + "name" : "view-realm", + "description" : "${role_view-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "fa74c4e8-a9c0-4fa9-bb21-2ad3535b08ef", + "attributes" : { } + }, { + "id" : "96a21ada-31a8-4d6a-9e26-f7551ca6ec3b", + "name" : "query-clients", + "description" : "${role_query-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "fa74c4e8-a9c0-4fa9-bb21-2ad3535b08ef", + "attributes" : { } + }, { + "id" : "7fcf212c-4371-48be-a75a-ec93830c4f8b", + "name" : "manage-users", + "description" : "${role_manage-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "fa74c4e8-a9c0-4fa9-bb21-2ad3535b08ef", + "attributes" : { } + }, { + "id" : "26f88bad-f69b-464f-89f1-43b987589173", + "name" : "manage-authorization", + "description" : "${role_manage-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "fa74c4e8-a9c0-4fa9-bb21-2ad3535b08ef", + "attributes" : { } + }, { + "id" : "13ff84e3-fef2-4c52-a30b-89602dd22457", + "name" : "query-users", + "description" : "${role_query-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "fa74c4e8-a9c0-4fa9-bb21-2ad3535b08ef", + "attributes" : { } + }, { + "id" : "24928355-a003-4dc5-8272-71f32c3982e5", + "name" : "manage-events", + "description" : "${role_manage-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "fa74c4e8-a9c0-4fa9-bb21-2ad3535b08ef", + "attributes" : { } + }, { + "id" : "60932726-3a3b-44f0-b668-b1ec55946404", + "name" : "view-users", + "description" : "${role_view-users}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-groups", "query-users" ] + } + }, + "clientRole" : true, + "containerId" : "fa74c4e8-a9c0-4fa9-bb21-2ad3535b08ef", + "attributes" : { } + }, { + "id" : "18447ab5-84fc-4dc5-8f1b-ac39bfbd72a6", + "name" : "realm-admin", + "description" : "${role_realm-admin}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "create-client", "manage-clients", "manage-realm", "query-groups", "impersonation", "view-authorization", "view-clients", "manage-identity-providers", "view-events", "query-realms", "view-realm", "query-clients", "manage-users", "manage-authorization", "query-users", "manage-events", "view-users", "view-identity-providers" ] + } + }, + "clientRole" : true, + "containerId" : "fa74c4e8-a9c0-4fa9-bb21-2ad3535b08ef", + "attributes" : { } + }, { + "id" : "ca1a9e13-0f97-4c69-a37a-0edc9a822485", + "name" : "view-identity-providers", + "description" : "${role_view-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "fa74c4e8-a9c0-4fa9-bb21-2ad3535b08ef", + "attributes" : { } + } ], + "security-admin-console" : [ ], + "admin-cli" : [ ], + "c4po_local" : [ { + "id" : "e26a27e7-1648-491b-832a-8bf751d378bb", + "name" : "user", + "composite" : false, + "clientRole" : true, + "containerId" : "6cbc559d-073e-40d7-8b73-b2dcdc438461", + "attributes" : { } + } ], + "security-c4po-api" : [ ], + "account-console" : [ ], + "broker" : [ { + "id" : "ef655eb1-164c-49e3-be85-510395bfd7d9", + "name" : "read-token", + "description" : "${role_read-token}", + "composite" : false, + "clientRole" : true, + "containerId" : "f90fb534-a4bf-4e08-b0d3-8a5552eb5a12", + "attributes" : { } + } ], + "security-c4po-angular" : [ ], + "account" : [ { + "id" : "1d2d7350-47be-4131-b634-297b59731ccf", + "name" : "view-profile", + "description" : "${role_view-profile}", + "composite" : false, + "clientRole" : true, + "containerId" : "a7f62881-aa9e-4565-afeb-1d6305d3c56e", + "attributes" : { } + }, { + "id" : "14139dff-c524-4efd-84a1-9fbb3e8bafae", + "name" : "manage-account", + "description" : "${role_manage-account}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "manage-account-links" ] + } + }, + "clientRole" : true, + "containerId" : "a7f62881-aa9e-4565-afeb-1d6305d3c56e", + "attributes" : { } + }, { + "id" : "f590afe8-3e54-491d-97b1-e29f56b22df3", + "name" : "manage-account-links", + "description" : "${role_manage-account-links}", + "composite" : false, + "clientRole" : true, + "containerId" : "a7f62881-aa9e-4565-afeb-1d6305d3c56e", + "attributes" : { } + }, { + "id" : "897b62b3-c4d8-4998-9536-9c2d59bd2896", + "name" : "manage-consent", + "description" : "${role_manage-consent}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "view-consent" ] + } + }, + "clientRole" : true, + "containerId" : "a7f62881-aa9e-4565-afeb-1d6305d3c56e", + "attributes" : { } + }, { + "id" : "34488e12-5873-490b-a25b-986e62a21caa", + "name" : "view-consent", + "description" : "${role_view-consent}", + "composite" : false, + "clientRole" : true, + "containerId" : "a7f62881-aa9e-4565-afeb-1d6305d3c56e", + "attributes" : { } + }, { + "id" : "18770e33-50c1-4bb8-960d-d8acd163f5ab", + "name" : "view-applications", + "description" : "${role_view-applications}", + "composite" : false, + "clientRole" : true, + "containerId" : "a7f62881-aa9e-4565-afeb-1d6305d3c56e", + "attributes" : { } + } ] + } + }, + "groups" : [ ], + "defaultRoles" : [ "uma_authorization", "offline_access" ], + "requiredCredentials" : [ "password" ], + "otpPolicyType" : "totp", + "otpPolicyAlgorithm" : "HmacSHA1", + "otpPolicyInitialCounter" : 0, + "otpPolicyDigits" : 6, + "otpPolicyLookAheadWindow" : 1, + "otpPolicyPeriod" : 30, + "otpSupportedApplications" : [ "FreeOTP", "Google Authenticator" ], + "webAuthnPolicyRpEntityName" : "keycloak", + "webAuthnPolicySignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyRpId" : "", + "webAuthnPolicyAttestationConveyancePreference" : "not specified", + "webAuthnPolicyAuthenticatorAttachment" : "not specified", + "webAuthnPolicyRequireResidentKey" : "not specified", + "webAuthnPolicyUserVerificationRequirement" : "not specified", + "webAuthnPolicyCreateTimeout" : 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyAcceptableAaguids" : [ ], + "webAuthnPolicyPasswordlessRpEntityName" : "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyPasswordlessRpId" : "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference" : "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment" : "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey" : "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement" : "not specified", + "webAuthnPolicyPasswordlessCreateTimeout" : 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ], + "users" : [ { + "id" : "10e06d7a-8dd0-4ecd-8963-056b45079c4f", + "createdTimestamp" : 1617897245335, + "username" : "ttt", + "enabled" : true, + "totp" : false, + "emailVerified" : false, + "firstName" : "test", + "lastName" : "user", + "credentials" : [ { + "id" : "7026fefc-ae26-442b-acae-92f1f2d24eac", + "type" : "password", + "createdDate" : 1617897287400, + "secretData" : "{\"value\":\"mhW4yxOg+8bcyPF4yWsfPZnLGUp4oaqc9aNA+WBcpr9qXgs/Jw+rM2VlLEgeD/kXGItcScA8V20sVGrMWT94Yw==\",\"salt\":\"nkH510WAwjKZJqd/ZEkIHA==\"}", + "credentialData" : "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\"}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "uma_authorization", "c4po_user", "offline_access", "c4po_admin" ], + "clientRoles" : { + "c4po_local" : [ "user" ], + "account" : [ "view-profile", "manage-account" ] + }, + "notBefore" : 0, + "groups" : [ ] + } ], + "scopeMappings" : [ { + "clientScope" : "offline_access", + "roles" : [ "offline_access" ] + } ], + "clientScopeMappings" : { + "account" : [ { + "client" : "account-console", + "roles" : [ "manage-account" ] + } ] + }, + "clients" : [ { + "id" : "a7f62881-aa9e-4565-afeb-1d6305d3c56e", + "clientId" : "account", + "name" : "${client_account}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/c4po_realm_local/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "secret" : "**********", + "defaultRoles" : [ "view-profile", "manage-account" ], + "redirectUris" : [ "/realms/c4po_realm_local/account/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "role_list", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "da51d616-1ca6-4434-a16d-b543d2a4e4c0", + "clientId" : "account-console", + "name" : "${client_account-console}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/c4po_realm_local/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "secret" : "**********", + "redirectUris" : [ "/realms/c4po_realm_local/account/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "pkce.code.challenge.method" : "S256" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "0c5a81d7-f454-4793-b4e4-60c924b73533", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { } + } ], + "defaultClientScopes" : [ "web-origins", "role_list", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "63cb2215-d2f1-4229-96fc-82fb843e283a", + "clientId" : "admin-cli", + "name" : "${client_admin-cli}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "secret" : "**********", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : false, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "role_list", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "f90fb534-a4bf-4e08-b0d3-8a5552eb5a12", + "clientId" : "broker", + "name" : "${client_broker}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "secret" : "**********", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "role_list", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "6cbc559d-073e-40d7-8b73-b2dcdc438461", + "clientId" : "c4po_local", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "secret" : "**********", + "redirectUris" : [ "http://localhost:4200/*" ], + "webOrigins" : [ "*" ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "saml.assertion.signature" : "false", + "saml.force.post.binding" : "false", + "saml.multivalued.roles" : "false", + "saml.encrypt" : "false", + "saml.server.signature" : "false", + "saml.server.signature.keyinfo.ext" : "false", + "exclude.session.state.from.auth.response" : "false", + "saml_force_name_id_format" : "false", + "saml.client.signature" : "false", + "tls.client.certificate.bound.access.tokens" : "false", + "saml.authnstatement" : "false", + "display.on.consent.screen" : "false", + "saml.onetimeuse.condition" : "false" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : true, + "nodeReRegistrationTimeout" : -1, + "defaultClientScopes" : [ "web-origins", "role_list", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "fa74c4e8-a9c0-4fa9-bb21-2ad3535b08ef", + "clientId" : "realm-management", + "name" : "${client_realm-management}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "secret" : "**********", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : true, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "role_list", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "7e165a51-6cb8-43cf-a4fe-1d0ad513586b", + "clientId" : "security-admin-console", + "name" : "${client_security-admin-console}", + "rootUrl" : "${authAdminUrl}", + "baseUrl" : "/admin/c4po_realm_local/console/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "secret" : "**********", + "redirectUris" : [ "/admin/c4po_realm_local/console/*" ], + "webOrigins" : [ "+" ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "pkce.code.challenge.method" : "S256" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "4fd1eab6-f53b-4d37-b65c-bea9845b3e9f", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + } ], + "defaultClientScopes" : [ "web-origins", "role_list", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "7f731c1c-4fd8-470a-a995-b242fc5b550d", + "clientId" : "security-c4po-angular", + "rootUrl" : "", + "adminUrl" : "", + "baseUrl" : "http://localhost:4200/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "secret" : "482621c2-e0fe-43b9-bb62-7469158e1966", + "redirectUris" : [ "http://localhost:4200/*" ], + "webOrigins" : [ "*" ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "saml.assertion.signature" : "false", + "saml.multivalued.roles" : "false", + "saml.force.post.binding" : "false", + "saml.encrypt" : "false", + "saml.server.signature" : "false", + "saml.server.signature.keyinfo.ext" : "false", + "exclude.session.state.from.auth.response" : "false", + "saml_force_name_id_format" : "false", + "saml.client.signature" : "false", + "tls.client.certificate.bound.access.tokens" : "false", + "saml.authnstatement" : "false", + "display.on.consent.screen" : "false", + "saml.onetimeuse.condition" : "false" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : true, + "nodeReRegistrationTimeout" : -1, + "defaultClientScopes" : [ "web-origins", "role_list", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "8badc11a-50e4-44ae-a292-47e3759fcaeb", + "clientId" : "security-c4po-api", + "rootUrl" : "http://localhost:8443", + "adminUrl" : "", + "baseUrl" : "http://localhost:8443/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "secret" : "0aef07ba-d8b4-405d-9dcb-f3743f966856", + "redirectUris" : [ "http://localhost:8443/*" ], + "webOrigins" : [ "*" ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "saml.assertion.signature" : "false", + "saml.multivalued.roles" : "false", + "saml.force.post.binding" : "false", + "saml.encrypt" : "false", + "saml.server.signature" : "false", + "saml.server.signature.keyinfo.ext" : "false", + "exclude.session.state.from.auth.response" : "false", + "saml_force_name_id_format" : "false", + "saml.client.signature" : "false", + "tls.client.certificate.bound.access.tokens" : "false", + "saml.authnstatement" : "false", + "display.on.consent.screen" : "false", + "saml.onetimeuse.condition" : "false" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : true, + "nodeReRegistrationTimeout" : -1, + "defaultClientScopes" : [ "web-origins", "role_list", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + } ], + "clientScopes" : [ { + "id" : "8d428e56-80df-4505-8e1a-26537e793b31", + "name" : "offline_access", + "description" : "OpenID Connect built-in scope: offline_access", + "protocol" : "openid-connect", + "attributes" : { + "consent.screen.text" : "${offlineAccessScopeConsentText}", + "display.on.consent.screen" : "true" + } + }, { + "id" : "4b171f57-736a-41b4-b67b-585bac1d8d24", + "name" : "role_list", + "description" : "SAML role list", + "protocol" : "saml", + "attributes" : { + "consent.screen.text" : "${samlRoleListScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "2b161cf6-2c63-45e8-a698-48f7297cc303", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + } ] + }, { + "id" : "ac7d05f9-d505-42e9-9b7c-1984b31e653d", + "name" : "profile", + "description" : "OpenID Connect built-in scope: profile", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${profileScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "2be3fbed-d2c8-46eb-94e0-b2efdf20ad60", + "name" : "middle name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "middleName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "middle_name", + "jsonType.label" : "String" + } + }, { + "id" : "d3465101-1321-43a7-8f65-8b782c390297", + "name" : "website", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "website", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "website", + "jsonType.label" : "String" + } + }, { + "id" : "68391807-41ec-4ce2-877d-3a808bb1bbe4", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + }, { + "id" : "d8d837e5-e1f8-45af-96b0-7c5607780e0b", + "name" : "nickname", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "nickname", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "nickname", + "jsonType.label" : "String" + } + }, { + "id" : "0667458d-83c3-4cd1-b60a-436a3bb42d2e", + "name" : "birthdate", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "birthdate", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "birthdate", + "jsonType.label" : "String" + } + }, { + "id" : "e8cd5b57-ee69-46eb-afd7-71cc68ca5384", + "name" : "full name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-full-name-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "access.token.claim" : "true", + "userinfo.token.claim" : "true" + } + }, { + "id" : "643f5ffd-4c38-4228-808d-2fd9f2a075ba", + "name" : "family name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "lastName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "family_name", + "jsonType.label" : "String" + } + }, { + "id" : "b41a9738-9529-47f8-bd90-461c072212af", + "name" : "username", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "preferred_username", + "jsonType.label" : "String" + } + }, { + "id" : "29c5817f-6101-48ff-a1e5-dbb23e3b0534", + "name" : "profile", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "profile", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "profile", + "jsonType.label" : "String" + } + }, { + "id" : "a4a193ec-25bb-4457-8287-ca2abaff5940", + "name" : "zoneinfo", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "zoneinfo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "zoneinfo", + "jsonType.label" : "String" + } + }, { + "id" : "3c132112-0285-4ef4-9317-2d94c58c9bc6", + "name" : "picture", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "picture", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "picture", + "jsonType.label" : "String" + } + }, { + "id" : "7bbf2f74-db95-47f1-8736-8b0864a01d5a", + "name" : "gender", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "gender", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "gender", + "jsonType.label" : "String" + } + }, { + "id" : "7f92e589-d307-4574-bf84-0f34bdbef9f3", + "name" : "updated at", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "updatedAt", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "updated_at", + "jsonType.label" : "String" + } + }, { + "id" : "efe55944-ab38-4fe2-9452-8499f9d52e80", + "name" : "given name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "firstName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "given_name", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "32f1098d-79a9-4da4-a94a-c873fcc0f6e1", + "name" : "email", + "description" : "OpenID Connect built-in scope: email", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${emailScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "92afef33-2843-40bc-aba1-58d462fa81cc", + "name" : "email verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "emailVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email_verified", + "jsonType.label" : "boolean" + } + }, { + "id" : "4b4d33d1-ed47-40db-a05f-4253c25dbbff", + "name" : "email", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "email", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "412cfb80-d33e-44da-a0e2-b0bde0423c00", + "name" : "address", + "description" : "OpenID Connect built-in scope: address", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${addressScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "261a490f-073d-4975-af5b-e2d9e21ea768", + "name" : "address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-address-mapper", + "consentRequired" : false, + "config" : { + "user.attribute.formatted" : "formatted", + "user.attribute.country" : "country", + "user.attribute.postal_code" : "postal_code", + "userinfo.token.claim" : "true", + "user.attribute.street" : "street", + "id.token.claim" : "true", + "user.attribute.region" : "region", + "access.token.claim" : "true", + "user.attribute.locality" : "locality" + } + } ] + }, { + "id" : "faf5c077-e43d-4433-9f5d-ddfc10f31385", + "name" : "phone", + "description" : "OpenID Connect built-in scope: phone", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${phoneScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "57383851-63a0-4599-8074-ecaddfbf5164", + "name" : "phone number verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumberVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number_verified", + "jsonType.label" : "boolean" + } + }, { + "id" : "8992684a-ea4f-490c-8cd4-6af77ab77b64", + "name" : "phone number", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumber", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "21ac5f31-d742-40c7-89a1-cd7f35036450", + "name" : "roles", + "description" : "OpenID Connect scope for add user roles to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${rolesScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "c9ab2a7d-062d-419f-90d4-7682c854857e", + "name" : "realm roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "access.token.claim" : "true", + "claim.name" : "realm_access.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + }, { + "id" : "46b6c2ed-6b50-4205-a7c2-d2fd2c93353c", + "name" : "client roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-client-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "access.token.claim" : "true", + "claim.name" : "resource_access.${client_id}.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + }, { + "id" : "60e36e8d-7456-4581-9c35-068942b61a40", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { } + } ] + }, { + "id" : "cd5f153a-ff23-43d5-81a0-6c8dc6f39a4e", + "name" : "web-origins", + "description" : "OpenID Connect scope for add allowed web origins to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false", + "consent.screen.text" : "" + }, + "protocolMappers" : [ { + "id" : "9a8031f8-997b-4899-ba60-05868f8e4b18", + "name" : "allowed web origins", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-allowed-origins-mapper", + "consentRequired" : false, + "config" : { } + } ] + }, { + "id" : "b18623a4-3595-4993-b2bd-79e94778d28b", + "name" : "microprofile-jwt", + "description" : "Microprofile - JWT built-in scope", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "a59540b9-81a8-4ca8-b0ff-bff6ceb049c2", + "name" : "upn", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "upn", + "jsonType.label" : "String" + } + }, { + "id" : "0eee48de-8c6f-4167-8958-fb4d3ef973cc", + "name" : "groups", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "multivalued" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "foo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "groups", + "jsonType.label" : "String" + } + } ] + } ], + "defaultDefaultClientScopes" : [ "role_list", "profile", "email", "roles", "web-origins" ], + "defaultOptionalClientScopes" : [ "offline_access", "address", "phone", "microprofile-jwt" ], + "browserSecurityHeaders" : { + "contentSecurityPolicyReportOnly" : "", + "xContentTypeOptions" : "nosniff", + "xRobotsTag" : "none", + "xFrameOptions" : "SAMEORIGIN", + "contentSecurityPolicy" : "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection" : "1; mode=block", + "strictTransportSecurity" : "max-age=31536000; includeSubDomains" + }, + "smtpServer" : { }, + "eventsEnabled" : false, + "eventsListeners" : [ "jboss-logging" ], + "enabledEventTypes" : [ ], + "adminEventsEnabled" : false, + "adminEventsDetailsEnabled" : false, + "components" : { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy" : [ { + "id" : "56f53138-a448-42f5-ba77-b026b1b179d0", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "46301b5f-58b3-48f2-8844-e82f1b5b5ad3", + "name" : "Max Clients Limit", + "providerId" : "max-clients", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "max-clients" : [ "200" ] + } + }, { + "id" : "898437e1-5717-4010-9306-6c3582ca5b09", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper", "oidc-address-mapper", "saml-user-attribute-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-property-mapper", "saml-user-property-mapper", "saml-role-list-mapper" ] + } + }, { + "id" : "cc2d0cd7-3d3f-4b0a-ad95-7118f36bf188", + "name" : "Full Scope Disabled", + "providerId" : "scope", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "e1ded6a4-e0af-4c3a-bc5d-a142701302c4", + "name" : "Consent Required", + "providerId" : "consent-required", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "12513189-c247-4869-8a24-ba7f694e8668", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "7511759b-c33d-4bb4-bd46-724599ea2efb", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "saml-user-attribute-mapper", "saml-role-list-mapper", "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper", "oidc-usermodel-property-mapper", "saml-user-property-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper" ] + } + }, { + "id" : "92230e65-7480-44c3-af2d-72ddee758cbc", + "name" : "Trusted Hosts", + "providerId" : "trusted-hosts", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "host-sending-registration-request-must-match" : [ "true" ], + "client-uris-must-match" : [ "true" ] + } + } ], + "org.keycloak.keys.KeyProvider" : [ { + "id" : "ea025a18-d77a-4bbc-8e3a-c6b55ccf4b3f", + "name" : "hmac-generated", + "providerId" : "hmac-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "ddd6d915-c898-4e32-b9de-f1469a2dfb6a" ], + "secret" : [ "GegBlWTwur6eFVW_fdHBsmbWZmpkLcZrhZS028OOUG3bErTFFxgjqHfH-cZ8au5uOFyquTYB1_IrzKNQB1HyMg" ], + "priority" : [ "100" ], + "algorithm" : [ "HS256" ] + } + }, { + "id" : "d54e6431-5a1c-4783-a9ca-dbbedd0b0f20", + "name" : "aes-generated", + "providerId" : "aes-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "40b8e0ac-9300-4736-9668-713676911d5e" ], + "secret" : [ "kJGFh7LGYAI2ged6rJQVDQ" ], + "priority" : [ "100" ] + } + }, { + "id" : "38c2dd59-c891-4558-8102-c26ada370abd", + "name" : "rsa-generated", + "providerId" : "rsa-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEpAIBAAKCAQEA5l/1wCtrIxaD7HW7/Qlmb4DtH1KdHzXc3J24pyLO914lvwfZBaRz4mcY9nia9/R+oCr62/EjIxVa4/SfA+O7ZUgi2TMJtwtUX3CKRlm+ktK51wnJ1TNN38zYEB9fFKuElP3Sqva+nLlmVTMyqXkDzLLhFJnc1Szu8tWXJKyrim4Oo/iAfvbmTisldgJ1YH/wcqfH08jgVhySDSXde6HejThhuDsM4FqeaPANBztpxDwYDo7sj9mD8UE9NvyzXr2o0NV+JvZv2H0RHth3KUf8iJta5tjo+wY4vQAPvkfOOdwn+XEsWIhZwKGMPeYeqETT6kKQX//UbNc+5YT513U1PQIDAQABAoIBAQCTXRrUfHGiFkr5PS6tZA/0j6IfATekuU24ieOOdkOyFLVMh3aZl2LRlmVvdCKdLfa5+gRSIOP7EzP60YXOdSRwWz5/dZhnUIX+Lv0kl0/Cl61tEsPa72CHIn+rgIXPsQ+0RtE1r3SqyCXfpkpoAhMeI+a6yNlsO7v19g8i1Jk+iIUiQxtsCGGUt+FsTao1cXq/i7F7NCS9PUD5aAVyURI5IEJ5+YXJZN68y0EBf8B2kWToMVEgLM5BJZraH+APuDbndmRFmNqe8w6X1PAYBAzubuAHrNfda/PC6whuSsupI9oRugU4LFIPdBVZLyL833xYQgAA7OaEF4KzK/E00f75AoGBAP7sm2AWtgQ6f0QPQfcS6zJJNwrfU86ay1NDoVL3eYY9rz9FXtAfUq/+Wo6nTnez1YSJjhnLeTpngquTlSlqlJdHqoAgPaOtAnZltVZ21hU3/9KHfF3Bi5X8T0nvoJ8FZTiOCRKKNGr9FEVdYOG+avtF6+TaEQyeW2q7PDkwkSPfAoGBAOdY1RxPEjTvuBrgGGBJ3sDd75okVc3OTxHsmZ2j1dqftk0euU8kNcQ40L2rwJ/OdnThFsyHfnN9lLoEQzc/rDWm74pOgQGqJ+AQZzEt0kWV1TmOwWQEx8dvipxMgI3xWzQ4BnzK3LTWl19LrP2f9V4F/iv//EqqHN3btojIxmpjAoGAT0wGYPNvlw0jPWaJnHOUGcZQit9BUIkyKiplakt0z98sPkAz6AV2USyLusgsTmvwRRQM0dVLVnGQYhK8WW+/3Pb8AHMG+Xz9wRqON+ErYtpSh4iBlSB5bSRY/aS+j/i7rcXw6IV5HLawsYsEcfQrjxIG+N8kYop34VFwwFFtNH0CgYBC4Tlqn6lByHNOwa4KWBCfbQmRZCOyBYxyQBYArpZqR3WqQdGMgYlRmqMt0fMzv7oa+99dbWYu+QT/6a+Mz0l3kTT6pMtCsfApJFTQsNBy8Wfc5doekgs3KpXFrZAUeMvHAtNNTFAVr1u/Xt85yv5iGhVqnuxOoXMK6DOSw4goxQKBgQDvTnYvpyRlHvTFHgutWocGWZA/k9XwwZJJaferWYD79EZkZHG7vkNp1JyxiuL2nuCidQp6psuOl5HeauhPHzFmpnq014Ju49t8CklKbiQt51T8Ir0S3nXs6F3e31c2t9fgMwLDfrNW1713wwHntDCWcQjU2aTDpLk9MY+ZaBaQhg==" ], + "certificate" : [ "MIICrzCCAZcCBgF4ra3edDANBgkqhkiG9w0BAQsFADAbMRkwFwYDVQQDDBBjNHBvX3JlYWxtX2xvY2FsMB4XDTIxMDQwNzE4NTA1NloXDTMxMDQwNzE4NTIzNlowGzEZMBcGA1UEAwwQYzRwb19yZWFsbV9sb2NhbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOZf9cArayMWg+x1u/0JZm+A7R9SnR813NyduKcizvdeJb8H2QWkc+JnGPZ4mvf0fqAq+tvxIyMVWuP0nwPju2VIItkzCbcLVF9wikZZvpLSudcJydUzTd/M2BAfXxSrhJT90qr2vpy5ZlUzMql5A8yy4RSZ3NUs7vLVlySsq4puDqP4gH725k4rJXYCdWB/8HKnx9PI4FYckg0l3Xuh3o04Ybg7DOBanmjwDQc7acQ8GA6O7I/Zg/FBPTb8s169qNDVfib2b9h9ER7YdylH/IibWubY6PsGOL0AD75HzjncJ/lxLFiIWcChjD3mHqhE0+pCkF//1GzXPuWE+dd1NT0CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAgat1JOdb/cvKGc2rFRH4XuiP7mUrzJo+gCQNbikXEFZQHYeKUci3n9QIeFGUCwALgQdgWeeQG4wR1Hu95FkGFk9uMlt2w5lh5eCuTHX1fW6UMiUL4Cw1kgfyW6Lw8Ffk58qW6BAM76yTKgyYc6o8Hhvgw5X/sqxz4IXWgSB2+Zj8AjdaeThsOtnefpXChSSrGnoJwZqXLc1rLWvqtqiIVtJau0CO4qZ7VXiSHwJvZpF7+vLMWig9zEVnvNX38HGWcfGCymGRxjVP8mjC/GE1WG9jLU55otvE3Fll6/XXhndXh6imRzgvG41qdlvOz/gQgtcI5LBw8YCZ5EJQay93oQ==" ], + "priority" : [ "100" ] + } + } ] + }, + "internationalizationEnabled" : false, + "supportedLocales" : [ ], + "authenticationFlows" : [ { + "id" : "2c54f19d-5992-447d-a2b3-58953c5a92d9", + "alias" : "Account verification options", + "description" : "Method with which to verity the existing account", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-email-verification", + "requirement" : "ALTERNATIVE", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "requirement" : "ALTERNATIVE", + "priority" : 20, + "flowAlias" : "Verify Existing Account by Re-authentication", + "userSetupAllowed" : false, + "autheticatorFlow" : true + } ] + }, { + "id" : "58e457d2-c138-401b-94e6-aa0c89d40be5", + "alias" : "Authentication Options", + "description" : "Authentication options.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "basic-auth", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "basic-auth-otp", + "requirement" : "DISABLED", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "auth-spnego", + "requirement" : "DISABLED", + "priority" : 30, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "ed8a0f0d-c571-4c7c-8177-51ff71e0cb0e", + "alias" : "Browser - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "auth-otp-form", + "requirement" : "REQUIRED", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "b12f09d6-8e47-4fa2-80cb-6adece38f970", + "alias" : "Direct Grant - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "direct-grant-validate-otp", + "requirement" : "REQUIRED", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "571ebbe3-1dbd-4c11-a048-431ebe7b9ba0", + "alias" : "First broker login - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "auth-otp-form", + "requirement" : "REQUIRED", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "9efe3906-9789-45d5-bd69-17657e7d0dd1", + "alias" : "Handle Existing Account", + "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-confirm-link", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "requirement" : "REQUIRED", + "priority" : 20, + "flowAlias" : "Account verification options", + "userSetupAllowed" : false, + "autheticatorFlow" : true + } ] + }, { + "id" : "ff2fc973-bd97-49f6-a5b9-234904131b12", + "alias" : "Reset - Conditional OTP", + "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "reset-otp", + "requirement" : "REQUIRED", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "b4b2f650-4ae7-4d26-ae58-59e6074fb067", + "alias" : "User creation or linking", + "description" : "Flow for the existing/non-existing user alternatives", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticatorConfig" : "create unique user config", + "authenticator" : "idp-create-user-if-unique", + "requirement" : "ALTERNATIVE", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "requirement" : "ALTERNATIVE", + "priority" : 20, + "flowAlias" : "Handle Existing Account", + "userSetupAllowed" : false, + "autheticatorFlow" : true + } ] + }, { + "id" : "5848478b-6cb0-4460-a0be-9393e8835382", + "alias" : "Verify Existing Account by Re-authentication", + "description" : "Reauthentication of existing account", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-username-password-form", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "requirement" : "CONDITIONAL", + "priority" : 20, + "flowAlias" : "First broker login - Conditional OTP", + "userSetupAllowed" : false, + "autheticatorFlow" : true + } ] + }, { + "id" : "79af744f-037e-49b7-b469-4581078db93a", + "alias" : "browser", + "description" : "browser based authentication", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "auth-cookie", + "requirement" : "ALTERNATIVE", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "auth-spnego", + "requirement" : "DISABLED", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "identity-provider-redirector", + "requirement" : "ALTERNATIVE", + "priority" : 25, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "requirement" : "ALTERNATIVE", + "priority" : 30, + "flowAlias" : "forms", + "userSetupAllowed" : false, + "autheticatorFlow" : true + } ] + }, { + "id" : "bed9a42e-29ce-42e3-b217-4c82ddc1da60", + "alias" : "clients", + "description" : "Base authentication for clients", + "providerId" : "client-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "client-secret", + "requirement" : "ALTERNATIVE", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "client-jwt", + "requirement" : "ALTERNATIVE", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "client-secret-jwt", + "requirement" : "ALTERNATIVE", + "priority" : 30, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "client-x509", + "requirement" : "ALTERNATIVE", + "priority" : 40, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "90ed59d7-616e-4db0-b5b7-b02c4778bfe6", + "alias" : "direct grant", + "description" : "OpenID Connect Resource Owner Grant", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "direct-grant-validate-username", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "direct-grant-validate-password", + "requirement" : "REQUIRED", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "requirement" : "CONDITIONAL", + "priority" : 30, + "flowAlias" : "Direct Grant - Conditional OTP", + "userSetupAllowed" : false, + "autheticatorFlow" : true + } ] + }, { + "id" : "02136f0a-354a-41d6-8a81-82d9d61f8ae1", + "alias" : "docker auth", + "description" : "Used by Docker clients to authenticate against the IDP", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "docker-http-basic-authenticator", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "61049b62-3f5e-440e-b3d3-7955c74ce79a", + "alias" : "first broker login", + "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticatorConfig" : "review profile config", + "authenticator" : "idp-review-profile", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "requirement" : "REQUIRED", + "priority" : 20, + "flowAlias" : "User creation or linking", + "userSetupAllowed" : false, + "autheticatorFlow" : true + } ] + }, { + "id" : "a3416906-b38a-4626-9759-99010c6e27b9", + "alias" : "forms", + "description" : "Username, password, otp and other auth forms.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "auth-username-password-form", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "requirement" : "CONDITIONAL", + "priority" : 20, + "flowAlias" : "Browser - Conditional OTP", + "userSetupAllowed" : false, + "autheticatorFlow" : true + } ] + }, { + "id" : "e53c6c19-574d-4ba0-b8b5-55631d71328d", + "alias" : "http challenge", + "description" : "An authentication flow based on challenge-response HTTP Authentication Schemes", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "no-cookie-redirect", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "requirement" : "REQUIRED", + "priority" : 20, + "flowAlias" : "Authentication Options", + "userSetupAllowed" : false, + "autheticatorFlow" : true + } ] + }, { + "id" : "3bc89297-8fa2-45a0-a4f9-64166c5f53f2", + "alias" : "registration", + "description" : "registration flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-page-form", + "requirement" : "REQUIRED", + "priority" : 10, + "flowAlias" : "registration form", + "userSetupAllowed" : false, + "autheticatorFlow" : true + } ] + }, { + "id" : "7f080e51-3567-498e-ae34-1abeefe07495", + "alias" : "registration form", + "description" : "registration form", + "providerId" : "form-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-user-creation", + "requirement" : "REQUIRED", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "registration-profile-action", + "requirement" : "REQUIRED", + "priority" : 40, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "registration-password-action", + "requirement" : "REQUIRED", + "priority" : 50, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "registration-recaptcha-action", + "requirement" : "DISABLED", + "priority" : 60, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "d0fe2454-5b33-47bd-9909-fd81dc62e27b", + "alias" : "reset credentials", + "description" : "Reset credentials for a user if they forgot their password or something", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "reset-credentials-choose-user", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "reset-credential-email", + "requirement" : "REQUIRED", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "reset-password", + "requirement" : "REQUIRED", + "priority" : 30, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "requirement" : "CONDITIONAL", + "priority" : 40, + "flowAlias" : "Reset - Conditional OTP", + "userSetupAllowed" : false, + "autheticatorFlow" : true + } ] + }, { + "id" : "f41e0e59-1927-4f6b-9917-1fffc76ed300", + "alias" : "saml ecp", + "description" : "SAML ECP Profile Authentication Flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "http-basic-authenticator", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + } ], + "authenticatorConfig" : [ { + "id" : "7328eecb-235e-4d51-aeb8-60f040bdce55", + "alias" : "create unique user config", + "config" : { + "require.password.update.after.registration" : "false" + } + }, { + "id" : "3ca8532e-719d-420c-9c96-684c1195ece6", + "alias" : "review profile config", + "config" : { + "update.profile.on.first.login" : "missing" + } + } ], + "requiredActions" : [ { + "alias" : "CONFIGURE_TOTP", + "name" : "Configure OTP", + "providerId" : "CONFIGURE_TOTP", + "enabled" : true, + "defaultAction" : false, + "priority" : 10, + "config" : { } + }, { + "alias" : "terms_and_conditions", + "name" : "Terms and Conditions", + "providerId" : "terms_and_conditions", + "enabled" : false, + "defaultAction" : false, + "priority" : 20, + "config" : { } + }, { + "alias" : "UPDATE_PASSWORD", + "name" : "Update Password", + "providerId" : "UPDATE_PASSWORD", + "enabled" : true, + "defaultAction" : false, + "priority" : 30, + "config" : { } + }, { + "alias" : "UPDATE_PROFILE", + "name" : "Update Profile", + "providerId" : "UPDATE_PROFILE", + "enabled" : true, + "defaultAction" : false, + "priority" : 40, + "config" : { } + }, { + "alias" : "VERIFY_EMAIL", + "name" : "Verify Email", + "providerId" : "VERIFY_EMAIL", + "enabled" : true, + "defaultAction" : false, + "priority" : 50, + "config" : { } + }, { + "alias" : "update_user_locale", + "name" : "Update User Locale", + "providerId" : "update_user_locale", + "enabled" : true, + "defaultAction" : false, + "priority" : 1000, + "config" : { } + } ], + "browserFlow" : "browser", + "registrationFlow" : "registration", + "directGrantFlow" : "direct grant", + "resetCredentialsFlow" : "reset credentials", + "clientAuthenticationFlow" : "clients", + "dockerAuthenticationFlow" : "docker auth", + "attributes" : { + "clientOfflineSessionMaxLifespan" : "0", + "clientSessionIdleTimeout" : "0", + "clientSessionMaxLifespan" : "0", + "clientOfflineSessionIdleTimeout" : "0" + }, + "keycloakVersion" : "11.0.3", + "userManagedAccessAllowed" : false +} \ No newline at end of file diff --git a/security-c4po-cfg/cfg/keycloak.env b/security-c4po-cfg/cfg/keycloak.env new file mode 100644 index 0000000..c2e568b --- /dev/null +++ b/security-c4po-cfg/cfg/keycloak.env @@ -0,0 +1,9 @@ +# cfg for local keycloak +DB_VENDOR=postgres +DB_ADDR=keycloak-postgres +DB_PORT=5432 +DB_USER=c4po_kc_local +DB_PASSWORD=Test1234! +KEYCLOAK_USER=admin +KEYCLOAK_PASSWORD=admin +KEYCLOAK_IMPORT=/tmp/c4po_realm_export.json diff --git a/security-c4po-cfg/cfg/keycloakdb.env b/security-c4po-cfg/cfg/keycloakdb.env new file mode 100644 index 0000000..a649ddc --- /dev/null +++ b/security-c4po-cfg/cfg/keycloakdb.env @@ -0,0 +1,4 @@ +# database.env +POSTGRES_USER=c4po_kc_local +POSTGRES_PASSWORD=Test1234! +POSTGRES_DB=keycloak diff --git a/security-c4po-cfg/frontend/docker-compose.frontend.yml b/security-c4po-cfg/frontend/docker-compose.frontend.yml new file mode 100644 index 0000000..234dc26 --- /dev/null +++ b/security-c4po-cfg/frontend/docker-compose.frontend.yml @@ -0,0 +1,16 @@ +version: '3.1' + +services: + c4po-angular: + build: '../../security-c4po-angular' + image: security-c4po-angular:latest + container_name: security-c4po-angular + deploy: + resources: + limits: + memory: "1G" + ports: + - '4200:4200' + +networks: + c4po: diff --git a/security-c4po-cfg/kc/docker-compose.keycloak.yml b/security-c4po-cfg/kc/docker-compose.keycloak.yml new file mode 100644 index 0000000..84bf79d --- /dev/null +++ b/security-c4po-cfg/kc/docker-compose.keycloak.yml @@ -0,0 +1,24 @@ +version: '3.1' + +services: + c4po-keycloak: + container_name: security-c4po-keycloak + depends_on: + - keycloak-postgres + image: jboss/keycloak:11.0.3 + volumes: + - ../cfg/c4po_realm_export.json:/tmp/c4po_realm_export.json + ports: + - 8888:8080 + - 9990:9990 + env_file: + - ../cfg/keycloak.env + keycloak-postgres: + container_name: security-c4po-postgres-keycloak + image: postgres:latest + env_file: + - ../cfg/keycloakdb.env + ports: + - 5433:5432 + volumes: + - ../volumes/keycloak/data:/var/lib/postgresql/data