From 5e50497baba8d6988cd97139ad3cd090ecb4c243 Mon Sep 17 00:00:00 2001 From: Guillaume Heurtier <guillaume.heurtier@maarch.org> Date: Mon, 8 Jun 2020 18:26:05 +0200 Subject: [PATCH] FEAT #8841 TIME 4:00 begin password rules front --- lang/fr.json | 23 ++- .../securities-administration.component.html | 135 ++++++++++++++++++ .../securities-administration.component.ts | 101 +++++++++++++ src/frontend/app/app-routing.module.ts | 8 +- src/frontend/app/app.module.ts | 4 +- .../app/service/auth-interceptor.service.ts | 2 +- 6 files changed, 267 insertions(+), 6 deletions(-) create mode 100644 src/frontend/app/administration/security/securities-administration.component.html create mode 100644 src/frontend/app/administration/security/securities-administration.component.ts diff --git a/lang/fr.json b/lang/fr.json index be42d50935..c272218173 100755 --- a/lang/fr.json +++ b/lang/fr.json @@ -270,6 +270,27 @@ "note" : "Note", "collapseNote" : "Réduire la note", "expandNote" : "Ouvrir la note", - "convertingDocument" : "Document en cours de conversion" + "convertingDocument" : "Document en cours de conversion", + "manage_password_rulesAdmin" : "Administrer les règles de mots de passes", + "manage_password_rules" : "Sécurités", + "manage_password_rulesDesc" : "Administrer les règles de mots de passes", + "passwordRulesUpdated" : "Règle de mot de passe mise à jour", + "password_complexityNumberRequired": "Chiffre requis", + "password_complexitySpecial": "1 caractère spécial au minimum", + "password_complexitySpecialRequired": "Caractère spécial requis", + "password_complexityUpper": "1 majuscule au minimum", + "password_complexityUpperRequired": "Majuscule requise", + "password_historyLastUseDesc": "Vous ne pouvez pas utiliser les", + "password_historyLastUseDesc2": "dernier(s) mot(s) de passe", + "password_historyLastUseRequired": "Nombre de mots de passe sauvegardés", + "password_lockAttemptsRequired": "Nombre de tentatives de connexions", + "password_lockTimeRequired": "Temps de blocage", + "password_minLength": "caractère(s) au minimum", + "password_minLengthRequired": "Longueur minimale", + "password_renewal": "Veuillez noter que ce nouveau mot de passe ne sera valide que", + "password_renewalRequired": "Expiration du mot de passe", + "chars": "caractère(s)", + "days": "jour(s)", + "minutes": "minute(s)" } } diff --git a/src/frontend/app/administration/security/securities-administration.component.html b/src/frontend/app/administration/security/securities-administration.component.html new file mode 100644 index 0000000000..363f4d80fc --- /dev/null +++ b/src/frontend/app/administration/security/securities-administration.component.html @@ -0,0 +1,135 @@ +<mat-sidenav-container autosize class="maarch-container"> + <mat-sidenav #snav [disableClose]="!signaturesService.mobileMode" + [mode]="signaturesService.mobileMode ? 'over': 'side'" fixedInViewport="true" + [opened]="!signaturesService.mobileMode" [style.width.px]="350"> + <app-admin-sidebar [snavLeftComponent]="this.snav" [snavRightComponent]="this.snavRight"></app-admin-sidebar> + </mat-sidenav> + <mat-sidenav-content> + <header class="header"> + <div class="header-title"> + <button *ngIf="signaturesService.mobileMode" mat-icon-button (click)="this.snav.toggle();"> + <mat-icon fontSet="fas" fontIcon="fa-bars" style="font-size: 24px;"></mat-icon> + </button> + <span *ngIf="!loading">{{'lang.manage_password_rules' | translate}}</span> + </div> + </header> + <div class="container"> + <div *ngIf="loading" style="display:flex;height:100%;"> + <mat-spinner style="margin:auto;"></mat-spinner> + </div> + <mat-card *ngIf="!loading" class="card-app-content"> + <mat-tab-group> + <mat-tab label="{{'lang.password' | translate}}"> + <form (ngSubmit)="onSubmit()" #passwordForm="ngForm"> + <mat-list> + <p style="margin-bottom: 40px;text-align: center;"> + <mat-slide-toggle [name]="passwordRules['complexityUpper'].label" + [checked]="passwordRules['complexityUpper'].enabled" color="primary" + (change)="toggleRule(passwordRules['complexityUpper']);" + style="padding-left:10px;padding-right:10px;"> + {{passwordRules['complexityUpper'].label}}</mat-slide-toggle> + <mat-slide-toggle [name]="passwordRules['complexityNumber'].label" + [checked]="passwordRules['complexityNumber'].enabled" color="primary" + (change)="toggleRule(passwordRules['complexityNumber']);" + style="padding-left:10px;padding-right:10px;"> + {{passwordRules['complexityNumber'].label}}</mat-slide-toggle> + <mat-slide-toggle [name]="passwordRules['complexitySpecial'].label" + [checked]="passwordRules['complexitySpecial'].enabled" color="primary" + (change)="toggleRule(passwordRules['complexitySpecial']);" + style="padding-left:10px;padding-right:10px;"> + {{passwordRules['complexitySpecial'].label}}</mat-slide-toggle> + </p> + <mat-list-item style="margin-top: 15px;margin-bottom: 15px;"> + <mat-icon mat-list-icon> + <mat-slide-toggle style="position: relative;top:-10px;" + [checked]="passwordRules['minLength'].enabled" color="primary" + (change)="toggleRule(passwordRules['minLength']);"></mat-slide-toggle> + </mat-icon> + <p mat-line> + <mat-form-field style="width: 100%"> + <input type="number" [disabled]="!passwordRules['minLength'].enabled" + [name]="passwordRules['minLength'].label" + [(ngModel)]="passwordRules['minLength'].value" min="1" + pattern="^[1-9][0-9]*" matInput + placeholder="{{passwordRules['minLength'].label}}" required> + <span matSuffix> {{'lang.chars' | translate}}</span> + </mat-form-field> + </p> + </mat-list-item> + <mat-list-item style="margin-top: 15px;margin-bottom: 15px;"> + <mat-icon mat-list-icon> + <mat-slide-toggle style="position: relative;top:-10px;" + [checked]="passwordRules['lockAttempts'].enabled" color="primary" + (change)="toggleRule(passwordRules['lockAttempts']);"> + </mat-slide-toggle> + </mat-icon> + <p mat-line style="display:flex;"> + <mat-form-field style="flex:1;padding-right: 10px;"> + <input type="number" [disabled]="!passwordRules['lockAttempts'].enabled" + [name]="passwordRules['lockAttempts'].label" + [(ngModel)]="passwordRules['lockAttempts'].value" min="1" + pattern="^[1-9][0-9]*" matInput + placeholder="{{passwordRules['lockAttempts'].label}}" required> + </mat-form-field> + <mat-form-field style="flex:1;"> + <input type="number" [disabled]="!passwordRules['lockTime'].enabled" + [name]="passwordRules['lockTime'].label" + [(ngModel)]="passwordRules['lockTime'].value" min="1" + pattern="^[1-9][0-9]*" matInput + placeholder="{{passwordRules['lockTime'].label}}" required> + <span matSuffix> {{'lang.minutes' | translate}}</span> + </mat-form-field> + </p> + </mat-list-item> + <mat-list-item style="margin-top: 15px;margin-bottom: 15px;"> + <mat-icon mat-list-icon> + <mat-slide-toggle style="position: relative;top:-10px;" + [checked]="passwordRules['renewal'].enabled" color="primary" + (change)="toggleRule(passwordRules['renewal']);"></mat-slide-toggle> + </mat-icon> + <p mat-line> + <mat-form-field style="width: 100%"> + <input type="number" [disabled]="!passwordRules['renewal'].enabled" + [name]="passwordRules['renewal'].label" + [(ngModel)]="passwordRules['renewal'].value" min="1" + pattern="^[1-9][0-9]*" matInput + placeholder="{{passwordRules['renewal'].label}}" required> + <span matSuffix> {{'lang.days' | translate}}</span> + </mat-form-field> + </p> + </mat-list-item> + <mat-list-item style="margin-top: 15px;margin-bottom: 15px;"> + <mat-icon mat-list-icon> + <mat-slide-toggle style="position: relative;top:-10px;" + [checked]="passwordRules['historyLastUse'].enabled" color="primary" + (change)="toggleRule(passwordRules['historyLastUse']);"> + </mat-slide-toggle> + </mat-icon> + <p mat-line> + <mat-form-field style="width: 100%;"> + <input type="number" + [disabled]="!passwordRules['historyLastUse'].enabled" + [name]="passwordRules['historyLastUse'].label" + [(ngModel)]="passwordRules['historyLastUse'].value" min="1" + pattern="^[1-9][0-9]*" matInput + placeholder="{{passwordRules['historyLastUse'].label}}" required> + </mat-form-field> + </p> + </mat-list-item> + </mat-list> + <div class="col-md-12 text-center" style="padding:10px; text-align: center"> + <button mat-raised-button type="submit" color="primary" style="margin: 10px" + [disabled]="(!passwordForm.valid && !disabledForm()) || checkModif()">{{'lang.validate' | translate}}</button> + <button mat-raised-button type="button" color="default" [disabled]="checkModif()" + (click)="cancelModification()">{{'lang.cancel' | translate}}</button> + </div> + </form> + </mat-tab> + </mat-tab-group> + </mat-card> + </div> + </mat-sidenav-content> + <mat-sidenav #snavRight disableClose [mode]="signaturesService.mobileMode ? 'over': 'side'" [opened]="false" + fixedInViewport="true" position='end'> + </mat-sidenav> +</mat-sidenav-container> diff --git a/src/frontend/app/administration/security/securities-administration.component.ts b/src/frontend/app/administration/security/securities-administration.component.ts new file mode 100644 index 0000000000..dc3e2d4cff --- /dev/null +++ b/src/frontend/app/administration/security/securities-administration.component.ts @@ -0,0 +1,101 @@ +import { Component, OnInit } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import {TranslateService} from '@ngx-translate/core'; +import {NotificationService} from '../../service/notification.service'; +import {SignaturesContentService} from '../../service/signatures.service'; + +@Component({ + templateUrl: 'securities-administration.component.html', + styleUrls: ['../administration.scss'] +}) +export class SecuritiesAdministrationComponent implements OnInit { + + loading: boolean = false; + + passwordRules: any = { + minLength: { enabled: false, value: 0 }, + complexityUpper: { enabled: false, value: 0 }, + complexityNumber: { enabled: false, value: 0 }, + complexitySpecial: { enabled: false, value: 0 }, + renewal: { enabled: false, value: 0 }, + historyLastUse: { enabled: false, value: 0 }, + lockTime: { enabled: false, value: 0 }, + lockAttempts: { enabled: false, value: 0 }, + }; + passwordRulesClone: any = {}; + + passwordRulesList: any[] = []; + + + constructor( + public http: HttpClient, + private translate: TranslateService, + private notify: NotificationService, + public signaturesService: SignaturesContentService + ) { } + + ngOnInit(): void { + this.loading = true; + + this.http.get('../rest/passwordRules') + .subscribe((data: any) => { + this.passwordRulesList = data.rules; + + data.rules.forEach((rule: any) => { + this.passwordRules[rule.label].enabled = rule.enabled; + this.passwordRules[rule.label].value = rule.value; + this.passwordRules[rule.label].label = this.translate.instant('lang.password_' + rule.label + 'Required'); + this.passwordRules[rule.label].id = rule.label; + + this.loading = false; + }); + + this.passwordRulesClone = JSON.parse(JSON.stringify(this.passwordRules)); + + }, (err) => { + this.notify.error(err.error.errors); + }); + } + + cancelModification() { + this.passwordRules = JSON.parse(JSON.stringify(this.passwordRulesClone)); + this.passwordRulesList.forEach((rule: any) => { + rule.enabled = this.passwordRules[rule.label].enabled; + rule.value = this.passwordRules[rule.label].value; + }); + } + + checkModif() { + return JSON.stringify(this.passwordRules) === JSON.stringify(this.passwordRulesClone); + } + + disabledForm() { + return !this.passwordRules['lockTime'].enabled && !this.passwordRules['minLength'].enabled && !this.passwordRules['lockAttempts'].enabled && !this.passwordRules['renewal'].enabled && !this.passwordRules['historyLastUse'].enabled; + } + + toggleRule(rule: any) { + rule.enabled = !rule.enabled; + this.passwordRulesList.forEach((rule2: any) => { + if (rule.id === 'lockAttempts' && (rule2.label === 'lockTime' || rule2.label === 'lockAttempts')) { + rule2.enabled = rule.enabled; + this.passwordRules['lockTime'].enabled = rule.enabled; + } else if (rule.id === rule2.label) { + rule2.enabled = rule.enabled; + } + }); + } + + onSubmit() { + this.passwordRulesList.forEach((rule: any) => { + rule.enabled = this.passwordRules[rule.label].enabled; + rule.value = this.passwordRules[rule.label].value; + }); + this.http.put('../rest/passwordRules', { rules: this.passwordRulesList }) + .subscribe(() => { + this.passwordRulesClone = JSON.parse(JSON.stringify(this.passwordRules)); + this.notify.success('lang.passwordRulesUpdated'); + }, (err: any) => { + this.notify.error(err.error.errors); + }); + } +} diff --git a/src/frontend/app/app-routing.module.ts b/src/frontend/app/app-routing.module.ts index 6704cb994d..90a4a946e2 100755 --- a/src/frontend/app/app-routing.module.ts +++ b/src/frontend/app/app-routing.module.ts @@ -1,6 +1,6 @@ -import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; -import { AuthGuard } from './service/auth.guard'; +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { AuthGuard } from './service/auth.guard'; import { AdministrationComponent } from './administration/home/administration.component'; import { UsersListComponent } from './administration/user/users-list.component'; @@ -15,6 +15,7 @@ import { DocumentComponent } from './document/document.component'; import { LoginComponent } from './login/login.component'; import { ForgotPasswordComponent } from './login/forgotPassword/forgotPassword.component'; import { UpdatePasswordComponent } from './login/updatePassword/updatePassword.component'; +import {SecuritiesAdministrationComponent} from './administration/security/securities-administration.component'; @NgModule({ imports: [ @@ -31,6 +32,7 @@ import { UpdatePasswordComponent } from './login/updatePassword/updatePassword.c { path: 'administration/connections/ldaps/new', canActivate: [AuthGuard], component: LdapComponent }, { path: 'administration/connections/ldaps/:id', canActivate: [AuthGuard], component: LdapComponent }, { path: 'administration/emailConfiguration', canActivate: [AuthGuard], component: SendmailComponent }, + { path: 'administration/passwordRules', canActivate: [AuthGuard], component: SecuritiesAdministrationComponent }, { path: 'documents/:id', canActivate: [AuthGuard], component: DocumentComponent }, { path: 'documents', canActivate: [AuthGuard], component: DocumentComponent }, { path: 'login', component: LoginComponent }, diff --git a/src/frontend/app/app.module.ts b/src/frontend/app/app.module.ts index e5a955484c..6afb72a861 100755 --- a/src/frontend/app/app.module.ts +++ b/src/frontend/app/app.module.ts @@ -64,6 +64,7 @@ import { LdapComponent } from './administration/connection/ldap/ldap.component'; import { SendmailComponent } from './administration/sendmail/sendmail.component'; import { GroupsListComponent } from './administration/group/groups-list.component'; import { GroupComponent } from './administration/group/group.component'; +import {SecuritiesAdministrationComponent} from './administration/security/securities-administration.component'; // SERVICES @@ -117,7 +118,8 @@ import { SortPipe } from './plugins/sorting.pipe'; GroupsListComponent, GroupComponent, PluginAutocompleteComponent, - SortPipe + SortPipe, + SecuritiesAdministrationComponent ], imports: [ FormsModule, diff --git a/src/frontend/app/service/auth-interceptor.service.ts b/src/frontend/app/service/auth-interceptor.service.ts index 8018640165..3910756db2 100644 --- a/src/frontend/app/service/auth-interceptor.service.ts +++ b/src/frontend/app/service/auth-interceptor.service.ts @@ -34,7 +34,7 @@ export class AuthInterceptor implements HttpInterceptor { intercept(request: HttpRequest<any>, next: HttpHandler): Observable<any> { // We don't want to intercept some routes - if (this.excludeUrls.indexOf(request.url) > -1 || request.url.indexOf('/password') > -1) { + if ((this.excludeUrls.indexOf(request.url) > -1 || request.url.indexOf('/password') > -1) && request.url.indexOf('/passwordRules') === -1 && request.method.indexOf('PUT') === -1) { return next.handle(request); } else { // Add current token in header request -- GitLab