Merge branch 'develop' into pagination

develop
Yanaël GRETTE 4 years ago
commit 44072ac899
  1. 1
      .gitignore
  2. 3
      angular.json
  3. 25
      package-lock.json
  4. 3
      package.json
  5. 6
      src/app/app-routing.module.ts
  6. 35
      src/app/app.module.ts
  7. 23
      src/app/auth/auth-config.ts
  8. 12
      src/app/auth/auth-module-config.ts
  9. 28
      src/app/auth/auth.guard.ts
  10. 61
      src/app/auth/auth.module.ts
  11. 6
      src/app/auth/auth.service.spec.ts
  12. 263
      src/app/auth/auth.service.ts
  13. 13
      src/app/collaborateurs/collaborateurs.routing.module.ts
  14. 2
      src/app/collaborateurs/details-collaborateur/details-collaborateur.component.html
  15. 15
      src/app/collaborateurs/details-collaborateur/details-collaborateur.component.ts
  16. 6
      src/app/demandes-delegation/demandes-delegation.routing.module.ts
  17. 8
      src/app/demandes-formation/demandes-formation.routing.module.ts
  18. 10
      src/app/ep-saisie/ep-saisie.routing.module.ts
  19. 32
      src/app/ep/ep.routing.module.ts
  20. 10
      src/app/formations/formations.routing.module.ts
  21. 6
      src/app/home/home-assistante/home-assistante.component.ts
  22. 9
      src/app/home/home.component.ts
  23. 6
      src/app/referents/referents.routing.module.ts
  24. 2
      src/app/shared/api-swagger/api/ep.service.ts
  25. 40
      src/app/shared/guards/keycloakguard.ts
  26. 15
      src/app/shared/nav-menu/nav-menu.component.ts
  27. 39
      src/environments/environment.ts
  28. 31
      src/silent-refresh.html

1
.gitignore vendored

@ -30,6 +30,7 @@ speed-measure-plugin*.json
!.vscode/launch.json !.vscode/launch.json
!.vscode/extensions.json !.vscode/extensions.json
.history/* .history/*
.vs/*
# misc # misc
/.sass-cache /.sass-cache

@ -21,7 +21,8 @@
"aot": true, "aot": true,
"assets": [ "assets": [
"src/favicon.ico", "src/favicon.ico",
"src/assets" "src/assets",
"src/silent-refresh.html"
], ],
"styles": [ "styles": [
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",

25
package-lock.json generated

@ -2202,6 +2202,14 @@
"dev": true, "dev": true,
"optional": true "optional": true
}, },
"angular-oauth2-oidc": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/angular-oauth2-oidc/-/angular-oauth2-oidc-9.2.2.tgz",
"integrity": "sha512-aMQXeujzhubvxGw3ujw9FGwTC+L8m7CXnzVntpNJRkJsgMiuZFrXzgeiG87tvAE61J+PlOVIb/UkJjYDgDVU6Q==",
"requires": {
"js-sha256": "^0.9.0"
}
},
"ansi-colors": { "ansi-colors": {
"version": "3.2.4", "version": "3.2.4",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz",
@ -2754,7 +2762,8 @@
"base64-js": { "base64-js": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==",
"dev": true
}, },
"base64id": { "base64id": {
"version": "2.0.0", "version": "2.0.0",
@ -7824,20 +7833,6 @@
"source-map-support": "^0.5.5" "source-map-support": "^0.5.5"
} }
}, },
"keycloak-angular": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/keycloak-angular/-/keycloak-angular-7.3.1.tgz",
"integrity": "sha512-lU1ErzCOOmvwmfM0rAf6Nlf65CkG1EpnXpGpdaeViCUcJOdU+ChMcjez78Ekv5qUFkNZsNccs2m5xUaU/1bqlw=="
},
"keycloak-js": {
"version": "10.0.2",
"resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-10.0.2.tgz",
"integrity": "sha512-7nkg4Ob1khHGcNbuK36AMndKUEuIQFpNlWU9ygWs7nSBPCI9VZ8dJjjXfKJHm0ewgcqLFGPIJ6bxxRlfcQ6sLg==",
"requires": {
"base64-js": "1.3.1",
"js-sha256": "0.9.0"
}
},
"killable": { "killable": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",

@ -23,8 +23,7 @@
"@angular/platform-browser": "~9.1.9", "@angular/platform-browser": "~9.1.9",
"@angular/platform-browser-dynamic": "~9.1.9", "@angular/platform-browser-dynamic": "~9.1.9",
"@angular/router": "~9.1.9", "@angular/router": "~9.1.9",
"keycloak-angular": "^7.3.1", "angular-oauth2-oidc": "^9.2.2",
"keycloak-js": "^10.0.2",
"rxjs": "~6.5.4", "rxjs": "~6.5.4",
"tslib": "^1.10.0", "tslib": "^1.10.0",
"zone.js": "~0.10.2" "zone.js": "~0.10.2"

@ -5,7 +5,7 @@ import { Routes } from '@angular/router';
import { HomeComponent } from './home/'; import { HomeComponent } from './home/';
import { KeycloakGuard } from '@shared/guards/keycloakguard'; import { AuthGuard } from './auth/auth.guard';
import { paths_collaborateurs, paths_demandes_delegation, paths_demandes_formation, import { paths_collaborateurs, paths_demandes_delegation, paths_demandes_formation,
paths_ep, paths_saisie_ep, paths_formation, paths_home, paths_referents } from '@shared/utils/paths'; paths_ep, paths_saisie_ep, paths_formation, paths_home, paths_referents } from '@shared/utils/paths';
@ -25,7 +25,7 @@ const routes: Routes = [
{ {
path: paths_home.path, path: paths_home.path,
component: HomeComponent, component: HomeComponent,
canActivate: [KeycloakGuard], canActivate: [AuthGuard],
pathMatch: 'full' pathMatch: 'full'
}, },
//chargement des chemins du module collaborateur à partir du routing de ce module //chargement des chemins du module collaborateur à partir du routing de ce module
@ -68,6 +68,6 @@ const routes: Routes = [
@NgModule({ @NgModule({
imports: [RouterModule.forRoot(routes)], imports: [RouterModule.forRoot(routes)],
exports: [RouterModule], exports: [RouterModule],
providers: [KeycloakGuard] providers: [AuthGuard]
}) })
export class AppRoutingModule {} export class AppRoutingModule {}

@ -2,8 +2,6 @@ import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule, DoBootstrap } from '@angular/core'; import { NgModule, DoBootstrap } from '@angular/core';
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http';
import { KeycloakAngularModule, KeycloakService } from 'keycloak-angular';
import { RouterModule } from '@angular/router';
import { registerLocaleData } from '@angular/common'; import { registerLocaleData } from '@angular/common';
import localeFr from '@angular/common/locales/fr'; import localeFr from '@angular/common/locales/fr';
@ -24,49 +22,26 @@ import { DemandesFormationModule } from './demandes-formation';
import { DemandesDelegationModule } from './demandes-delegation'; import { DemandesDelegationModule } from './demandes-delegation';
import { EpSaisieModule } from "./ep-saisie"; import { EpSaisieModule } from "./ep-saisie";
import { EpModule } from "./ep" import { EpModule } from "./ep"
import { AuthModule } from './auth/auth.module';
import { environment } from '@env';
/**
* constante Keycloak qui pourra être utilisé dans tout le projet.
*/
let keycloakService: KeycloakService = new KeycloakService();
@NgModule({ @NgModule({
declarations: [ declarations: [
AppComponent AppComponent
], ],
imports: [ imports: [
BrowserModule, BrowserAnimationsModule, BrowserModule, BrowserAnimationsModule,
KeycloakAngularModule, AppRoutingModule, AuthModule.forRoot(), AppRoutingModule,
HttpClientModule, ApiModule, HttpClientModule, ApiModule,
HomeModule, CollaborateursModule, HomeModule, CollaborateursModule,
ReferentsModule, FormationsModule, ReferentsModule, FormationsModule,
DemandesFormationModule, DemandesDelegationModule, DemandesFormationModule, DemandesDelegationModule,
EpSaisieModule, EpModule EpSaisieModule, EpModule
], ],
providers: [ bootstrap: [AppComponent]
{
provide: KeycloakService,
useValue: keycloakService
}
],
entryComponents: [AppComponent]
}) })
export class AppModule implements DoBootstrap { export class AppModule {
//Configuration de la connexion avec Keycloak
async ngDoBootstrap(app) {
const { keycloakConfig } = environment;
try {
await keycloakService.init({ config: keycloakConfig });
app.bootstrap(AppComponent);
} catch (error) {
console.error('Keycloak init failed', error);
}
}
} }

@ -0,0 +1,23 @@
import { AuthConfig } from 'angular-oauth2-oidc';
import { environment } from '@env';
/**
* Configuration du serveur Keycloak.
*/
export const authConfig: AuthConfig = {
issuer: environment.keycloakConfig.issuer,
clientId: environment.keycloakConfig.clientId,
dummyClientSecret: environment.keycloakConfig.dummyClientSecret,
responseType: environment.keycloakConfig.responseType,
redirectUri: environment.keycloakConfig.redirectUri,
silentRefreshRedirectUri: environment.keycloakConfig.silentRefreshRedirectUri,
scope: environment.keycloakConfig.scope,
useSilentRefresh: environment.keycloakConfig.useSilentRefresh,
silentRefreshTimeout: environment.keycloakConfig.silentRefreshTimeout,
timeoutFactor: environment.keycloakConfig.timeoutFactor,
sessionChecksEnabled: environment.keycloakConfig.sessionChecksEnabled,
showDebugInformation: environment.keycloakConfig.showDebugInformation,
clearHashAfterLogin: environment.keycloakConfig.clearHashAfterLogin,
nonceStateSeparator : environment.keycloakConfig.nonceStateSeparator
};

@ -0,0 +1,12 @@
import { OAuthModuleConfig } from 'angular-oauth2-oidc';
/**
* Liste des urls pour lesquelles les appels doivent être interceptés.
* Si la propriété sendAccessToken est défini sur true, l'access_token est envoyé dans le header.
*/
export const authModuleConfig: OAuthModuleConfig = {
resourceServer: {
allowedUrls: ['https://localhost:44393/api'],
sendAccessToken: true,
}
};

@ -0,0 +1,28 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { AuthService } from './auth.service';
/**
* Guard permettant de gérer les autorisations au niveau des routes.
*/
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
return this.authService.canActivateProtectedRoutes$.pipe(tap(isLoggin => this.login(isLoggin)));
}
/**
* Affiche la page de connexion si l'utilisateur n'est pas connecté.
* @param isLoggin Booléen permettant de savoir si l'utilisateur est connecté ou non
*/
private login(isLoggin: boolean) {
if (!isLoggin) {
this.authService.login();
}
}
}

@ -0,0 +1,61 @@
import { HttpClientModule } from '@angular/common/http';
import { ModuleWithProviders, NgModule, Optional, SkipSelf, APP_INITIALIZER } from '@angular/core';
import { AuthConfig, OAuthModule, OAuthModuleConfig, OAuthStorage } from 'angular-oauth2-oidc';
import { authConfig } from './auth-config';
import { AuthGuard } from './auth.guard';
import { authModuleConfig } from './auth-module-config';
import { AuthService } from './auth.service';
/**
* Nous avons besoin d'une usine de stockage car le localStorage
* n'est pas disponible au moment de la compilation de l'AOT. (Ahead-of-time)
*/
export function storageFactory(): OAuthStorage {
return localStorage;
}
/**
* Exécute la méthode qui permet d'afficher la page de connexion.
* @param authService Service d'authentification
*/
export function init_app(authService: AuthService) {
return () => authService.runInitialLoginSequence();
}
/**
* Module d'authentification.
*/
@NgModule({
imports: [
HttpClientModule,
OAuthModule.forRoot(),
],
providers: [
AuthService,
AuthGuard,
],
})
export class AuthModule {
static forRoot(): ModuleWithProviders<AuthModule> {
return {
ngModule: AuthModule,
providers: [
{ provide: AuthConfig, useValue: authConfig },
{ provide: OAuthModuleConfig, useValue: authModuleConfig },
{ provide: OAuthStorage, useFactory: storageFactory },
{
provide: APP_INITIALIZER,
useFactory: init_app, // Affiche la page de connexion au démarrage de l'application
deps: [ AuthService ],
multi: true
}
]
};
}
constructor (@Optional() @SkipSelf() parentModule: AuthModule) {
if (parentModule) {
throw new Error("AuthModule est déjà chargé. Importez-le dans uniquement l'AppModule.");
}
}
}

@ -0,0 +1,6 @@
describe('AuthService', () => {
it('should pass', () => {
expect(true).toBeTruthy();
});
});

@ -0,0 +1,263 @@
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { OAuthErrorEvent, OAuthService } from 'angular-oauth2-oidc';
import { BehaviorSubject, combineLatest, Observable, ReplaySubject } from 'rxjs';
import { filter, map } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class AuthService {
private isAuthenticatedSubject$ = new BehaviorSubject<boolean>(false);
private isDoneLoadingSubject$ = new ReplaySubject<boolean>();
/**
* Observable permettant de savoir si l'utilisateur est authentifié.
*/
public isAuthenticated$ = this.isAuthenticatedSubject$.asObservable();
/**
* Observable permettant de savoir si le chargement de la connexion initiale est terminé.
*/
public isDoneLoading$ = this.isDoneLoadingSubject$.asObservable();
/**
* Retourne "true" si et seulement si tous les appels asynchrones de la connexion initiale
* sont terminés (ou comportent une erreur), et si l'utilisateur
* a fini par être authentifié.
*
* En résumé, il combine:
*
* - Le dernier état connu indiquant si l'utilisateur est autorisé
* - Si les appels ajax pour la connexion initiale ont tous é effectués
*/
public canActivateProtectedRoutes$: Observable<boolean> = combineLatest([
this.isAuthenticated$, this.isDoneLoading$ ]).pipe(map(values => values.every(b => b)));
/**
* Navigue vers la page de connexion.
*/
private navigateToLoginPage() {
// Pour naviguer vers un composant
//this.router.navigateByUrl('/should-login');
// Pour naviguer vers la page de connexion du server Keycloak
this.login();
}
/**
* Constructeur du service d'authentification.
* @param oauthService Service d'authentification de la librairie angular-oauth2-oidc
* @param router Service permettant de naviguer et de manipuler les urls
*/
constructor(private oauthService: OAuthService, private router: Router) {
/*
// Utile pour le débogage:
this.oauthService.events.subscribe(event => {
if (event instanceof OAuthErrorEvent) {
console.error('OAuthErrorEvent Object:', event);
} else {
console.warn('OAuthEvent Object:', event);
}
});
*/
// Ajout un évènement afin de gérer l'acces_token dans le cas où l'application est ouverte dans deux onglets différents.
// TODO: Pour améliorer cette configuration. Voir: https://github.com/jeroenheijmans/sample-angular-oauth2-oidc-with-auth-guards/issues/2
window.addEventListener('storage', (event) => {
// La propriété "key" à la valeur "null" si l'événement a été causé par la méthode ".clear()".
if (event.key !== 'access_token' && event.key !== null) {
return;
}
console.warn("Changements remarqués dans l'access_token (très probablement depuis un autre onglet). Mise à jour de l'observable isAuthenticated.") ;
this.isAuthenticatedSubject$.next(this.oauthService.hasValidAccessToken());
// Si l'acces_token n'est pas valide, on redirige l'utilisateur vers la page de connexion
if (!this.oauthService.hasValidAccessToken()) {
this.navigateToLoginPage();
}
});
// Met à jour l'observable lorsque l'access_token est valide
this.oauthService.events
.subscribe(_ => {
this.isAuthenticatedSubject$.next(this.oauthService.hasValidAccessToken());
});
// Charge le profil de l'utilisateur lorsque le token est reçu
this.oauthService.events
.pipe(filter(e => ['token_received'].includes(e.type)))
.subscribe(e => this.oauthService.loadUserProfile());
// Redirige l'utilisateur vers la page de connexion lorsque la session est terminée ou lorsque qu'il y a une erreur
this.oauthService.events
.pipe(filter(e => ['session_terminated', 'session_error'].includes(e.type)))
.subscribe(e => this.navigateToLoginPage());
// Mise en place d'un rafraîchissement silencieux lorsque le jeton est sur le point d'expirer
this.oauthService.setupAutomaticSilentRefresh();
}
/**
* Lance la séquence de connexion initiale.
*/
public runInitialLoginSequence(): Promise<void> {
if (location.hash) {
console.log("Fragment de hachage rencontré, tracé sous forme de tableau...") ;
console.table(location.hash.substr(1).split('&').map(kvp => kvp.split('=')));
}
// 0. CONFIGURATION DU CHARGEMENT:
// Il faut d'abord vérifier comment le serveur Keycloak est actuellement configuré:
return this.oauthService.loadDiscoveryDocument()
// 1. HASH LOGIN:
// Essaye de se connecter via le fragment de hachage
// après avoir été redirigé depuis le serveur Keycloak depuis initImplicitFlow :
.then(() => this.oauthService.tryLogin())
.then(() => {
if (this.oauthService.hasValidAccessToken()) {
return Promise.resolve();
}
// 2. SILENT LOGIN:
// Effectue un rafraîchissement silencieux pour le flux implicite.
// Utilise cette méthode pour obtenir des nouveaux tokens quand/avant que les tokens existants expirent.
return this.oauthService.silentRefresh()
.then(() => Promise.resolve())
.catch(result => {
// Sous-ensemble de situations de https://openid.net/specs/openid-connect-core-1_0.html#AuthError
// Seulement celles où il est raisonnablement certain que l'envoi de l'utilisateur au serveur Keycloak est utile.
const errorResponsesRequiringUserInteraction = [
'interaction_required',
'login_required',
'account_selection_required',
'consent_required',
];
if (result
&& result.reason
&& errorResponsesRequiringUserInteraction.indexOf(result.reason.error) >= 0) {
// 3. DEMANDE DE CONNEXION:
// A ce stade, nous savons avec certitude que nous devons demander à
// l'utilisateur de se connecter, nous le redirigeons donc vers la page de connexion.
// Activez cette option pour TOUJOURS forcer un utilisateur à se connecter.
this.login();
// Afficher un mesage d'avertissement:
//console.warn("Une interaction de l'utilisateur est nécessaire pour se connecter, nous allons attendre que l'utilisateur se connecte manuellement.");
return Promise.resolve();
}
return Promise.reject(result);
});
})
.then(() => {
// Met à jour l'observable isDoneLoadingSubject
this.isDoneLoadingSubject$.next(true);
// Vérifie les valeurs "undefined" et "null" pour être sûr.
// Notre login actuel ne devrait jamais avoir cela, mais au cas
// où quelqu'un appellerait la méthode initImplicitFlow(undefined|null), cela pourrait arriver.
if (this.oauthService.state && this.oauthService.state !== 'undefined' && this.oauthService.state !== 'null') {
let stateUrl = this.oauthService.state;
if (stateUrl.startsWith('/') === false) {
stateUrl = decodeURIComponent(stateUrl);
}
console.log(`Il y a eu l'état de ${this.oauthService.state}, donc nous vous envoyons à ${stateUrl}`) ;
this.router.navigateByUrl(stateUrl);
}
})
.catch(() => this.isDoneLoadingSubject$.next(true)); // Met à jour l'observable isDoneLoadingSubject
}
/**
* Affiche la page de connexion.
* @param targetUrl Url de destination
*/
public login(targetUrl?: string) {
// Note : avant la version 9.1.0 de la librairie, il fallait
// passer le composant encodageURIC dans les arguments de la méthode.
this.oauthService.initLoginFlow(targetUrl || this.router.url);
}
/**
* Supprime tous les tokens et déconnecte l'utilisateur.
*/
public logout() { this.oauthService.logOut(); }
/**
* Permet d'obtenir des nouveaux tokens quand/avant que les tokens existants expirent.
*/
public refresh() { this.oauthService.silentRefresh(); }
/**
* Vérifie si l'access_token est valide.
*/
public hasValidToken() { return this.oauthService.hasValidAccessToken(); }
/**
* Premier rôle dans la liste des rôles de l'utilisateur.
*/
public get firstRole() {
if(this.identityClaims != null)
return this.oauthService.getIdentityClaims()['roles'][0];
else
return;
}
/**
* Liste des rôles de l'utilisateur.
*/
public get roles() {
if(this.identityClaims != null)
return this.oauthService.getIdentityClaims()['roles'];
else
return;
}
/**
* Nom et prénom de l'utilisateur.
*/
public get userInfo() {
if(this.identityClaims != null)
return this.oauthService.getIdentityClaims()['name'];
else
return;
}
// Normalement, un service comme celui-ci ne les expose pas,
// mais pour le débogage, c'est logique.
/**
* Access_token actuel.
*/
public get accessToken() { return this.oauthService.getAccessToken(); }
/**
* Refresh_Token actuel.
*/
public get refreshToken() { return this.oauthService.getRefreshToken(); }
/**
* Claims concernant l'utilisateur.
*/
public get identityClaims() { return this.oauthService.getIdentityClaims(); }
/**
* Id_token actuel.
*/
public get idToken() { return this.oauthService.getIdToken(); }
/**
* Url de déconnexion.
*/
public get logoutUrl() { return this.oauthService.logoutUrl; }
}

@ -9,17 +9,16 @@ import { EditEvaluationComponent } from './formations-collaborateur/edit-evaluat
import { paths_collaborateurs } from '@shared/utils/paths'; import { paths_collaborateurs } from '@shared/utils/paths';
import { KeycloakGuard } from '@shared/guards/keycloakguard'; import { AuthGuard } from 'app/auth/auth.guard';
/** /**
* Routes du module collaborateur * Routes du module collaborateur
*/ */
const routes: Routes = [ const routes: Routes = [
{ path:'', component: CollaborateursComponent, pathMatch: 'full', canActivate: [KeycloakGuard] }, { path:'', component: CollaborateursComponent, pathMatch: 'full', canActivate: [AuthGuard] },
{ path:paths_collaborateurs.formations, component: FormationsCollaboateurComponent, canActivate: [KeycloakGuard] }, { path:paths_collaborateurs.formations, component: FormationsCollaboateurComponent, canActivate: [AuthGuard] },
{ path:paths_collaborateurs.evaluation, component: EvaluationComponent, canActivate: [KeycloakGuard] }, { path:paths_collaborateurs.evaluation, component: EvaluationComponent, canActivate: [AuthGuard] },
{ path:paths_collaborateurs.edit, component: EditEvaluationComponent, canActivate: [KeycloakGuard] }, { path:paths_collaborateurs.edit, component: EditEvaluationComponent, canActivate: [AuthGuard] },
{ path:paths_collaborateurs.get, component: DetailsCollaborateurComponent, canActivate: [KeycloakGuard] } { path:paths_collaborateurs.get, component: DetailsCollaborateurComponent, canActivate: [AuthGuard] }
]; ];

@ -24,7 +24,7 @@
<ng-container matColumnDef="referent"> <ng-container matColumnDef="referent">
<mat-header-cell *matHeaderCellDef mat-sort-header>Référent</mat-header-cell> <mat-header-cell *matHeaderCellDef mat-sort-header>Référent</mat-header-cell>
<mat-cell *matCellDef="let row"> {{ row.referent.prenom }} {{ row.referent.nom }}</mat-cell> <mat-cell *matCellDef="let row"> {{ getReferent(row.referent) }}</mat-cell>
</ng-container> </ng-container>
<ng-container matColumnDef="type"> <ng-container matColumnDef="type">

@ -9,7 +9,7 @@ import {MatSort} from '@angular/material/sort';
import { CollaborateursService, EpService } from "@shared/api-swagger/api/api"; import { CollaborateursService, EpService } from "@shared/api-swagger/api/api";
import { EpInformationDTO, CollaborateurDTO } from "@shared/api-swagger/model/models"; import { EpInformationDTO, CollaborateurDTO, ReferentDTO } from "@shared/api-swagger/model/models";
/** /**
* Composant pour gérer l'affichage des détails d'un collaborateur et de ses EP * Composant pour gérer l'affichage des détails d'un collaborateur et de ses EP
@ -93,15 +93,18 @@ export class DetailsCollaborateurComponent implements OnInit {
this.idCollaborateur = this.route.snapshot.paramMap.get('id'); this.idCollaborateur = this.route.snapshot.paramMap.get('id');
this.collaborateurSubscription = this.collaborateusrService.getCollaborateurById(this.idCollaborateur).subscribe( this.collaborateurSubscription = this.collaborateusrService.getCollaborateurById(this.idCollaborateur).subscribe(
collaborateur => { collaborateur => {
if(this.collaborateur != null) { this.collaborateur = collaborateur;
this.collaborateur = collaborateur[0];
this.updateEP(); this.updateEP();
}
}, },
err => console.log(err) err => console.log(err)
); );
} }
getReferent(referent : ReferentDTO) {
if(referent == undefined || referent == null) { return "Referent indisponible"}
return referent.prenom + " "+ referent.nom;
}
/** /**
* Mise à jour du tableau des EP lors d'un changement de page du tableau, du nombre d'élément à afficher ou d'un tri. * Mise à jour du tableau des EP lors d'un changement de page du tableau, du nombre d'élément à afficher ou d'un tri.
* La fonction est aussi appelé au début du chargement et à l'utilisation de la barre de recherche. * La fonction est aussi appelé au début du chargement et à l'utilisation de la barre de recherche.
@ -110,8 +113,12 @@ export class DetailsCollaborateurComponent implements OnInit {
this.epSubscription = this.epService.getEPByCollaborateur(this.asc, this.idCollaborateur, this.numPage, this.parPage, undefined, this.search, this.tri).subscribe( this.epSubscription = this.epService.getEPByCollaborateur(this.asc, this.idCollaborateur, this.numPage, this.parPage, undefined, this.search, this.tri).subscribe(
ep => ep =>
{ {
console.log(ep);
if(ep != null) {
this.nbEP = ep.length; this.nbEP = ep.length;
this.dataSource = new MatTableDataSource(ep); this.dataSource = new MatTableDataSource(ep);
}
}, },
err => console.log(err) err => console.log(err)
); );

@ -5,7 +5,7 @@ import { Routes, RouterModule } from '@angular/router';
import { DemandesDelegationComponent } from "./demandes-delegation.component"; import { DemandesDelegationComponent } from "./demandes-delegation.component";
import { DemandeDelegationComponent } from "./details-demande-delegation/demande-delegation.component"; import { DemandeDelegationComponent } from "./details-demande-delegation/demande-delegation.component";
import { KeycloakGuard } from '@shared/guards/keycloakguard'; import { AuthGuard } from 'app/auth/auth.guard';
import { paths_demandes_delegation } from "@shared/utils/paths"; import { paths_demandes_delegation } from "@shared/utils/paths";
@ -17,12 +17,12 @@ const routes: Routes = [
path:'', path:'',
component: DemandesDelegationComponent, component: DemandesDelegationComponent,
pathMatch: 'full', pathMatch: 'full',
canActivate: [KeycloakGuard] canActivate: [AuthGuard]
}, },
{ {
path: paths_demandes_delegation.get, path: paths_demandes_delegation.get,
component: DemandeDelegationComponent, component: DemandeDelegationComponent,
canActivate: [KeycloakGuard] canActivate: [AuthGuard]
} }
]; ];

@ -6,7 +6,7 @@ import { DemandesFormationComponent } from "./demandes-formation.component";
import { DemandeFormationComponent } from "./details-demande-formation/demande-formation.component"; import { DemandeFormationComponent } from "./details-demande-formation/demande-formation.component";
import { NewDemandeFormationComponent } from "./new-demande-formation/new-demande-formation.component"; import { NewDemandeFormationComponent } from "./new-demande-formation/new-demande-formation.component";
import { KeycloakGuard } from '@shared/guards/keycloakguard'; import { AuthGuard } from 'app/auth/auth.guard';
import { paths_demandes_formation } from "@shared/utils/paths"; import { paths_demandes_formation } from "@shared/utils/paths";
@ -14,9 +14,9 @@ import { paths_demandes_formation } from "@shared/utils/paths";
* Routes du module demandes formation * Routes du module demandes formation
*/ */
const routes: Routes = [ const routes: Routes = [
{ path:'', component: DemandesFormationComponent, pathMatch: 'full', canActivate: [KeycloakGuard] }, { path:'', component: DemandesFormationComponent, pathMatch: 'full', canActivate: [AuthGuard] },
{ path:paths_demandes_formation.new, component: NewDemandeFormationComponent, canActivate: [KeycloakGuard] }, { path:paths_demandes_formation.new, component: NewDemandeFormationComponent, canActivate: [AuthGuard] },
{ path:paths_demandes_formation.get, component: DemandeFormationComponent, canActivate: [KeycloakGuard] } { path:paths_demandes_formation.get, component: DemandeFormationComponent, canActivate: [AuthGuard] }
]; ];

@ -7,7 +7,7 @@ import { EpsSaisieComponent } from "./eps-saisie/eps-saisie.component";
import { EpaSaisieComponent } from "./epa-saisie/epa-saisie.component"; import { EpaSaisieComponent } from "./epa-saisie/epa-saisie.component";
import { EpaSixAnsSaisieComponent } from "./epa-six-ans-saisie/epa-six-ans-saisie.component"; import { EpaSixAnsSaisieComponent } from "./epa-six-ans-saisie/epa-six-ans-saisie.component";
import { KeycloakGuard } from '@shared/guards/keycloakguard'; import { AuthGuard } from 'app/auth/auth.guard';
import { paths_saisie_ep } from "@shared/utils/paths"; import { paths_saisie_ep } from "@shared/utils/paths";
@ -17,11 +17,11 @@ import { paths_saisie_ep } from "@shared/utils/paths";
const routes: Routes = [ const routes: Routes = [
{ path:'', { path:'',
component: EpSaisieComponent, component: EpSaisieComponent,
canActivate: [KeycloakGuard], canActivate: [AuthGuard],
children: [ children: [
{ path:paths_saisie_ep.epa, component: EpaSaisieComponent, canActivate: [KeycloakGuard] }, { path:paths_saisie_ep.epa, component: EpaSaisieComponent, canActivate: [AuthGuard] },
{ path:paths_saisie_ep.eps, component: EpsSaisieComponent, canActivate: [KeycloakGuard] }, { path:paths_saisie_ep.eps, component: EpsSaisieComponent, canActivate: [AuthGuard] },
{ path:paths_saisie_ep.epa6ans, component: EpaSixAnsSaisieComponent, canActivate: [KeycloakGuard] } { path:paths_saisie_ep.epa6ans, component: EpaSixAnsSaisieComponent, canActivate: [AuthGuard] }
] ]
} }
]; ];

@ -18,7 +18,7 @@ import { EpCommentaireAssistantComponent } from "./ep-commentaire-assistant/ep-c
import { EpCommentaireReferentComponent } from "./ep-commentaire-referent/ep-commentaire-referent.component"; import { EpCommentaireReferentComponent } from "./ep-commentaire-referent/ep-commentaire-referent.component";
import { NewParticipantComponent } from "./ep-participants/new-participant/new-participant.component"; import { NewParticipantComponent } from "./ep-participants/new-participant/new-participant.component";
import { KeycloakGuard } from '@shared/guards/keycloakguard'; import { AuthGuard } from 'app/auth/auth.guard';
import { paths_ep } from "@shared/utils/paths"; import { paths_ep } from "@shared/utils/paths";
/** /**
@ -28,31 +28,31 @@ const routes: Routes = [
{ {
path:'', path:'',
component: EpComponent, component: EpComponent,
canActivate: [KeycloakGuard] canActivate: [AuthGuard]
}, },
{ {
path:paths_ep.consultation, path:paths_ep.consultation,
component: EpConsultationComponent, component: EpConsultationComponent,
canActivate: [KeycloakGuard], canActivate: [AuthGuard],
children: [ children: [
{path:paths_ep.salaire, component: EpAugmentationSalaireComponent, canActivate: [KeycloakGuard]}, {path:paths_ep.salaire, component: EpAugmentationSalaireComponent, canActivate: [AuthGuard]},
{path:paths_ep.choixdate, component: EpChoixDateComponent, canActivate: [KeycloakGuard]}, {path:paths_ep.choixdate, component: EpChoixDateComponent, canActivate: [AuthGuard]},
{path:paths_ep.demandedelegation, component: EpDemandeDelegationComponent, canActivate: [KeycloakGuard]}, {path:paths_ep.demandedelegation, component: EpDemandeDelegationComponent, canActivate: [AuthGuard]},
{path:paths_ep.demandesformation, component: EpDemandesFormationComponent, canActivate: [KeycloakGuard]}, {path:paths_ep.demandesformation, component: EpDemandesFormationComponent, canActivate: [AuthGuard]},
{path:paths_ep.participants, component: EpParticipantsComponent, canActivate: [KeycloakGuard]}, {path:paths_ep.participants, component: EpParticipantsComponent, canActivate: [AuthGuard]},
{path:paths_ep.propositionsdates, component: EpPropositionsDatesComponent, canActivate: [KeycloakGuard]}, {path:paths_ep.propositionsdates, component: EpPropositionsDatesComponent, canActivate: [AuthGuard]},
{path:paths_ep.signature, component: EpSignatureComponent, canActivate: [KeycloakGuard]}, {path:paths_ep.signature, component: EpSignatureComponent, canActivate: [AuthGuard]},
{path:paths_ep.epa, component: EpaComponent, canActivate: [KeycloakGuard]}, {path:paths_ep.epa, component: EpaComponent, canActivate: [AuthGuard]},
{path:paths_ep.eps, component: EpsComponent, canActivate: [KeycloakGuard]}, {path:paths_ep.eps, component: EpsComponent, canActivate: [AuthGuard]},
{path:paths_ep.epa6ans, component: EpaSixAnsComponent, canActivate: [KeycloakGuard]}, {path:paths_ep.epa6ans, component: EpaSixAnsComponent, canActivate: [AuthGuard]},
{path:paths_ep.assistant, component: EpCommentaireAssistantComponent, canActivate: [KeycloakGuard]}, {path:paths_ep.assistant, component: EpCommentaireAssistantComponent, canActivate: [AuthGuard]},
{path:paths_ep.referent, component: EpCommentaireReferentComponent, canActivate: [KeycloakGuard]} {path:paths_ep.referent, component: EpCommentaireReferentComponent, canActivate: [AuthGuard]}
] ]
}, },
{ {
path:paths_ep.newparticipant, path:paths_ep.newparticipant,
component: NewParticipantComponent, component: NewParticipantComponent,
canActivate: [KeycloakGuard] canActivate: [AuthGuard]
}, },
]; ];

@ -7,17 +7,17 @@ import { FormationComponent } from "./details-formation/formation.component";
import { NewFormationComponent } from "./new-formation/new-formation.component"; import { NewFormationComponent } from "./new-formation/new-formation.component";
import { EditFormationComponent } from "./edit-formation/edit-formation.component"; import { EditFormationComponent } from "./edit-formation/edit-formation.component";
import { KeycloakGuard } from '@shared/guards/keycloakguard'; import { AuthGuard } from 'app/auth/auth.guard';
import { paths_formation } from "@shared/utils/paths"; import { paths_formation } from "@shared/utils/paths";
/** /**
* Routes du module formation * Routes du module formation
*/ */
const routes: Routes = [ const routes: Routes = [
{ path:'', component: FormationsComponent, pathMatch: 'full', canActivate: [KeycloakGuard] }, { path:'', component: FormationsComponent, pathMatch: 'full', canActivate: [AuthGuard] },
{ path:paths_formation.edit, component: EditFormationComponent, canActivate: [KeycloakGuard] }, { path:paths_formation.edit, component: EditFormationComponent, canActivate: [AuthGuard] },
{ path:paths_formation.new, component: NewFormationComponent, canActivate: [KeycloakGuard] }, { path:paths_formation.new, component: NewFormationComponent, canActivate: [AuthGuard] },
{ path:paths_formation.get, component: FormationComponent, canActivate: [KeycloakGuard] } { path:paths_formation.get, component: FormationComponent, canActivate: [AuthGuard] }
]; ];

@ -1,6 +1,5 @@
import { Component, OnInit, OnDestroy, ViewChild, ViewChildren, AfterViewInit } from '@angular/core'; import { Component, OnInit, OnDestroy, ViewChild, ViewChildren, AfterViewInit } from '@angular/core';
import { KeycloakService } from 'keycloak-angular';
import { Observable, Subscription } from 'rxjs'; import { Observable, Subscription } from 'rxjs';
import {MatTableDataSource} from '@angular/material/table'; import {MatTableDataSource} from '@angular/material/table';
@ -9,6 +8,7 @@ import {MatSort} from '@angular/material/sort';
import { EpInformationDTO, CollaborateurDTO } from "@shared/api-swagger/model/models"; import { EpInformationDTO, CollaborateurDTO } from "@shared/api-swagger/model/models";
import { EpService } from "@shared/api-swagger/api/api"; import { EpService } from "@shared/api-swagger/api/api";
import { AuthService } from 'app/auth/auth.service';
/** /**
@ -96,8 +96,7 @@ export class HomeAssistanteComponent implements OnInit, AfterViewInit {
*/ */
chargement = true; chargement = true;
constructor(public authService : AuthService, private service:EpService) {
constructor(public keycloakService : KeycloakService, private service:EpService) {
} }
/** /**
@ -113,6 +112,7 @@ export class HomeAssistanteComponent implements OnInit, AfterViewInit {
updateDataSource() { updateDataSource() {
this.epDisponiblesSubscription = this.service.getEPEnCours(this.asc, this.numPage, this.parPage, 1, this.choixBU, this.search, this.tri). this.epDisponiblesSubscription = this.service.getEPEnCours(this.asc, this.numPage, this.parPage, 1, this.choixBU, this.search, this.tri).
subscribe(eps => { subscribe(eps => {
this.dataSource = new MatTableDataSource(eps); this.dataSource = new MatTableDataSource(eps);
err => console.log(err); err => console.log(err);
}); });

@ -1,6 +1,6 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { KeycloakService } from 'keycloak-angular'; import { AuthService } from 'app/auth/auth.service';
import { Role } from '@shared/utils/roles'; import { Role } from '@shared/utils/roles';
@ -22,10 +22,9 @@ export class HomeComponent implements OnInit {
* Le rôle de l'utilisateur. * Le rôle de l'utilisateur.
*/ */
userRole : string; userRole : string;
constructor(private keycloakService : KeycloakService) {
let clientId = environment.keycloakConfig.clientId; constructor(private authService: AuthService) {
//récupérer les informations Keycloak de l'utilisateur this.userRole = authService.firstRole;
this.userRole = this.keycloakService.getKeycloakInstance().resourceAccess[clientId]["roles"][0];
} }
ngOnInit() { ngOnInit() {

@ -7,7 +7,7 @@ import { DetailsReferentComponent } from "./details-referent/details-referent.co
import { paths_referents } from "@shared/utils/paths"; import { paths_referents } from "@shared/utils/paths";
import { KeycloakGuard } from '@shared/guards/keycloakguard'; import { AuthGuard } from 'app/auth/auth.guard';
/** /**
* Routes du module référents * Routes du module référents
@ -17,12 +17,12 @@ const routes: Routes = [
path:'', path:'',
component: ReferentsComponent, component: ReferentsComponent,
pathMatch: 'full', pathMatch: 'full',
canActivate: [KeycloakGuard] canActivate: [AuthGuard]
}, },
{ {
path:paths_referents.get, path:paths_referents.get,
component: DetailsReferentComponent, component: DetailsReferentComponent,
canActivate: [KeycloakGuard] canActivate: [AuthGuard]
} }
]; ];

@ -30,7 +30,7 @@ import { Configuration } from '../configurat
@Injectable() @Injectable()
export class EpService { export class EpService {
protected basePath = 'http://localhost:3000/api'; protected basePath = 'https://localhost:44393/api';
public defaultHeaders = new HttpHeaders(); public defaultHeaders = new HttpHeaders();
public configuration = new Configuration(); public configuration = new Configuration();

@ -1,40 +0,0 @@
import { Injectable } from '@angular/core';
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { KeycloakService, KeycloakAuthGuard } from 'keycloak-angular';
/**
* KeycloakGuard est la classe qui va gérer l'authentification et les droits d'accès en fonction des rôles Keycloak.
*/
@Injectable()
export class KeycloakGuard extends KeycloakAuthGuard {
constructor(protected router: Router, protected keycloakAngular: KeycloakService) {
super(router, keycloakAngular);
}
isAccessAllowed(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
return new Promise(async (resolve, reject) => {
if (!this.authenticated) {
this.keycloakAngular.login();
return;
}
const requiredRoles = route.data.roles;
if (!requiredRoles || requiredRoles.length === 0) {
return resolve(true);
} else {
if (!this.roles || this.roles.length === 0) {
resolve(false);
}
let granted: boolean = false;
for (const requiredRole of requiredRoles) {
if (this.roles.indexOf(requiredRole) > -1) {
granted = true;
break;
}
}
resolve(granted);
}
});
}
}

@ -1,5 +1,5 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { KeycloakService } from 'keycloak-angular'; import { AuthService } from 'app/auth/auth.service';
import { Role } from '@shared/utils/roles'; import { Role } from '@shared/utils/roles';
@ -27,17 +27,16 @@ export class NavMenuComponent {
* Les informations (nom+prénom) de l'utilisateur. * Les informations (nom+prénom) de l'utilisateur.
*/ */
userInfo : string; userInfo : string;
constructor(private keycloakService : KeycloakService){
let clientId = environment.keycloakConfig.clientId; constructor(private authService : AuthService){
this.userRole = this.keycloakService.getKeycloakInstance().resourceAccess[clientId]["roles"][0]; this.userRole = authService.firstRole;
let profil = keycloakService.getKeycloakInstance().profile; this.userInfo = authService.userInfo;
this.userInfo = profil.firstName+" "+profil.lastName;
} }
//isExpanded = false; //isExpanded = false;
async logout() { logout() {
await this.keycloakService.logout(); this.authService.logout();
} }
} }

@ -2,13 +2,40 @@
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`. // The list of file replacements can be found in `angular.json`.
import { KeycloakConfig } from 'keycloak-angular'; import { AuthConfig } from 'angular-oauth2-oidc';
// Add here your keycloak setup infos const keycloakConfig: AuthConfig = {
const keycloakConfig: KeycloakConfig = { // Url of the Identity Provider
url: 'http://localhost:8080/auth', issuer: 'http://localhost:8080/auth/realms/Apside',
realm: 'apside',
clientId: 'gestionepa' // URL of the SPA to redirect the user to after login
redirectUri: 'http://localhost:4200',
// The SPA's id. The SPA is registerd with this id at the auth-server
clientId: 'GestionEPA',
// Just needed if your auth server demands a secret. In general, this
// is a sign that the auth server is not configured with SPAs in mind
// and it might not enforce further best practices vital for security
// such applications.
//dummyClientSecret: 'f27746f4-e603-441e-a256-3ddd5b19ba54',
dummyClientSecret: '82702d7b-e44b-4415-9c80-54774a58e1dc',
responseType: 'code',
silentRefreshRedirectUri: window.location.origin + '/silent-refresh.html' ,
// set the scope for the permissions the client should request
// The first four are defined by OIDC.
// Important: Request offline_access to get a refresh token
// The api scope is a usecase specific one
scope: 'openid profile email',
showDebugInformation: true,
useSilentRefresh: true, // Needed for Code Flow to suggest using iframe-based refreshes
silentRefreshTimeout: 5000, // For faster testing
timeoutFactor: 0.25, // For faster testing
sessionChecksEnabled: true,
clearHashAfterLogin: false, // https://github.com/manfredsteyer/angular-oauth2-oidc/issues/457#issuecomment-431807040,
nonceStateSeparator : 'semicolon' // Real semicolon gets mangled by IdentityServer's URI encoding
}; };
export const environment = { export const environment = {

@ -0,0 +1,31 @@
<html>
<body>
<script>
// Based on: https://manfredsteyer.github.io/angular-oauth2-oidc/docs/additional-documentation/silent-refresh.html
const checks = [/[\?|&|#]code=/, /[\?|&|#]error=/, /[\?|&|#]token=/, /[\?|&|#]id_token=/];
function isResponse(str) {
let count = 0;
if (!str) {
return false;
}
for (let i = 0; i < checks.length; i++) {
if (str.match(checks[i])) return true;
}
return false;
}
let message = isResponse(location.hash) ? location.hash : '#' + location.search;
console.log("Rafraîchissement silencieux de l'iframe affichée dans l'application parente, message: ", message);
(window.opener || window.parent).postMessage(message, location.origin);
</script>
</body>
</html>
Loading…
Cancel
Save