diff --git a/.env b/.env index 6ac6765..1b6ac34 100644 --- a/.env +++ b/.env @@ -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" diff --git a/config/routes.yaml b/config/routes.yaml index fa16e6a..b537b5d 100644 --- a/config/routes.yaml +++ b/config/routes.yaml @@ -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 diff --git a/config/services.yaml b/config/services.yaml index 6b64a05..9815ea5 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -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)%' diff --git a/containers/hydra-dispatcher/hydra/providers.yml b/containers/hydra-dispatcher/hydra/providers.yml index 5a7dccc..2c5d356 100644 --- a/containers/hydra-dispatcher/hydra/providers.yml +++ b/containers/hydra-dispatcher/hydra/providers.yml @@ -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 diff --git a/containers/hydra-dispatcher/theme.css b/containers/hydra-dispatcher/theme.css index 804d4f6..3064f2b 100644 --- a/containers/hydra-dispatcher/theme.css +++ b/containers/hydra-dispatcher/theme.css @@ -1,5 +1,5 @@ body > section { - background-color: rgba(214, 170, 214, 0.575); + } input[type="radio"]:checked ~ .app-item { diff --git a/containers/hydra/clients.d/nineskeletor.json b/containers/hydra/clients.d/nineskeletor.json index 02c3380..4e66d72 100644 --- a/containers/hydra/clients.d/nineskeletor.json +++ b/containers/hydra/clients.d/nineskeletor.json @@ -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" } \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 71a0824..539be7e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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: diff --git a/src/Controller/HydraController.php b/src/Controller/HydraController.php new file mode 100644 index 0000000..0a889c8 --- /dev/null +++ b/src/Controller/HydraController.php @@ -0,0 +1,197 @@ +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); + } + +} \ No newline at end of file diff --git a/src/Controller/MinioController.php b/src/Controller/MinioController.php index 84f402b..3c29611 100644 --- a/src/Controller/MinioController.php +++ b/src/Controller/MinioController.php @@ -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"); diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php index 4102530..a350eb6 100755 --- a/src/Controller/SecurityController.php +++ b/src/Controller/SecurityController.php @@ -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")); diff --git a/src/Service/ApiService.php b/src/Service/ApiService.php index 5e2c97c..4388d96 100644 --- a/src/Service/ApiService.php +++ b/src/Service/ApiService.php @@ -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); diff --git a/src/Service/PasswordEncoder.php b/src/Service/PasswordEncoder.php index 807f5a2..acc1010 100644 --- a/src/Service/PasswordEncoder.php +++ b/src/Service/PasswordEncoder.php @@ -25,7 +25,6 @@ class PasswordEncoder implements LegacyPasswordHasherInterface return false; } - var_dump($salt); return $this->hash($plainPassword,$salt) === $hashedPassword; } diff --git a/templates/Home/loginHYDRA.html.twig b/templates/Home/loginHYDRA.html.twig new file mode 100755 index 0000000..7ce37cf --- /dev/null +++ b/templates/Home/loginHYDRA.html.twig @@ -0,0 +1,34 @@ +{% extends "base.html.twig" %} + +{% block body %} +
+ +

{{app.session.get('appname')}}

+ {% if mode=="SQL" %} + {% set route="app_hydra_checkloginsql" %} + {% else %} + {% set route="app_hydra_checkloginldap" %} + {% endif %} + + {{ form_start(form, {'action': path(route), 'method': 'POST'}) }} +
+
+ {{ form_row(form.username) }} + {{ form_row(form.password) }} + {{ form_row(form.submit) }} +
+
+ {% if mode == "SQL"%} + Mot de passe oublié ? + {% endif %} + {{ form_end(form) }} +
+{% endblock %} + +{% block localscript %} + +{% endblock %} \ No newline at end of file