Skip to content
Snippets Groups Projects
Commit c912d754 authored by Bertrand Gauthier's avatar Bertrand Gauthier
Browse files

Merge branch 'local-auth'

parents ca921e67 d84d6b2d
No related branches found
No related tags found
No related merge requests found
Showing
with 669 additions and 46 deletions
<?php
/**
* Affichage d'un avertissement concernant la mise à jour nécessaire du scema de BDD
* depuis l'ajout de la fonctionnalité 'mot de passe oublié'.
*/
//require '../../../autoload.php';
$message = <<<EOS
/*******************************************************************
* ! ATTENTION !
*
* Si vous installez cette version d'unicaen/auth, vous devez
* vous assurer que la colonne PASSWORD_RESET_TOKEN existe bien
* dans votre table utilisateur (nommee USER par défaut).
* Si ce n'est pas le cas, utilisez ceci pour l'ajouter :
*
* alter table "USER" add PASSWORD_RESET_TOKEN varchar2(256);
* create unique index USER_PASSWORD_RESET_TOKEN_UN on "USER"(PASSWORD_RESET_TOKEN);
*
******************************************************************/
EOS;
echo $message;
readline("Appuyez sur entrée pour pousuivre le processus 'composer' ou CTRL-C pour abandonner... ");
echo PHP_EOL;
...@@ -11,7 +11,8 @@ ...@@ -11,7 +11,8 @@
"unicaen/app": "^1.3", "unicaen/app": "^1.3",
"zf-commons/zfc-user-doctrine-orm": ">=0.1", "zf-commons/zfc-user-doctrine-orm": ">=0.1",
"jasig/phpcas": ">=1.3.3", "jasig/phpcas": ">=1.3.3",
"bjyoungblood/bjy-authorize": ">=1.4" "bjyoungblood/bjy-authorize": ">=1.4",
"ramsey/uuid": "^3.8"
}, },
"require-dev": { "require-dev": {
"phpunit/PHPUnit": ">=3.7" "phpunit/PHPUnit": ">=3.7"
...@@ -24,5 +25,8 @@ ...@@ -24,5 +25,8 @@
"classmap": [ "classmap": [
"./Module.php" "./Module.php"
] ]
},
"scripts": {
"pre-update-cmd": "@php bin/password-reset-requires-schema-update-warning.php"
} }
} }
\ No newline at end of file
...@@ -5,12 +5,24 @@ use UnicaenAuth\Controller\AuthControllerFactory; ...@@ -5,12 +5,24 @@ use UnicaenAuth\Controller\AuthControllerFactory;
use UnicaenAuth\Service\ShibService; use UnicaenAuth\Service\ShibService;
use UnicaenAuth\Service\ShibServiceFactory; use UnicaenAuth\Service\ShibServiceFactory;
use UnicaenAuth\Service\UserContextFactory; use UnicaenAuth\Service\UserContextFactory;
use UnicaenAuth\Service\UserMapperFactory;
use UnicaenAuth\View\Helper\LdapConnectViewHelperFactory; use UnicaenAuth\View\Helper\LdapConnectViewHelperFactory;
use UnicaenAuth\View\Helper\LocalConnectViewHelperFactory;
use UnicaenAuth\View\Helper\ShibConnectViewHelperFactory; use UnicaenAuth\View\Helper\ShibConnectViewHelperFactory;
use UnicaenAuth\View\Helper\UserUsurpationHelperFactory; use UnicaenAuth\View\Helper\UserUsurpationHelperFactory;
$settings = [ $settings = [
/**
* Configuration de l'authentification locale.
*/
'local' => [
/**
* Possibilité ou non de s'authentifier à l'aide d'un compte local.
*/
'enabled' => true,
],
/** /**
* Configuration de l'authentification LDAP. * Configuration de l'authentification LDAP.
*/ */
...@@ -143,6 +155,8 @@ return [ ...@@ -143,6 +155,8 @@ return [
['controller' => 'UnicaenAuth\Controller\Utilisateur', 'action' => 'selectionner-profil', 'roles' => []], ['controller' => 'UnicaenAuth\Controller\Utilisateur', 'action' => 'selectionner-profil', 'roles' => []],
['controller' => 'UnicaenAuth\Controller\Auth', 'action' => 'shibboleth', 'roles' => []], ['controller' => 'UnicaenAuth\Controller\Auth', 'action' => 'shibboleth', 'roles' => []],
['controller' => 'UnicaenAuth\Controller\Auth', 'action' => 'requestPasswordReset', 'roles' => []],
['controller' => 'UnicaenAuth\Controller\Auth', 'action' => 'changePassword', 'roles' => []],
], ],
], ],
], ],
...@@ -210,6 +224,24 @@ return [ ...@@ -210,6 +224,24 @@ return [
], ],
], ],
], ],
'requestPasswordReset' => [
'type' => 'Segment',
'options' => [
'route' => '/request-password-reset',
'defaults' => [
'action' => 'requestPasswordReset',
],
],
],
'changePassword' => [
'type' => 'Segment',
'options' => [
'route' => '/change-password/:token',
'defaults' => [
'action' => 'changePassword',
],
],
],
], ],
], ],
'zfcuser' => [ 'zfcuser' => [
...@@ -417,6 +449,7 @@ return [ ...@@ -417,6 +449,7 @@ return [
'zfcuser_redirect_callback' => 'UnicaenAuth\Authentication\RedirectCallbackFactory', // substituion 'zfcuser_redirect_callback' => 'UnicaenAuth\Authentication\RedirectCallbackFactory', // substituion
ShibService::class => ShibServiceFactory::class, ShibService::class => ShibServiceFactory::class,
'UnicaenAuth\Service\UserContext' => UserContextFactory::class, 'UnicaenAuth\Service\UserContext' => UserContextFactory::class,
'zfcuser_user_mapper' => UserMapperFactory::class,
'MouchardCompleterAuth' => 'UnicaenAuth\Mouchard\MouchardCompleterAuthFactory', 'MouchardCompleterAuth' => 'UnicaenAuth\Mouchard\MouchardCompleterAuthFactory',
], ],
'shared' => [ 'shared' => [
...@@ -453,6 +486,7 @@ return [ ...@@ -453,6 +486,7 @@ return [
'userProfileSelect' => 'UnicaenAuth\View\Helper\UserProfileSelectFactory', 'userProfileSelect' => 'UnicaenAuth\View\Helper\UserProfileSelectFactory',
'userProfileSelectRadioItem' => 'UnicaenAuth\View\Helper\UserProfileSelectRadioItemFactory', 'userProfileSelectRadioItem' => 'UnicaenAuth\View\Helper\UserProfileSelectRadioItemFactory',
'userUsurpation' => UserUsurpationHelperFactory::class, 'userUsurpation' => UserUsurpationHelperFactory::class,
'localConnect' => LocalConnectViewHelperFactory::class,
'ldapConnect' => LdapConnectViewHelperFactory::class, 'ldapConnect' => LdapConnectViewHelperFactory::class,
'shibConnect' => ShibConnectViewHelperFactory::class, 'shibConnect' => ShibConnectViewHelperFactory::class,
], ],
......
...@@ -7,6 +7,16 @@ ...@@ -7,6 +7,16 @@
*/ */
$settings = [ $settings = [
/**
* Configuration de l'authentification locale.
*/
'local' => [
/**
* Possibilité ou non de s'authentifier à l'aide d'un compte local.
*/
'enabled' => true,
],
/** /**
* Configuration de l'authentification LDAP. * Configuration de l'authentification LDAP.
*/ */
......
...@@ -9,6 +9,10 @@ CREATE TABLE user ( ...@@ -9,6 +9,10 @@ CREATE TABLE user (
UNIQUE INDEX `unique_username` (`username` ASC) UNIQUE INDEX `unique_username` (`username` ASC)
) ENGINE=InnoDB DEFAULT CHARACTER SET = utf8 COLLATE = utf8_unicode_ci; ) ENGINE=InnoDB DEFAULT CHARACTER SET = utf8 COLLATE = utf8_unicode_ci;
alter table user add PASSWORD_RESET_TOKEN varchar2(256) default null;
create unique index USER_PASSWORD_RESET_TOKEN_UN on user (PASSWORD_RESET_TOKEN);
CREATE TABLE IF NOT EXISTS `user_role` ( CREATE TABLE IF NOT EXISTS `user_role` (
`id` INT(11) NOT NULL AUTO_INCREMENT, `id` INT(11) NOT NULL AUTO_INCREMENT,
......
...@@ -10,6 +10,10 @@ CREATE TABLE "USER" ...@@ -10,6 +10,10 @@ CREATE TABLE "USER"
); );
CREATE SEQUENCE "USER_ID_SEQ" ; CREATE SEQUENCE "USER_ID_SEQ" ;
alter table "USER" add PASSWORD_RESET_TOKEN varchar2(256) default null;
create unique index USER_PASSWORD_RESET_TOKEN_UN on "USER" (PASSWORD_RESET_TOKEN);
CREATE TABLE USER_ROLE CREATE TABLE USER_ROLE
( "ID" NUMBER(*,0) NOT NULL ENABLE, ( "ID" NUMBER(*,0) NOT NULL ENABLE,
......
...@@ -8,6 +8,10 @@ CREATE TABLE "user" ( ...@@ -8,6 +8,10 @@ CREATE TABLE "user" (
) ; ) ;
CREATE UNIQUE INDEX user_username_unique ON "user" (username); CREATE UNIQUE INDEX user_username_unique ON "user" (username);
alter table "user" add PASSWORD_RESET_TOKEN varchar2(256) default null;
create unique index USER_PASSWORD_RESET_TOKEN_UN on "user" (PASSWORD_RESET_TOKEN);
CREATE TABLE user_role ( CREATE TABLE user_role (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
role_id VARCHAR(64) NOT NULL, role_id VARCHAR(64) NOT NULL,
......
...@@ -2,19 +2,27 @@ ...@@ -2,19 +2,27 @@
namespace UnicaenAuth\Controller; namespace UnicaenAuth\Controller;
use Doctrine\ORM\NoResultException;
use UnicaenApp\Controller\Plugin\AppInfos;
use UnicaenApp\Controller\Plugin\Mail;
use UnicaenApp\Exception\RuntimeException; use UnicaenApp\Exception\RuntimeException;
use UnicaenAuth\Service\Traits\ShibServiceAwareTrait; use UnicaenAuth\Service\Traits\ShibServiceAwareTrait;
use UnicaenAuth\Service\Traits\UserServiceAwareTrait; use UnicaenAuth\Service\Traits\UserServiceAwareTrait;
use Zend\Authentication\AuthenticationService; use Zend\Authentication\AuthenticationService;
use Zend\Authentication\Exception\ExceptionInterface; use Zend\Authentication\Exception\ExceptionInterface;
use Zend\Http\Request;
use Zend\Http\Response; use Zend\Http\Response;
use Zend\Mvc\Controller\AbstractActionController; use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use ZfcUser\Controller\Plugin\ZfcUserAuthentication; use ZfcUser\Controller\Plugin\ZfcUserAuthentication;
/** /**
* Classe ajoutée lors de l'implémentation de l'auth Shibboleth. * Classe ajoutée lors de l'implémentation de l'auth Shibboleth.
* *
* @method ZfcUserAuthentication zfcUserAuthentication() * @method ZfcUserAuthentication zfcUserAuthentication()
* @method AppInfos appInfos()
* @method Mail mail()
*
* @author Bertrand GAUTHIER <bertrand.gauthier at unicaen.fr> * @author Bertrand GAUTHIER <bertrand.gauthier at unicaen.fr>
*/ */
class AuthController extends AbstractActionController class AuthController extends AbstractActionController
...@@ -107,4 +115,114 @@ class AuthController extends AbstractActionController ...@@ -107,4 +115,114 @@ class AuthController extends AbstractActionController
throw new RuntimeException("Impossible d'écrire dans le storage"); throw new RuntimeException("Impossible d'écrire dans le storage");
} }
} }
/**
* @return Response|ViewModel
*/
public function requestPasswordResetAction()
{
$form = $this->userService->createResetPasswordEmailForm();
$view = new ViewModel();
$view->setVariable('form', $form);
$view->setTemplate('unicaen-auth/auth/request-password-reset-form');
/** @var Request $request */
$request = $this->getRequest();
if ($request->isPost()) {
$data = $request->getPost();
$form->setData($data);
if ($form->isValid()) {
$email = $data['email'];
$this->processPasswordResetRequest($email);
$view->setVariable('email', $email);
$view->setTemplate('unicaen-auth/auth/request-password-reset-success');
}
}
return $view;
}
private function processPasswordResetRequest($email)
{
try {
$token = $this->userService->updateUserPasswordResetToken($email);
} catch (NoResultException $nre) {
// aucun utilisateur trouvé tel que username = $email
return;
}
// envoi du mail contenant le lien de changement de mdp
$app = $this->appInfos()->getNom();
$subject = "[$app] Demande de changement de mot de passe";
$changePasswordUrl = $this->url()->fromRoute('auth/changePassword', ['token' => $token], ['force_canonical' => true]);
$body = <<<EOS
<p>Une demande de changement de mot de passe a été faite sur l'application $app.</p>
<p>Si vous n'en êtes pas l'auteur, vous pouvez ignorer ce message.</p>
<p>Cliquez sur le lien suivant pour accéder au formulaire de changement de votre mot de passe :<br><a href='$changePasswordUrl'>$changePasswordUrl</a></p>
EOS;
$message = $this->mail()->createNewMessage($body, $subject);
$message->setTo($email);
$this->mail()->send($message);
}
/**
* @return array|ViewModel
*/
public function changePasswordAction()
{
$token = $this->params()->fromRoute('token');
$view = new ViewModel();
// recherche du token spécifié dans table utilisateur
$user = $this->userService->getUserMapper()->findOneByPasswordResetToken($token);
if ($user === null) {
// token inexistant
$view->setVariable('result', 'unknown_token');
$view->setTemplate('unicaen-auth/auth/change-password-result');
return $view;
}
$form = $this->userService->createPasswordChangeForm();
/** @var Request $request */
$request = $this->getRequest();
if ($request->isPost()) {
$data = $request->getPost();
$form->setData($data);
if ($form->isValid()) {
// màj password
$password = $this->params()->fromPost('password');
$this->userService->updateUserPassword($user, $password);
$view->setVariable('result', 'success');
$view->setTemplate('unicaen-auth/auth/change-password-result');
// todo: faut-il déconnecter l'utilisateur (attention au logout shib différent) ?
return $view;
}
}
// test durée de vie du token
$date = $this->userService->extractDateFromResetPasswordToken($token);
if ($date < date_create()) {
// token expiré, on le raz
$this->userService->clearUserPasswordResetToken($user);
$view->setVariable('result', 'dead_token');
$view->setTemplate('unicaen-auth/auth/change-password-result');
return $view;
}
$view->setVariable('form', $form);
$view->setTemplate('unicaen-auth/auth/change-password-form');
return $view;
}
} }
\ No newline at end of file
...@@ -52,6 +52,12 @@ abstract class AbstractUser implements UserInterface, ProviderInterface ...@@ -52,6 +52,12 @@ abstract class AbstractUser implements UserInterface, ProviderInterface
*/ */
protected $state; protected $state;
/**
* @var string
* @ORM\Column(type="string", length=256)
*/
protected $passwordResetToken;
/** /**
* @var Collection * @var Collection
* @ORM\ManyToMany(targetEntity="UnicaenAuth\Entity\Db\Role") * @ORM\ManyToMany(targetEntity="UnicaenAuth\Entity\Db\Role")
...@@ -202,6 +208,22 @@ abstract class AbstractUser implements UserInterface, ProviderInterface ...@@ -202,6 +208,22 @@ abstract class AbstractUser implements UserInterface, ProviderInterface
$this->state = $state; $this->state = $state;
} }
/**
* @return string
*/
public function getPasswordResetToken()
{
return $this->passwordResetToken;
}
/**
* @param string $passwordResetToken
*/
public function setPasswordResetToken($passwordResetToken = null)
{
$this->passwordResetToken = $passwordResetToken;
}
/** /**
* Get role. * Get role.
* *
......
...@@ -9,6 +9,13 @@ namespace UnicaenAuth\Options; ...@@ -9,6 +9,13 @@ namespace UnicaenAuth\Options;
*/ */
class ModuleOptions extends \ZfcUser\Options\ModuleOptions class ModuleOptions extends \ZfcUser\Options\ModuleOptions
{ {
/**
* Paramètres concernant l'authentification locale.
*
* @var array
*/
protected $local = [];
/** /**
* Paramètres concernant l'authentification LDAP. * Paramètres concernant l'authentification LDAP.
* *
...@@ -46,6 +53,25 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions ...@@ -46,6 +53,25 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
*/ */
protected $entityManagerName = 'doctrine.entitymanager.orm_default'; protected $entityManagerName = 'doctrine.entitymanager.orm_default';
/**
* @return array
*/
public function getLocal()
{
return $this->local;
}
/**
* @param array $local
* @return self
*/
public function setLocal(array $local)
{
$this->local = $local;
return $this;
}
/** /**
* Retourne les paramètres concernant l'authentification LDAP. * Retourne les paramètres concernant l'authentification LDAP.
* *
......
...@@ -327,6 +327,9 @@ EOS; ...@@ -327,6 +327,9 @@ EOS;
if ($this->getShibbolethSimulate()) { if ($this->getShibbolethSimulate()) {
return '/'; return '/';
} }
if ($this->getAuthenticatedUser() === null) {
return '/';
}
$logoutRelativeUrl = '/Shibboleth.sso/Logout?return='; // NB: '?return=' semble obligatoire! $logoutRelativeUrl = '/Shibboleth.sso/Logout?return='; // NB: '?return=' semble obligatoire!
......
...@@ -2,24 +2,34 @@ ...@@ -2,24 +2,34 @@
namespace UnicaenAuth\Service; namespace UnicaenAuth\Service;
use UnicaenAuth\Event\UserAuthenticatedEvent; use DateTime;
use PDOException; use Doctrine\ORM\NoResultException;
use Ramsey\Uuid\Uuid;
use UnicaenApp\Entity\Ldap\People; use UnicaenApp\Entity\Ldap\People;
use UnicaenApp\Exception\RuntimeException; use UnicaenApp\Exception\RuntimeException;
use UnicaenApp\Mapper\Ldap\People as LdapPeopleMapper; use UnicaenApp\Mapper\Ldap\People as LdapPeopleMapper;
use UnicaenAuth\Entity\Shibboleth\ShibUser; use UnicaenAuth\Entity\Shibboleth\ShibUser;
use UnicaenAuth\Event\UserAuthenticatedEvent;
use UnicaenAuth\Options\ModuleOptions; use UnicaenAuth\Options\ModuleOptions;
use Zend\Crypt\Password\Bcrypt;
use Zend\EventManager\EventManagerAwareInterface; use Zend\EventManager\EventManagerAwareInterface;
use Zend\EventManager\EventManagerInterface; use Zend\EventManager\EventManagerInterface;
use Zend\Form\Element\Csrf;
use Zend\Form\Element\Password;
use Zend\Form\Element\Submit;
use Zend\Form\Element\Text;
use Zend\Form\Form;
use Zend\InputFilter\Input;
use Zend\ServiceManager\ServiceLocatorAwareInterface; use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorAwareTrait; use Zend\ServiceManager\ServiceLocatorAwareTrait;
use Zend\Validator\Identical;
use ZfcUser\Entity\UserInterface; use ZfcUser\Entity\UserInterface;
use ZfcUser\Options\AuthenticationOptionsInterface; use ZfcUser\Options\AuthenticationOptionsInterface;
use ZfcUser\Options\ModuleOptions as ZfcUserModuleOptions; use ZfcUser\Options\ModuleOptions as ZfcUserModuleOptions;
use UnicaenAuth\Entity\Db\AbstractUser;
/** /**
* Service d'enregistrement dans la table des utilisateurs de l'application * Service traitant des utilisateurs locaux de l'application.
* de l'utilisateur authentifié avec succès.
* *
* @see \UnicaenAuth\Authentication\Adapter\AbstractFactory * @see \UnicaenAuth\Authentication\Adapter\AbstractFactory
* @author Unicaen * @author Unicaen
...@@ -45,15 +55,20 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface ...@@ -45,15 +55,20 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface
*/ */
protected $zfcUserOptions; protected $zfcUserOptions;
/**
* @var UserMapper
*/
protected $userMapper;
/** /**
* @var LdapPeopleMapper * @var LdapPeopleMapper
*/ */
protected $ldapPeopleMapper; protected $ldapPeopleMapper;
/** /**
* Save authenticated user in database from LDAP data. * Save authenticated user in database from LDAP or Shibboleth data.
* *
* @param UserInterface|People $userData * @param People|ShibUser $userData
* @return bool * @return bool
*/ */
public function userAuthenticated($userData) public function userAuthenticated($userData)
...@@ -68,7 +83,6 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface ...@@ -68,7 +83,6 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface
$email = $userData->getMail(); $email = $userData->getMail();
$password = 'ldap'; $password = 'ldap';
$state = in_array('deactivated', ldap_explode_dn($userData->getDn(), 1)) ? 0 : 1; $state = in_array('deactivated', ldap_explode_dn($userData->getDn(), 1)) ? 0 : 1;
break; break;
case $userData instanceof ShibUser: case $userData instanceof ShibUser:
$username = $userData->getUsername(); $username = $userData->getUsername();
...@@ -94,9 +108,8 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface ...@@ -94,9 +108,8 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface
throw new RuntimeException("Identité rencontrée inattendue."); throw new RuntimeException("Identité rencontrée inattendue.");
} }
// update/insert de l'utilisateur dans la table de l'appli $mapper = $this->getUserMapper();
$mapper = $this->getServiceLocator()->get('zfcuser_user_mapper'); /* @var $mapper \ZfcUserDoctrineORM\Mapper\User */
try {
/** @var UserInterface $entity */ /** @var UserInterface $entity */
$entity = $mapper->findByUsername($username); $entity = $mapper->findByUsername($username);
if (!$entity) { if (!$entity) {
...@@ -123,15 +136,12 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface ...@@ -123,15 +136,12 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface
// post-persist // post-persist
$event = new UserAuthenticatedEvent(UserAuthenticatedEvent::POST_PERSIST); $event = new UserAuthenticatedEvent(UserAuthenticatedEvent::POST_PERSIST);
$this->triggerEvent($event, $entity, $userData); $this->triggerEvent($event, $entity, $userData);
}
catch (PDOException $pdoe) {
throw new RuntimeException("Impossible d'enregistrer l'utilisateur authentifié dans la base de données.", null, $pdoe);
}
return true; return true;
} }
/** /**
* @param UserAuthenticatedEvent $event
* @param UserInterface $entity * @param UserInterface $entity
* @param People|ShibUser $userData * @param People|ShibUser $userData
*/ */
...@@ -148,6 +158,18 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface ...@@ -148,6 +158,18 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface
$this->getEventManager()->trigger($event); $this->getEventManager()->trigger($event);
} }
/**
* @return UserMapper
*/
public function getUserMapper()
{
if ($this->userMapper === null) {
$this->userMapper = $this->getServiceLocator()->get('zfcuser_user_mapper');
}
return $this->userMapper;
}
/** /**
* Retrieve the event manager * Retrieve the event manager
* *
...@@ -222,4 +244,137 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface ...@@ -222,4 +244,137 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface
return $this->zfcUserOptions; return $this->zfcUserOptions;
} }
const PASSWORD_RESET_TOKEN_SEP = '-';
const PASSWORD_RESET_TOKEN_DATE_FORMAT = 'YmdHis';
/**
* Construit le formulaire de saisie de l'adresse électronique à laquelle envoyer le lien de
* changement de mot de passe.
*
* @return Form
*/
public function createResetPasswordEmailForm()
{
$form = new Form();
$form->add((new Text('email'))->setLabel("Adresse électronique :"));
$form->add((new Csrf('csrf')));
$form->add((new Submit('submit'))->setLabel("Envoyer le lien"));
$form->getInputFilter()->add((new Input('email'))->setRequired(true));
return $form;
}
/**
* Construit le formulaire de saisie d'un nouveau mot de passe.
*
* @return Form
*/
public function createPasswordChangeForm()
{
$form = new Form();
$form->add((new Password('password'))->setLabel("Nouveau mot de passe :"));
$form->add((new Password('passwordbis'))->setLabel("Confirmation du nouveau mot de passe :"));
$form->add((new Csrf('csrf')));
$form->add((new Submit('submit'))->setLabel("Enregistrer"));
$form->getInputFilter()->add((new Input('password'))->setRequired(true));
$passwordbisInput = (new Input('passwordbis'))->setRequired(true);
$passwordbisInput->getValidatorChain()->attach(new Identical('password'));
$form->getInputFilter()->add($passwordbisInput);
return $form;
}
/**
* Si l'utilisateur dont le username égale l'email spécifié est trouvé,
* génère puis enregistre le token permettant d'autoriser cet utilisateur à changer son mot de passe.
*
* @param string $email Email de l'utilisateur qui doit être aussi son username
* @return string|null Token généré
* @throws NoResultException Aucun utilisateur trouvé avec cet email
*/
public function updateUserPasswordResetToken($email)
{
// Si l'email est inconnu, on ne fera rien mais on ne le signale pas sinon le formulaire permettrait
// de tester si des emails potentiellement valides existent dans la base.
$user = $this->getUserMapper()->findByEmail($email); /** @var User $user */
if ($user === null) {
throw new NoResultException();
}
// Génération du token.
$token = $this->generatePasswordResetToken();
// Enregistrement du token dans la table des utilisateurs
$user->setPasswordResetToken($token);
$this->getUserMapper()->update($user);
return $token;
}
/**
* @param AbstractUser $user
*/
public function clearUserPasswordResetToken(AbstractUser $user)
{
$user->setPasswordResetToken(null);
$this->getUserMapper()->update($user);
}
/**
* @param AbstractUser $user
* @param string $password
*/
public function updateUserPassword(AbstractUser $user, $password)
{
$bcrypt = new Bcrypt();
$bcrypt->setCost($this->getZfcUserOptions()->getPasswordCost());
$password = $bcrypt->create($password);
$user->setPasswordResetToken(null);
$user->setPassword($password);
$this->getUserMapper()->update($user);
}
/**
* Génération d'un token pour la demande de renouvellement de mot de passe.
*
* @return string
*/
public function generatePasswordResetToken()
{
// NB: la date de fin de vie du token est concaténée à la fin.
$token = Uuid::uuid4()->toString() . self::PASSWORD_RESET_TOKEN_SEP . date('YmdHis', time() + 3600*24);
// durée de vie = 24h
return $token;
}
/**
* Génération du motif permettant de rechercher un token dans la table des utilisateurs.
*
* Rappel: la date de génération est concaténée à la fin.
*
* @param string $tokenUnderTest Le token recherché
* @return string
*/
public function generatePasswordResetTokenSearchPattern($tokenUnderTest)
{
return $tokenUnderTest . self::PASSWORD_RESET_TOKEN_SEP . '%';
}
/**
* Extrait la date de fin de vie d'un token.
*
* @param string $token
* @return DateTime
*/
public function extractDateFromResetPasswordToken($token)
{
$ts = ltrim(strrchr($token, $sep = self::PASSWORD_RESET_TOKEN_SEP), $sep);
$date = DateTime::createFromFormat(self::PASSWORD_RESET_TOKEN_DATE_FORMAT, $ts);
return $date;
}
} }
\ No newline at end of file
<?php
namespace UnicaenAuth\Service;
use UnicaenAuth\Entity\Db\User;
use ZfcUserDoctrineORM\Mapper\User as ZfcUserDoctrineORMUserMapper;
class UserMapper extends ZfcUserDoctrineORMUserMapper
{
/**
* @param string $token
* @return User
*/
public function findOneByPasswordResetToken($token)
{
/** @var User $user */
$user = $this->em->getRepository($this->options->getUserEntityClass())->findOneBy(['passwordResetToken' => $token]);
return $user;
}
}
\ No newline at end of file
<?php
namespace UnicaenAuth\Service;
use Doctrine\ORM\EntityManagerInterface;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class UserMapperFactory implements FactoryInterface
{
/**
* Create service
*
* @param ServiceLocatorInterface $serviceLocator
* @return UserMapper
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
/** @var EntityManagerInterface $em */
$em = $serviceLocator->get('zfcuser_doctrine_em');
/** @var \ZfcUserDoctrineORM\Options\ModuleOptions $options */
$options = $serviceLocator->get('zfcuser_module_options');
return new UserMapper($em, $options);
}
}
...@@ -62,10 +62,12 @@ class LdapConnectViewHelper extends AbstractHelper ...@@ -62,10 +62,12 @@ class LdapConnectViewHelper extends AbstractHelper
} }
try { try {
return $this->getView()->render("ldap-connect", [ return $this->getView()->render("connect", [
'title' => null,
'enabled' => $this->enabled, 'enabled' => $this->enabled,
'form' => $this->form, 'form' => $this->form,
'redirect' => null, 'redirect' => null,
'password_reset' => false,
]); ]);
} catch (\Exception $e) { } catch (\Exception $e) {
return '<p>' . $e->getMessage() . '</p><p>' . $e->getTraceAsString() . '</p>'; return '<p>' . $e->getMessage() . '</p><p>' . $e->getTraceAsString() . '</p>';
......
<?php
namespace UnicaenAuth\View\Helper;
use Zend\Form\Form;
use Zend\View\Helper\AbstractHelper;
use Zend\View\Renderer\PhpRenderer;
use Zend\View\Resolver\TemplatePathStack;
/**
* Aide de vue dessinant le formulaire d'authentification locale,
* si l'authentification locale est activée.
*
* @method PhpRenderer getView()
* @author Unicaen
*/
class LocalConnectViewHelper extends AbstractHelper
{
/**
* @var bool
*/
protected $enabled = true;
/**
* @var Form
*/
protected $form;
/**
* @param bool $enabled
* @return $this
*/
public function setEnabled($enabled = true)
{
$this->enabled = $enabled;
return $this;
}
/**
* @param Form $form
* @return $this
*/
public function __invoke(Form $form)
{
$this->form = $form;
$this->getView()->resolver()->attach(
new TemplatePathStack(['script_paths' => [__DIR__ . "/partial"]])
);
return $this;
}
/**
* @return string
*/
public function __toString()
{
if (! $this->enabled) {
return '';
}
try {
return $this->getView()->render("connect", [
'title' => "Avec un compte local",
'enabled' => $this->enabled,
'form' => $this->form,
'redirect' => null,
'passwordReset' => true,
]);
} catch (\Exception $e) {
return '<p>' . $e->getMessage() . '</p><p>' . $e->getTraceAsString() . '</p>';
}
}
}
\ No newline at end of file
<?php
namespace UnicaenAuth\View\Helper;
use UnicaenAuth\Options\ModuleOptions;
use Zend\View\HelperPluginManager;
class LocalConnectViewHelperFactory
{
/**
* @param HelperPluginManager $hpm
* @return LocalConnectViewHelper
*/
public function __invoke(HelperPluginManager $hpm)
{
/** @var ModuleOptions $moduleOptions */
$moduleOptions = $hpm->getServiceLocator()->get('unicaen-auth_module_options');
$config = $moduleOptions->getLocal();
$enabled = isset($config['enabled']) && (bool) $config['enabled'];
$helper = new LocalConnectViewHelper();
$helper->setEnabled($enabled);
return $helper;
}
}
\ No newline at end of file
...@@ -41,7 +41,7 @@ class ShibConnectViewHelper extends AbstractHelper ...@@ -41,7 +41,7 @@ class ShibConnectViewHelper extends AbstractHelper
$shibUrl = $this->getView()->url('auth/shibboleth', [], ['query' => $this->getView()->queryParams()], true); $shibUrl = $this->getView()->url('auth/shibboleth', [], ['query' => $this->getView()->queryParams()], true);
return <<<EOS return <<<EOS
Se connecter via la <h3 class="connect-title">Via la fédération d'identité</h3>
<a href="$shibUrl" class="btn btn-success btn-lg">Fédération d'identité Renater</a> <a href="$shibUrl" class="btn btn-success btn-lg">Fédération d'identité Renater</a>
EOS; EOS;
} }
......
...@@ -5,32 +5,46 @@ use Zend\Form\Form; ...@@ -5,32 +5,46 @@ use Zend\Form\Form;
/** /**
* @var bool $enabled * @var bool $enabled
* @var Form $form * @var Form $form
* @var string $title
* @var string $redirect * @var string $redirect
* @var bool $passwordReset
*/ */
?> ?>
<?php if ($title): ?>
<h3 class="connect-title">
<?php echo $title ?>
</h3>
<?php endif ?>
<?php echo $this->form()->openTag($form) ?> <?php echo $this->form()->openTag($form) ?>
<?php if (($errors = $this->formErrors($form))): ?> <?php if (($errors = $this->formErrors($form))): ?>
<p><?php echo $errors ?></p> <p><?php echo $errors ?></p>
<?php endif ?> <?php endif ?>
<p> <p class="connect-identity">
<?php <?php
$identity = $form->get($name = 'identity')->setAttributes(['id' => $name, 'class' => 'form-control']); $identity = $form->get($name = 'identity')->setAttributes(['id' => $name, 'class' => 'form-control']);
echo $this->formLabel($identity); echo $this->formLabel($identity);
echo $this->formInput($identity); echo $this->formInput($identity);
?> ?>
</p> </p>
<p> <p class="connect-credentials">
<?php <?php
$identity = $form->get($name = 'credential')->setAttributes(['id' => $name, 'class' => 'form-control']); $identity = $form->get($name = 'credential')->setAttributes(['id' => $name, 'class' => 'form-control']);
echo $this->formLabel($identity); echo $this->formLabel($identity);
echo $this->formInput($identity); echo $this->formInput($identity);
?> ?>
<?php if ($passwordReset): ?>
<a class="connect-credentials-lost" href="<?php echo $this->url('auth/requestPasswordReset') ?>">Mot de passe oublié</a>
<?php endif ?>
</p> </p>
<?php if ($redirect): ?> <?php if ($redirect): ?>
<input type="hidden" name="redirect" value="<?php echo $redirect ?>"/> <input type="hidden" name="redirect" value="<?php echo $redirect ?>"/>
<?php endif ?> <?php endif ?>
<p>
<p class="connect-submit">
<?php echo $this->formButton($form->get('submit')->setAttribute('class', 'btn btn-primary')) ?> <?php echo $this->formButton($form->get('submit')->setAttribute('class', 'btn btn-primary')) ?>
</p> </p>
<?php echo $this->form()->closeTag() ?> <?php echo $this->form()->closeTag() ?>
<?php
/**
* @var string $status
*/
?>
<h2 class="page-header password-change-title">
Formulaire de changement de mot de passe
</h2>
<p class="lead">
Veuillez choisir puis confirmer votre nouveau mot de passe.<br>
</p>
<div class="col-sm-4">
<?php echo $this->form()->openTag($form) ?>
<p class="password-change-password">
<?php
$password = $form->get($name = 'password')->setAttributes(['id' => $name, 'class' => 'form-control']);
echo $this->formLabel($password);
echo $this->formInput($password);
echo $this->formElementErrors($password, ['class' => 'text-danger']);
?>
</p>
<p class="password-change-passwordbis">
<?php
$passwordbis = $form->get($name = 'passwordbis')->setAttributes(['id' => $name, 'class' => 'form-control']);
echo $this->formLabel($passwordbis);
echo $this->formInput($passwordbis);
echo $this->formElementErrors($passwordbis, ['class' => 'text-danger']);
?>
</p>
<?php echo $this->formInput($form->get('csrf')); ?>
<p class="password-change-submit">
<?php echo $this->formButton($form->get('submit')->setAttribute('class', 'btn btn-primary')) ?>
</p>
<?php echo $this->form()->closeTag() ?>
</div>
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment