feat: added header and project overview to FE and fixed keycloak test container
This commit is contained in:
parent
0541586aaf
commit
d1c1a3814b
|
@ -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": [
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
module.exports = {
|
||||
moduleNameMapper: {
|
||||
'@core/(.*)': '<rootDir>/src/app/core/$1',
|
||||
'@assets/(.*)': '<rootDir>/src/assets/$1',
|
||||
'@shared/(.*)': '<rootDir>/src/shared/$1'
|
||||
},
|
||||
preset: 'jest-preset-angular',
|
||||
setupFilesAfterEnv: ['<rootDir>/setup-jest.ts'],
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 { }
|
|
@ -1 +0,0 @@
|
|||
<p>dashboard works!</p>
|
|
@ -1,27 +0,0 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DashboardComponent } from './dashboard.component';
|
||||
|
||||
describe('DashboardComponent', () => {
|
||||
let component: DashboardComponent;
|
||||
let fixture: ComponentFixture<DashboardComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
DashboardComponent
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DashboardComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -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 {
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
export {DashboardModule} from './dashboard.module';
|
||||
export {DashboardRoutingModule} from './dashboard-routing.module';
|
|
@ -1,4 +1,26 @@
|
|||
<div class="c4po-header">
|
||||
<p>header works!</p>
|
||||
<div class="header" fxLayout="row" fxLayoutAlign="center center" fxLayoutGap="2rem">
|
||||
<img *ngIf="currentTheme === 'corporate', else changeImage"
|
||||
src="../../assets/images/favicons/favicon.ico" alt="logo dark" class="header-icon" width="60rem" height="60rem">
|
||||
<ng-template #changeImage>
|
||||
<img src="../../assets/images/favicons/corporate_favicon.ico" alt="logo light" class="header-icon" width="60rem" height="60rem">
|
||||
</ng-template>
|
||||
|
||||
<div class="logo-container" fxLayoutAlign="center center">
|
||||
<h1 >{{SECURITYC4PO_TITLE}} </h1>
|
||||
</div>
|
||||
<div fxLayoutAlign="end" fxLayoutGap="4rem">
|
||||
<nb-actions size="medium">
|
||||
<nb-action class="toggle-theme">
|
||||
<button nbButton
|
||||
(click)="onClickSwitchTheme()">
|
||||
<fa-icon *ngIf="currentTheme === 'corporate', else changeIcon" [icon]="fa.faMoon"
|
||||
class="new-element-icon"></fa-icon>
|
||||
<ng-template #changeIcon>
|
||||
<fa-icon [icon]="fa.faSun" class="new-element-icon"></fa-icon>
|
||||
</ng-template>
|
||||
</button>
|
||||
</nb-action>
|
||||
</nb-actions>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
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,7 +16,23 @@ 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();
|
||||
});
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
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 {
|
||||
}
|
||||
|
|
|
@ -1,16 +1 @@
|
|||
<nb-card>
|
||||
|
||||
<nb-card-header>
|
||||
<button (click)="onClickGetProjects()" nbButton>get Projects</button>
|
||||
</nb-card-header>
|
||||
|
||||
<nb-card-body>
|
||||
<div *ngIf="projects.getValue().length > 0, else noProjects">
|
||||
{{projects.getValue() | json}}
|
||||
</div>
|
||||
<ng-template #noProjects>
|
||||
{{'No Projects available!'}}
|
||||
</ng-template>
|
||||
</nb-card-body>
|
||||
|
||||
</nb-card>
|
||||
<p>app-home-component works...</p>
|
||||
|
|
|
@ -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<Project[]> = new BehaviorSubject<Project[]>([]);
|
||||
|
||||
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 {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
export {ProjectOverviewModule} from './project-overview.module';
|
||||
export {ProjectOverviewRoutingModule} from './project-overview-routing.module';
|
|
@ -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 { }
|
|
@ -0,0 +1,84 @@
|
|||
<div fxLayout="row" fxLayoutGap="2rem">
|
||||
<div *ngFor="let project of projects | async">
|
||||
<nb-card accent="success" class="project-card">
|
||||
<nb-card-header fxLayoutAlign="start center"
|
||||
routerLink="id"
|
||||
fragment="{{project.id}}"
|
||||
class="project-link project-header"
|
||||
[state]="{selectedProject:project}">
|
||||
<h4>{{project?.title}}</h4>
|
||||
</nb-card-header>
|
||||
<nb-card-body class="project-link"
|
||||
routerLink="id"
|
||||
fragment="{{project.id}}"
|
||||
[state]="{selectedProject:project}">
|
||||
<p class="project-subheader">
|
||||
{{'project.client' | translate}}:
|
||||
</p>
|
||||
<span class="project-paragraph">
|
||||
{{project?.client}}
|
||||
</span>
|
||||
|
||||
<p class="project-subheader">
|
||||
{{'project.tester' | translate}}:
|
||||
</p>
|
||||
<span class="project-paragraph">
|
||||
{{project?.tester}}
|
||||
</span>
|
||||
|
||||
<p class="project-subheader">
|
||||
{{'project.createdAt' | translate}}:
|
||||
</p>
|
||||
<span class="project-paragraph">
|
||||
{{project?.createdAt | dateTimeFormat}}
|
||||
</span>
|
||||
</nb-card-body>
|
||||
<nb-card-footer>
|
||||
<!--ToDo: Display correct progress of project-->
|
||||
<div fxLayout="row" fxLayoutGap="1rem" fxLayoutAlign="start end">
|
||||
<nb-progress-bar class="project-progress"
|
||||
status="warning"
|
||||
[value]="40"
|
||||
[displayValue]="true">
|
||||
</nb-progress-bar>
|
||||
<button nbButton
|
||||
status="primary"
|
||||
size="small"
|
||||
class="project-button"
|
||||
(click)="onClickEditProject()">
|
||||
<fa-icon [icon]="fa.faPencilAlt"></fa-icon>
|
||||
</button>
|
||||
<button nbButton
|
||||
status="danger"
|
||||
size="small"
|
||||
class="project-button"
|
||||
(click)="onClickDeleteProject()">
|
||||
<fa-icon [icon]="fa.faTrash"></fa-icon>
|
||||
</button>
|
||||
</div>
|
||||
</nb-card-footer>
|
||||
</nb-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="projects.getValue().length === 0 && !isLoading()" fxLayout="row" fxLayoutAlign="center center">
|
||||
<p class="error-text">
|
||||
{{'project.overview.no.projects' | translate}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div fxLayoutAlign="end end">
|
||||
<button nbButton
|
||||
status="primary"
|
||||
size="large"
|
||||
shape="round"
|
||||
class="add-project-button"
|
||||
(click)="onClickAddProject()">
|
||||
<fa-icon [icon]="fa.faPlus" class="new-project-icon"></fa-icon>
|
||||
{{'project.overview.add.project' | translate}}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<app-loading-spinner [isLoading$]="isLoading()" *ngIf="isLoading() | async"></app-loading-spinner>
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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<ProjectOverviewComponent>;
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
|
@ -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<boolean> = new BehaviorSubject<boolean>(true);
|
||||
projects: BehaviorSubject<Project[]> = new BehaviorSubject<Project[]>([]);
|
||||
|
||||
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<boolean> {
|
||||
return this.loading$.asObservable();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
// This method must be present when using ngx-take-until-destroy
|
||||
// even when empty
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export {ProjectModule} from './project.module';
|
||||
export {ProjectRoutingModule} from './project-routing.module';
|
|
@ -0,0 +1,12 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
imports: [
|
||||
CommonModule
|
||||
]
|
||||
})
|
||||
export class ProjectRoutingModule { }
|
|
@ -0,0 +1,6 @@
|
|||
<nb-layout>
|
||||
<nb-layout-header>
|
||||
<p>{{selectedProjectTitle}} works!</p>
|
||||
</nb-layout-header>
|
||||
</nb-layout>
|
||||
|
|
@ -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<ProjectComponent>;
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
|
@ -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 {
|
||||
}
|
||||
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
@mixin ngx-layout() {
|
||||
@include media-breakpoint-down(is) {
|
||||
.row {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 */
|
||||
|
|
|
@ -47,7 +47,7 @@ export class ThemeModule {
|
|||
providers: [
|
||||
...NbThemeModule.forRoot(
|
||||
{
|
||||
name: 'corporate',
|
||||
name: DARK_THEME.name,
|
||||
},
|
||||
[CORPORATE_THEME, DARK_THEME],
|
||||
).providers,
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 8.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
After Width: | Height: | Size: 264 KiB |
Before Width: | Height: | Size: 547 KiB After Width: | Height: | Size: 547 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.1 KiB |
|
@ -5,7 +5,7 @@
|
|||
<title>SecurityC4POAngular</title>
|
||||
<base href="/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!--<link rel="icon" type="image/x-icon" href="src/favicon.ico">-->
|
||||
<link rel="icon" type="image/x-icon" href="assets/images/favicons/favicon.ico">
|
||||
</head>
|
||||
<body id="loader-wrapper">
|
||||
<app-root id="loader"></app-root>
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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<boolean> {
|
||||
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<boolean> {
|
||||
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<boolean> {
|
||||
// ToDo: Should check from Authentication Provider
|
||||
return of(this.store.selectSnapshot(SessionState.isAuthenticated))
|
||||
.pipe(
|
||||
map((isLoggedIn: boolean) => {
|
||||
return isLoggedIn;
|
||||
}),
|
||||
catchError(() => {
|
||||
return of(false);
|
||||
})
|
||||
);
|
||||
}*/
|
||||
}
|
||||
|
|
@ -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'
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export enum NumberAndDateFormatSystem {
|
||||
ENGLISH = 'en-US',
|
||||
GERMAN = 'de-DE'
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
import { v4 as UUID } from 'uuid';
|
||||
|
||||
export class Project {
|
||||
id: string;
|
||||
client: string;
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
*/
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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<ProjectService> {
|
||||
|
||||
private http: HttpClient;
|
||||
|
||||
getProjects(): Observable<Project[]> {
|
||||
return of([]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
<div fxFill fxLayout="row" fxLayoutAlign="center center" *ngIf="loading">
|
||||
<nb-spinner fxFlexAlign="center center" status="danger" size="giant" message=""></nb-spinner>
|
||||
</div>
|
|
@ -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<LoadingSpinnerComponent>;
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
|
@ -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<boolean> = of(false);
|
||||
loading: boolean;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loading = false;
|
||||
this.isLoading$
|
||||
.pipe(untilDestroyed(this))
|
||||
.subscribe((value: boolean): void => {
|
||||
this.loading = value;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
}
|
||||
}
|
|
@ -5,6 +5,9 @@
|
|||
"outDir": "./out-tsc/app",
|
||||
"types": []
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableIvy": false
|
||||
},
|
||||
"files": [
|
||||
"src/main.ts",
|
||||
"src/polyfills.ts"
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"dom"
|
||||
],
|
||||
"paths": {
|
||||
"@shared/*": ["./src/app/shared/*"],
|
||||
"@shared/*": ["./src/shared/*"],
|
||||
"@assets/*": ["./src/assets/*"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
],
|
||||
"module": "commonjs",
|
||||
"emitDecoratorMetadata": true,
|
||||
"allowJs": true
|
||||
"allowJs": true,
|
||||
},
|
||||
"files": [
|
||||
"src/polyfills.ts"
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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<T>(
|
||||
var data: T
|
||||
) {
|
||||
@Id
|
||||
lateinit var id: String
|
||||
}
|
||||
|
|
|
@ -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<GrantedAuthority> {
|
||||
return listOf("user").stream().map {
|
||||
|
|
|
@ -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<Jwt, Mono<AbstractAuthenticationToken>> {
|
||||
class AppuserJwtAuthConverter(private val appuserDetailsService: UserAccountDetailsService) :
|
||||
Converter<Jwt, Mono<AbstractAuthenticationToken>> {
|
||||
|
||||
override fun convert(jwt: Jwt): Mono<AbstractAuthenticationToken> {
|
||||
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 {
|
||||
|
|
|
@ -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<UserDetails> {
|
||||
return Appuser().toMono()
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<ProjectEntity>.toProjects(): List<Project> {
|
||||
return this.map {
|
||||
it.toProject()
|
||||
|
|
|
@ -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<ProjectService>()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
keycloak.client.url=http://localhost:8888
|
||||
keycloak.client.realm.path=auth/realms/c4po_realm_local/
|
|
@ -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<Any, Any>()
|
||||
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<Any>(map, headers), String::class.java)
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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
|
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
||||
|
|
|
@ -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,11 +17,12 @@ 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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue