From aa847ee3cdddff15d760121ebf5c11c93fab24c3 Mon Sep 17 00:00:00 2001 From: "florian.azizian" <florian.azizian@maarch.org> Date: Wed, 2 Dec 2020 23:52:54 +0100 Subject: [PATCH] FEAT #14566 TIME 6 INCA + Kerberos authentication --- lang/en.json | 4 +++ lang/fr.json | 4 +++ sql/2010.sql | 3 ++ sql/structure.sql | 1 + .../controllers/ConfigurationController.php | 13 +++++--- .../controllers/AuthenticationController.php | 30 +++++++++++++++---- .../connection/connection.component.html | 5 ++-- .../connection/connection.component.ts | 5 ++-- src/frontend/app/login/login.component.html | 4 +++ src/frontend/app/login/login.component.ts | 9 ++++++ src/frontend/app/service/auth.guard.ts | 6 ++++ .../app/sidebar/sidebar.component.html | 2 +- 12 files changed, 71 insertions(+), 15 deletions(-) diff --git a/lang/en.json b/lang/en.json index 195af096cd..60169e99bf 100755 --- a/lang/en.json +++ b/lang/en.json @@ -191,6 +191,8 @@ "yes": "Yes", "no": "No", "ldapEnabled": "LDAP enabled", + "kerberosEnabled": "Kerberos enabled", + "x509Enabled": "Certificate x509 enbaled", "userCreation": "Add a user", "manage_ldap_configurations": "Ldap", "manage_ldap_configurationsDesc": "Add / Update / Delete ldap entries in order to authenticate users.", @@ -211,6 +213,8 @@ "manage_connectionsDesc": "Manage all available connections modes in application.", "defaultConnection": "Default", "ldapConnection": "Ldap", + "kerberosConnection" : "Kerberos", + "x509Connection" : "Certificate x509", "connectionMode": "Connection mode", "connectionModeUpdated": "Connection mode updated", "changeConnectionWarn": "This cause a change in login mechanics for users !", diff --git a/lang/fr.json b/lang/fr.json index f58f603500..8230de8272 100755 --- a/lang/fr.json +++ b/lang/fr.json @@ -191,6 +191,8 @@ "yes": "Oui", "no": "Non", "ldapEnabled": "LDAP activé", + "kerberosEnabled": "Kerberos activé", + "x509Enabled": "Certificat x509 activé", "userCreation": "Ajouter un utilisateur", "manage_ldap_configurations": "Annuaires ldap", "manage_ldap_configurationsAlt": "Annuaire(s) ldap", @@ -212,6 +214,8 @@ "manage_connectionsDesc" : "Administrer les différents modes de connexion disponibles de l'application.", "defaultConnection" : "Standard", "ldapConnection" : "Ldap", + "kerberosConnection" : "Kerberos", + "x509Connection" : "Certificat x509", "connectionMode" : "Mode de connexion", "connectionModeUpdated" : "Mode de connexion mis à jour", "changeConnectionWarn" : "Cela modifie le mode de connexion à l'application !", diff --git a/sql/2010.sql b/sql/2010.sql index a69d8c9178..619d079072 100755 --- a/sql/2010.sql +++ b/sql/2010.sql @@ -54,6 +54,9 @@ END$$; ALTER TABLE users DROP COLUMN IF EXISTS signature_modes; ALTER TABLE users ADD COLUMN signature_modes jsonb DEFAULT '["stamp"]'; +ALTER TABLE users DROP COLUMN IF EXISTS x509_fingerprint; +ALTER TABLE users ADD COLUMN x509_fingerprint text; + ALTER TABLE workflows DROP COLUMN IF EXISTS signature_positions; ALTER TABLE workflows ADD COLUMN signature_positions jsonb DEFAULT '[]'; diff --git a/sql/structure.sql b/sql/structure.sql index c0974338ca..4cb13347c5 100755 --- a/sql/structure.sql +++ b/sql/structure.sql @@ -213,6 +213,7 @@ CREATE TABLE users failed_authentication INTEGER DEFAULT 0, locked_until TIMESTAMP without time zone, signature_modes jsonb DEFAULT '["stamp"]', + x509_fingerprint text, CONSTRAINT users_pkey PRIMARY KEY (id), CONSTRAINT users_login_key UNIQUE (login) ) diff --git a/src/app/configuration/controllers/ConfigurationController.php b/src/app/configuration/controllers/ConfigurationController.php index 2076b45f5c..4633b741d6 100755 --- a/src/app/configuration/controllers/ConfigurationController.php +++ b/src/app/configuration/controllers/ConfigurationController.php @@ -24,7 +24,7 @@ use SrcCore\models\AuthenticationModel; class ConfigurationController { - const CONNECTION_MODES = ['default', 'ldap']; + const CONNECTION_MODES = ['default', 'ldap', 'kerberos', 'x509']; public function get(Request $request, Response $response) { @@ -45,10 +45,15 @@ class ConfigurationController $configurations = ConfigurationModel::getByIdentifier(['identifier' => $queryParams['identifier'], 'select' => $select]); if ($queryParams['identifier'] == 'connection') { - $ldapConfigurations = ConfigurationModel::getByIdentifier(['identifier' => 'ldapServer', 'select' => [1]]); - $configurations = $configurations[0]; + $ldapConfigurations = ConfigurationModel::getByIdentifier(['identifier' => 'ldapServer', 'select' => [1]]); + $configurations = $configurations[0]; $configurations['value'] = json_decode($configurations['value']); - $configurations['availableConnections'] = [['id' => 'default', 'allowed' => true], ['id' => 'ldap', 'allowed' => !empty($ldapConfigurations)]]; + $configurations['availableConnections'] = [ + ['id' => 'default', 'allowed' => true], + ['id' => 'kerberos', 'allowed' => true], + ['id' => 'x509', 'allowed' => true], + ['id' => 'ldap', 'allowed' => !empty($ldapConfigurations)] + ]; } return $response->withJson(['configurations' => $configurations]); diff --git a/src/core/controllers/AuthenticationController.php b/src/core/controllers/AuthenticationController.php index 84c4b7c835..46b3017b61 100755 --- a/src/core/controllers/AuthenticationController.php +++ b/src/core/controllers/AuthenticationController.php @@ -88,14 +88,15 @@ class AuthenticationController { $body = $request->getParsedBody(); - $check = Validator::stringType()->notEmpty()->validate($body['login']); - $check = $check && Validator::stringType()->notEmpty()->validate($body['password']); - if (!$check) { - return $response->withStatus(400)->withJson(['errors' => 'Bad Request']); + $connection = ConfigurationModel::getConnection(); + if (in_array($connection, ['default', 'ldap'])) { + if (!Validator::stringType()->notEmpty()->validate($body['login']) || !Validator::stringType()->notEmpty()->validate($body['password'])) { + return $response->withStatus(400)->withJson(['errors' => 'Bad Request']); + } } $login = strtolower($body['login']); - $connection = ConfigurationModel::getConnection(); + if ($connection == 'ldap') { $ldapConfigurations = ConfigurationModel::getByIdentifier(['identifier' => 'ldapServer', 'select' => ['value']]); if (empty($ldapConfigurations)) { @@ -131,6 +132,25 @@ class AuthenticationController if (empty($authenticated) && !empty($error) && $error != 'Invalid credentials') { return $response->withStatus(400)->withJson(['errors' => $error]); } + } elseif ($connection == 'x509') { + if (!empty($_SERVER['SSL_CLIENT_CERT'])) { + $x509Fingerprint = openssl_x509_fingerprint($_SERVER["SSL_CLIENT_CERT"]); + $user = UserModel::get(['select' => ['login'], 'where' => ['x509_fingerprint = ?'], 'data' => [strtoupper(wordwrap($x509Fingerprint, 2, " ", true))]]); + if (empty($user)) { + return $response->withStatus(401)->withJson(['errors' => 'Authentication unauthorized']); + } + $login = $user[0]['login']; + } else { + return $response->withStatus(401)->withJson(['errors' => 'No certificate detected']); + } + $authenticated = true; + } elseif ($connection == 'kerberos') { + if (!empty($_SERVER['REMOTE_USER']) && $_SERVER['AUTH_TYPE'] == 'Negotiate') { + $login = strtolower($_SERVER['REMOTE_USER']); + } else { + return $response->withStatus(401)->withJson(['errors' => 'No identifier detected for kerberos']); + } + $authenticated = true; } else { $authenticated = AuthenticationModel::authentication(['login' => $login, 'password' => $body['password']]); } diff --git a/src/frontend/app/administration/connection/connection.component.html b/src/frontend/app/administration/connection/connection.component.html index 060a961b80..850c2668ec 100644 --- a/src/frontend/app/administration/connection/connection.component.html +++ b/src/frontend/app/administration/connection/connection.component.html @@ -9,8 +9,9 @@ <ion-content #mainContent> <ion-item> <ion-label color="secondary">{{'lang.connectionMode' | translate}}</ion-label> - <ion-select *ngIf="currentConnection" [value]="currentConnection.id" cancelText="Annuler"> - <ion-select-option *ngFor="let connection of connectionList" [value]="connection.id"> + <ion-select *ngIf="currentConnection" [value]="currentConnection.id" cancelText="{{'lang.cancel' | translate}}" (ionChange)="changeConnection($event.detail.value)"> + <ion-select-option *ngFor="let connection of connectionList" [value]="connection.id" + [disabled]="!connection.allowed"> {{'lang.' + connection.id + 'Connection' | translate}}</ion-select-option> </ion-select> </ion-item> diff --git a/src/frontend/app/administration/connection/connection.component.ts b/src/frontend/app/administration/connection/connection.component.ts index e8cb938cc8..2fac384a65 100644 --- a/src/frontend/app/administration/connection/connection.component.ts +++ b/src/frontend/app/administration/connection/connection.component.ts @@ -77,14 +77,13 @@ export class ConnectionComponent implements OnInit { dialogRef.afterClosed().subscribe(result => { if (result === 'yes') { this.loading = true; - this.http.patch('../rest/configurations/' + this.id, { label: this.label, value: connection.id }) + this.http.patch('../rest/configurations/' + this.id, { label: this.label, value: connection }) .pipe( finalize(() => this.loading = false) ) .subscribe({ next: () => { - this.authService.authMode = connection.id; - this.currentConnection = connection; + this.authService.authMode = connection; this.notificationService.success('lang.connectionModeUpdated'); }, }); diff --git a/src/frontend/app/login/login.component.html b/src/frontend/app/login/login.component.html index 812c73c9ff..059b878088 100644 --- a/src/frontend/app/login/login.component.html +++ b/src/frontend/app/login/login.component.html @@ -15,6 +15,10 @@ class="forgot-password">{{'lang.forgotPassword' | translate}}</a> <a href="" *ngIf="authService.authMode === 'ldap'" class="forgot-password">{{'lang.ldapEnabled' | translate}}</a> + <a href="" *ngIf="authService.authMode === 'kerberos'" + class="forgot-password">{{'lang.kerberosEnabled' | translate}}</a> + <a href="" *ngIf="authService.authMode === 'x509'" + class="forgot-password">{{'lang.x509Enabled' | translate}}</a> <ion-button type="submit" expand="block" [disabled]="loginForm.invalid || loading">{{'lang.connect' | translate}} </ion-button> </form> diff --git a/src/frontend/app/login/login.component.ts b/src/frontend/app/login/login.component.ts index 04d80d673b..efc1a8650f 100644 --- a/src/frontend/app/login/login.component.ts +++ b/src/frontend/app/login/login.component.ts @@ -58,6 +58,7 @@ export class LoginComponent implements OnInit, AfterViewInit { setTimeout(() => { this.showForm = true; this.fixAutoFill(); + this.initConnection(); }, 500); } @@ -100,4 +101,12 @@ export class LoginComponent implements OnInit, AfterViewInit { ) .subscribe(); } + + initConnection() { + if (['kerberos', 'x509'].indexOf(this.authService.authMode) > -1) { + this.loginForm.disable(); + this.loginForm.setValidators(null); + this.onSubmit(); + } + } } diff --git a/src/frontend/app/service/auth.guard.ts b/src/frontend/app/service/auth.guard.ts index 29d692715d..820b09254d 100644 --- a/src/frontend/app/service/auth.guard.ts +++ b/src/frontend/app/service/auth.guard.ts @@ -34,6 +34,12 @@ export class AuthGuard implements CanActivate { this.router.navigate(['/home']); return false; } else { + this.http.get('../rest/authenticationInformations').pipe( + map((data: any) => { + this.authService.authMode = data.connection; + this.authService.changeKey = data.changeKey; + }) + ). subscribe(); return true; } } else { diff --git a/src/frontend/app/sidebar/sidebar.component.html b/src/frontend/app/sidebar/sidebar.component.html index a86094a17d..d64fac48a2 100755 --- a/src/frontend/app/sidebar/sidebar.component.html +++ b/src/frontend/app/sidebar/sidebar.component.html @@ -10,7 +10,7 @@ <ion-header [translucent]="true"> <ion-toolbar color="primary"> <ion-buttons slot="start"> - <ion-button (click)="authService.logout()" [title]="'lang.logout' | translate"> + <ion-button (click)="authService.logout()" [title]="'lang.logout' | translate" *ngIf="authService.authMode !== 'x509' && authService.authMode !== 'kerberos'"> <ion-icon slot="icon-only" name="power"></ion-icon> </ion-button> <ion-button *ngIf="!isAdminRoute() && authService.user.administrativePrivileges.length > 0" (click)="openAdmin()" [title]="'lang.administration' | translate"> -- GitLab