integration hydra apps

This commit is contained in:
Arnaud Fornerot 2022-08-27 18:24:56 +02:00
parent 21fb28a6f0
commit c3328a1ba0
13 changed files with 329 additions and 8 deletions

8
.env
View File

@ -118,7 +118,7 @@ SONDE_URL=
# Mercure
MERCURE_URL=https://127.0.0.1/.well-known/mercure
MERCURE_PUBLIC_URL=https://127.0.0.1/.well-known/mercure
MERCURE_JWT_SECRET="!ChangeMe!"
MERCURE_JWT_SECRET="!changeme!changeme!changeme!changeme!changeme!changeme!"
# Minio
MINIO_URL=http://127.0.0.1:9000
@ -129,6 +129,12 @@ MINIO_ROOT=
MINIO_PATH_STYLE=1
MINIO_SECURE=0
# Hydra apps
HYDRA_LOGINCHALLENGE="http://127.0.0.1:4445/oauth2/auth/requests/login?login_challenge="
HYDRA_LOGINCHALLENGEACCEPT="http://127.0.0.1:4445/oauth2/auth/requests/login/accept?login_challenge="
HYDRA_CONSENTCHALLENGE="http://127.0.0.1:4445/oauth2/auth/requests/consent?consent_challenge="
HYDRA_CONSENTCHALLENGEACCEPT="http://127.0.0.1:4445/oauth2/auth/requests/consent/accept?consent_challenge="
# Lock
LOCK_DSN="postgresql://symfony:ChangeMe@127.0.0.1:5432/app?serverVersion=13&charset=utf8"

View File

@ -185,6 +185,10 @@ app_user_minio_document:
controller: App\Controller\MinioController::document
#-- Access public
app_minio_logo:
path: /minio/logo
controller: App\Controller\MinioController::logo
app_minio_image:
path: /minio/image
controller: App\Controller\MinioController::image
@ -193,6 +197,28 @@ app_minio_document:
path: /minio/document
controller: App\Controller\MinioController::document
#== Hydra =======================================================================================================
app_hydra_loginsql:
path: /hydra/loginsql
controller: App\Controller\HydraController::loginsql
app_hydra_checkloginsql:
path: /hydra/checkloginsql
controller: App\Controller\HydraController::checkloginsql
app_hydra_loginldap:
path: /hydra/loginldap
controller: App\Controller\HydraController::loginldap
app_hydra_checkloginldap:
path: /hydra/checkloginldap
controller: App\Controller\HydraController::checkloginldap
app_hydra_consent:
path: /hydra/consent
controller: App\Controller\HydraController::consent
#== Ckeditor ====================================================================================================
app_ckeditor_upload:
path: /user/upload

View File

@ -100,6 +100,11 @@ parameters:
minioPathstyle: '%env(resolve:MINIO_PATH_STYLE)%'
minioSecure: '%env(resolve:MINIO_SECURE)%'
hydraLoginchallenge: '%env(resolve:HYDRA_LOGINCHALLENGE)%'
hydraLoginchallengeaccept: '%env(resolve:HYDRA_LOGINCHALLENGEACCEPT)%'
hydraConsentchallenge: '%env(resolve:HYDRA_CONSENTCHALLENGE)%'
hydraConsentchallengeaccept: '%env(resolve:HYDRA_CONSENTCHALLENGEACCEPT)%'
sondeUse: '%env(resolve:SONDE_USE)%'
sondeUrl: '%env(resolve:SONDE_URL)%'

View File

@ -19,5 +19,44 @@ hydra:
email_verified:
- consent.session.id_token.email_verified
- id: ninesql
title:
fr: NINE SQL
en: NINE SQL
description:
fr: Authentification via NINESQL
en: Authentication by NINESQL
icon_url: https://127.0.0.1:8000/minio/logo
login_url: http://127.0.0.1:8000/hydra/loginsql
consent_url: http://127.0.0.1:8000/hydra/consent
logout_url: http://127.0.0.1:8000/hydra/logoutsql
attributes_rewrite_rules:
username:
- consent.session.id_token.username
email:
- consent.session.id_token.email
firstname:
- consent.session.id_token.firstname
lastname:
- consent.session.id_token.lastname
- id: nineldap
title:
fr: NINE LDAP
en: NINE LDAP
description:
fr: Authentification via NINELDAP
en: Authentication by NINELDAP
icon_url: https://127.0.0.1:8000/minio/logo
login_url: http://127.0.0.1:8000/hydra/loginldap
consent_url: http://127.0.0.1:8000/hydra/consent
logout_url: http://127.0.0.1:8000/hydra/logoutldap
attributes_rewrite_rules:
username:
- consent.session.id_token.username
email:
- consent.session.id_token.email
firstname:
- consent.session.id_token.firstname
lastname:
- consent.session.id_token.lastname

View File

@ -1,5 +1,5 @@
body > section {
background-color: rgba(214, 170, 214, 0.575);
}
input[type="radio"]:checked ~ .app-item {

View File

@ -14,6 +14,6 @@
"response_types": [
"code"
],
"logo_uri": "https://upload.wikimedia.org/wikipedia/commons/e/e1/Password.svg",
"logo_uri": "https://127.0.0.1:8000/minio/logo",
"scope": "openid"
}

View File

@ -49,8 +49,8 @@ services:
- "80"
environment:
SERVER_NAME: ':80'
MERCURE_PUBLISHER_JWT_KEY: '!ChangeMe!'
MERCURE_SUBSCRIBER_JWT_KEY: '!ChangeMe!'
MERCURE_PUBLISHER_JWT_KEY: '!changeme!changeme!changeme!changeme!changeme!changeme!'
MERCURE_SUBSCRIBER_JWT_KEY: '!changeme!changeme!changeme!changeme!changeme!changeme!'
MERCURE_EXTRA_DIRECTIVES: |
cors_origins https://127.0.0.1:8000
# Comment the following line to disable the development mode
@ -157,6 +157,7 @@ services:
- ./containers/hydra/clients.d:/etc/hydra/clients.d
ports:
- 7080:4444
- 4445:4445
links:
- postgresql
depends_on:

View File

@ -0,0 +1,197 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Doctrine\Persistence\ManagerRegistry;
use App\Service\ApiService;
use App\Service\PasswordEncoder;
use App\Service\LdapService;
use App\Form\LoginType;
class HydraController extends AbstractController
{
private $apiservice;
private $passwordencoder;
private $ldapservice;
public function __construct(ApiService $apiservice,LdapService $ldapservice,PasswordEncoder $passwordencoder)
{
$this->apiservice = $apiservice;
$this->passwordencoder = $passwordencoder;
$this->ldapservice = $ldapservice;
}
public function loginsql(Request $request): Response
{
$challenge = $request->query->get('login_challenge');
// S'il n'y a pas de challenge, on déclenche une bad request
if (!$challenge) {
throw new BadRequestException('pas de challenge');
}
// On vérifie que la requête d'identification provient bien de hydra
$response = $this->apiservice->run("GET",$this->getParameter('hydraLoginchallenge').$challenge,null);
if(!$response)
throw new BadRequestException('challenge invalide');
// si le challenge est validé par hydra, on le stocke en session pour l'utiliser par la suite et on redirige vers une route interne protégée qui va déclencher l'identification FranceConnect
$request->getSession()->set('hydraChallenge', $challenge);
// Création du formulaire
$form = $this->createForm(LoginType::class);
// Récupération des data du formulaire
$form->handleRequest($request);
// Affichage du formulaire
return $this->render("Home/loginHYDRA.html.twig", [
"useheader"=>false,
"usemenu"=>false,
"usesidebar"=>false,
"form"=>$form->createView(),
"mode"=>"SQL",
]);
}
public function checkloginsql(Request $request,ManagerRegistry $em) {
$username=$request->get('login')["username"];
$password=$request->get('login')["password"];
// user exist ?
$user=$em->getRepository("App\Entity\User")->findOneBy(["username"=>$username]);
if(!$user) return $this->redirect($this->generateUrl('app_hydra_loginsql',["login_challenge"=>$request->getSession()->get("hydraChallenge")]));
$islogin=$this->passwordencoder->verify($user->getPassword(),$password,$user->getSalt());
if(!$islogin) return $this->redirect($this->generateUrl('app_hydra_loginsql',["login_challenge"=>$request->getSession()->get("hydraChallenge")]));
$response = $this->apiservice->run("PUT",$this->getParameter('hydraLoginchallengeaccept').$request->getSession()->get('hydraChallenge'),["subject"=>$user->getEmail(),"acr"=>"string"]);
if(!$response||$response->code!="200")
throw new BadRequestException('login accept invalide');
$datas=[
"username"=>$user->getUsername(),
"email"=>$user->getEmail(),
"firstname"=>$user->getFirstname(),
"lastname"=>$user->getLastname()
];
$request->getSession()->set("datas",$datas);
$redirect=$response->body->redirect_to;
return $this->redirect($redirect, 301);
}
public function loginldap(Request $request): Response
{
$challenge = $request->query->get('login_challenge');
// S'il n'y a pas de challenge, on déclenche une bad request
if (!$challenge) {
throw new BadRequestException('pas de challenge');
}
// On vérifie que la requête d'identification provient bien de hydra
$response = $this->apiservice->run("GET",$this->getParameter('hydraLoginchallenge').$challenge,null);
if(!$response)
throw new BadRequestException('challenge invalide');
// si le challenge est validé par hydra, on le stocke en session pour l'utiliser par la suite et on redirige vers une route interne protégée qui va déclencher l'identification FranceConnect
$request->getSession()->set('hydraChallenge', $challenge);
// Création du formulaire
$form = $this->createForm(LoginType::class);
// Récupération des data du formulaire
$form->handleRequest($request);
// Affichage du formulaire
return $this->render("Home/loginHYDRA.html.twig", [
"useheader"=>false,
"usemenu"=>false,
"usesidebar"=>false,
"form"=>$form->createView(),
"mode"=>"LDAP",
]);
}
public function checkloginldap(Request $request,ManagerRegistry $em) {
$username=$request->get('login')["username"];
$password=$request->get('login')["password"];
// L'utilisateur se co à l'annuaire ?
$userldap=$this->ldapservice->userconnect($username,$password);
if(!$userldap)
return $this->redirect($this->generateUrl('app_hydra_loginldap',["login_challenge"=>$request->getSession()->get("hydraChallenge")]));
$userldap=$userldap[0];
// Init
$email = "$username@nomail.fr";
$lastname = $username;
$firstname = " ";
// Rechercher l'utilisateur
if(isset($userldap[$this->getParameter('ldapFirstname')]))
$firstname = $userldap[$this->getParameter('ldapFirstname')];
if(isset($userldap[$this->getParameter('ldapLastname')]))
$lastname = $userldap[$this->getParameter('ldapLastname')];
if(isset($userldap[$this->getParameter('ldapEmail')]))
$email = $userldap[$this->getParameter('ldapEmail')];
$response = $this->apiservice->run("PUT",$this->getParameter('hydraLoginchallengeaccept').$request->getSession()->get('hydraChallenge'),["subject"=>$email,"acr"=>"string"]);
if(!$response||$response->code!="200")
throw new BadRequestException('login accept invalide');
$datas=[
"username"=>$username,
"email"=>$email,
"firstname"=>$firstname,
"lastname"=>$lastname
];
$request->getSession()->set("datas",$datas);
$redirect=$response->body->redirect_to;
return $this->redirect($redirect, 301);
}
public function consent(Request $request)
{
$challenge = $request->query->get('consent_challenge');
if (!$challenge) {
throw new BadRequestException("Le challenge n'est pas disponible");
}
// On vérifie que la requête d'identification provient bien de hydra
$response = $this->apiservice->run("GET",$this->getParameter('hydraConsentchallenge').$challenge,null);
if(!$response)
throw new BadRequestException('challenge invalide');
$response = $this->apiservice->run("PUT",$this->getParameter('hydraConsentchallengeaccept').$challenge,[
'grant_scope' => ['openid', 'offline_access'],
'session' => ['id_token' => $request->getSession()->get('datas')]
]);
if(!$response)
throw new BadRequestException('challenge not accept');
$redirect=$response->body->redirect_to;
return $this->redirect($redirect, 301);
}
}

View File

@ -47,6 +47,11 @@ class MinioController extends AbstractController
return new Response(json_encode($output));
}
public function logo(Request $request): Response {
return $this->redirectToRoute("app_minio_image",["file"=>"uploads/logo/".$request->getSession()->get("logolight")]);
}
public function image(Request $request): Response
{
$file=$request->query->get("file");

View File

@ -365,7 +365,7 @@ class SecurityController extends AbstractController
$callback=($request->isSecure()?"https://":"http://").str_replace("//","/",$this->getParameter("appWeburl").$this->getParameter("appAlias").$this->generateUrl('app_home'));
$callback=substr($callback, 0, -1);
$url.="?id_token_hint=$idtoken&scope=openid&state=$state&post_logout_redirect_uri=$callback";
$url.="?id_token_hint=$idtoken&scope=openid&post_logout_redirect_uri=$callback";
return $this->redirect($url);
} else return $this->redirect($this->generateUrl("app_home"));

View File

@ -75,6 +75,15 @@ class ApiService
}
break;
case "PUT":
try{
$response = \Unirest\Request::put($url,$header,$query);
}
catch (\Exception $e) {
return false;
}
break;
case "DELETE":
try{
$response = \Unirest\Request::delete($url,$header,$query);

View File

@ -25,7 +25,6 @@ class PasswordEncoder implements LegacyPasswordHasherInterface
return false;
}
var_dump($salt);
return $this->hash($plainPassword,$salt) === $hashedPassword;
}

View File

@ -0,0 +1,34 @@
{% extends "base.html.twig" %}
{% block body %}
<div style="text-align:center">
<img src="{{ path('app_minio_image',{file:"uploads/logo/"~app.session.get("logolight")}) }}" style="height:120px;margin-top:10px;margin-bottom:20px;">
<h1 style="border:none">{{app.session.get('appname')}}</h1>
{% if mode=="SQL" %}
{% set route="app_hydra_checkloginsql" %}
{% else %}
{% set route="app_hydra_checkloginldap" %}
{% endif %}
{{ form_start(form, {'action': path(route), 'method': 'POST'}) }}
<div class="card homecard mb-3" style="width:400px; margin:auto; text-align: left;">
<div class="card-body">
{{ form_row(form.username) }}
{{ form_row(form.password) }}
{{ form_row(form.submit) }}
</div>
</div>
{% if mode == "SQL"%}
<a href="{{path("app_resetpwd01")}}" class="mt-3">Mot de passe oublié ?</a>
{% endif %}
{{ form_end(form) }}
</div>
{% endblock %}
{% block localscript %}
<script>
$(document).ready(function() {
$("#login_username").focus();
});
</script>
{% endblock %}