diff --git a/security-c4po-angular/angular.json b/security-c4po-angular/angular.json index 4368127..6eb197a 100644 --- a/security-c4po-angular/angular.json +++ b/security-c4po-angular/angular.json @@ -24,8 +24,8 @@ "tsConfig": "tsconfig.app.json", "aot": true, "assets": [ - "src/favicon.ico", - "src/favicon-c4po.ico", + "src/assets/images/favicons/favicon.ico", + "src/assets/images/favicons/corporate_favicon.ico", "src/assets" ], "styles": [ @@ -96,8 +96,8 @@ "polyfills": "src/polyfills.ts", "tsConfig": "tsconfig.spec.json", "assets": [ - "src/favicon.ico", - "src/favicon-c4po.ico", + "src/assets/images/favicons/favicon.ico", + "src/assets/images/favicons/corporate_favicon.ico", "src/assets" ], "styles": [ diff --git a/security-c4po-angular/jest.config.js b/security-c4po-angular/jest.config.js index 7ef195b..daca5fe 100644 --- a/security-c4po-angular/jest.config.js +++ b/security-c4po-angular/jest.config.js @@ -1,6 +1,8 @@ module.exports = { moduleNameMapper: { '@core/(.*)': '/src/app/core/$1', + '@assets/(.*)': '/src/assets/$1', + '@shared/(.*)': '/src/shared/$1' }, preset: 'jest-preset-angular', setupFilesAfterEnv: ['/setup-jest.ts'], diff --git a/security-c4po-angular/package-lock.json b/security-c4po-angular/package-lock.json index f5a7e9c..3e4d52f 100644 --- a/security-c4po-angular/package-lock.json +++ b/security-c4po-angular/package-lock.json @@ -2510,9 +2510,9 @@ "dev": true }, "@types/node": { - "version": "12.19.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.8.tgz", - "integrity": "sha512-D4k2kNi0URNBxIRCb1khTnkWNHv8KSL1owPmS/K5e5t8B2GzMReY7AsJIY1BnP5KdlgC4rj9jk2IkDMasIE7xg==", + "version": "12.20.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.33.tgz", + "integrity": "sha512-5XmYX2GECSa+CxMYaFsr2mrql71Q4EvHjKS+ox/SiwSdaASMoBIWE6UmZqFO+VX1jIcsYLStI4FFoB6V7FeIYw==", "dev": true }, "@types/normalize-package-data": { diff --git a/security-c4po-angular/package.json b/security-c4po-angular/package.json index dda4d09..f623976 100644 --- a/security-c4po-angular/package.json +++ b/security-c4po-angular/package.json @@ -58,7 +58,7 @@ "@types/jasmine": "~3.5.0", "@types/jasminewd2": "~2.0.3", "@types/jest": "26.0.15", - "@types/node": "^12.19.8", + "@types/node": "^12.20.33", "codelyzer": "^6.0.0", "font-awesome": "^4.7.0", "jasmine-core": "~3.6.0", diff --git a/security-c4po-angular/src/app/app-routing.module.ts b/security-c4po-angular/src/app/app-routing.module.ts index b84f70a..7f29841 100644 --- a/security-c4po-angular/src/app/app-routing.module.ts +++ b/security-c4po-angular/src/app/app-routing.module.ts @@ -3,7 +3,7 @@ import { Routes, RouterModule } from '@angular/router'; import {HomeComponent} from './home/home.component'; import {AuthGuardService} from '../shared/guards/auth-guard.service'; -export const START_PAGE = 'home'; +export const START_PAGE = 'projects'; const routes: Routes = [ { @@ -12,11 +12,11 @@ const routes: Routes = [ canActivate: [AuthGuardService] }, { - path: 'dashboard', - loadChildren: () => import('./dashboard').then(mod => mod.DashboardModule), + path: 'projects', + loadChildren: () => import('./project-overview').then(mod => mod.ProjectOverviewModule), canActivate: [AuthGuardService] }, - // ToDo: Exchange default Keycloak login with self made login + // ToDo: Remove after default Keycloak login mask got reworked /*{ path: 'login', loadChildren: () => import('./login').then(mod => mod.LoginModule), diff --git a/security-c4po-angular/src/app/app.component.scss b/security-c4po-angular/src/app/app.component.scss index 2048294..31f7946 100644 --- a/security-c4po-angular/src/app/app.component.scss +++ b/security-c4po-angular/src/app/app.component.scss @@ -1,15 +1,9 @@ @import "../assets/@theme/styles/_variables.scss"; .content-container { - width: 90vw; - height: calc(90vh - #{$header-height}); .scrollable-content { - width: 100%; - max-width: 100vw; - height: 100%; - max-height: calc(100vh - #{$header-height}); - overflow: auto; } + } diff --git a/security-c4po-angular/src/app/app.component.spec.ts b/security-c4po-angular/src/app/app.component.spec.ts index 889ebdf..6fa8739 100644 --- a/security-c4po-angular/src/app/app.component.spec.ts +++ b/security-c4po-angular/src/app/app.component.spec.ts @@ -6,9 +6,9 @@ import {NbEvaIconsModule} from '@nebular/eva-icons'; import {TranslateLoader, TranslateModule} from '@ngx-translate/core'; import {HttpLoaderFactory} from './common-app.module'; import {HttpClient} from '@angular/common/http'; -import {ThemeModule} from '../assets/@theme/theme.module'; +import {ThemeModule} from '@assets/@theme/theme.module'; import {HttpClientTestingModule} from '@angular/common/http/testing'; -import {SessionState} from '../shared/stores/session-state/session-state'; +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'; diff --git a/security-c4po-angular/src/app/app.module.ts b/security-c4po-angular/src/app/app.module.ts index 2ce09d6..e3bc9bc 100644 --- a/security-c4po-angular/src/app/app.module.ts +++ b/security-c4po-angular/src/app/app.module.ts @@ -17,14 +17,15 @@ import {FaConfig, FaIconLibrary, FontAwesomeModule} from '@fortawesome/angular-f import {fas} from '@fortawesome/free-solid-svg-icons'; import {far} from '@fortawesome/free-regular-svg-icons'; import {NgxsModule} from '@ngxs/store'; -import {SessionState} from '../shared/stores/session-state/session-state'; +import {SessionState} from '@shared/stores/session-state/session-state'; import {environment} from '../environments/environment'; -import {NotificationService} from '../shared/services/notification.service'; +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'; +import {httpInterceptorProviders} from '@shared/interceptors'; +import {FlexLayoutModule} from '@angular/flex-layout'; @NgModule({ declarations: [ @@ -53,7 +54,8 @@ import {httpInterceptorProviders} from '../shared/interceptors'; } }), HeaderModule, - HomeModule + HomeModule, + FlexLayoutModule ], providers: [ HttpClient, diff --git a/security-c4po-angular/src/app/dashboard/dashboard-routing.module.ts b/security-c4po-angular/src/app/dashboard/dashboard-routing.module.ts deleted file mode 100644 index cffddf6..0000000 --- a/security-c4po-angular/src/app/dashboard/dashboard-routing.module.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { NgModule } from '@angular/core'; -import {RouterModule, Routes} from '@angular/router'; -import {DashboardComponent} from './dashboard.component'; - -const routes: Routes = [ - { - path: '', - component: DashboardComponent - } -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) -export class DashboardRoutingModule { } diff --git a/security-c4po-angular/src/app/dashboard/dashboard.component.html b/security-c4po-angular/src/app/dashboard/dashboard.component.html deleted file mode 100644 index 9c5fce9..0000000 --- a/security-c4po-angular/src/app/dashboard/dashboard.component.html +++ /dev/null @@ -1 +0,0 @@ -

dashboard works!

diff --git a/security-c4po-angular/src/app/dashboard/dashboard.component.spec.ts b/security-c4po-angular/src/app/dashboard/dashboard.component.spec.ts deleted file mode 100644 index a83926d..0000000 --- a/security-c4po-angular/src/app/dashboard/dashboard.component.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { DashboardComponent } from './dashboard.component'; - -describe('DashboardComponent', () => { - let component: DashboardComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ - DashboardComponent - ] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(DashboardComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/security-c4po-angular/src/app/dashboard/dashboard.component.ts b/security-c4po-angular/src/app/dashboard/dashboard.component.ts deleted file mode 100644 index fbe68b4..0000000 --- a/security-c4po-angular/src/app/dashboard/dashboard.component.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {Component, OnDestroy, OnInit} from '@angular/core'; - -@Component({ - selector: 'app-dashboard', - templateUrl: './dashboard.component.html', - styleUrls: ['./dashboard.component.scss'] -}) -export class DashboardComponent implements OnInit, OnDestroy { - - constructor() { } - - ngOnInit(): void { - } - - ngOnDestroy(): void { - } -} diff --git a/security-c4po-angular/src/app/dashboard/dashboard.module.ts b/security-c4po-angular/src/app/dashboard/dashboard.module.ts deleted file mode 100644 index 43913fa..0000000 --- a/security-c4po-angular/src/app/dashboard/dashboard.module.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {DashboardComponent} from './dashboard.component'; - -@NgModule({ - declarations: [ - DashboardComponent, - ], - exports: [ - DashboardComponent - ], - imports: [ - CommonModule - ] -}) -export class DashboardModule { -} diff --git a/security-c4po-angular/src/app/dashboard/index.ts b/security-c4po-angular/src/app/dashboard/index.ts deleted file mode 100644 index 87d1201..0000000 --- a/security-c4po-angular/src/app/dashboard/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export {DashboardModule} from './dashboard.module'; -export {DashboardRoutingModule} from './dashboard-routing.module'; diff --git a/security-c4po-angular/src/app/header/header.component.html b/security-c4po-angular/src/app/header/header.component.html index eff6a91..88d7422 100644 --- a/security-c4po-angular/src/app/header/header.component.html +++ b/security-c4po-angular/src/app/header/header.component.html @@ -1,4 +1,26 @@ -
-

header works!

+
+ logo dark + + logo light + + +
+

{{SECURITYC4PO_TITLE}}

+
+
+ + + + + +
diff --git a/security-c4po-angular/src/app/header/header.component.scss b/security-c4po-angular/src/app/header/header.component.scss index 9792749..821592f 100644 --- a/security-c4po-angular/src/app/header/header.component.scss +++ b/security-c4po-angular/src/app/header/header.component.scss @@ -1,9 +1,31 @@ + +@import '~@nebular/theme/styles/global/breakpoints'; @import "../../assets/@theme/styles/_variables.scss"; -.c4po-header { - height: $header-height; +@mixin nb-overrides { + display: inline-flex; + justify-content: space-between; + width: 100%; - .toggle-dark-mode { - text-align: right; + .header { + display: flex; + align-items: center; + width: auto; + + .logo-container { + font-style: oblique; + color: #e74c3c; + } + + nb-action { + height: auto; + display: flex; + align-content: center; + } + + .toggle-theme { + align-content: flex-end; + display: flex; + } } } diff --git a/security-c4po-angular/src/app/header/header.component.spec.ts b/security-c4po-angular/src/app/header/header.component.spec.ts index 381e8e8..b5e5bb4 100644 --- a/security-c4po-angular/src/app/header/header.component.spec.ts +++ b/security-c4po-angular/src/app/header/header.component.spec.ts @@ -1,6 +1,14 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import {ComponentFixture, TestBed} from '@angular/core/testing'; -import { HeaderComponent } from './header.component'; +import {HeaderComponent} from './header.component'; +import {CommonModule} from '@angular/common'; +import {FontAwesomeTestingModule} from '@fortawesome/angular-fontawesome/testing'; +import {NbActionsModule} from '@nebular/theme'; +import {ThemeModule} from '@assets/@theme/theme.module'; +import {TranslateLoader, TranslateModule} from '@ngx-translate/core'; +import {HttpLoaderFactory} from '../common-app.module'; +import {HttpClient} from '@angular/common/http'; +import {RouterTestingModule} from '@angular/router/testing'; describe('HeaderComponent', () => { let component: HeaderComponent; @@ -8,9 +16,25 @@ describe('HeaderComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ HeaderComponent ] + declarations: [ + HeaderComponent + ], + imports: [ + CommonModule, + NbActionsModule, + FontAwesomeTestingModule, + ThemeModule.forRoot(), + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient] + } + }), + RouterTestingModule.withRoutes([]) + ] }) - .compileComponents(); + .compileComponents(); }); beforeEach(() => { diff --git a/security-c4po-angular/src/app/header/header.component.ts b/security-c4po-angular/src/app/header/header.component.ts index 59874a9..83d5485 100644 --- a/security-c4po-angular/src/app/header/header.component.ts +++ b/security-c4po-angular/src/app/header/header.component.ts @@ -1,14 +1,43 @@ -import { Component, OnInit } from '@angular/core'; +import {Component, OnDestroy, OnInit} from '@angular/core'; +import * as FA from '@fortawesome/free-solid-svg-icons'; +import {NbThemeService} from '@nebular/theme'; +import {map} from 'rxjs/operators'; +import {untilDestroyed} from 'ngx-take-until-destroy'; +import {GlobalTitlesVariables} from '@shared/config/global-variables'; + @Component({ selector: 'app-header', templateUrl: './header.component.html', styleUrls: ['./header.component.scss'] }) -export class HeaderComponent implements OnInit { +export class HeaderComponent implements OnInit, OnDestroy { - constructor() { } + readonly fa = FA; + readonly SECURITYC4PO_TITLE = GlobalTitlesVariables.SECURITYC4PO_TITLE; + + currentTheme = ''; + + constructor(private themeService: NbThemeService) { } ngOnInit(): void { + this.themeService.onThemeChange() + .pipe( + map(({ name }) => name), + untilDestroyed(this), + ) + .subscribe(themeName => this.currentTheme = themeName); } + onClickSwitchTheme(): void { + if (this.currentTheme === 'corporate') { + this.themeService.changeTheme('dark'); + } else if (this.currentTheme === 'dark') { + this.themeService.changeTheme('corporate'); + } + } + + ngOnDestroy(): void { + // This method must be present when using ngx-take-until-destroy + // even when empty + } } diff --git a/security-c4po-angular/src/app/header/header.module.ts b/security-c4po-angular/src/app/header/header.module.ts index ebaf0a3..2c0aeb6 100644 --- a/security-c4po-angular/src/app/header/header.module.ts +++ b/security-c4po-angular/src/app/header/header.module.ts @@ -1,6 +1,9 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; +import {NgModule} from '@angular/core'; +import {CommonModule} from '@angular/common'; import {HeaderComponent} from './header.component'; +import {NbActionsModule, NbButtonModule, NbCardModule} from '@nebular/theme'; +import {FontAwesomeModule} from '@fortawesome/angular-fontawesome'; +import {FlexLayoutModule} from '@angular/flex-layout'; @NgModule({ declarations: [ @@ -10,7 +13,13 @@ import {HeaderComponent} from './header.component'; HeaderComponent ], imports: [ - CommonModule + CommonModule, + NbButtonModule, + FontAwesomeModule, + NbCardModule, + NbActionsModule, + FlexLayoutModule ] }) -export class HeaderModule { } +export class HeaderModule { +} diff --git a/security-c4po-angular/src/app/home/home.component.html b/security-c4po-angular/src/app/home/home.component.html index 379ce84..9217591 100644 --- a/security-c4po-angular/src/app/home/home.component.html +++ b/security-c4po-angular/src/app/home/home.component.html @@ -1,16 +1 @@ - - - - - - - -
- {{projects.getValue() | json}} -
- - {{'No Projects available!'}} - -
- -
+

app-home-component works...

diff --git a/security-c4po-angular/src/app/home/home.component.ts b/security-c4po-angular/src/app/home/home.component.ts index b4515fa..5e3d7fc 100644 --- a/security-c4po-angular/src/app/home/home.component.ts +++ b/security-c4po-angular/src/app/home/home.component.ts @@ -1,36 +1,14 @@ -import {Component, OnDestroy, OnInit} from '@angular/core'; -import {BehaviorSubject} from 'rxjs'; -import {Project} from '../../shared/models/project.model'; -import {ProjectService} from '../../shared/services/project.service'; -import {untilDestroyed} from 'ngx-take-until-destroy'; +import {Component, OnInit} from '@angular/core'; @Component({ selector: 'app-home', templateUrl: './home.component.html', styleUrls: ['./home.component.scss'] }) -export class HomeComponent implements OnInit, OnDestroy { +export class HomeComponent implements OnInit { - projects: BehaviorSubject = new BehaviorSubject([]); - - constructor(private projectService: ProjectService) { } + constructor() { } ngOnInit(): void { } - - onClickGetProjects(): void { - this.getProjects(); - } - - getProjects(): void { - this.projectService.getProjects() - .pipe(untilDestroyed(this)) - .subscribe((projects) => { - this.projects.next(projects); - }); - } - - ngOnDestroy(): void { - } - } diff --git a/security-c4po-angular/src/app/project-overview/index.ts b/security-c4po-angular/src/app/project-overview/index.ts new file mode 100644 index 0000000..dd66278 --- /dev/null +++ b/security-c4po-angular/src/app/project-overview/index.ts @@ -0,0 +1,2 @@ +export {ProjectOverviewModule} from './project-overview.module'; +export {ProjectOverviewRoutingModule} from './project-overview-routing.module'; diff --git a/security-c4po-angular/src/app/project-overview/project-overview-routing.module.ts b/security-c4po-angular/src/app/project-overview/project-overview-routing.module.ts new file mode 100644 index 0000000..17ae336 --- /dev/null +++ b/security-c4po-angular/src/app/project-overview/project-overview-routing.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import {RouterModule, Routes} from '@angular/router'; +import {ProjectOverviewComponent} from './project-overview.component'; + +const routes: Routes = [ + { + path: '', + component: ProjectOverviewComponent + }, + { + path: 'id', + loadChildren: () => import('./project').then(mod => mod.ProjectModule), + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class ProjectOverviewRoutingModule { } diff --git a/security-c4po-angular/src/app/project-overview/project-overview.component.html b/security-c4po-angular/src/app/project-overview/project-overview.component.html new file mode 100644 index 0000000..41cb515 --- /dev/null +++ b/security-c4po-angular/src/app/project-overview/project-overview.component.html @@ -0,0 +1,84 @@ +
+
+ + +

{{project?.title}}

+
+ +

+ {{'project.client' | translate}}: +

+ + {{project?.client}} + + +

+ {{'project.tester' | translate}}: +

+ + {{project?.tester}} + + +

+ {{'project.createdAt' | translate}}: +

+ + {{project?.createdAt | dateTimeFormat}} + +
+ + +
+ + + + +
+
+
+
+
+ +
+

+ {{'project.overview.no.projects' | translate}} +

+
+ +
+ +
+ + + + diff --git a/security-c4po-angular/src/app/project-overview/project-overview.component.scss b/security-c4po-angular/src/app/project-overview/project-overview.component.scss new file mode 100644 index 0000000..c411834 --- /dev/null +++ b/security-c4po-angular/src/app/project-overview/project-overview.component.scss @@ -0,0 +1,50 @@ +.project-card { + max-width: 22rem; + width: 22rem; + min-width: 20rem; + max-height: 100%; + height: 100%; + min-height: 100%; + + .project-header { + max-height: 8rem; + height: 8rem; + min-height: 6rem; + } + + .project-subheader { + font-size: 1.25rem; + font-weight: bold; + } + + .project-paragraph { + font-size: 1.15rem; + font-style: italic; + } + + .project-progress { + max-width: 65%; + width: 65%; + min-width: 65%; + } + + .project-button { + height: 1.425rem; + } +} + +.project-link:hover { + cursor: pointer !important; +} + +.add-project-button { + margin: 6rem 2rem 6rem 0; + .new-project-icon { + padding-right: 0.5rem; + } +} + +.error-text { + font-size: 1.25rem; + font-weight: bold; +} diff --git a/security-c4po-angular/src/app/project-overview/project-overview.component.spec.ts b/security-c4po-angular/src/app/project-overview/project-overview.component.spec.ts new file mode 100644 index 0000000..062e6c2 --- /dev/null +++ b/security-c4po-angular/src/app/project-overview/project-overview.component.spec.ts @@ -0,0 +1,79 @@ +import {ComponentFixture, TestBed} from '@angular/core/testing'; + +import {ProjectOverviewComponent} from './project-overview.component'; +import {DateTimeFormatPipe} from '@shared/pipes/date-time-format.pipe'; +import {CommonModule} from '@angular/common'; +import {ProjectOverviewRoutingModule} from './project-overview-routing.module'; +import {NbButtonModule, NbCardModule, NbProgressBarModule, NbSpinnerModule} from '@nebular/theme'; +import {FlexLayoutModule} from '@angular/flex-layout'; +import {FontAwesomeModule} from '@fortawesome/angular-fontawesome'; +import {TranslateLoader, TranslateModule} from '@ngx-translate/core'; +import {ProjectService} from '@shared/services/project.service'; +import {HttpLoaderFactory} from '../common-app.module'; +import {HttpClient, HttpClientModule} from '@angular/common/http'; +import {RouterTestingModule} from '@angular/router/testing'; +import {NgxsModule} from '@ngxs/store'; +import {SessionState} from '@shared/stores/session-state/session-state'; +import {HttpClientTestingModule} from '@angular/common/http/testing'; +import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; +import {NotificationService} from '@shared/services/notification.service'; +import {NotificationServiceMock} from '@shared/services/notification.service.mock'; +import {ProjectServiceMock} from '@shared/services/project.service.mock'; +import {ThemeModule} from '@assets/@theme/theme.module'; +import {LoadingSpinnerComponent} from '@shared/widgets/loading-spinner/loading-spinner.component'; +import {KeycloakService} from 'keycloak-angular'; + +describe('ProjectOverviewComponent', () => { + let component: ProjectOverviewComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ + ProjectOverviewComponent, + LoadingSpinnerComponent, + DateTimeFormatPipe + ], + imports: [ + CommonModule, + ProjectOverviewRoutingModule, + NbCardModule, + NbButtonModule, + FlexLayoutModule, + BrowserAnimationsModule, + FontAwesomeModule, + TranslateModule, + NbProgressBarModule, + NbSpinnerModule, + ThemeModule.forRoot(), + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient] + } + }), + RouterTestingModule.withRoutes([]), + NgxsModule.forRoot([SessionState]), + HttpClientModule, + HttpClientTestingModule + ], + providers: [ + KeycloakService, + {provide: ProjectService, useValue: new ProjectServiceMock()}, + {provide: NotificationService, useValue: new NotificationServiceMock()} + ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ProjectOverviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/security-c4po-angular/src/app/project-overview/project-overview.component.ts b/security-c4po-angular/src/app/project-overview/project-overview.component.ts new file mode 100644 index 0000000..f8ddbac --- /dev/null +++ b/security-c4po-angular/src/app/project-overview/project-overview.component.ts @@ -0,0 +1,70 @@ +import {Component, OnDestroy, OnInit} from '@angular/core'; +import * as FA from '@fortawesome/free-solid-svg-icons'; +import {Project} from '@shared/models/project.model'; +import {BehaviorSubject, Observable} from 'rxjs'; +import {untilDestroyed} from 'ngx-take-until-destroy'; +import {ProjectService} from '@shared/services/project.service'; +import {NotificationService, PopupType} from '@shared/services/notification.service'; +import {tap} from 'rxjs/operators'; + +@Component({ + selector: 'app-project-overview', + templateUrl: './project-overview.component.html', + styleUrls: ['./project-overview.component.scss'] +}) +export class ProjectOverviewComponent implements OnInit, OnDestroy { + + readonly fa = FA; + + loading$: BehaviorSubject = new BehaviorSubject(true); + projects: BehaviorSubject = new BehaviorSubject([]); + + constructor( + private readonly projectService: ProjectService, + private readonly notificationService: NotificationService) { + } + + ngOnInit(): void { + this.getProjects(); + } + + getProjects(): void { + this.projectService.getProjects() + .pipe( + untilDestroyed(this), + tap(() => this.loading$.next(true)) + ) + .subscribe( { + next: (projects) => { + this.projects.next(projects); + this.loading$.next(false); + }, + error: err => { + console.log(err); + this.notificationService.showPopup('project.popup.not.found', PopupType.FAILURE); + this.loading$.next(false); + } + }); + } + + onClickAddProject(): void { + console.log('to be implemented...'); + } + + onClickEditProject(): void { + console.log('to be implemented...'); + } + + onClickDeleteProject(): void { + console.log('to be implemented...'); + } + + isLoading(): Observable { + return this.loading$.asObservable(); + } + + ngOnDestroy(): void { + // This method must be present when using ngx-take-until-destroy + // even when empty + } +} diff --git a/security-c4po-angular/src/app/project-overview/project-overview.module.ts b/security-c4po-angular/src/app/project-overview/project-overview.module.ts new file mode 100644 index 0000000..c8ac3b5 --- /dev/null +++ b/security-c4po-angular/src/app/project-overview/project-overview.module.ts @@ -0,0 +1,30 @@ +import {NgModule} from '@angular/core'; +import {CommonModule} from '@angular/common'; +import {ProjectOverviewComponent} from './project-overview.component'; +import {ProjectOverviewRoutingModule} from './project-overview-routing.module'; +import {NbButtonModule, NbCardModule, NbProgressBarModule} from '@nebular/theme'; +import {FlexLayoutModule} from '@angular/flex-layout'; +import {FontAwesomeModule} from '@fortawesome/angular-fontawesome'; +import {TranslateModule} from '@ngx-translate/core'; +import {DateTimeFormatPipe} from '@shared/pipes/date-time-format.pipe'; +import {ProjectModule} from './project'; + +@NgModule({ + declarations: [ + ProjectOverviewComponent, + DateTimeFormatPipe + ], + imports: [ + CommonModule, + ProjectOverviewRoutingModule, + NbCardModule, + NbButtonModule, + FlexLayoutModule, + FontAwesomeModule, + TranslateModule, + NbProgressBarModule, + ProjectModule + ] +}) +export class ProjectOverviewModule { +} diff --git a/security-c4po-angular/src/app/project-overview/project/index.ts b/security-c4po-angular/src/app/project-overview/project/index.ts new file mode 100644 index 0000000..c212fb4 --- /dev/null +++ b/security-c4po-angular/src/app/project-overview/project/index.ts @@ -0,0 +1,2 @@ +export {ProjectModule} from './project.module'; +export {ProjectRoutingModule} from './project-routing.module'; diff --git a/security-c4po-angular/src/app/project-overview/project/project-routing.module.ts b/security-c4po-angular/src/app/project-overview/project/project-routing.module.ts new file mode 100644 index 0000000..c5444ca --- /dev/null +++ b/security-c4po-angular/src/app/project-overview/project/project-routing.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + + + +@NgModule({ + declarations: [], + imports: [ + CommonModule + ] +}) +export class ProjectRoutingModule { } diff --git a/security-c4po-angular/src/app/project-overview/project/project.component.html b/security-c4po-angular/src/app/project-overview/project/project.component.html new file mode 100644 index 0000000..000715f --- /dev/null +++ b/security-c4po-angular/src/app/project-overview/project/project.component.html @@ -0,0 +1,6 @@ + + +

{{selectedProjectTitle}} works!

+
+
+ diff --git a/security-c4po-angular/src/app/dashboard/dashboard.component.scss b/security-c4po-angular/src/app/project-overview/project/project.component.scss similarity index 100% rename from security-c4po-angular/src/app/dashboard/dashboard.component.scss rename to security-c4po-angular/src/app/project-overview/project/project.component.scss diff --git a/security-c4po-angular/src/app/project-overview/project/project.component.spec.ts b/security-c4po-angular/src/app/project-overview/project/project.component.spec.ts new file mode 100644 index 0000000..fcd9d17 --- /dev/null +++ b/security-c4po-angular/src/app/project-overview/project/project.component.spec.ts @@ -0,0 +1,57 @@ +import {ComponentFixture, TestBed} from '@angular/core/testing'; + +import {ProjectComponent} from './project.component'; +import {CommonModule} from '@angular/common'; +import {ThemeModule} from '@assets/@theme/theme.module'; +import {TranslateLoader, TranslateModule} from '@ngx-translate/core'; +import {HttpLoaderFactory} from '../../common-app.module'; +import {HttpClient, HttpClientModule} from '@angular/common/http'; +import {RouterTestingModule} from '@angular/router/testing'; +import {NgxsModule} from '@ngxs/store'; +import {SessionState} from '@shared/stores/session-state/session-state'; +import {HttpClientTestingModule} from '@angular/common/http/testing'; +import {NbLayoutModule} from '@nebular/theme'; +import {KeycloakService} from 'keycloak-angular'; + +describe('ProjectComponent', () => { + let component: ProjectComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ + ProjectComponent + ], + imports: [ + CommonModule, + NbLayoutModule, + ThemeModule.forRoot(), + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient] + } + }), + RouterTestingModule.withRoutes([]), + NgxsModule.forRoot([SessionState]), + HttpClientModule, + HttpClientTestingModule + ], + providers: [ + KeycloakService + ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ProjectComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/security-c4po-angular/src/app/project-overview/project/project.component.ts b/security-c4po-angular/src/app/project-overview/project/project.component.ts new file mode 100644 index 0000000..47fa0a4 --- /dev/null +++ b/security-c4po-angular/src/app/project-overview/project/project.component.ts @@ -0,0 +1,17 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-project', + templateUrl: './project.component.html', + styleUrls: ['./project.component.scss'] +}) +export class ProjectComponent implements OnInit { + + selectedProjectTitle: string = history?.state?.selectedProject ? history?.state?.selectedProject.title : ''; + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/security-c4po-angular/src/app/project-overview/project/project.module.ts b/security-c4po-angular/src/app/project-overview/project/project.module.ts new file mode 100644 index 0000000..c26c1ab --- /dev/null +++ b/security-c4po-angular/src/app/project-overview/project/project.module.ts @@ -0,0 +1,30 @@ +import {NgModule} from '@angular/core'; +import {CommonModule} from '@angular/common'; +import {RouterModule} from '@angular/router'; +import {ProjectComponent} from './project.component'; +import {LoadingSpinnerComponent} from '@shared/widgets/loading-spinner/loading-spinner.component'; +import {NbCardModule, NbLayoutModule, NbSpinnerModule} from '@nebular/theme'; +import {FlexLayoutModule} from '@angular/flex-layout'; + +@NgModule({ + declarations: [ + ProjectComponent, + LoadingSpinnerComponent + ], + exports: [ + LoadingSpinnerComponent + ], + imports: [ + CommonModule, + RouterModule.forChild([{ + path: '', + component: ProjectComponent + }]), + NbCardModule, + NbSpinnerModule, + FlexLayoutModule, + NbLayoutModule, + ] +}) +export class ProjectModule { +} diff --git a/security-c4po-angular/src/assets/@theme/styles/_global.scss b/security-c4po-angular/src/assets/@theme/styles/_global.scss new file mode 100644 index 0000000..5ec1b7b --- /dev/null +++ b/security-c4po-angular/src/assets/@theme/styles/_global.scss @@ -0,0 +1,20 @@ +@import "_variables.scss"; + +nb-layout { + width: 100vw; + height: 100vh; + + nb-layout-header { + max-width: 100vw; + height: $header-height; + + justify-content: center; + } + + nb-layout-column { + width: 100vw; + height: calc(100vh - #{$header-height}); + + overflow: auto; + } +} diff --git a/security-c4po-angular/src/assets/@theme/styles/_layout.scss b/security-c4po-angular/src/assets/@theme/styles/_layout.scss deleted file mode 100644 index fc32414..0000000 --- a/security-c4po-angular/src/assets/@theme/styles/_layout.scss +++ /dev/null @@ -1,6 +0,0 @@ -@mixin ngx-layout() { - @include media-breakpoint-down(is) { - .row { - } - } -} diff --git a/security-c4po-angular/src/assets/@theme/styles/styles.scss b/security-c4po-angular/src/assets/@theme/styles/styles.scss index ec7e63c..741f677 100644 --- a/security-c4po-angular/src/assets/@theme/styles/styles.scss +++ b/security-c4po-angular/src/assets/@theme/styles/styles.scss @@ -6,8 +6,6 @@ // loading progress bar theme @import './pace.theme'; - -@import './layout'; @import './overrides'; @import './variables'; @@ -23,8 +21,6 @@ body { // framework global styles @include nb-theme-global(); - @include ngx-layout(); - @include nb-overrides(); }; /* You can add global styles to this file, and also import other style files */ diff --git a/security-c4po-angular/src/assets/@theme/theme.module.ts b/security-c4po-angular/src/assets/@theme/theme.module.ts index 3c1779a..c0c7960 100644 --- a/security-c4po-angular/src/assets/@theme/theme.module.ts +++ b/security-c4po-angular/src/assets/@theme/theme.module.ts @@ -47,7 +47,7 @@ export class ThemeModule { providers: [ ...NbThemeModule.forRoot( { - name: 'corporate', + name: DARK_THEME.name, }, [CORPORATE_THEME, DARK_THEME], ).providers, diff --git a/security-c4po-angular/src/assets/i18n/de-DE.json b/security-c4po-angular/src/assets/i18n/de-DE.json index 1143d9b..65a9cb8 100644 --- a/security-c4po-angular/src/assets/i18n/de-DE.json +++ b/security-c4po-angular/src/assets/i18n/de-DE.json @@ -22,5 +22,17 @@ "title": "Einloggen", "failed": "Benutzername oder Passwort falsch", "unauthorized": "Benutzer nicht gefunden. Bitte registrieren und erneut versuchen" + }, + "project": { + "overview": { + "add.project": "Projekt hinzufügen", + "no.projects": "Keine Projekte verfügbar" + }, + "popup": { + "not.found": "Keine Projekte gefunden" + }, + "client": "Klient", + "tester": "Tester", + "createdAt": "Erstellt am" } } diff --git a/security-c4po-angular/src/assets/i18n/en-US.json b/security-c4po-angular/src/assets/i18n/en-US.json index fa94e75..9e7d8a5 100644 --- a/security-c4po-angular/src/assets/i18n/en-US.json +++ b/security-c4po-angular/src/assets/i18n/en-US.json @@ -11,7 +11,7 @@ "warning": "!", "info": "", "error.position": { - "permissionDenied": "Permission Denied", + "permissionDenied": "Permission denied", "timeout": "Timeout" }, "login": { @@ -22,5 +22,17 @@ "title": "Login", "failed": "Wrong username or password", "unauthorized": "User not found. Please register and try again" + }, + "project": { + "overview": { + "add.project": "Add project", + "no.projects": "No projects available" + }, + "popup": { + "not.found": "No projects found" + }, + "client": "Client", + "tester": "Tester", + "createdAt": "Created at" } } diff --git a/security-c4po-angular/src/assets/images/dashboard/project_default_logo.png b/security-c4po-angular/src/assets/images/dashboard/project_default_logo.png new file mode 100644 index 0000000..b170a9a Binary files /dev/null and b/security-c4po-angular/src/assets/images/dashboard/project_default_logo.png differ diff --git a/security-c4po-angular/src/assets/images/dashboard/project_default_logo_corporate.png b/security-c4po-angular/src/assets/images/dashboard/project_default_logo_corporate.png new file mode 100644 index 0000000..83aab1d Binary files /dev/null and b/security-c4po-angular/src/assets/images/dashboard/project_default_logo_corporate.png differ diff --git a/security-c4po-angular/src/assets/images/favicons/corporate_favicon.ico b/security-c4po-angular/src/assets/images/favicons/corporate_favicon.ico new file mode 100644 index 0000000..a2309ee Binary files /dev/null and b/security-c4po-angular/src/assets/images/favicons/corporate_favicon.ico differ diff --git a/security-c4po-angular/src/favicon.ico b/security-c4po-angular/src/assets/images/favicons/favicon.ico similarity index 100% rename from security-c4po-angular/src/favicon.ico rename to security-c4po-angular/src/assets/images/favicons/favicon.ico diff --git a/security-c4po-angular/src/favicon-c4po.png b/security-c4po-angular/src/favicon-c4po.png deleted file mode 100644 index bfbabb5..0000000 Binary files a/security-c4po-angular/src/favicon-c4po.png and /dev/null differ diff --git a/security-c4po-angular/src/index.html b/security-c4po-angular/src/index.html index 63035e5..d4d461d 100644 --- a/security-c4po-angular/src/index.html +++ b/security-c4po-angular/src/index.html @@ -5,7 +5,7 @@ SecurityC4POAngular - + 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 c1f61b4..5feb4b5 100644 --- a/security-c4po-angular/src/shared/guards/auth-guard.service.ts +++ b/security-c4po-angular/src/shared/guards/auth-guard.service.ts @@ -1,12 +1,8 @@ import {Injectable} from '@angular/core'; import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router'; 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' 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 deleted file mode 100644 index 1f2767d..0000000 --- a/security-c4po-angular/src/shared/guards/login-guard.service.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { LoginGuardService } from './login-guard.service'; -import {RouterTestingModule} from '@angular/router/testing'; -import {HttpClientTestingModule} from '@angular/common/http/testing'; -import {NgxsModule} from '@ngxs/store'; -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; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ - HttpClientTestingModule, - RouterTestingModule, - TranslateModule.forRoot({ - loader: { - provide: TranslateLoader, - useFactory: HttpLoaderFactory, - deps: [HttpClient] - } - }), - NgxsModule.forRoot([SessionState]) - ], - providers: [ - KeycloakService - ] - }); - service = TestBed.inject(LoginGuardService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/security-c4po-angular/src/shared/guards/login-guard.service.ts b/security-c4po-angular/src/shared/guards/login-guard.service.ts deleted file mode 100644 index 831d852..0000000 --- a/security-c4po-angular/src/shared/guards/login-guard.service.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { Injectable } from '@angular/core'; -import {Store} from '@ngxs/store'; -import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router'; -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 extends KeycloakAuthGuard implements CanActivate { - constructor( - public readonly router: Router, - protected keycloakAngular: KeycloakService, - private readonly store: Store) { - super(router, keycloakAngular); - } - - 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) { - this.router.navigate(['']).then(); - } - }), - // return true if login should be loaded (=> invert) - map((canAccess: boolean) => !canAccess) - ); - } - - /!** - * @return state of authentication - *!/ - 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); - }) - ); - }*/ -} - diff --git a/security-c4po-angular/src/shared/models/custom-pipe.mode.ts b/security-c4po-angular/src/shared/models/custom-pipe.mode.ts new file mode 100644 index 0000000..4c0a8a3 --- /dev/null +++ b/security-c4po-angular/src/shared/models/custom-pipe.mode.ts @@ -0,0 +1,10 @@ +export enum CustomPipe { + DATE_FMT_EN = 'MM/dd/yyyy', + DATE_FMT_DE = 'dd.MM.yyyy', + DATE_TIME_FMT_EN = 'MM/dd/yyyy hh:mm a', + DATE_TIME_FMT_DE = 'dd.MM.yyyy HH:mm', + TIME_FMT_EN = 'hh:mm a', + TIME_FMT_DE = 'HH:mm', + PERCENT_FMT = '3.1-2', + TIME_FORMAT = 'shortTime' +} diff --git a/security-c4po-angular/src/shared/models/number-and-date-time-format.model.ts b/security-c4po-angular/src/shared/models/number-and-date-time-format.model.ts new file mode 100644 index 0000000..5fbc067 --- /dev/null +++ b/security-c4po-angular/src/shared/models/number-and-date-time-format.model.ts @@ -0,0 +1,4 @@ +export enum NumberAndDateFormatSystem { + ENGLISH = 'en-US', + GERMAN = 'de-DE' +} diff --git a/security-c4po-angular/src/shared/models/project.model.ts b/security-c4po-angular/src/shared/models/project.model.ts index 6f25567..971f868 100644 --- a/security-c4po-angular/src/shared/models/project.model.ts +++ b/security-c4po-angular/src/shared/models/project.model.ts @@ -1,5 +1,3 @@ -import { v4 as UUID } from 'uuid'; - export class Project { id: string; client: string; diff --git a/security-c4po-angular/src/shared/pipes/date-time-format.pipe.spec.ts b/security-c4po-angular/src/shared/pipes/date-time-format.pipe.spec.ts new file mode 100644 index 0000000..40ce517 --- /dev/null +++ b/security-c4po-angular/src/shared/pipes/date-time-format.pipe.spec.ts @@ -0,0 +1,128 @@ +import {DateTimeFormatPipe} from './date-time-format.pipe'; +import {formatDate, registerLocaleData} from '@angular/common'; +import localeDe from '@angular/common/locales/de'; +import {CustomPipe} from 'src/shared/models/custom-pipe.mode'; +import {User} from '@shared/models/user.model'; +import {UpdateUserSettings} from '@shared/stores/session-state/session-state.actions'; +import {NumberAndDateFormatSystem} from '@shared/models/number-and-date-time-format.model'; +import {SESSION_STATE_NAME, SessionState, SessionStateModel} from '@shared/stores/session-state/session-state'; +import {NgxsModule, Store} from '@ngxs/store'; +import {UserService} from '@shared/services/user.service'; +import {inject, TestBed} from '@angular/core/testing'; +import {HttpClient} from '@angular/common/http'; +import {HttpLoaderFactory} from '../../app/common-app.module'; +import {TranslateLoader, TranslateModule} from '@ngx-translate/core'; +import {HttpClientTestingModule} from '@angular/common/http/testing'; + +const DESIRED_STORE_STATE_SESSION: SessionStateModel = { + userAccount: { + ...new User('ttt', 'test', 'user', 'default.user@test.de', 'en-US'), + id: '11c47c56-3bcd-45f1-a05b-c197dbd33110' + }, + isAuthenticated: true +}; + +describe('DateTimeFormatPipe', () => { + let pipe: DateTimeFormatPipe; + let store: Store; + // tslint:disable-next-line:prefer-const + let dateAndNumberFormat: NumberAndDateFormatSystem; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + HttpClientTestingModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient] + } + }), + NgxsModule.forRoot([SessionState]), + ], + declarations: [ + DateTimeFormatPipe, + ], + providers: [ + {provide: UserService}, + ] + }).compileComponents(); + }); + + beforeEach(inject([Store], (inStore: Store) => { + store = inStore; + store.reset({ + ...store.snapshot(), + [SESSION_STATE_NAME]: DESIRED_STORE_STATE_SESSION + }); + pipe = new DateTimeFormatPipe(inStore); + }) + ); + + it('should init', () => { + expect(pipe).toBeTruthy(); + }); + + it('return "-" if value is null', () => { + dateAndNumberFormat = NumberAndDateFormatSystem.ENGLISH; + const value: string = null; + expect(pipe.transform(value)).toEqual('-'); + }); +// ToDo: Enable tests after angular version upgrade +/* + it('return english date time format if NumberAndDateFormatSystem is undefined', () => { + dateAndNumberFormat = undefined; + const value = '2019-11-24T14:34:16.802Z'; + const oldFormatDate = formatDate; + const formatSpy = jest.spyOn(require('@angular/common'), 'formatDate') + .mockReturnValue((val, format, locale, timezone) => { + expect(val).toBe(value); + expect(format).toBe(CustomPipe.DATE_TIME_FMT_EN); + expect(locale).toBe('en-US'); + expect(timezone).toBeUndefined(); + return oldFormatDate(val, format, locale, timezone); + }); + pipe.transform(value); + // Does unfortunately not work here: expect(formatSpy).toHaveBeenCalledWith(value, CustomPipe.DATE_FMT_EN, 'en-us'); + expect(formatSpy).toHaveBeenCalledTimes(1); + }); + + it('return english date time format if NumberAndDateFormatSystem is english', () => { + dateAndNumberFormat = NumberAndDateFormatSystem.ENGLISH; + const value = '2019-11-24T14:34:16.802Z'; + const oldFormatDate = formatDate; + const formatSpy = jest.spyOn(require('@angular/common'), 'formatDate') + .mockReturnValue((val, format, locale, timezone) => { + expect(val).toBe(value); + expect(format).toBe(CustomPipe.DATE_TIME_FMT_EN); + expect(locale).toBe('en-US'); + expect(timezone).toBeUndefined(); + return oldFormatDate(val, format, locale, timezone); + }); + pipe.transform(value); + // Does unfortunately not work here: expect(formatSpy).toHaveBeenCalledWith(value, CustomPipe.DATE_FMT_EN, 'en-us'); + expect(formatSpy).toHaveBeenCalledTimes(1); + }); + + it('return german date time format if NumberAndDateFormatSystem is german', () => { + registerLocaleData(localeDe, 'de-DE'); + dateAndNumberFormat = NumberAndDateFormatSystem.GERMAN; + const userAccountUpdate = new User('test', 'test', 'test', 'test@test.de', 'de-DE'); + store.dispatch(new UpdateUserSettings(userAccountUpdate)); + const value = '2019-11-24T14:34:16.802Z'; + const oldFormatDate = formatDate; + const formatSpy = jest.spyOn(require('@angular/common'), 'formatDate') + .mockReturnValue((val, format, locale, timezone) => { + expect(val).toBe(value); + expect(format).toBe(CustomPipe.DATE_TIME_FMT_DE); + expect(locale).toBe('de-DE'); + expect(timezone).toBeUndefined(); + return oldFormatDate(val, format, locale, timezone); + }); + expect(pipe.transform(value).endsWith(' Uhr')).toBeTruthy(); + // Does unfortunately not work here: expect(formatSpy).toHaveBeenCalledWith(value, CustomPipe.DATE_FMT_EN, 'en-us'); + expect(formatSpy).toHaveBeenCalledTimes(1); + }); +*/ +}); diff --git a/security-c4po-angular/src/shared/pipes/date-time-format.pipe.ts b/security-c4po-angular/src/shared/pipes/date-time-format.pipe.ts new file mode 100644 index 0000000..3355552 --- /dev/null +++ b/security-c4po-angular/src/shared/pipes/date-time-format.pipe.ts @@ -0,0 +1,39 @@ +import {Pipe, PipeTransform} from '@angular/core'; +import {formatDate} from '@angular/common'; +import {Store} from '@ngxs/store'; +import {SessionState} from '@shared/stores/session-state/session-state'; +import {CustomPipe} from '@shared/models/custom-pipe.mode'; + +@Pipe({ + name: 'dateTimeFormat' +}) +export class DateTimeFormatPipe implements PipeTransform { + + constructor(private store: Store) { + } + + /** + * Transforms the value to the appropriate date and time format according to the selected number and date format + * @param value of type any + * @return formatted value as string + */ + transform(value: any): string { + if (!value) { + return '-'; + } + + const localeDateAndNumberFormat = this.store.selectSnapshot(SessionState.userAccount) ? + // @ts-ignore + this.store.selectSnapshot(SessionState.userAccount.interfaceLang) : 'en-US'; + + if (!localeDateAndNumberFormat) { + return formatDate(value, CustomPipe.DATE_TIME_FMT_EN, 'en-US'); + } + if (localeDateAndNumberFormat === 'de-DE') { + return formatDate(value, CustomPipe.DATE_TIME_FMT_DE, localeDateAndNumberFormat) + ' Uhr'; + } + // @ts-ignore + return formatDate(value, CustomPipe.DATE_TIME_FMT_EN, localeDateAndNumberFormat); + } + +} diff --git a/security-c4po-angular/src/shared/services/project.service.mock.ts b/security-c4po-angular/src/shared/services/project.service.mock.ts new file mode 100644 index 0000000..150d583 --- /dev/null +++ b/security-c4po-angular/src/shared/services/project.service.mock.ts @@ -0,0 +1,14 @@ +import {ProjectService} from '@shared/services/project.service'; +import {HttpClient} from '@angular/common/http'; +import {Observable, of} from 'rxjs'; +import {Project} from '@shared/models/project.model'; + + +export class ProjectServiceMock implements Required { + + private http: HttpClient; + + getProjects(): Observable { + return of([]); + } +} diff --git a/security-c4po-angular/src/shared/widgets/loading-spinner/loading-spinner.component.html b/security-c4po-angular/src/shared/widgets/loading-spinner/loading-spinner.component.html new file mode 100644 index 0000000..12b4729 --- /dev/null +++ b/security-c4po-angular/src/shared/widgets/loading-spinner/loading-spinner.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/security-c4po-angular/src/shared/widgets/loading-spinner/loading-spinner.component.scss b/security-c4po-angular/src/shared/widgets/loading-spinner/loading-spinner.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/security-c4po-angular/src/shared/widgets/loading-spinner/loading-spinner.component.spec.ts b/security-c4po-angular/src/shared/widgets/loading-spinner/loading-spinner.component.spec.ts new file mode 100644 index 0000000..d7b70fb --- /dev/null +++ b/security-c4po-angular/src/shared/widgets/loading-spinner/loading-spinner.component.spec.ts @@ -0,0 +1,31 @@ +import {ComponentFixture, TestBed} from '@angular/core/testing'; + +import {LoadingSpinnerComponent} from './loading-spinner.component'; +import {NbSpinnerModule} from '@nebular/theme'; + +describe('LoadingSpinnerComponent', () => { + let component: LoadingSpinnerComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ + LoadingSpinnerComponent + ], + imports: [ + NbSpinnerModule + ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(LoadingSpinnerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/security-c4po-angular/src/shared/widgets/loading-spinner/loading-spinner.component.ts b/security-c4po-angular/src/shared/widgets/loading-spinner/loading-spinner.component.ts new file mode 100644 index 0000000..949d45a --- /dev/null +++ b/security-c4po-angular/src/shared/widgets/loading-spinner/loading-spinner.component.ts @@ -0,0 +1,26 @@ +import {Component, Input, OnDestroy, OnInit} from '@angular/core'; +import {Observable, of} from 'rxjs'; +import {untilDestroyed} from 'ngx-take-until-destroy'; + +@Component({ + selector: 'app-loading-spinner', + templateUrl: './loading-spinner.component.html', + styleUrls: ['./loading-spinner.component.scss'] +}) +export class LoadingSpinnerComponent implements OnInit, OnDestroy { + + @Input() isLoading$: Observable = of(false); + loading: boolean; + + ngOnInit(): void { + this.loading = false; + this.isLoading$ + .pipe(untilDestroyed(this)) + .subscribe((value: boolean): void => { + this.loading = value; + }); + } + + ngOnDestroy(): void { + } +} diff --git a/security-c4po-angular/tsconfig.app.json b/security-c4po-angular/tsconfig.app.json index 82d91dc..ba8f5b0 100644 --- a/security-c4po-angular/tsconfig.app.json +++ b/security-c4po-angular/tsconfig.app.json @@ -5,6 +5,9 @@ "outDir": "./out-tsc/app", "types": [] }, + "angularCompilerOptions": { + "enableIvy": false + }, "files": [ "src/main.ts", "src/polyfills.ts" diff --git a/security-c4po-angular/tsconfig.json b/security-c4po-angular/tsconfig.json index c172f60..bf60e0f 100644 --- a/security-c4po-angular/tsconfig.json +++ b/security-c4po-angular/tsconfig.json @@ -18,7 +18,7 @@ "dom" ], "paths": { - "@shared/*": ["./src/app/shared/*"], + "@shared/*": ["./src/shared/*"], "@assets/*": ["./src/assets/*"] } } diff --git a/security-c4po-angular/tsconfig.spec.json b/security-c4po-angular/tsconfig.spec.json index 58c6bb3..2b6775c 100644 --- a/security-c4po-angular/tsconfig.spec.json +++ b/security-c4po-angular/tsconfig.spec.json @@ -7,7 +7,7 @@ ], "module": "commonjs", "emitDecoratorMetadata": true, - "allowJs": true + "allowJs": true, }, "files": [ "src/polyfills.ts" diff --git a/security-c4po-api/build.gradle.kts b/security-c4po-api/build.gradle.kts index 828fab9..f200e78 100644 --- a/security-c4po-api/build.gradle.kts +++ b/security-c4po-api/build.gradle.kts @@ -74,7 +74,6 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server") implementation("org.springframework.boot:spring-boot-starter-oauth2-client") - implementation("com.auth0:java-jwt:3.18.1") implementation("org.modelmapper:modelmapper:2.3.2") api("org.springframework.security:spring-security-jwt:1.1.1.RELEASE") diff --git a/security-c4po-api/src/main/asciidoc/SecurityC4PO.adoc b/security-c4po-api/src/main/asciidoc/SecurityC4PO.adoc index 7f40413..08bfb63 100644 --- a/security-c4po-api/src/main/asciidoc/SecurityC4PO.adoc +++ b/security-c4po-api/src/main/asciidoc/SecurityC4PO.adoc @@ -29,15 +29,15 @@ To get projects, call the GET request /projects ==== Request example -#include::{snippets}/getProjects/com.securityc4po.api.http-request.adoc[] +include::{snippets}/getProjects/http-request.adoc[] ==== Response example -#include::{snippets}/getProjects/com.securityc4po.api.http-response.adoc[] +include::{snippets}/getProjects/http-response.adoc[] ==== Response structure -#include::{snippets}/getProjects/response-fields.adoc[] +include::{snippets}/getProjects/response-fields.adoc[] == Change History diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/BaseEntity.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/BaseEntity.kt index c18583d..8bc5572 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/BaseEntity.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/BaseEntity.kt @@ -1,6 +1,19 @@ package com.securityc4po.api +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.springframework.data.annotation.Id + +@SuppressFBWarnings( + NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR, + RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE, + MESSAGE_NOT_INITIALIZED_REDUNDANT_NULLCHECK +) abstract class BaseEntity( var data: T ) { + @Id + lateinit var id: String } 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 index 0db770d..b292d4e 100644 --- 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 @@ -5,11 +5,7 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority import org.springframework.security.core.GrantedAuthority import org.springframework.security.core.userdetails.UserDetails -class Appuser internal constructor( - val sub: String, - val extractedUsername: String, - val token: String -) : UserDetails { +class Appuser internal constructor() : UserDetails { override fun getAuthorities(): Collection { return listOf("user").stream().map { 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 index d1616b5..e313477 100644 --- 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 @@ -9,17 +9,20 @@ 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 reactor.kotlin.core.publisher.toMono import java.util.stream.Collectors -/** JWT converter that takes the roles from 'groups' claim of JWT token. */ -class AppuserJwtAuthConverter : Converter> { +class AppuserJwtAuthConverter(private val appuserDetailsService: UserAccountDetailsService) : + Converter> { override fun convert(jwt: Jwt): Mono { val authorities = extractAuthorities(jwt) - val sub = extractSub(jwt) + // val sub = extractSub(jwt) val username = extractUserName(jwt) - return UsernamePasswordAuthenticationToken(Appuser(sub, username, jwt.tokenValue!!), "n/a", authorities).toMono() + return appuserDetailsService + .findByUsername(username) + .map { user -> + UsernamePasswordAuthenticationToken(user, "n/a", authorities); + } } private fun extractSub(jwt: Jwt): String { @@ -51,7 +54,7 @@ class AppuserJwtAuthConverter : Converter val scopes = jwt.getClaims().get(GROUPS_CLAIM).toString() val roleStringValue = mapper.readTree(scopes).get("roles").toString() val roles = mapper.readValue>(roleStringValue) - if (!roles.isEmpty()){ + if (!roles.isEmpty()) { return roles } return emptyList() 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..56c485c --- /dev/null +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/UserAccountDetailsService.kt @@ -0,0 +1,14 @@ +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() + } +} \ No newline at end of file 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 index 27cb711..0ba7402 100644 --- 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 @@ -1,8 +1,9 @@ package com.securityc4po.api.configuration.security -import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration +import org.springframework.boot.autoconfigure.EnableAutoConfiguration import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Lazy +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration import org.springframework.http.HttpMethod import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity @@ -12,10 +13,12 @@ import org.springframework.web.cors.CorsConfiguration @EnableWebFluxSecurity @EnableReactiveMethodSecurity -class WebSecurityConfiguration { +@Configuration +@ComponentScan +class WebSecurityConfiguration(private val userAccountDetailsService: UserAccountDetailsService) { @Bean - fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { + fun setSecurityWebFilterChains(http: ServerHttpSecurity): SecurityWebFilterChain { http.cors().configurationSource { CorsConfiguration().apply { this.applyPermitDefaultValues() @@ -43,6 +46,6 @@ class WebSecurityConfiguration { @Bean fun appuserJwtAuthenticationConverter(): AppuserJwtAuthConverter { - return AppuserJwtAuthConverter() + return AppuserJwtAuthConverter(userAccountDetailsService) } } diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectEntity.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectEntity.kt index 9e6281b..daf88fa 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectEntity.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectEntity.kt @@ -1,6 +1,9 @@ package com.securityc4po.api.project import com.securityc4po.api.BaseEntity +import com.securityc4po.api.configuration.BC_BAD_CAST_TO_ABSTRACT_COLLECTION +import com.securityc4po.api.configuration.MESSAGE_BAD_CAST_TO_ABSTRACT_COLLECTION +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings import org.springframework.data.mongodb.core.mapping.Document @Document(collection = "projects") @@ -19,6 +22,7 @@ fun ProjectEntity.toProject() : Project { ) } +@SuppressFBWarnings(BC_BAD_CAST_TO_ABSTRACT_COLLECTION, MESSAGE_BAD_CAST_TO_ABSTRACT_COLLECTION) fun List.toProjects(): List { return this.map { it.toProject() diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectService.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectService.kt index 458434e..98163c2 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectService.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectService.kt @@ -1,10 +1,14 @@ package com.securityc4po.api.project +import com.securityc4po.api.configuration.BC_BAD_CAST_TO_ABSTRACT_COLLECTION +import com.securityc4po.api.configuration.MESSAGE_BAD_CAST_TO_ABSTRACT_COLLECTION import com.securityc4po.api.extensions.getLoggerFor +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings import org.springframework.stereotype.Service import reactor.core.publisher.Mono @Service +@SuppressFBWarnings(BC_BAD_CAST_TO_ABSTRACT_COLLECTION, MESSAGE_BAD_CAST_TO_ABSTRACT_COLLECTION) class ProjectService(private val projectRepository: ProjectRepository) { var logger = getLoggerFor() diff --git a/security-c4po-api/src/main/resources/application-COMPOSE.properties b/security-c4po-api/src/main/resources/application-COMPOSE.properties index 11fe6cf..e4888d6 100644 --- a/security-c4po-api/src/main/resources/application-COMPOSE.properties +++ b/security-c4po-api/src/main/resources/application-COMPOSE.properties @@ -1,7 +1,8 @@ ## IdentityProvider (Keycloak) ## -# spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8888/auth/realms/c4po_realm_local -# keycloakhost=localhost -# keycloak.client.url=http://localhost:8888/ +spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8888/auth/realms/c4po_realm_local +keycloakhost=localhost +keycloak.client.url=http://localhost:8888 +keycloak.client.realm.path=auth/realms/c4po_realm_local/ ## Database (MONGODB) Config ## spring.data.mongodb.host=c4po-db diff --git a/security-c4po-api/src/main/resources/application-DEV.properties b/security-c4po-api/src/main/resources/application-DEV.properties index f3fb2e6..9c88551 100644 --- a/security-c4po-api/src/main/resources/application-DEV.properties +++ b/security-c4po-api/src/main/resources/application-DEV.properties @@ -1,7 +1,7 @@ ## IdentityProvider (Keycloak) ## -# spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8888/auth/realms/c4po_realm_local -# keycloakhost=localhost -# keycloak.client.url=http://localhost:8888/ +spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8888/auth/realms/c4po_realm_local +keycloakhost=localhost +keycloak.client.url=http://localhost:8888/ ## Database (MONGODB) Config ## spring.data.mongodb.host=localhost diff --git a/security-c4po-api/src/main/resources/application-TEST.properties b/security-c4po-api/src/main/resources/application-TEST.properties new file mode 100644 index 0000000..d8e6f76 --- /dev/null +++ b/security-c4po-api/src/main/resources/application-TEST.properties @@ -0,0 +1,9 @@ +## IdentityProvider (Keycloak) ## +spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:9999/auth/realms/c4po_realm_local +keycloakhost=localhost +keycloak.client.url=http://localhost:9999 +keycloak.client.realm.path=auth/realms/c4po_realm_local/ + +## Database (MONGODB) Config ## +spring.data.mongodb.host=localhost +spring.data.mongodb.port=27021 \ No newline at end of file diff --git a/security-c4po-api/src/main/resources/application.properties b/security-c4po-api/src/main/resources/application.properties index 327d23c..6933b14 100644 --- a/security-c4po-api/src/main/resources/application.properties +++ b/security-c4po-api/src/main/resources/application.properties @@ -18,6 +18,5 @@ spring.data.mongodb.auto-index-creation=true ## IdentityProvider (Keycloak) ## spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8888/auth/realms/c4po_realm_local keycloakhost=localhost -keycloak.client.url=http://localhost:8888/ -# keycloak.client.realm.path=auth/realms/c4po_realm_local/ -idp.jwt.claim.name.user=username \ No newline at end of file +keycloak.client.url=http://localhost:8888 +keycloak.client.realm.path=auth/realms/c4po_realm_local/ \ No newline at end of file diff --git a/security-c4po-api/src/test/kotlin/com/securityc4po/api/BaseContainerizedTest.kt b/security-c4po-api/src/test/kotlin/com/securityc4po/api/BaseContainerizedTest.kt index c8f98c4..f1b69c2 100644 --- a/security-c4po-api/src/test/kotlin/com/securityc4po/api/BaseContainerizedTest.kt +++ b/security-c4po-api/src/test/kotlin/com/securityc4po/api/BaseContainerizedTest.kt @@ -11,6 +11,7 @@ import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock import org.springframework.http.HttpEntity import org.springframework.http.HttpHeaders import org.springframework.http.MediaType +import org.springframework.test.context.ActiveProfiles import org.springframework.test.context.TestPropertySource import org.springframework.util.LinkedMultiValueMap import org.springframework.web.client.RestTemplate @@ -24,7 +25,7 @@ import java.nio.file.Paths @TestInstance(TestInstance.Lifecycle.PER_CLASS) @AutoConfigureWireMock(port = 0) @TestPropertySource(properties = [ - "spring.data.mongodb.port=10002", + "spring.data.mongodb.port=27017", "spring.data.mongodb.authentication-database=admin", "spring.data.mongodb.password=test", "spring.data.mongodb.username=testuser", @@ -46,12 +47,12 @@ abstract class BaseContainerizedTest { }.withFileFromPath("insert-mongodb-user.js", Paths.get(MountableFile.forClasspathResource("insert-mongodb-user.js", 700).resolvedPath)) ).apply { withCreateContainerCmdModifier { - it.hostConfig?.withPortBindings(PortBinding(Ports.Binding.bindPort(10002), ExposedPort(27017))) + it.hostConfig?.withPortBindings(PortBinding(Ports.Binding.bindPort(27017), ExposedPort(27017))) } start() } - val keycloakContainer = KGenericContainerFromImage(DockerImageName.parse("jboss/keycloak:6.0.1")).apply { + val keycloakContainer = KGenericContainerFromImage(DockerImageName.parse("jboss/keycloak:11.0.3")).apply { withEnv("KEYCLOAK_USER", "admin") withEnv("KEYCLOAK_PASSWORD", "admin") withEnv("KEYCLOAK_IMPORT", "/tmp/realm.json") @@ -59,9 +60,7 @@ abstract class BaseContainerizedTest { withCreateContainerCmdModifier { it.hostConfig?.withPortBindings(PortBinding(Ports.Binding.bindPort(8888), ExposedPort(8080))) } - withCopyFileToContainer(MountableFile.forClasspathResource("outdated_realm-export.json", 700), "/tmp/realm.json") - withCopyFileToContainer(MountableFile.forClasspathResource("create-keycloak-user.sh", 700), - "/opt/jboss/create-keycloak-user.sh") + withCopyFileToContainer(MountableFile.forClasspathResource("realm-export.json", 700), "/tmp/realm.json") start() println("== Inserting users must wait until Keycloak is started completely ==") execInContainer("sh", "/opt/jboss/create-keycloak-user.sh") @@ -80,10 +79,10 @@ abstract class BaseContainerizedTest { headers.contentType = MediaType.APPLICATION_FORM_URLENCODED val map = LinkedMultiValueMap() - map.add("grant_type", "password") map.add("client_id", clientId) map.add("username", username) map.add("password", password) + map.add("grant_type", "password") map.add("client_secret", "secret") val responseString = restTemplate.postForObject("$keycloakHost/auth/realms/$realm/protocol/openid-connect/token", HttpEntity(map, headers), String::class.java) diff --git a/security-c4po-api/src/test/kotlin/com/securityc4po/api/project/ProjectControllerDocumentationTest.kt b/security-c4po-api/src/test/kotlin/com/securityc4po/api/project/ProjectControllerDocumentationTest.kt index a97ef60..ab79926 100644 --- a/security-c4po-api/src/test/kotlin/com/securityc4po/api/project/ProjectControllerDocumentationTest.kt +++ b/security-c4po-api/src/test/kotlin/com/securityc4po/api/project/ProjectControllerDocumentationTest.kt @@ -2,13 +2,15 @@ package com.securityc4po.api.project import com.github.tomakehurst.wiremock.common.Json import com.securityc4po.api.BaseDocumentationIntTest +import com.securityc4po.api.configuration.NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR +import com.securityc4po.api.configuration.RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE import com.securityc4po.api.configuration.SIC_INNER_SHOULD_BE_STATIC import edu.umd.cs.findbugs.annotations.SuppressFBWarnings +import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired -import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock import org.springframework.data.mongodb.core.MongoTemplate import org.springframework.data.mongodb.core.query.Query import org.springframework.restdocs.operation.preprocess.Preprocessors @@ -16,8 +18,11 @@ import org.springframework.restdocs.payload.JsonFieldType import org.springframework.restdocs.payload.PayloadDocumentation import org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation -@AutoConfigureWireMock(port = 0) -@SuppressFBWarnings(SIC_INNER_SHOULD_BE_STATIC) +@SuppressFBWarnings( + SIC_INNER_SHOULD_BE_STATIC, + NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR, + RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE +) class ProjectControllerDocumentationTest : BaseDocumentationIntTest() { @Autowired @@ -25,18 +30,21 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() { @BeforeEach fun init() { - cleanUp() + configureAdminToken() persistBasicTestScenario() } + @AfterEach + fun destroy() { + cleanUp() + } + @Nested inner class GetProjects { @Test fun getProjects() { - /* Implement after the implementation of database */ - - /*webTestClient.get().uri("/v1/projects") - .header("") + webTestClient.get().uri("/projects") + .header("Authorization", "Bearer $tokenAdmin") .exchange() .expectStatus().isOk .expectHeader().doesNotExist("") @@ -49,14 +57,14 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() { Preprocessors.prettyPrint() ), PayloadDocumentation.relaxedResponseFields( - PayloadDocumentation.fieldWithPath("[].id").type(JsonFieldType.STRING).description("The id of the requested Project"), - PayloadDocumentation.fieldWithPath("[].client").type(JsonFieldType.STRING).description("The name of the client of the requested Project"), - PayloadDocumentation.fieldWithPath("[].title").type(JsonFieldType.STRING).description("The title of the requested Project"), - PayloadDocumentation.fieldWithPath("[].createdAt").type(JsonFieldType.STRING).description("The date where the Project was created at"), - PayloadDocumentation.fieldWithPath("[].tester").type(JsonFieldType.STRING).description("The user that is used as a tester in the Project"), - PayloadDocumentation.fieldWithPath("[].logo").type(JsonFieldType.STRING).description("The sensors contained in the Project") + PayloadDocumentation.fieldWithPath("[].id").type(JsonFieldType.STRING).description("The id of the requested project"), + PayloadDocumentation.fieldWithPath("[].client").type(JsonFieldType.STRING).description("The name of the client of the requested project"), + PayloadDocumentation.fieldWithPath("[].title").type(JsonFieldType.STRING).description("The title of the requested project"), + PayloadDocumentation.fieldWithPath("[].createdAt").type(JsonFieldType.STRING).description("The date where the project was created at"), + PayloadDocumentation.fieldWithPath("[].tester").type(JsonFieldType.STRING).description("The user that is assigned as a tester in the project"), + PayloadDocumentation.fieldWithPath("[].createdBy").type(JsonFieldType.STRING).description("The id of the user that created the project") ) - ))*/ + )) } val projectOne = Project( @@ -82,30 +90,36 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() { ) } - private fun cleanUp() { - mongoTemplate.findAllAndRemove(Query(), Project::class.java) - } - private fun persistBasicTestScenario() { // setup test data val projectOne = Project( - id = "260aa538-0873-43fc-84de-3a09b008646d", - client = "", - title = "", - createdAt = "", - tester = "", - createdBy = "" + id = "4f6567a8-76fd-487b-8602-f82d0ca4d1f9", + client = "E Corp", + title = "Some Mock API (v1.0) Scanning", + createdAt = "2021-01-10T18:05:00Z", + tester = "Novatester", + createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" ) val projectTwo = Project( - id = "260aa538-0873-43fc-84de-3a09b008646d", - client = "", - title = "", - createdAt = "", - tester = "", - createdBy = "" + id = "61360a47-796b-4b3f-abf9-c46c668596c5", + client = "Allsafe", + title = "CashMyData (iOS)", + createdAt = "2021-01-10T18:05:00Z", + tester = "Elliot", + createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" ) - cleanUp() + // persist test data in database mongoTemplate.save(ProjectEntity(projectOne)) mongoTemplate.save(ProjectEntity(projectTwo)) } + + private fun configureAdminToken() { + tokenAdmin = getAccessToken("test_admin", "test", "c4po_local", "c4po_realm_local") + } + + private fun cleanUp() { + mongoTemplate.findAllAndRemove(Query(), ProjectEntity::class.java) + + tokenAdmin = "n/a" + } } \ No newline at end of file diff --git a/security-c4po-api/src/test/kotlin/com/securityc4po/api/project/ProjectControllerIntTest.kt b/security-c4po-api/src/test/kotlin/com/securityc4po/api/project/ProjectControllerIntTest.kt index e2988fb..fe56a2e 100644 --- a/security-c4po-api/src/test/kotlin/com/securityc4po/api/project/ProjectControllerIntTest.kt +++ b/security-c4po-api/src/test/kotlin/com/securityc4po/api/project/ProjectControllerIntTest.kt @@ -2,34 +2,25 @@ package com.securityc4po.api.project import com.github.tomakehurst.wiremock.common.Json import com.securityc4po.api.BaseIntTest +import com.securityc4po.api.configuration.NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR +import com.securityc4po.api.configuration.RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE 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 io.netty.handler.ssl.SslContextBuilder +import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.web.server.LocalServerPort -import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock import org.springframework.data.mongodb.core.MongoTemplate import org.springframework.data.mongodb.core.query.Query -import org.springframework.test.context.TestPropertySource import org.springframework.test.web.reactive.server.WebTestClient -import org.springframework.util.ResourceUtils -import reactor.netty.http.client.HttpClient import java.time.Duration -@AutoConfigureWireMock(port = 0) -/*@TestPropertySource( - properties = [ - "keycloak.client.url=http://localhost:${'$'}{wiremock.server.port}" - ] -)*/ @SuppressFBWarnings( SIC_INNER_SHOULD_BE_STATIC, - URF_UNREAD_FIELD, - "Unread field will become used after database implementation" + NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR, + RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE ) class ProjectControllerIntTest : BaseIntTest() { @@ -39,6 +30,7 @@ class ProjectControllerIntTest : BaseIntTest() { @Autowired lateinit var mongoTemplate: MongoTemplate + @Autowired private lateinit var webTestClient: WebTestClient @BeforeEach @@ -51,20 +43,24 @@ class ProjectControllerIntTest : BaseIntTest() { @BeforeEach fun init() { - cleanUp() configureAdminToken() persistBasicTestScenario() } + @AfterEach + fun destroy() { + cleanUp() + } + @Nested inner class GetProjects { @Test fun `requesting projects successfully`() { - webTestClient.get().uri("/v1/projects") + webTestClient.get().uri("/projects") .header("Authorization", "Bearer $tokenAdmin") .exchange() .expectStatus().isOk - .expectHeader().valueEquals("Application-Name", "security-c4po-api") + .expectHeader().valueEquals("Application-Name", "SecurityC4PO") .expectBody().json(Json.write(getProjects())) } @@ -91,12 +87,6 @@ class ProjectControllerIntTest : BaseIntTest() { ) } - private fun cleanUp() { - mongoTemplate.findAllAndRemove(Query(), Project::class.java) - - tokenAdmin = "n/a" - } - private fun persistBasicTestScenario() { // setup test data val projectOne = Project( @@ -115,7 +105,7 @@ class ProjectControllerIntTest : BaseIntTest() { tester = "Elliot", createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" ) - cleanUp() + // persist test data in database mongoTemplate.save(ProjectEntity(projectOne)) mongoTemplate.save(ProjectEntity(projectTwo)) } @@ -123,4 +113,10 @@ class ProjectControllerIntTest : BaseIntTest() { private fun configureAdminToken() { tokenAdmin = getAccessToken("test_admin", "test", "c4po_local", "c4po_realm_local") } + + private fun cleanUp() { + mongoTemplate.findAllAndRemove(Query(), ProjectEntity::class.java) + + tokenAdmin = "n/a" + } } \ No newline at end of file diff --git a/security-c4po-api/src/test/resources/create-keycloak-user.sh b/security-c4po-api/src/test/resources/create-keycloak-user.sh deleted file mode 100644 index 4ec0d95..0000000 --- a/security-c4po-api/src/test/resources/create-keycloak-user.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bash -cd keycloak/bin -sleep 20 -./kcadm.sh config credentials --server http://localhost:8080/auth --realm master --user admin --password admin - -USERID=$(./kcadm.sh create users -r c4po_realm_local -s username=test_admin \ --s email=Test.Admin@heros.com \ --s firstName=test \ --s lastName=admin \ --s attributes.lang="de-DE" \ --s attributes.datenumberformat="en-US" \ --o --fields id | jq '.id' | tr -d '"') - -./kcadm.sh update users/$USERID/reset-password -r c4po_realm_test -s type=password -s value=test -s temporary=false -n -./kcadm.sh add-roles --uusername test_admin --rolename c4po_admin -r c4po_realm_test -./kcadm.sh add-roles -r c4po_realm_test --uusername test_admin --cclientid realm-management --rolename create-client --rolename view-users - -USERID=$(./kcadm.sh create users -r c4po_realm_local -s username=test_user \ --s email=Test.User@heros.com \ --s firstName=test \ --s lastName=user \ --s attributes.lang="de-DE" \ --s attributes.datenumberformat="en-US" \ --o --fields id | jq '.id' | tr -d '"') - -./kcadm.sh update users/$USERID/reset-password -r c4po_realm_test -s type=password -s value=test -s temporary=false -n -./kcadm.sh add-roles --uusername test_user --rolename c4po_user -r c4po_realm_test -./kcadm.sh add-roles -r c4po_realm_test --uusername test_user --cclientid realm-management --rolename create-client --rolename view-users \ No newline at end of file diff --git a/security-c4po-api/src/test/resources/outdated_realm-export.json b/security-c4po-api/src/test/resources/outdated_realm-export.json deleted file mode 100644 index d321e8f..0000000 --- a/security-c4po-api/src/test/resources/outdated_realm-export.json +++ /dev/null @@ -1,2230 +0,0 @@ -{ - "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, - "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": "ffe6c698-0281-404a-8f8b-16b8b489f76f", - "name": "offline_access", - "description": "${role_offline-access}", - "composite": false, - "clientRole": false, - "containerId": "c4po_realm_local", - "attributes": {} - }, - { - "id": "1cd0f230-beec-4e5e-9a59-8553da3ee74b", - "name": "USER", - "composite": true, - "composites": { - "client": { - "realm-management": [ - "view-clients", - "manage-identity-providers", - "manage-users", - "manage-realm", - "query-realms", - "view-realm", - "query-users", - "query-groups", - "impersonation", - "view-authorization", - "manage-authorization", - "view-users", - "create-client", - "manage-events", - "view-events", - "manage-clients", - "realm-admin", - "view-identity-providers", - "query-clients" - ] - } - }, - "clientRole": false, - "containerId": "c4po_realm_local", - "attributes": {} - }, - { - "id": "e67c470f-5fe5-4e4b-a996-0ef8e6df7585", - "name": "PROJECTLEAD", - "composite": true, - "composites": { - "client": { - "realm-management": [ - "view-clients", - "manage-identity-providers", - "manage-users", - "manage-realm", - "query-realms", - "view-realm", - "query-users", - "query-groups", - "impersonation", - "view-authorization", - "manage-authorization", - "view-users", - "create-client", - "manage-events", - "view-events", - "manage-clients", - "realm-admin", - "view-identity-providers", - "query-clients" - ] - } - }, - "clientRole": false, - "containerId": "c4po_realm_local", - "attributes": {} - }, - { - "id": "04320a32-4608-447a-98ab-422de17b1e97", - "name": "CUSTOMERADMIN", - "composite": true, - "composites": { - "client": { - "realm-management": [ - "view-clients", - "manage-identity-providers", - "manage-users", - "manage-realm", - "query-realms", - "view-realm", - "query-users", - "query-groups", - "impersonation", - "view-authorization", - "manage-authorization", - "view-users", - "create-client", - "manage-events", - "view-events", - "manage-clients", - "realm-admin", - "view-identity-providers", - "query-clients" - ] - } - }, - "clientRole": false, - "containerId": "c4po_realm_local", - "attributes": {} - }, - { - "id": "62b91792-977d-448b-8163-8d7bc8cd7e8b", - "name": "OWNER", - "composite": true, - "composites": { - "client": { - "realm-management": [ - "view-clients", - "manage-identity-providers", - "manage-users", - "manage-realm", - "query-realms", - "view-realm", - "query-users", - "query-groups", - "impersonation", - "view-authorization", - "manage-authorization", - "view-users", - "create-client", - "manage-events", - "view-events", - "manage-clients", - "realm-admin", - "view-identity-providers", - "query-clients" - ] - } - }, - "clientRole": false, - "containerId": "c4po_realm_local", - "attributes": {} - }, - { - "id": "f7584ecd-24e2-41ef-89bf-245671a31163", - "name": "uma_authorization", - "description": "${role_uma_authorization}", - "composite": false, - "clientRole": false, - "containerId": "c4po_realm_local", - "attributes": {} - }, - { - "id": "80382db6-cd7f-44e4-b3c3-4a59bbabdccc", - "name": "OBSERVER", - "composite": true, - "composites": { - "client": { - "realm-management": [ - "view-clients", - "manage-identity-providers", - "manage-users", - "manage-realm", - "query-realms", - "view-realm", - "query-users", - "query-groups", - "impersonation", - "view-authorization", - "manage-authorization", - "view-users", - "create-client", - "manage-events", - "view-events", - "manage-clients", - "realm-admin", - "view-identity-providers", - "query-clients" - ] - } - }, - "clientRole": false, - "containerId": "c4po_realm_local", - "attributes": {} - }, - { - "id": "9681689b-afc8-4252-8339-5ed97eac19b5", - "name": "COMPANYADMIN", - "composite": true, - "composites": { - "client": { - "realm-management": [ - "view-clients", - "manage-identity-providers", - "manage-users", - "manage-realm", - "query-realms", - "view-realm", - "query-users", - "query-groups", - "impersonation", - "view-authorization", - "manage-authorization", - "view-users", - "create-client", - "manage-events", - "view-events", - "manage-clients", - "realm-admin", - "view-identity-providers", - "query-clients" - ] - } - }, - "clientRole": false, - "containerId": "c4po_realm_local", - "attributes": {} - }, - { - "id": "7222919e-386a-4c70-a5d8-1b3d3bf06f57", - "name": "cuid:11c47c56-3bcd-45f1-a05b-c197dbd32111", - "composite": false, - "clientRole": false, - "containerId": "c4po_realm_local", - "attributes": {} - } - ], - "client": { - "useraccount-service": [ - { - "id": "7a7e2bee-36c1-4b59-9623-bbe4f81d069e", - "name": "CUSTOMERADMIN", - "composite": false, - "clientRole": true, - "containerId": "0dc2a195-7b23-4e58-bf81-3b6566e4c36c", - "attributes": {} - }, - { - "id": "76591939-e3e2-4e35-b9c8-14a19f0ac50b", - "name": "uma_protection", - "composite": false, - "clientRole": true, - "containerId": "0dc2a195-7b23-4e58-bf81-3b6566e4c36c", - "attributes": {} - }, - { - "id": "b9fae233-7d6b-4a12-8951-5c20fba7e560", - "name": "OBSERVER", - "composite": false, - "clientRole": true, - "containerId": "0dc2a195-7b23-4e58-bf81-3b6566e4c36c", - "attributes": {} - }, - { - "id": "f5efb02b-c6ed-48fe-92bf-47cc8b1c783f", - "name": "USER", - "composite": false, - "clientRole": true, - "containerId": "0dc2a195-7b23-4e58-bf81-3b6566e4c36c", - "attributes": {} - }, - { - "id": "74ef1b12-b236-4dfb-a251-8f9c38fedab9", - "name": "PROJECTLEAD", - "composite": false, - "clientRole": true, - "containerId": "0dc2a195-7b23-4e58-bf81-3b6566e4c36c", - "attributes": {} - }, - { - "id": "9cae2c55-361b-4d34-b4aa-55d8877948ac", - "name": "cuid:11c47c56-3bcd-45f1-a05b-c197dbd32111", - "composite": false, - "clientRole": true, - "containerId": "0dc2a195-7b23-4e58-bf81-3b6566e4c36c", - "attributes": {} - }, - { - "id": "06be41f6-43d9-48aa-8e36-dad567e2eb7f", - "name": "COMPANYADMIN", - "description": "", - "composite": false, - "clientRole": true, - "containerId": "0dc2a195-7b23-4e58-bf81-3b6566e4c36c", - "attributes": {} - }, - { - "id": "00e583d6-dedd-43f8-b7cb-e2aefe313ac5", - "name": "OWNER", - "composite": false, - "clientRole": true, - "containerId": "0dc2a195-7b23-4e58-bf81-3b6566e4c36c", - "attributes": {} - }, - { - "id": "d65244ef-f2cc-4ca1-83cd-b80d2139cb87", - "name": "SENSORADMIN", - "composite": false, - "clientRole": true, - "containerId": "0dc2a195-7b23-4e58-bf81-3b6566e4c36c", - "attributes": {} - }, - { - "id": "fe51cb66-706f-42ea-aa95-2dc9a67b7bd9", - "name": "SENSORCLIENT", - "composite": false, - "clientRole": true, - "containerId": "0dc2a195-7b23-4e58-bf81-3b6566e4c36c", - "attributes": {} - } - ], - "realm-management": [ - { - "id": "e3ee3ecd-c0f0-4f21-8ed9-26f188182873", - "name": "view-clients", - "description": "${role_view-clients}", - "composite": true, - "composites": { - "client": { - "realm-management": [ - "query-clients" - ] - } - }, - "clientRole": true, - "containerId": "1f6ed582-3900-4f1d-8f5e-7e43ec70d721", - "attributes": {} - }, - { - "id": "d9b446db-f795-4e96-a7ec-a4fd5efe6fbc", - "name": "manage-identity-providers", - "description": "${role_manage-identity-providers}", - "composite": false, - "clientRole": true, - "containerId": "1f6ed582-3900-4f1d-8f5e-7e43ec70d721", - "attributes": {} - }, - { - "id": "61e592ba-4f37-4f7a-83d1-9b61dd870f74", - "name": "manage-realm", - "description": "${role_manage-realm}", - "composite": false, - "clientRole": true, - "containerId": "1f6ed582-3900-4f1d-8f5e-7e43ec70d721", - "attributes": {} - }, - { - "id": "407b4eee-745d-4f84-a278-18cdc99e9bc7", - "name": "manage-users", - "description": "${role_manage-users}", - "composite": false, - "clientRole": true, - "containerId": "1f6ed582-3900-4f1d-8f5e-7e43ec70d721", - "attributes": {} - }, - { - "id": "ead18270-d63c-4634-853d-9da14a060c08", - "name": "query-realms", - "description": "${role_query-realms}", - "composite": false, - "clientRole": true, - "containerId": "1f6ed582-3900-4f1d-8f5e-7e43ec70d721", - "attributes": {} - }, - { - "id": "3d8b1ae3-06b3-4699-bdb4-c18a81f1deca", - "name": "view-realm", - "description": "${role_view-realm}", - "composite": false, - "clientRole": true, - "containerId": "1f6ed582-3900-4f1d-8f5e-7e43ec70d721", - "attributes": {} - }, - { - "id": "04416ae9-7437-4107-b958-802fdc8909f5", - "name": "query-users", - "description": "${role_query-users}", - "composite": false, - "clientRole": true, - "containerId": "1f6ed582-3900-4f1d-8f5e-7e43ec70d721", - "attributes": {} - }, - { - "id": "a33907aa-eae0-4154-85d7-6acda98bbe79", - "name": "query-groups", - "description": "${role_query-groups}", - "composite": false, - "clientRole": true, - "containerId": "1f6ed582-3900-4f1d-8f5e-7e43ec70d721", - "attributes": {} - }, - { - "id": "ddfe71ae-8181-4cd8-9fdd-2b8f2c524101", - "name": "impersonation", - "description": "${role_impersonation}", - "composite": false, - "clientRole": true, - "containerId": "1f6ed582-3900-4f1d-8f5e-7e43ec70d721", - "attributes": {} - }, - { - "id": "2d7fb697-7c73-4854-87f2-c84974b2f73a", - "name": "view-authorization", - "description": "${role_view-authorization}", - "composite": false, - "clientRole": true, - "containerId": "1f6ed582-3900-4f1d-8f5e-7e43ec70d721", - "attributes": {} - }, - { - "id": "e927482a-dd4b-475a-b4f9-b7224a8fbac1", - "name": "create-client", - "description": "${role_create-client}", - "composite": false, - "clientRole": true, - "containerId": "1f6ed582-3900-4f1d-8f5e-7e43ec70d721", - "attributes": {} - }, - { - "id": "5c8d303a-853d-4c2d-abac-77056f424a5f", - "name": "view-users", - "description": "${role_view-users}", - "composite": true, - "composites": { - "client": { - "realm-management": [ - "query-users", - "query-groups" - ] - } - }, - "clientRole": true, - "containerId": "1f6ed582-3900-4f1d-8f5e-7e43ec70d721", - "attributes": {} - }, - { - "id": "00ab84fb-a0b0-4ef4-8b43-e09f5da7e350", - "name": "manage-authorization", - "description": "${role_manage-authorization}", - "composite": false, - "clientRole": true, - "containerId": "1f6ed582-3900-4f1d-8f5e-7e43ec70d721", - "attributes": {} - }, - { - "id": "bf58f8f7-e097-4dec-96cd-aab047c80c99", - "name": "manage-events", - "description": "${role_manage-events}", - "composite": false, - "clientRole": true, - "containerId": "1f6ed582-3900-4f1d-8f5e-7e43ec70d721", - "attributes": {} - }, - { - "id": "50e67c01-1b81-45c9-bda1-93205f61a4e1", - "name": "view-events", - "description": "${role_view-events}", - "composite": false, - "clientRole": true, - "containerId": "1f6ed582-3900-4f1d-8f5e-7e43ec70d721", - "attributes": {} - }, - { - "id": "f50a4f70-3ca9-48b4-a567-3f838d93bc4a", - "name": "manage-clients", - "description": "${role_manage-clients}", - "composite": false, - "clientRole": true, - "containerId": "1f6ed582-3900-4f1d-8f5e-7e43ec70d721", - "attributes": {} - }, - { - "id": "e159661b-c630-4279-a7fd-da46d2aaa734", - "name": "realm-admin", - "description": "${role_realm-admin}", - "composite": true, - "composites": { - "client": { - "realm-management": [ - "view-clients", - "manage-identity-providers", - "manage-users", - "manage-realm", - "query-realms", - "view-realm", - "query-users", - "query-groups", - "impersonation", - "view-authorization", - "manage-authorization", - "view-users", - "create-client", - "manage-events", - "view-events", - "manage-clients", - "view-identity-providers", - "query-clients" - ] - } - }, - "clientRole": true, - "containerId": "1f6ed582-3900-4f1d-8f5e-7e43ec70d721", - "attributes": {} - }, - { - "id": "912fb2b9-5db4-473f-820b-5048cc85e8db", - "name": "view-identity-providers", - "description": "${role_view-identity-providers}", - "composite": false, - "clientRole": true, - "containerId": "1f6ed582-3900-4f1d-8f5e-7e43ec70d721", - "attributes": {} - }, - { - "id": "4672a0f6-0522-40cc-b560-68f060f6d877", - "name": "query-clients", - "description": "${role_query-clients}", - "composite": false, - "clientRole": true, - "containerId": "1f6ed582-3900-4f1d-8f5e-7e43ec70d721", - "attributes": {} - } - ], - "security-admin-console": [], - "admin-cli": [], - "broker": [ - { - "id": "6aadda6f-299f-41e4-9954-009cee812cf8", - "name": "read-token", - "description": "${role_read-token}", - "composite": false, - "clientRole": true, - "containerId": "934c22be-97c3-44b3-a5c0-2f1bdc93e540", - "attributes": {} - } - ], - "account": [ - { - "id": "b2a111c4-4574-4f09-92ea-bccf7fe7400f", - "name": "view-profile", - "description": "${role_view-profile}", - "composite": false, - "clientRole": true, - "containerId": "5f58a3c9-fb96-4565-aa97-24001d045829", - "attributes": {} - }, - { - "id": "d441f7b3-d8a8-4644-bde4-e99938520c51", - "name": "manage-account", - "description": "${role_manage-account}", - "composite": true, - "composites": { - "client": { - "account": [ - "manage-account-links" - ] - } - }, - "clientRole": true, - "containerId": "5f58a3c9-fb96-4565-aa97-24001d045829", - "attributes": {} - }, - { - "id": "68d365a0-df3f-4f12-ba1a-739acceec1e0", - "name": "manage-account-links", - "description": "${role_manage-account-links}", - "composite": false, - "clientRole": true, - "containerId": "5f58a3c9-fb96-4565-aa97-24001d045829", - "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" - ], - "scopeMappings": [ - { - "client": "useraccount-service", - "roles": [ - "COMPANYADMIN", - "OWNER", - "cuid:11c47c56-3bcd-45f1-a05b-c197dbd32111", - "OBSERVER", - "CUSTOMERADMIN", - "PROJECTLEAD", - "USER", - "SENSORADMIN", - "SENSORCLIENT" - ] - }, - { - "clientScope": "offline_access", - "roles": [ - "offline_access" - ] - } - ], - "clientScopeMappings": { - "realm-management": [ - { - "client": "useraccount-service", - "roles": [ - "view-identity-providers", - "view-realm", - "manage-identity-providers", - "impersonation", - "realm-admin", - "create-client", - "manage-users", - "query-realms", - "view-authorization", - "query-clients", - "query-users", - "manage-events", - "manage-realm", - "view-events", - "view-users", - "view-clients", - "manage-authorization", - "manage-clients", - "query-groups" - ] - } - ], - "account": [ - { - "client": "useraccount-service", - "roles": [ - "view-profile" - ] - } - ] - }, - "clients": [ - { - "id": "0dc2a195-7b23-4e58-bf81-3b6566e4c36c", - "clientId": "useraccount-service", - "surrogateAuthRequired": false, - "enabled": true, - "clientAuthenticatorType": "client-secret", - "secret": "secret", - "redirectUris": [ - "http://awstslx070:4200/*", - "http://localhost:4200/*", - "http://awstslx071:4200/*" - ], - "webOrigins": [ - "*" - ], - "notBefore": 0, - "bearerOnly": false, - "consentRequired": false, - "standardFlowEnabled": true, - "implicitFlowEnabled": false, - "directAccessGrantsEnabled": true, - "serviceAccountsEnabled": true, - "authorizationServicesEnabled": true, - "publicClient": false, - "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": false, - "nodeReRegistrationTimeout": -1, - "protocolMappers": [ - { - "id": "e467088f-3ad5-439e-a507-6213ec96dc7e", - "name": "Client IP Address", - "protocol": "openid-connect", - "protocolMapper": "oidc-usersessionmodel-note-mapper", - "consentRequired": false, - "config": { - "user.session.note": "clientAddress", - "userinfo.token.claim": "true", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "clientAddress", - "jsonType.label": "String" - } - }, - { - "id": "f593e14f-701b-4318-9c45-06b46596df9b", - "name": "Client ID", - "protocol": "openid-connect", - "protocolMapper": "oidc-usersessionmodel-note-mapper", - "consentRequired": false, - "config": { - "user.session.note": "clientId", - "userinfo.token.claim": "true", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "clientId", - "jsonType.label": "String" - } - }, - { - "id": "fb2f5eff-a705-4069-8578-57419ed6ea79", - "name": "Cognito Groups", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-realm-role-mapper", - "consentRequired": false, - "config": { - "multivalued": "true", - "userinfo.token.claim": "true", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "cognito:groups", - "jsonType.label": "String" - } - }, - { - "id": "73eaf490-b6d8-4f6b-a9a0-1823c95c129d", - "name": "Client Host", - "protocol": "openid-connect", - "protocolMapper": "oidc-usersessionmodel-note-mapper", - "consentRequired": false, - "config": { - "user.session.note": "clientHost", - "userinfo.token.claim": "true", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "clientHost", - "jsonType.label": "String" - } - } - ], - "defaultClientScopes": [ - "web-origins", - "role_list", - "profile", - "roles", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ], - "authorizationSettings": { - "allowRemoteResourceManagement": true, - "policyEnforcementMode": "ENFORCING", - "resources": [ - { - "name": "Default Resource", - "type": "urn:useraccount-service:resources:default", - "ownerManagedAccess": false, - "attributes": {}, - "_id": "b6b38f1d-9b2f-45b4-903b-75e4caa04822", - "uris": [ - "/*" - ] - } - ], - "policies": [ - { - "id": "028c93ef-a91a-4cb4-9658-e9df81a2c7d1", - "name": "Default Policy", - "description": "A policy that grants access only for users within this realm", - "type": "js", - "logic": "POSITIVE", - "decisionStrategy": "AFFIRMATIVE", - "config": { - "code": "// by default, grants any permission associated with this policy\n$evaluation.grant();\n" - } - }, - { - "id": "b10ccbd7-080f-46d8-b064-1082bcbc8025", - "name": "Default Permission", - "description": "A permission that applies to the default resource type", - "type": "resource", - "logic": "POSITIVE", - "decisionStrategy": "UNANIMOUS", - "config": { - "defaultResourceType": "urn:useraccount-service:resources:default", - "applyPolicies": "[\"Default Policy\"]" - } - } - ], - "scopes": [] - } - }, - { - "id": "20111584-1444-43f4-a8bb-824139f991ea", - "clientId": "security-admin-console", - "name": "${client_security-admin-console}", - "baseUrl": "/auth/admin/c4po_realm_local/console/index.html", - "surrogateAuthRequired": false, - "enabled": true, - "clientAuthenticatorType": "client-secret", - "secret": "secret", - "redirectUris": [ - "/auth/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": {}, - "authenticationFlowBindingOverrides": {}, - "fullScopeAllowed": false, - "nodeReRegistrationTimeout": 0, - "protocolMappers": [ - { - "id": "4d4a3b4a-866a-48fd-9ed7-cf86eed3ec93", - "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": "15a976bb-49f0-4abc-a2f4-54694144aa42", - "clientId": "admin-cli", - "name": "${client_admin-cli}", - "surrogateAuthRequired": false, - "enabled": true, - "clientAuthenticatorType": "client-secret", - "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": "5f58a3c9-fb96-4565-aa97-24001d045829", - "clientId": "account", - "name": "${client_account}", - "baseUrl": "/auth/realms/c4po_realm_local/account", - "surrogateAuthRequired": false, - "enabled": true, - "clientAuthenticatorType": "client-secret", - "secret": "secret", - "defaultRoles": [ - "view-profile", - "manage-account" - ], - "redirectUris": [ - "/auth/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": { - "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": false, - "nodeReRegistrationTimeout": 0, - "defaultClientScopes": [ - "web-origins", - "role_list", - "profile", - "roles", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] - }, - { - "id": "1f6ed582-3900-4f1d-8f5e-7e43ec70d721", - "clientId": "realm-management", - "name": "${client_realm-management}", - "surrogateAuthRequired": false, - "enabled": true, - "clientAuthenticatorType": "client-secret", - "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": "934c22be-97c3-44b3-a5c0-2f1bdc93e540", - "clientId": "broker", - "name": "${client_broker}", - "surrogateAuthRequired": false, - "enabled": true, - "clientAuthenticatorType": "client-secret", - "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" - ] - } - ], - "clientScopes": [ - { - "id": "47257d76-9d63-49f5-a902-34df495a43a4", - "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": "66fb76f1-0cde-4791-9957-163fd18cb034", - "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": "075e5aee-2465-42a0-a653-b4d5273e273d", - "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": "ef81e559-91a4-461c-871a-30848376ccb1", - "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": "78100256-46c2-4fd9-94d5-b552d7baa837", - "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": "b5ebe782-a5bf-4cc6-8606-2ffe41c609af", - "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": "33aeb362-e0a7-47ed-8f30-e257d262f2c7", - "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" - } - }, - { - "id": "6e3f86f8-6f52-4d0d-bcd6-62cd2b48217c", - "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": "df76f14a-1373-4866-b859-4ec4474150bf", - "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": "ac5cf00b-0079-4bd0-8e3a-bf228ee622e5", - "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": "c167feb2-2750-4916-9a3a-7a8b5bf8be20", - "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": "e2f48d7b-be91-480c-a23b-77eb40b887bc", - "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": "345ab109-6915-4487-b22b-625c3d416897", - "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": "1f743089-4d5c-48a3-91d0-db1ef3c12a14", - "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": "3de9048d-cc21-4c70-989f-d926d89b0766", - "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": "6988c27c-baf5-476d-b268-64175380ff15", - "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": "cd8fce4a-c143-4093-bf04-a0a208f96ad8", - "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": "774bb1ac-94dc-4024-869f-7ac3c74a3049", - "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": "4fd53d8d-abf9-4cc7-accd-54d1fc836dfb", - "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": "ec02c8fd-945e-4ca9-a4f4-063860c4c005", - "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": "282efa0d-e0b6-4eda-a16e-f46f9f2258dc", - "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": "03f06be8-0e88-479b-9c94-99221d84c2e9", - "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": "35423b7e-f94b-4ebc-a98a-7929cfc1ffc7", - "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": "8a711ca2-9cc4-4f0d-9e68-e2fc8a584eca", - "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": "3b1a7f22-88b0-4c50-aff7-b3e944d9c863", - "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": "20ea37d6-3f1b-4744-a273-2894a8e5194b", - "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": "325e7a38-d5c3-4494-bda0-bd26b77e97f4", - "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": "ca8687eb-111b-4004-84c8-3983b1e4c8f7", - "name": "role_list", - "description": "SAML role list", - "protocol": "saml", - "attributes": { - "consent.screen.text": "${samlRoleListScopeConsentText}", - "display.on.consent.screen": "true" - }, - "protocolMappers": [ - { - "id": "86dcf3ff-37bd-4e59-a873-8c48a84f3768", - "name": "role list", - "protocol": "saml", - "protocolMapper": "saml-role-list-mapper", - "consentRequired": false, - "config": { - "single": "false", - "attribute.nameformat": "Basic", - "attribute.name": "Role" - } - } - ] - }, - { - "id": "3b49b3b4-bebc-446c-b5b5-181e5bf25f5a", - "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": "290b8e1f-ab02-4c15-ad33-208d43b25f22", - "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": "d4161ff3-251b-4812-bbd2-47ecec67f2f0", - "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": "03d5aaf1-d43b-4739-97ec-9906e034d09e", - "name": "audience resolve", - "protocol": "openid-connect", - "protocolMapper": "oidc-audience-resolve-mapper", - "consentRequired": false, - "config": {} - } - ] - }, - { - "id": "5f39e087-9178-404d-be5a-81034370de82", - "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": "7a931e44-5929-4b71-b40d-5bf96c7ee1f0", - "name": "allowed web origins", - "protocol": "openid-connect", - "protocolMapper": "oidc-allowed-origins-mapper", - "consentRequired": false, - "config": {} - } - ] - } - ], - "defaultDefaultClientScopes": [ - "email", - "profile", - "roles", - "web-origins", - "role_list" - ], - "defaultOptionalClientScopes": [ - "address", - "phone", - "microprofile-jwt", - "offline_access" - ], - "browserSecurityHeaders": { - "contentSecurityPolicyReportOnly": "", - "xContentTypeOptions": "nosniff", - "xRobotsTag": "none", - "xFrameOptions": "SAMEORIGIN", - "xXSSProtection": "1; mode=block", - "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", - "strictTransportSecurity": "max-age=31536000; includeSubDomains" - }, - "smtpServer": {}, - "eventsEnabled": false, - "eventsListeners": [ - "jboss-logging" - ], - "enabledEventTypes": [], - "adminEventsEnabled": false, - "adminEventsDetailsEnabled": false, - "components": { - "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ - { - "id": "eab6b4c5-33c3-4a40-8fa9-9d98ce0992c2", - "name": "Max Clients Limit", - "providerId": "max-clients", - "subType": "anonymous", - "subComponents": {}, - "config": { - "max-clients": [ - "200" - ] - } - }, - { - "id": "f6203bd5-ba45-45df-bf45-741e0e2202f5", - "name": "Allowed Client Scopes", - "providerId": "allowed-client-templates", - "subType": "authenticated", - "subComponents": {}, - "config": { - "allow-default-scopes": [ - "true" - ] - } - }, - { - "id": "f7d196b9-3d1a-48af-82bc-8c3409209838", - "name": "Allowed Protocol Mapper Types", - "providerId": "allowed-protocol-mappers", - "subType": "authenticated", - "subComponents": {}, - "config": { - "allowed-protocol-mapper-types": [ - "oidc-usermodel-property-mapper", - "oidc-usermodel-attribute-mapper", - "oidc-full-name-mapper", - "saml-role-list-mapper", - "saml-user-property-mapper", - "oidc-sha256-pairwise-sub-mapper", - "oidc-address-mapper", - "saml-user-attribute-mapper" - ] - } - }, - { - "id": "90993401-7ea6-4213-8a53-d49ed7bebea0", - "name": "Allowed Protocol Mapper Types", - "providerId": "allowed-protocol-mappers", - "subType": "anonymous", - "subComponents": {}, - "config": { - "allowed-protocol-mapper-types": [ - "oidc-full-name-mapper", - "oidc-usermodel-property-mapper", - "saml-role-list-mapper", - "saml-user-attribute-mapper", - "saml-user-property-mapper", - "oidc-address-mapper", - "oidc-usermodel-attribute-mapper", - "oidc-sha256-pairwise-sub-mapper" - ] - } - }, - { - "id": "9ee10d51-fb23-4781-abd8-aed83ccbee4e", - "name": "Allowed Client Scopes", - "providerId": "allowed-client-templates", - "subType": "anonymous", - "subComponents": {}, - "config": { - "allow-default-scopes": [ - "true" - ] - } - }, - { - "id": "adad855f-8eb4-452b-9c01-f3df1d06dec2", - "name": "Full Scope Disabled", - "providerId": "scope", - "subType": "anonymous", - "subComponents": {}, - "config": {} - }, - { - "id": "9f64e8b7-b375-43a9-948b-49155901b906", - "name": "Trusted Hosts", - "providerId": "trusted-hosts", - "subType": "anonymous", - "subComponents": {}, - "config": { - "host-sending-registration-request-must-match": [ - "true" - ], - "client-uris-must-match": [ - "true" - ] - } - }, - { - "id": "5926dcbc-4191-4f1c-8c48-0414d0e4e959", - "name": "Consent Required", - "providerId": "consent-required", - "subType": "anonymous", - "subComponents": {}, - "config": {} - } - ], - "org.keycloak.keys.KeyProvider": [ - { - "id": "5228af06-9a4a-4c1c-aafc-36b1e554ee61", - "name": "hmac-generated", - "providerId": "hmac-generated", - "subComponents": {}, - "config": { - "priority": [ - "100" - ], - "algorithm": [ - "HS256" - ] - } - }, - { - "id": "e2223ecb-b36c-4e5d-ad0b-0e0f94aa5242", - "name": "rsa-generated", - "providerId": "rsa-generated", - "subComponents": {}, - "config": { - "priority": [ - "100" - ] - } - }, - { - "id": "b37b8cac-96e4-4c1c-a9c5-1d44b9447412", - "name": "aes-generated", - "providerId": "aes-generated", - "subComponents": {}, - "config": { - "priority": [ - "100" - ] - } - } - ] - }, - "internationalizationEnabled": false, - "supportedLocales": [], - "authenticationFlows": [ - { - "id": "04667e23-0791-4598-9e81-71dad2f8850e", - "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 - }, - { - "authenticator": "idp-email-verification", - "requirement": "ALTERNATIVE", - "priority": 20, - "userSetupAllowed": false, - "autheticatorFlow": false - }, - { - "requirement": "ALTERNATIVE", - "priority": 30, - "flowAlias": "Verify Existing Account by Re-authentication", - "userSetupAllowed": false, - "autheticatorFlow": true - } - ] - }, - { - "id": "d12d870d-a9c8-447d-9b2a-8513daaa1ff4", - "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 - }, - { - "authenticator": "auth-otp-form", - "requirement": "OPTIONAL", - "priority": 20, - "userSetupAllowed": false, - "autheticatorFlow": false - } - ] - }, - { - "id": "a9d235f0-562f-44d8-8034-374b427375a4", - "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": "0eb4370f-846b-4938-99a3-e41daf4b8f99", - "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": "6aa71b0a-0fbc-478f-9e70-3438a363d17d", - "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 - }, - { - "authenticator": "direct-grant-validate-otp", - "requirement": "OPTIONAL", - "priority": 30, - "userSetupAllowed": false, - "autheticatorFlow": false - } - ] - }, - { - "id": "9d882641-2247-4c39-bcdc-b6a731fb9d23", - "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": "e4f1f2c7-4171-4b8b-8759-279bbf1be477", - "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 - }, - { - "authenticatorConfig": "create unique user config", - "authenticator": "idp-create-user-if-unique", - "requirement": "ALTERNATIVE", - "priority": 20, - "userSetupAllowed": false, - "autheticatorFlow": false - }, - { - "requirement": "ALTERNATIVE", - "priority": 30, - "flowAlias": "Handle Existing Account", - "userSetupAllowed": false, - "autheticatorFlow": true - } - ] - }, - { - "id": "e00fbb15-36a1-483e-87ce-4b209b61ecfc", - "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 - }, - { - "authenticator": "auth-otp-form", - "requirement": "OPTIONAL", - "priority": 20, - "userSetupAllowed": false, - "autheticatorFlow": false - } - ] - }, - { - "id": "042387a5-bc8c-4dd7-9b87-9ff1f4e0ca1e", - "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 - }, - { - "authenticator": "basic-auth", - "requirement": "REQUIRED", - "priority": 20, - "userSetupAllowed": false, - "autheticatorFlow": false - }, - { - "authenticator": "basic-auth-otp", - "requirement": "DISABLED", - "priority": 30, - "userSetupAllowed": false, - "autheticatorFlow": false - }, - { - "authenticator": "auth-spnego", - "requirement": "DISABLED", - "priority": 40, - "userSetupAllowed": false, - "autheticatorFlow": false - } - ] - }, - { - "id": "1c00ba91-5e86-4037-bd34-cea2aca0871b", - "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": "51aaecfb-6135-4e8c-a154-98454e51b3f1", - "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": "f1d62ae4-4a19-4f58-9301-dc014c064439", - "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 - }, - { - "authenticator": "reset-otp", - "requirement": "OPTIONAL", - "priority": 40, - "userSetupAllowed": false, - "autheticatorFlow": false - } - ] - }, - { - "id": "e4405727-0f00-4b39-87b8-002d91b67579", - "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": "bca740b1-4261-45b4-a91b-ef679c92a79d", - "alias": "create unique user config", - "config": { - "require.password.update.after.registration": "false" - } - }, - { - "id": "dd6821e3-694a-4e46-b284-1664ba568ade", - "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": {} - } - ], - "browserFlow": "browser", - "registrationFlow": "registration", - "directGrantFlow": "direct grant", - "resetCredentialsFlow": "reset credentials", - "clientAuthenticationFlow": "clients", - "dockerAuthenticationFlow": "docker auth", - "attributes": { - "_browser_header.xXSSProtection": "1; mode=block", - "_browser_header.xFrameOptions": "SAMEORIGIN", - "_browser_header.strictTransportSecurity": "max-age=31536000; includeSubDomains", - "permanentLockout": "false", - "quickLoginCheckMilliSeconds": "1000", - "_browser_header.xRobotsTag": "none", - "maxFailureWaitSeconds": "900", - "minimumQuickLoginWaitSeconds": "60", - "failureFactor": "30", - "actionTokenGeneratedByUserLifespan": "300", - "maxDeltaTimeSeconds": "43200", - "_browser_header.xContentTypeOptions": "nosniff", - "offlineSessionMaxLifespan": "5184000", - "actionTokenGeneratedByAdminLifespan": "43200", - "_browser_header.contentSecurityPolicyReportOnly": "", - "bruteForceProtected": "false", - "_browser_header.contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", - "waitIncrementSeconds": "60", - "offlineSessionMaxLifespanEnabled": "false" - }, - "keycloakVersion": "6.0.1", - "userManagedAccessAllowed": false -} \ No newline at end of file diff --git a/security-c4po-api/src/test/resources/realm-export.json b/security-c4po-api/src/test/resources/realm-export.json index 63addac..710b398 100644 --- a/security-c4po-api/src/test/resources/realm-export.json +++ b/security-c4po-api/src/test/resources/realm-export.json @@ -365,26 +365,51 @@ "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister" : false, "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ], "users" : [ { - "id" : "10e06d7a-8dd0-4ecd-8963-056b45079c4f", - "createdTimestamp" : 1617897245335, - "username" : "ttt", + "id" : "8f725a10-bdf5-4530-a185-4627fb092d78", + "createdTimestamp" : 1634988614562, + "username" : "test_admin", "enabled" : true, "totp" : false, - "emailVerified" : false, + "emailVerified" : true, "firstName" : "test", - "lastName" : "user", + "lastName" : "admin", + "email" : "testadmin@test.de", "credentials" : [ { - "id" : "7026fefc-ae26-442b-acae-92f1f2d24eac", + "id" : "52ec7433-9b1c-46f1-ae5b-4f9851d1424c", "type" : "password", - "createdDate" : 1617897287400, - "secretData" : "{\"value\":\"mhW4yxOg+8bcyPF4yWsfPZnLGUp4oaqc9aNA+WBcpr9qXgs/Jw+rM2VlLEgeD/kXGItcScA8V20sVGrMWT94Yw==\",\"salt\":\"nkH510WAwjKZJqd/ZEkIHA==\"}", + "createdDate" : 1634988624873, + "secretData" : "{\"value\":\"acmk/oJGV9GGtvrUjT1NJZPTizGvO9gvCj5tvzOSZKxW3OPwPYWfCE91OfmmLXOZcVddO8xdDufDLFliu52bxA==\",\"salt\":\"oHvWAC39azkfs7wJOwLdlQ==\"}", "credentialData" : "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\"}" } ], "disableableCredentialTypes" : [ ], "requiredActions" : [ ], - "realmRoles" : [ "uma_authorization", "c4po_user", "c4po_admin" ], + "realmRoles" : [ "uma_authorization", "offline_access", "c4po_admin" ], + "clientRoles" : { + "account" : [ "view-profile", "manage-account" ] + }, + "notBefore" : 0, + "groups" : [ ] + }, { + "id" : "34869cfa-6454-4379-94d7-3a19fc2d98e0", + "createdTimestamp" : 1634988651658, + "username" : "test_user", + "enabled" : true, + "totp" : false, + "emailVerified" : true, + "firstName" : "test", + "lastName" : "user", + "email" : "testuser@test.de", + "credentials" : [ { + "id" : "58d1b3a1-7d28-4d40-b4cd-bfab0c9472d9", + "type" : "password", + "createdDate" : 1634988666427, + "secretData" : "{\"value\":\"IUz/GL+eof5R8MNEy4VB04sr1YBmyyy6/HdR0QFzKXRkhHcDWxO+vn1S3jy5n+iB/JBdFcrprI3/5rPgfyBiaA==\",\"salt\":\"l5BdVHeOO3RVvpxDWK2Jnw==\"}", + "credentialData" : "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\"}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "uma_authorization", "c4po_user", "offline_access" ], "clientRoles" : { - "c4po_local" : [ "user" ], "account" : [ "view-profile", "manage-account" ] }, "notBefore" : 0, @@ -1208,7 +1233,7 @@ "subType" : "anonymous", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "saml-role-list-mapper", "oidc-full-name-mapper", "saml-user-attribute-mapper", "oidc-usermodel-attribute-mapper", "oidc-usermodel-property-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-property-mapper" ] + "allowed-protocol-mapper-types" : [ "oidc-full-name-mapper", "saml-role-list-mapper", "oidc-address-mapper", "oidc-usermodel-property-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-attribute-mapper", "saml-user-property-mapper", "saml-user-attribute-mapper" ] } }, { "id" : "cc2d0cd7-3d3f-4b0a-ad95-7118f36bf188", @@ -1240,7 +1265,7 @@ "subType" : "authenticated", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "saml-user-attribute-mapper", "saml-role-list-mapper", "saml-user-property-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper", "oidc-usermodel-property-mapper" ] + "allowed-protocol-mapper-types" : [ "oidc-full-name-mapper", "oidc-address-mapper", "saml-role-list-mapper", "saml-user-property-mapper", "saml-user-attribute-mapper", "oidc-usermodel-attribute-mapper", "oidc-usermodel-property-mapper", "oidc-sha256-pairwise-sub-mapper" ] } }, { "id" : "92230e65-7480-44c3-af2d-72ddee758cbc", @@ -1289,7 +1314,7 @@ "internationalizationEnabled" : false, "supportedLocales" : [ ], "authenticationFlows" : [ { - "id" : "fa5fc78f-19a9-4737-868b-618163f28c79", + "id" : "9f2b9c09-a331-4126-9f89-5d459e911053", "alias" : "Account verification options", "description" : "Method with which to verity the existing account", "providerId" : "basic-flow", @@ -1309,7 +1334,7 @@ "autheticatorFlow" : true } ] }, { - "id" : "01735b0f-139f-46e5-bb63-f797a27efa77", + "id" : "6802ac9b-69cf-4838-99d4-747dd9de3f32", "alias" : "Authentication Options", "description" : "Authentication options.", "providerId" : "basic-flow", @@ -1335,7 +1360,7 @@ "autheticatorFlow" : false } ] }, { - "id" : "a7666cf0-626c-48c4-9e71-e408832de725", + "id" : "c1e6806d-ff33-46bc-be0f-70eb6924fc92", "alias" : "Browser - Conditional OTP", "description" : "Flow to determine if the OTP is required for the authentication", "providerId" : "basic-flow", @@ -1355,7 +1380,7 @@ "autheticatorFlow" : false } ] }, { - "id" : "1dfabb7a-efdd-4964-bba5-389cad79b654", + "id" : "8b0f8be2-5933-4fc1-9546-484ecdf0766b", "alias" : "Direct Grant - Conditional OTP", "description" : "Flow to determine if the OTP is required for the authentication", "providerId" : "basic-flow", @@ -1375,7 +1400,7 @@ "autheticatorFlow" : false } ] }, { - "id" : "c3b2bf2b-3da8-430d-a9b7-8793c3dc30a3", + "id" : "d1b5d09c-36eb-40e2-8c1f-9b414e63b44e", "alias" : "First broker login - Conditional OTP", "description" : "Flow to determine if the OTP is required for the authentication", "providerId" : "basic-flow", @@ -1395,7 +1420,7 @@ "autheticatorFlow" : false } ] }, { - "id" : "44343bdf-8592-4242-835f-e349943a110b", + "id" : "ffeb5151-3357-4cd6-aa17-860c60de0cb3", "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", @@ -1415,7 +1440,7 @@ "autheticatorFlow" : true } ] }, { - "id" : "e72b8fcb-cd8b-4e7a-a057-3446b806b538", + "id" : "816cc72c-df79-49ee-b7a4-291f496e145b", "alias" : "Reset - Conditional OTP", "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", "providerId" : "basic-flow", @@ -1435,7 +1460,7 @@ "autheticatorFlow" : false } ] }, { - "id" : "2416145b-4d20-493c-bdf7-419898c002ee", + "id" : "5a782d53-4c32-437e-803a-a9e893b0f5eb", "alias" : "User creation or linking", "description" : "Flow for the existing/non-existing user alternatives", "providerId" : "basic-flow", @@ -1456,7 +1481,7 @@ "autheticatorFlow" : true } ] }, { - "id" : "b7ff8aad-2daa-4736-8815-f3e8f0df391e", + "id" : "3faef6d7-2cfd-4190-a8d1-db1b8e953304", "alias" : "Verify Existing Account by Re-authentication", "description" : "Reauthentication of existing account", "providerId" : "basic-flow", @@ -1476,7 +1501,7 @@ "autheticatorFlow" : true } ] }, { - "id" : "8339d3ba-2d0a-4d23-bbfa-a78e4973d3c9", + "id" : "5a57eb55-59f8-4d28-9003-ec350b4053cf", "alias" : "browser", "description" : "browser based authentication", "providerId" : "basic-flow", @@ -1508,7 +1533,7 @@ "autheticatorFlow" : true } ] }, { - "id" : "5ece002a-4e62-4d0d-8705-4b116164b424", + "id" : "1cf8aebb-27ae-4df5-96d4-82d6a0ba3bfd", "alias" : "clients", "description" : "Base authentication for clients", "providerId" : "client-flow", @@ -1540,7 +1565,7 @@ "autheticatorFlow" : false } ] }, { - "id" : "bd27b0dc-bc87-40b7-a626-491b9955668d", + "id" : "1ec7e0bf-7bbe-4841-a21d-bfbc6cb492ae", "alias" : "direct grant", "description" : "OpenID Connect Resource Owner Grant", "providerId" : "basic-flow", @@ -1566,7 +1591,7 @@ "autheticatorFlow" : true } ] }, { - "id" : "2db79d60-7c9d-4516-80f0-0c5d60349899", + "id" : "d633bda1-5c4e-43c4-9a0e-776502549b57", "alias" : "docker auth", "description" : "Used by Docker clients to authenticate against the IDP", "providerId" : "basic-flow", @@ -1580,7 +1605,7 @@ "autheticatorFlow" : false } ] }, { - "id" : "25a92fbe-7d4d-46bc-a751-29ef844290a3", + "id" : "ac608b5f-4849-4830-a928-b51f996f77a8", "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", @@ -1601,7 +1626,7 @@ "autheticatorFlow" : true } ] }, { - "id" : "26f6a5db-9be8-446c-82d0-6f4e29b5f08d", + "id" : "b0731e4d-5935-4c29-8a4d-e7124e9eb164", "alias" : "forms", "description" : "Username, password, otp and other auth forms.", "providerId" : "basic-flow", @@ -1621,7 +1646,7 @@ "autheticatorFlow" : true } ] }, { - "id" : "05a94701-ad98-4bbc-a162-746a107afba5", + "id" : "b09ede5d-b7cd-4e9a-bb8d-e7caa880bc6d", "alias" : "http challenge", "description" : "An authentication flow based on challenge-response HTTP Authentication Schemes", "providerId" : "basic-flow", @@ -1641,7 +1666,7 @@ "autheticatorFlow" : true } ] }, { - "id" : "75347884-d4cb-4eba-9b89-63566d509b92", + "id" : "96479ab9-2db1-47c9-ba6b-c9832e54937d", "alias" : "registration", "description" : "registration flow", "providerId" : "basic-flow", @@ -1656,7 +1681,7 @@ "autheticatorFlow" : true } ] }, { - "id" : "74e3a2d3-ecda-400d-8bff-0926dc272e4b", + "id" : "ae46ccfd-1bd2-4717-8380-de1040d7fe43", "alias" : "registration form", "description" : "registration form", "providerId" : "form-flow", @@ -1688,7 +1713,7 @@ "autheticatorFlow" : false } ] }, { - "id" : "6eae8652-baf7-4a7d-80a4-1711906caec7", + "id" : "99d2284f-52e8-4c09-a500-6a7d1c5b108e", "alias" : "reset credentials", "description" : "Reset credentials for a user if they forgot their password or something", "providerId" : "basic-flow", @@ -1720,7 +1745,7 @@ "autheticatorFlow" : true } ] }, { - "id" : "6135710b-b019-4117-ba32-578d3d496b2a", + "id" : "db4d353d-4b8d-4241-a09b-d4246590c82a", "alias" : "saml ecp", "description" : "SAML ECP Profile Authentication Flow", "providerId" : "basic-flow", @@ -1735,13 +1760,13 @@ } ] } ], "authenticatorConfig" : [ { - "id" : "3d3735a0-1362-4f0d-9306-bfc727da1b5b", + "id" : "1a64855a-6fe8-40df-90eb-bd936aed6e76", "alias" : "create unique user config", "config" : { "require.password.update.after.registration" : "false" } }, { - "id" : "c1f4a15f-8234-4f0f-affa-baf610b001e1", + "id" : "e10161fb-9f1c-4c5c-ba84-5bcbf0dd358d", "alias" : "review profile config", "config" : { "update.profile.on.first.login" : "missing" diff --git a/security-c4po-api/src/test/resources/script_local b/security-c4po-api/src/test/resources/script_local index f6c82e4..db262e0 100644 --- a/security-c4po-api/src/test/resources/script_local +++ b/security-c4po-api/src/test/resources/script_local @@ -4,11 +4,12 @@ sleep 20 ./kcadm.sh config credentials --server http://localhost:8888/auth --realm master --user admin --password admin USERID=$(./kcadm.sh create users -r c4po_realm_local -s username=test_admin \ --s email=Troy.Stewart@heros.com \ +-s email=testadmin@test.de \ -s firstName=test \ -s lastName=admin \ -s attributes.lang="de-DE" \ -s attributes.datenumberformat="en-US" \ +-s enabled=true \ -o --fields id | jq '.id' | tr -d '"') ./kcadm.sh update users/$USERID/reset-password -r c4po_realm_test -s type=password -s value=test -s temporary=false -n @@ -16,13 +17,14 @@ USERID=$(./kcadm.sh create users -r c4po_realm_local -s username=test_admin \ ./kcadm.sh add-roles -r c4po_realm_test --uusername test_admin --cclientid realm-management --rolename create-client --rolename view-users USERID=$(./kcadm.sh create users -r c4po_realm_local -s username=test_user \ --s email=Troy.Stewart@heros.com \ +-s email=testuser@test.de \ -s firstName=test \ -s lastName=user \ -s attributes.lang="de-DE" \ -s attributes.datenumberformat="en-US" \ +-s enabled=true \ -o --fields id | jq '.id' | tr -d '"') ./kcadm.sh update users/$USERID/reset-password -r c4po_realm_test -s type=password -s value=test -s temporary=false -n ./kcadm.sh add-roles --uusername test_user --rolename c4po_user -r c4po_realm_test -./kcadm.sh add-roles -r c4po_realm_test --uusername test_user --cclientid realm-management --rolename create-client --rolename view-users \ No newline at end of file +./kcadm.sh add-roles -r c4po_realm_test --uusername test_user --cclientid realm-management --rolename create-client --rolename view-users diff --git a/security-c4po-cfg/backend/docker-compose.backend.yml b/security-c4po-cfg/backend/docker-compose.backend.yml index 00983fc..80bc233 100644 --- a/security-c4po-cfg/backend/docker-compose.backend.yml +++ b/security-c4po-cfg/backend/docker-compose.backend.yml @@ -7,12 +7,6 @@ services: container_name: c4po-api environment: - SPRING_PROFILES_ACTIVE=COMPOSE - depends_on: - - c4po-db - - c4po-keycloak - links: - - c4po-db - - c4po-keycloak deploy: resources: limits: diff --git a/security-c4po-cfg/frontend/docker-compose.frontend.yml b/security-c4po-cfg/frontend/docker-compose.frontend.yml index d88f900..4bdbf1f 100644 --- a/security-c4po-cfg/frontend/docker-compose.frontend.yml +++ b/security-c4po-cfg/frontend/docker-compose.frontend.yml @@ -5,8 +5,6 @@ services: build: '../../security-c4po-angular' image: security-c4po-angular:latest container_name: c4po-angular - depends_on: - - c4po-keycloak deploy: resources: limits: diff --git a/security-c4po-cfg/kc/docker-compose.keycloak.yml b/security-c4po-cfg/kc/docker-compose.keycloak.yml index ac3b62d..050428c 100644 --- a/security-c4po-cfg/kc/docker-compose.keycloak.yml +++ b/security-c4po-cfg/kc/docker-compose.keycloak.yml @@ -3,8 +3,6 @@ version: '3.1' services: c4po-keycloak: container_name: c4po-keycloak - depends_on: - - c4po-keycloak-postgres image: jboss/keycloak:11.0.3 volumes: - ../cfg/c4po_realm_export.json:/tmp/c4po_realm_export.json