Yanaël GRETTE 4 years ago
commit 1692adaf42
  1. 3
      angular.json
  2. 4
      src/app/app-routing.module.ts
  3. 2
      src/app/app.module.ts
  4. 28
      src/app/auth/auth.guard.ts
  5. 13
      src/app/collaborateurs/collaborateurs.routing.module.ts
  6. 2
      src/app/demandes-delegation/demandes-delegation.routing.module.ts
  7. 2
      src/app/demandes-formation/demandes-formation.routing.module.ts
  8. 2
      src/app/ep-saisie/ep-saisie.routing.module.ts
  9. 2
      src/app/ep/ep.routing.module.ts
  10. 2
      src/app/formations/formations.routing.module.ts
  11. 4
      src/app/home/home-assistante/home-assistante.component.ts
  12. 2
      src/app/home/home.component.ts
  13. 2
      src/app/referents/referents.routing.module.ts
  14. 0
      src/app/shared/auth/auth-config.ts
  15. 0
      src/app/shared/auth/auth-module-config.ts
  16. 45
      src/app/shared/auth/auth.guard.ts
  17. 10
      src/app/shared/auth/auth.module.ts
  18. 0
      src/app/shared/auth/auth.service.spec.ts
  19. 93
      src/app/shared/auth/auth.service.ts
  20. 2
      src/app/shared/nav-menu/nav-menu.component.ts
  21. 14
      src/environments/environment.ts
  22. 31
      src/silent-refresh.html

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

@ -5,11 +5,12 @@ import { Routes } from '@angular/router';
import { HomeComponent } from './home/';
import { AuthGuard } from './auth/auth.guard';
import { AuthGuard } from '@shared/auth/auth.guard';
import { paths_collaborateurs, paths_demandes_delegation, paths_demandes_formation,
paths_ep, paths_saisie_ep, paths_formation, paths_home, paths_referents } from '@shared/utils/paths';
import { Role } from '@shared/utils/roles';
/**
* L'ensemble des routes du client Angular
@ -26,6 +27,7 @@ const routes: Routes = [
path: paths_home.path,
component: HomeComponent,
canActivate: [AuthGuard],
data: { roles: [Role.assistante, Role.commercial, Role.rh, Role.collaborateur] },
pathMatch: 'full'
},
//chargement des chemins du module collaborateur à partir du routing de ce module

@ -22,7 +22,7 @@ import { DemandesFormationModule } from './demandes-formation';
import { DemandesDelegationModule } from './demandes-delegation';
import { EpSaisieModule } from "./ep-saisie";
import { EpModule } from "./ep"
import { AuthModule } from './auth/auth.module';
import { AuthModule } from '@shared/auth/auth.module';

@ -1,28 +0,0 @@
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();
}
}
}

@ -9,12 +9,21 @@ import { EditEvaluationComponent } from './formations-collaborateur/edit-evaluat
import { paths_collaborateurs } from '@shared/utils/paths';
import { AuthGuard } from 'app/auth/auth.guard';
import { AuthGuard } from '@shared/auth/auth.guard';
import { Role } from '@shared/utils/roles';
/**
* Routes du module collaborateur
*/
const routes: Routes = [
{ path:'', component: CollaborateursComponent, pathMatch: 'full', canActivate: [AuthGuard] },
{
path:'',
component: CollaborateursComponent,
canActivate: [AuthGuard],
data: { roles: [Role.assistante, Role.commercial, Role.rh] },
pathMatch: 'full'
},
{ path:paths_collaborateurs.formations, component: FormationsCollaboateurComponent, canActivate: [AuthGuard] },
{ path:paths_collaborateurs.evaluation, component: EvaluationComponent, canActivate: [AuthGuard] },
{ path:paths_collaborateurs.edit, component: EditEvaluationComponent, canActivate: [AuthGuard] },

@ -5,7 +5,7 @@ import { Routes, RouterModule } from '@angular/router';
import { DemandesDelegationComponent } from "./demandes-delegation.component";
import { DemandeDelegationComponent } from "./details-demande-delegation/demande-delegation.component";
import { AuthGuard } from 'app/auth/auth.guard';
import { AuthGuard } from '@shared/auth/auth.guard';
import { paths_demandes_delegation } from "@shared/utils/paths";

@ -6,7 +6,7 @@ import { DemandesFormationComponent } from "./demandes-formation.component";
import { DemandeFormationComponent } from "./details-demande-formation/demande-formation.component";
import { NewDemandeFormationComponent } from "./new-demande-formation/new-demande-formation.component";
import { AuthGuard } from 'app/auth/auth.guard';
import { AuthGuard } from '@shared/auth/auth.guard';
import { paths_demandes_formation } from "@shared/utils/paths";

@ -7,7 +7,7 @@ import { EpsSaisieComponent } from "./eps-saisie/eps-saisie.component";
import { EpaSaisieComponent } from "./epa-saisie/epa-saisie.component";
import { EpaSixAnsSaisieComponent } from "./epa-six-ans-saisie/epa-six-ans-saisie.component";
import { AuthGuard } from 'app/auth/auth.guard';
import { AuthGuard } from '@shared/auth/auth.guard';
import { paths_saisie_ep } from "@shared/utils/paths";

@ -18,7 +18,7 @@ import { EpCommentaireAssistantComponent } from "./ep-commentaire-assistant/ep-c
import { EpCommentaireReferentComponent } from "./ep-commentaire-referent/ep-commentaire-referent.component";
import { NewParticipantComponent } from "./ep-participants/new-participant/new-participant.component";
import { AuthGuard } from 'app/auth/auth.guard';
import { AuthGuard } from '@shared/auth/auth.guard';
import { paths_ep } from "@shared/utils/paths";
/**

@ -7,7 +7,7 @@ import { FormationComponent } from "./details-formation/formation.component";
import { NewFormationComponent } from "./new-formation/new-formation.component";
import { EditFormationComponent } from "./edit-formation/edit-formation.component";
import { AuthGuard } from 'app/auth/auth.guard';
import { AuthGuard } from '@shared/auth/auth.guard';
import { paths_formation } from "@shared/utils/paths";
/**

@ -8,7 +8,7 @@ import {MatSort} from '@angular/material/sort';
import { EpInformationDTO, CollaborateurDTO } from "@shared/api-swagger/model/models";
import { EpService } from "@shared/api-swagger/api/api";
import { AuthService } from 'app/auth/auth.service';
import { AuthService } from '@shared/auth/auth.service';
/**
@ -96,7 +96,7 @@ export class HomeAssistanteComponent implements OnInit, AfterViewInit {
*/
chargement = true;
constructor(public authService : AuthService, private service:EpService) {
constructor(private service:EpService) {
}
/**

@ -1,6 +1,6 @@
import { Component, OnInit } from '@angular/core';
import { AuthService } from 'app/auth/auth.service';
import { AuthService } from '@shared/auth/auth.service';
import { Role } from '@shared/utils/roles';

@ -7,7 +7,7 @@ import { DetailsReferentComponent } from "./details-referent/details-referent.co
import { paths_referents } from "@shared/utils/paths";
import { AuthGuard } from 'app/auth/auth.guard';
import { AuthGuard } from '@shared/auth/auth.guard';
/**
* Routes du module référents

@ -0,0 +1,45 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot, Router } 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, private router: Router) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
return this.authService.canActivateProtectedRoutes$.pipe(tap(isLoggin => this.login(isLoggin,route)));
}
/**
* Vérifie si l'utilisateur est connecté et si l'utilisateur est autorisé à accéder à la route.
* @param isLoggin Booléen permettant de savoir si l'utilisateur est connecté ou non
* @param route Route à laquelle l'utilisateur souhaite accéder
*/
private login(isLoggin: boolean, route: ActivatedRouteSnapshot): boolean {
if (!isLoggin) {
this.authService.login();
}
const firstRole = this.authService.firstRole;
if (firstRole) {
// Vérifie si la route est restreinte par des rôles
if (route.data.roles && route.data.roles.indexOf(firstRole) === -1) {
// l'utisateur n'est pas autorisé alors on le redirige vers la page d'accueil
this.router.navigate(['/']);
return false;
}
// L'utilisateur est autorisé
return true;
}
}
}

@ -7,15 +7,15 @@ 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)
* Utilisation du localstorage pour stocker le token
* Voir: https://manfredsteyer.github.io/angular-oauth2-oidc/docs/additional-documentation/configure-custom-oauthstorage.html
*/
export function storageFactory(): OAuthStorage {
return localStorage;
}
/**
* Exécute la méthode qui permet d'afficher la page de connexion.
* Fonction permettant d'appeler la méthode qui va démarrer la séquence de connexion initiale
* @param authService Service d'authentification
*/
export function init_app(authService: AuthService) {
@ -44,8 +44,8 @@ export class AuthModule {
{ 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
provide: APP_INITIALIZER, // Gestion de l'authentification au démarrage de l'application
useFactory: init_app,
deps: [ AuthService ],
multi: true
}

@ -70,7 +70,7 @@ export class AuthService {
return;
}
console.warn("Changements remarqués dans l'access_token (très probablement depuis un autre onglet). Mise à jour de l'observable isAuthenticated.") ;
//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
@ -103,78 +103,29 @@ export class AuthService {
* 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())
// Charge la configuration de Keycloak et tente de se connecter
return this.oauthService.loadDiscoveryDocumentAndTryLogin()
.then(() => {
// Si l'acces token est valide alors tout est ok
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);
});
})
// Renvoie l'utilisateur vers la page de connexion
this.navigateToLoginPage();
// Afficher un mesage d'avertissement:
//console.warn("Une interaction de l'utilisateur est nécessaire pour se connecter, l'utilisateur doit se connecter manuellement.");
return Promise.reject();
})
.then(() => {
// Met à jour l'observable isDoneLoadingSubject
// Met à jour de 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
.catch(() => this.isDoneLoadingSubject$.next(true)); // Met à jour l'observable isDoneLoadingSubject même si il y a une erreur
}
/**
@ -182,9 +133,7 @@ export class AuthService {
* @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);
this.oauthService.initCodeFlow(targetUrl || this.router.url);
}
/**
@ -207,7 +156,7 @@ export class AuthService {
*/
public get firstRole() {
if(this.identityClaims != null)
return this.oauthService.getIdentityClaims()['roles'][0];
return this.identityClaims['roles'][0];
else
return;
}
@ -217,7 +166,7 @@ export class AuthService {
*/
public get roles() {
if(this.identityClaims != null)
return this.oauthService.getIdentityClaims()['roles'];
return this.identityClaims['roles'];
else
return;
}
@ -227,14 +176,14 @@ export class AuthService {
*/
public get userInfo() {
if(this.identityClaims != null)
return this.oauthService.getIdentityClaims()['name'];
return this.identityClaims['name'];
else
return;
}
// Normalement, un service comme celui-ci ne les expose pas,
// mais pour le débogage, c'est logique.
// Normalement, ce genre de service ne les expose pas,
// mais pour le débogage, ça peut-être utile.
/**
* Access_token actuel.

@ -1,5 +1,5 @@
import { Component } from '@angular/core';
import { AuthService } from 'app/auth/auth.service';
import { AuthService } from '@shared/auth/auth.service';
import { Role } from '@shared/utils/roles';

@ -21,21 +21,19 @@ const keycloakConfig: AuthConfig = {
//dummyClientSecret: 'f27746f4-e603-441e-a256-3ddd5b19ba54',
dummyClientSecret: '201a3d53-7cd5-4613-bcb6-f2e98c1ba2ec',
responseType: 'code',
silentRefreshRedirectUri: window.location.origin + '/silent-refresh.html' ,
// To configure your solution for code flow + PKCE you have to set the responseType to code
responseType: 'code',
// 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
useSilentRefresh: true, // Activate silent refresh for code flow
silentRefreshTimeout: 5000,
timeoutFactor: 0.25,
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
nonceStateSeparator : 'semicolon' // Real semicolon gets mangled by Keycloak's URI encoding
};
export const environment = {

@ -1,31 +0,0 @@
<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