diff --git a/VERSIONS_WIP/40971 b/VERSIONS_WIP/40971
new file mode 100644
index 0000000000000000000000000000000000000000..6bac9e99215e8a61ddfbf5562fcafba8f5e936b9
--- /dev/null
+++ b/VERSIONS_WIP/40971
@@ -0,0 +1 @@
+ - ticket #40971 : Nanook : Pouvoir prendre l'email comme identifiant alternatif de l'abonné
\ No newline at end of file
diff --git a/application/modules/opac/controllers/AuthController.php b/application/modules/opac/controllers/AuthController.php
index c9a803952143459e3d3e8a3c7fd9cf31b81c92f6..ff318b380bc7bc1507a7d1cbf6a58b32a20b2792 100644
--- a/application/modules/opac/controllers/AuthController.php
+++ b/application/modules/opac/controllers/AuthController.php
@@ -26,6 +26,13 @@ class AuthController extends ZendAfi_Controller_Action {
   }
 
 
+  public function onPasswordNotSecure($message, $pattern) {
+    $this->_setParam('password_hint', $message)
+         ->_setParam('pattern', $pattern)
+         ->_forward('secure-password');
+  }
+
+
   public function indexAction() {
     $this->_redirect('/opac');
   }
@@ -90,8 +97,61 @@ class AuthController extends ZendAfi_Controller_Action {
   }
 
 
+  public function securePasswordAction() {
+    $this->_setupSecurePasswordForm();
+  }
+
+
+  protected function _setupSecurePasswordForm() {
+    $form = new ZendAfi_Form_SecurePassword();
+    $form->current_password->setValue($this->_request->getPost('password'));
+    $form->password_hint->setValue($this->_getParam('password_hint'));
+    $form->pattern->setValue($this->_getParam('pattern'));
+    $form
+      ->secure_password
+      ->addValidator(
+                     (new Zend_Validate_Regex($this->_getParam('pattern')))
+                     ->setMessage($this->_getParam('password_hint'), Zend_Validate_Regex::NOT_MATCH));
+
+    $this->view->title = $this->_('Sécurisation du compte');
+
+    $this->view->message = $this->_('Vous vous connectez pour la première fois avec votre courriel. Vous devez modifier votre mot de passe pour sécuriser l\'accès par courriel.');
+
+    $this->view->password_hint = $this->_getParam('password_hint');
+
+    $this->view->form = $form;
+    return $form->setAction($this->view->url(['action' => 'do-secure-password']));
+  }
+
+
+  public function doSecurePasswordAction() {
+    $card = $this->_request->getPost('card');
+    $current_password = $this->_request->getPost('current_password');
+
+    $form = $this->_setupSecurePasswordForm();
+    if (!$form->isValid($this->_request->getPost()))
+      return $this->renderScript('auth/secure-password.phtml');
+
+    if (!ZendAfi_Auth::getInstance()->authenticateLoginPassword($card, $current_password)) {
+      $form->card->addError($this->_('Numéro de carte incorrect'));
+      return $this->renderScript('auth/secure-password.phtml');
+    }
+
+    $user = Class_Users::getIdentity();
+    $user->getFicheSIGB(); // init sigb infos
+    $user
+      ->setPassword($this->_request->getPost('secure_password'))
+      ->save();
+
+    $this->getHelper('notify')->bePopup();
+    $this->_helper->notify($this->_('Votre compte est sécurisé. Vous pouvez maintenant vous connecter avec votre courriel et votre nouveau mot de passe.'));
+
+    $this->_redirect('/');
+  }
+
+
   //see http://www.jasig.org/cas/protocol#cas-uris
-  function validateAction() {
+  public function validateAction() {
     $this->_forward('validate', 'cas-server');
   }
 
@@ -110,9 +170,11 @@ class AuthController extends ZendAfi_Controller_Action {
 
     $strategy = Auth_Strategy_Abstract::strategyForController($this);
     $strategy->setDefaultUrl($redirect);
-    $strategy->onLoginSuccess(function($user) {
-                                                 $user->registerNotificationsOn($this->getHelper('notify')->bePopup());
-                                               });
+    $strategy
+      ->onLoginSuccess(function($user)
+                       {
+                         $user->registerNotificationsOn($this->getHelper('notify')->bePopup());
+                       });
     $strategy->processLogin();
     $this->view->form_action = 'login';
   }
@@ -124,19 +186,20 @@ class AuthController extends ZendAfi_Controller_Action {
 
     $this->view->preferences = $this->_loginPrefFromWidgetOrModule();
 
-    $strategy = Auth_Strategy_Abstract::strategyForController($this);
-    $strategy->disableRedirect();
-    $strategy->onLoginSuccess(function($user) use ($redirect, $location) {
-                                                                            $user->registerNotificationsOn($this->getHelper('notify'));
-                                                                            $this->renderPopup($redirect, $location);
-                                                                          });
-    $strategy->processLogin();
-
-    if (!Class_Users::hasIdentity()) {
-      $this->renderPopup($this->view->url(['action' => 'popup-login'])
-                         . '?redirect=' . urlencode($redirect)
-                         . ($location ? '&location=' . urlencode($location) : ''));
-    }
+    Auth_Strategy_Abstract::strategyForController($this)
+      ->disableRedirect()
+      ->onLoginSuccess(function($user) use ($redirect, $location)
+                       {
+                         $user->registerNotificationsOn($this->getHelper('notify'));
+                         $this->renderPopup($redirect, $location);
+                       })
+      ->onLoginFail(function() use ($redirect, $location)
+                    {
+                      $this->renderPopup($this->view->url(['action' => 'popup-login'])
+                                         . '?redirect=' . urlencode($redirect)
+                                         . ($location ? '&location=' . urlencode($location) : ''));
+                    })
+      ->processLogin();
   }
 
 
@@ -191,7 +254,11 @@ class AuthController extends ZendAfi_Controller_Action {
     $this->view->preferences = Class_Profil::getCurrentProfil()
       ->getModuleAccueilPreferencesByType('LOGIN');
 
-    $url = ($target = Class_Profil::find((int)$this->view->preferences['profil_redirect']))
+    $profil_redirect = isset($this->view->preferences['profil_redirect'])
+      ? (int)$this->view->preferences['profil_redirect']
+      : 0;
+
+    $url = ($target = Class_Profil::find($profil_redirect))
       ? $target->getUrl()
       : $this->_request->getServer('HTTP_REFERER');
 
@@ -510,7 +577,9 @@ class AuthController extends ZendAfi_Controller_Action {
 abstract class Auth_Strategy_Abstract {
   protected
     $redirect_url = '',
-    $disable_redirect = false;
+    $disable_redirect = false,
+    $on_login_success_callback,
+    $on_login_fail_callback;
 
   static public function strategyForController($controller) {
     if ($controller->isCasRequest() && static::isLogged())
@@ -522,7 +591,6 @@ abstract class Auth_Strategy_Abstract {
     if ($controller->isLectura())
       return new Auth_Strategy_Lectura($controller);
 
-
     if (static::isLogged())
       return new Auth_Strategy_Logged($controller);
 
@@ -534,71 +602,119 @@ abstract class Auth_Strategy_Abstract {
     return Class_Users::getIdentity();
   }
 
+
   public function disableRedirect() {
     $this->disable_redirect = true;
+    return $this;
   }
 
+
   public function __construct($controller) {
     $this->controller = $controller;
     $this->default_url = $this->controller->getRedirectDefaultUrl();
   }
 
+
   public function getRequest(){
     return $this->controller->getRequest();
   }
 
+
   public function processLogin() {
     $this->prepareLogin();
-    if ($this->getRequest()->isPost())
-      $this->handlePost();
 
+    if (!$this->getRequest()->isPost())
+      return $this->_handleRedirect();
+
+    $callback = $this->_handlePost();
+
+    $password_not_secure = Class_WebService_SIGB_Nanook_PatronPasswordNotSecure::getInstance();
+
+    if (($message = $password_not_secure->getMessage())
+        && !$this->controller->view->isPopup())
+      return $this->controller->onPasswordNotSecure($message,
+                                                    $password_not_secure->getPattern());
+
+    $callback();
+    return $this->_handleRedirect();
+  }
+
+
+  protected function _handleRedirect() {
     if ($this->shouldRedirect())
       $this->controller->redirect($this->redirect_url);
+
+    return $this;
   }
 
 
   public function setDefaultUrl($url) {
-    $this->default_url=$url;
+    $this->default_url = $url;
   }
 
 
   public function prepareLogin() {
-
   }
 
-  /** @codeCoverageIgnore */
-  public function handlePost() {
 
+  /** @codeCoverageIgnore */
+  protected function _handlePost() {
+    return function() {};
   }
 
+
   public function shouldRedirect() {
     return ($this->getRedirectUrl()!='');
   }
 
+
   public function getRedirectUrl() {
     if ($this->disable_redirect)
       return '';
     return $this->redirect_url;
   }
 
+
   public function onLoginSuccess($do) {
     $this->on_login_success_callback = $do;
+    return $this;
   }
-}
 
 
+  public function onLoginFail($do) {
+    $this->on_login_fail_callback = $do;
+    return $this;
+  }
 
 
-class Auth_Strategy_NotLogged extends Auth_Strategy_Abstract{
-  public function handlePost() {
-    $this->redirect_url=$this->default_url;
-    if ($error=$this->controller->_authenticate()) {
-      $this->controller->notify($error);
-    }
+  protected function _doOnLoginFail() {
+    if (isset($this->on_login_fail_callback))
+      call_user_func($this->on_login_fail_callback);
+    return $this;
+  }
+
 
+  protected function _doOnLoginSuccess() {
     if (($user = Class_Users::getIdentity()) && isset($this->on_login_success_callback))
       call_user_func($this->on_login_success_callback, $user);
+    return $this;
+  }
+}
+
+
+
+
+class Auth_Strategy_NotLogged extends Auth_Strategy_Abstract{
+  protected function _handlePost() {
+    $this->redirect_url = $this->default_url;
 
+    if (!$error = $this->controller->_authenticate())
+      return function() { return $this->_doOnLoginSuccess(); };
+
+    return function() use($error) {
+      $this->controller->notify($error);
+      return $this->_doOnLoginFail();
+    };
   }
 }
 
@@ -660,10 +776,12 @@ class Auth_Strategy_Cas_Logged extends Auth_Strategy_Cas_Abstract{
 
 
 class Auth_Strategy_Cas_NotLogged extends Auth_Strategy_Cas_Abstract{
-  public function handlePost() {
-    if ($error=$this->controller->_authenticate())
-      return $this->controller->notify($error);
-    $this->redirect_url=$this->urlServiceCas();
+  protected function _handlePost() {
+    if ($error = $this->controller->_authenticate())
+      return function() use($error) { return $this->controller->notify($error); };
+
+    $this->redirect_url = $this->urlServiceCas();
+    return function() {};
   }
 }
 
@@ -671,16 +789,20 @@ class Auth_Strategy_Cas_NotLogged extends Auth_Strategy_Cas_Abstract{
 
 
 class Auth_Strategy_Lectura extends Auth_Strategy_Abstract {
-  public function handlePost() {
+  protected function _handlePost() {
     $this->controller->getHelper('ViewRenderer')->setNoRender();
-    $response= $this->controller->getResponse();
+
+    $response = $this->controller->getResponse();
     $view = $this->controller->view;
     $request = $this->controller->getRequest();
+
     $response->setHeader('Content-Type', 'application/xml;charset=utf-8');
     $login = $request->getPost('CAB');
     $password = $request->getPost('PWD');
     $response->setBody($this->getXmlResponse($view,
                                              ZendAfi_Auth::getInstance()->authenticateLoginPassword($login, $password)));
+
+    return function() {};
   }
 
 
@@ -696,10 +818,11 @@ class Auth_Strategy_Lectura extends Auth_Strategy_Abstract {
 
 
 class Auth_Strategy_OAuth extends Auth_Strategy_NotLogged {
-  public function handlePost() {
-    parent::handlePost();
+  protected function _handlePost() {
+    parent::_handlePost();
+
     if (!$user = Class_Users::getIdentity())
-      return $this;
+      return function() {};
 
     $request = $this->controller->getRequest();
 
@@ -708,5 +831,7 @@ class Auth_Strategy_OAuth extends Auth_Strategy_NotLogged {
     $this->redirect_url = sprintf('%s#token=%s',
                                   $request->getParam('redirect_uri'),
                                   $token->getToken());
+
+    return function() {};
   }
 }
\ No newline at end of file
diff --git a/application/modules/opac/views/scripts/auth/ajax-login.phtml b/application/modules/opac/views/scripts/auth/ajax-login.phtml
index dd16c16189591505d4ef03d3b87a89846b3928cf..ef0a781ecfa6a85dda1b830bfa1829c459dd58d8 100644
--- a/application/modules/opac/views/scripts/auth/ajax-login.phtml
+++ b/application/modules/opac/views/scripts/auth/ajax-login.phtml
@@ -29,14 +29,17 @@ function submit() {
   } else { ?>
   <div class="formTable">
     <?php
+    $action = ['controller' => 'auth',
+               'action' => 'ajax-login'];
+    if ($this->isPopup())
+      $action['render'] = 'popup';
+
     $form = ZendAfi_Form_Login::newWithOptions(['data' => array_merge($this->preferences,
                                                                       ['redirect_url' => $this->redirect,
                                                                        'id_notice' => $this->id_notice]),
 
-                                                'action' => $this->url(['controller' => 'auth',
-                                                                        'action' => 'ajax-login'],
-                                                                       null, true)
-                                                                                  . ($this->location ? '?location=' . $this->location : '')]);
+                                                'action' => $this->url($action, null, true)
+                                                                      . ($this->location ? '?location=' . $this->location : '')]);
 
     echo $this->renderForm($form);
 
diff --git a/application/modules/opac/views/scripts/auth/secure-password.phtml b/application/modules/opac/views/scripts/auth/secure-password.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..c353be76edbb720734bca4195a46cbeed29eb089
--- /dev/null
+++ b/application/modules/opac/views/scripts/auth/secure-password.phtml
@@ -0,0 +1,9 @@
+<?php
+$this->openBoite($this->title);
+
+echo $this->tag('p', $this->message);
+echo $this->tag('p', $this->password_hint);
+
+echo $this->renderForm($this->form);
+
+$this->closeBoite();
diff --git a/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/IntegrationControllerTest.php b/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/IntegrationControllerTest.php
index a625bef5ae55b8c88c9e910bf2f77a9d1b6ea794..e977750460a33fd098dbf4e9a1a1fabfd6cf37a7 100644
--- a/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/IntegrationControllerTest.php
+++ b/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/IntegrationControllerTest.php
@@ -26,7 +26,8 @@ class Cosmo_IntegrationControllerTestActionTest extends CosmoControllerTestCase
   public function setUp() {
     parent::setUp();
     $this->_service = $this->mock()->whenCalled('test')->answers('');
-    Class_WebService_SIGB_Nanook::setService($this->_service);
+    Class_WebService_SIGB_Nanook::setService(['url_serveur' => 'http://localhost/ilsdi'],
+                                             $this->_service);
   }
 
 
diff --git a/library/Class/WebService/SIGB/AbstractRESTService.php b/library/Class/WebService/SIGB/AbstractRESTService.php
index 857648b26603573f365e4efc3bc6b9bfe319fac9..d7415cddd5dd016aec4832bc49916fb5387db89a 100644
--- a/library/Class/WebService/SIGB/AbstractRESTService.php
+++ b/library/Class/WebService/SIGB/AbstractRESTService.php
@@ -188,27 +188,6 @@ abstract class Class_WebService_SIGB_AbstractRESTService extends Class_WebServic
   }
 
 
-  /**
-   * Authentifie un utilisateur via SIGB et si réussi affecte l'id_sigb reçu
-   * @param $user Class_Users
-   * @return boolean true if successful
-   */
-  public function ilsdiAuthenticatePatron($user) {
-    $params = ['service' => 'AuthenticatePatron',
-               'username' => $user->getLogin(),
-               'password' => $user->getPassword()];
-
-    $xml = $this->httpGet($params);
-
-    if ('' != $patronId = $this->_getTagData($xml, 'patronId')) {
-      $user->setIdSigb($patronId);
-      return true;
-    }
-
-    return false;
-  }
-
-
   /**
    * @param array $params
    * @return array
diff --git a/library/Class/WebService/SIGB/Nanook.php b/library/Class/WebService/SIGB/Nanook.php
index 4e71a26a73f7614d116b9e7249efc1ebdde4973b..b7a22e09fdfc7af40e87b7f9d1cb476a006c1644 100644
--- a/library/Class/WebService/SIGB/Nanook.php
+++ b/library/Class/WebService/SIGB/Nanook.php
@@ -18,21 +18,40 @@
  * along with BOKEH; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
-class Class_WebService_SIGB_Nanook extends Class_WebService_SIGB_Abstract {
+class Class_WebService_SIGB_Nanook  {
+  protected static $services = [];
+
+  public static function makeKey($params) {
+    return md5(serialize($params));
+  }
 
   public static function getService($params){
+    $key = static::makeKey($params);
+
     if (!isset($params['provide_suggest']))
       $params = array_merge(['provide_suggest' => ''],
                             $params);
 
-    if (!isset(static::$service)) {
+
+    if (!isset(static::$services[$key])) {
       $instance = new static();
       $classname = get_called_class().'_Service';
-      static::$service = $classname::getService($params['url_serveur'],
-                                                $params['provide_suggest'] === '1');
+      $service = $classname::getService($params['url_serveur'],
+                                        $params['provide_suggest'] === '1');
+      static::$services[$key] = $service;
     }
 
-    return static::$service;
+    return static::$services[$key];
+  }
+
+
+  public static function setService($params, $service) {
+    static::$services[static::makeKey($params)] = $service;
+  }
+
+
+  public static function reset() {
+    static::$services = [];
   }
 
 }
diff --git a/library/Class/WebService/SIGB/Nanook/PatronPasswordNotSecure.php b/library/Class/WebService/SIGB/Nanook/PatronPasswordNotSecure.php
new file mode 100644
index 0000000000000000000000000000000000000000..36ed6a8b106d3e74d838f84e34db3202a5143c61
--- /dev/null
+++ b/library/Class/WebService/SIGB/Nanook/PatronPasswordNotSecure.php
@@ -0,0 +1,26 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class Class_WebService_SIGB_Nanook_PatronPasswordNotSecure extends Class_Entity {
+  use Trait_Singleton;
+
+}
diff --git a/library/Class/WebService/SIGB/Nanook/Service.php b/library/Class/WebService/SIGB/Nanook/Service.php
index 457b1a8b43a55e59f7e1f24583d2932c150c4cdf..7c38e2365de84bb67e375b18f7ff02963e6de69c 100644
--- a/library/Class/WebService/SIGB/Nanook/Service.php
+++ b/library/Class/WebService/SIGB/Nanook/Service.php
@@ -60,6 +60,38 @@ class Class_Webservice_SIGB_Nanook_Service extends Class_WebService_SIGB_Abstrac
   }
 
 
+  /**
+   * Authentifie un utilisateur via SIGB et si réussi affecte l'id_sigb reçu
+   * @param $user Class_Users
+   * @return boolean true if successful
+   */
+  public function ilsdiAuthenticatePatron($user) {
+    $params = ['service' => 'AuthenticatePatron',
+               'username' => $user->getLogin(),
+               'password' => $user->getPassword()];
+
+    $xml = $this->httpGet($params);
+
+    if ('PatronPasswordNotSecure' == $this->_getTagData($xml, 'error')) {
+      $message = $this->_getTagData($xml, 'securePasswordLabel');
+      $pattern = '/' . $this->_getTagData($xml, 'securePasswordPattern') . '/';
+
+      Class_WebService_SIGB_Nanook_PatronPasswordNotSecure::getInstance()
+        ->setMessage($message)
+        ->setPattern($pattern);
+
+      return false;
+    }
+
+    if ('' != $patronId = $this->_getTagData($xml, 'patronId')) {
+      $user->setIdSigb($patronId);
+      return true;
+    }
+
+    return false;
+  }
+
+
   /**
    * @param Class_Users $user
    * @return Class_WebService_SIGB_Emprunteur
diff --git a/library/ZendAfi/Form/SecurePassword.php b/library/ZendAfi/Form/SecurePassword.php
new file mode 100644
index 0000000000000000000000000000000000000000..6692e75c543eb05f655a0f4dbf0625556a79feac
--- /dev/null
+++ b/library/ZendAfi/Form/SecurePassword.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_Form_SecurePassword extends ZendAfi_Form {
+  public function init() {
+    parent::init();
+
+    $this->addElement('text',
+                      'card',
+                      ['label' => $this->_('Numéro de carte'),
+                       'required' => true,
+                       'allowEmpty' => false])
+
+         ->addElement('password',
+                      'secure_password',
+                      ['label' => $this->_('Nouveau mot de passe'),
+                       'required' => true,
+                       'renderPassword' => true,
+                       'allowEmpty' => false])
+
+         ->addElement('password',
+                      'confirm_password',
+                      ['label' => $this->_('Confirmez le mot de passe'),
+                       'required' => true,
+                       'renderPassword' => true,
+                       'allowEmpty' => false,
+                       'validators' => [new ZendAfi_Validate_PasswordEquals('secure_password')]])
+
+         ->addElement('hidden', 'current_password')
+         ->addElement('hidden', 'password_hint')
+         ->addElement('hidden', 'pattern')
+
+         ->addUniqDisplayGroup('password', ['legend' => ''])
+      ;
+  }
+}
diff --git a/tests/application/modules/opac/controllers/AuthControllerWithNanookTest.php b/tests/application/modules/opac/controllers/AuthControllerWithNanookTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..f60991fb2144a922cc61178c975f82bd05a60479
--- /dev/null
+++ b/tests/application/modules/opac/controllers/AuthControllerWithNanookTest.php
@@ -0,0 +1,310 @@
+<?php
+/**
+ * Copyright (c) 2012, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+abstract class AuthControllerNanookTestCase extends AbstractControllerTestCase {
+
+  protected
+    $_storm_default_to_volatile = true,
+    $_web_client,
+    $_service;
+
+  public function setUp() {
+    parent::setUp();
+
+    Class_WebService_SIGB_Nanook::reset();
+
+    $this->_web_client = $this
+      ->mock()
+      ->whenCalled('open_url')
+      ->with('http://localhost:8080/afi_Nanook/ilsdi/service/AuthenticatePatron/username/name%40server.tld/password/1987')
+      ->answers('<?xml version="1.0" encoding="UTF-8"?><AuthenticatePatron><patronId>4</patronId><error>PatronPasswordNotSecure</error>'
+                . '<securePasswordPattern>^(?=.*[A-Za-z])(?=.*\d).{6,}$</securePasswordPattern>'
+                . '<securePasswordLabel>Le mot de passe doit comporter au minimum 6 caractères et doit être constitué d\'au moins un chiffre et une lettre</securePasswordLabel>'
+                . '</AuthenticatePatron>')
+      ->beStrict();
+
+    $this->_service = Class_WebService_SIGB_Nanook_Service::newInstance()
+      ->setServerRoot('http://localhost:8080/afi_Nanook/ilsdi/')
+      ->setWebClient($this->_web_client);
+
+    $params = ['url_serveur' => 'http://localhost:8080/afi_Nanook/ilsdi/',
+               'id_bib' => 5,
+               'type' => Class_IntBib::COM_NANOOK];
+
+    Class_WebService_SIGB_Nanook::setService($params, $this->_service);
+
+    ZendAfi_Auth::getInstance()->clearIdentity();
+
+    $this->fixture('Class_Bib',
+                   ['id' => 5,
+                    'libelle' => 'Seynod']);
+
+    $this->fixture('Class_IntBib',
+                   ['id' => 5,
+                    'comm_sigb' => Class_IntBib::COM_NANOOK,
+                    'comm_params' => serialize($params)]);
+  }
+
+
+  public function tearDown() {
+    Class_WebService_SIGB_Nanook::reset();
+    parent::tearDown();
+  }
+}
+
+
+
+
+class AuthControllerWithNanookPostLoginWithMailAndUnsecurePassword
+  extends AuthControllerNanookTestCase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->postDispatch('/opac/auth/boite-login', ['username' => 'name@server.tld',
+                                                   'password' => '1987']);
+  }
+
+  /** @test */
+  public function pageShouldContainsParagraphWithExplanation() {
+    $this->assertXPathContentContains('//p', 'Vous vous connectez');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsPasswordExplanation() {
+    $this->assertXPathContentContains('//p', 'Le mot de passe doit comporter au minimum 6');
+  }
+
+
+  /** @test */
+  public function formShouldContainsInputPasswordHint() {
+    $this->assertXPath('//form//input[@type="hidden"][contains(@value, "Le mot de passe")][@name="password_hint"]');
+  }
+
+
+/** @test */
+  public function formShouldContainsInputPattern() {
+    $this->assertXPath('//form//input[@type="hidden"][@value="/^(?=.*[A-Za-z])(?=.*\d).{6,}$/"][@name="pattern"]');
+  }
+
+
+  /** @test */
+  public function responseShouldContainsFormWithCard() {
+    $this->assertXPath('//form//input[@name="card"]');
+  }
+
+
+  /** @test */
+  public function formShouldContainsCurrentPasswordField() {
+    $this->assertXPath('//form//input[@type="hidden"][@name="current_password"][@value="1987"]');
+  }
+
+
+  /** @test */
+  public function formShouldContainsSecurePasswordField() {
+    $this->assertXPath('//form//input[@type="password"][@name="secure_password"]');
+  }
+
+
+  /** @test */
+  public function formShouldContainsConfirmPasswordField() {
+    $this->assertXPath('//form//input[@type="password"][@name="confirm_password"]');
+  }
+
+
+  /** @test */
+  public function formActionShouldBeAuthSecurePassword() {
+    $this->assertXPath('//form[contains(@action, "/auth/do-secure-password")]', $this->_response->getBody());
+  }
+}
+
+
+
+
+class AuthControllerWithNanookPostSecurePasswordWithMailAndUnsecurePassword
+  extends AuthControllerNanookTestCase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->_web_client
+      ->whenCalled('open_url')
+      ->with('http://localhost:8080/afi_Nanook/ilsdi/service/AuthenticatePatron/username/ZBTIC1234/password/1987')
+      ->answers('<?xml version="1.0" encoding="UTF-8"?><AuthenticatePatron><patronId>9876</patronId></AuthenticatePatron>')
+
+      ->whenCalled('open_url')
+      ->with('http://localhost:8080/afi_Nanook/ilsdi/service/GetPatronInfo/patronId/9876')
+      ->answers('<?xml version="1.0" encoding="utf-8"?><GetPatronInfo><patronId>9876</patronId><mail>name@server.tld</mail><barcode>03456</barcode></GetPatronInfo>')
+
+      ->whenCalled('postData')
+      ->with('http://localhost:8080/afi_Nanook/ilsdi/service/UpdatePatronInfo/patronId/9876',
+             ['password' => 'rox@r#1',
+              'mail' => 'name@server.tld',
+              'phoneNumber' => ''])
+      ->answers('<?xml version="1.0" encoding="utf-8"?><GetPatronInfo><patronId>9876</patronId><mail>name@server.tld</mail><barcode>03456</barcode></GetPatronInfo>')
+
+    ->beStrict();
+
+
+    $this->postDispatch('/opac/auth/do-secure-password', ['card' => 'ZBTIC1234',
+                                                          'current_password' => '1987',
+                                                          'secure_password' => 'rox@r#1',
+                                                          'confirm_password' => 'rox@r#1',
+                                                          'pattern' => '/^(?=.*[^0-9])(?=.*[0-9]).{6,}$/',
+                                                          'password_hint' => 'doit faire au moins 6',
+                                                          ]);
+  }
+
+
+  /** @test */
+  public function responseShouldRedirect() {
+    $this->assertRedirectTo('/');
+  }
+
+
+  /** @test */
+  public function usersZBTIC1234ShouldHavePasswordRoxor() {
+    $this->assertNotNull(Class_Users::findFirstBy(['login' => 'ZBTIC1234',
+                                                   'password' => 'rox@r#1']));
+  }
+
+
+  /** @test */
+  public function responseShouldNotifyAccountSuccessfullySecured() {
+    $this->assertFlashMessengerContentContains('compte est sécurisé');
+  }
+
+
+  /** @test */
+  public function userShouldBeConnected() {
+    $this->assertNotNull(Class_Users::getIdentity());
+  }
+}
+
+
+
+
+class AuthControllerWithNanookPostDoSecurePasswordWithWrongCard
+  extends AuthControllerNanookTestCase {
+  public function setUp() {
+    parent::setUp();
+
+    $this->_web_client
+      ->whenCalled('open_url')
+      ->with('http://localhost:8080/afi_Nanook/ilsdi/service/AuthenticatePatron/username/ZBTIC1234/password/1987')
+      ->answers('<?xml version="1.0" encoding="UTF-8"?><AuthenticatePatron><error>PatronNotFound</error></AuthenticatePatron>')
+      ->beStrict();
+
+    $this->postDispatch('/opac/auth/do-secure-password', ['card' => 'ZBTIC1234',
+                                                          'current_password' => '1987',
+                                                          'secure_password' => 'rox@r#1',
+                                                          'confirm_password' => 'rox@r#1',
+                                                          'pattern' => '/^(?=.*[^0-9])(?=.*[0-9]).{6,}$/',
+                                                          'password_hint' => 'doit faire au moins 6',
+                                                          ]);
+  }
+
+
+  /** @test */
+  public function formShouldDisplayErrorBadCard() {
+    $this->assertXPathContentContains('//li', 'Numéro de carte incorrect');
+  }
+
+
+  /** @test */
+  public function passwordShouldContains1987() {
+    $this->assertXPath('//form//input[@type="hidden"][@name="current_password"][@value="1987"]');
+  }
+
+
+  /** @test */
+  public function inputCardShouldContainsZBTIC1234() {
+    $this->assertXPath('//form//input[@type="text"][@name="card"][@value="ZBTIC1234"]');
+  }
+
+
+  /** @test */
+  public function securePasswordShouldContainsRoxor() {
+    $this->assertXPath('//form//input[@type="password"][@name="secure_password"][@value="rox@r#1"]');
+  }
+
+
+  /** @test */
+  public function confirmPasswordShouldContainsRoxor() {
+    $this->assertXPath('//form//input[@type="password"][@name="confirm_password"][@value="rox@r#1"]');
+  }
+}
+
+
+
+class AuthControllerWithNanookPostDoSecurePasswordWithErrorsTest
+  extends AuthControllerNanookTestCase {
+
+  /** @test */
+  public function formShouldDisplayErrorPasswordDoesNotMatch() {
+    $this->postDispatch('/opac/auth/do-secure-password', ['card' => 'ZBTIC1234',
+                                                          'current_password' => '1987',
+                                                          'secure_password' => 'rox@r#1',
+                                                          'confirm_password' => 'rouk1',
+                                                          'pattern' => '/^(?=.*[^0-9])(?=.*[0-9]).{6,}$/',
+                                                          'password_hint' => 'doit faire au moins 6',
+                                                          ]);
+    $this->assertXPathContentContains('//li', 'Les champs \'Mot de passe\' sont différents');
+  }
+
+
+  /** @test */
+  public function formShouldDisplayErrorPasswordDoesNotFollowPattern() {
+    $this->postDispatch('/opac/auth/do-secure-password', ['card' => 'ZBTIC1234',
+                                                          'current_password' => '1987',
+                                                          'secure_password' => 'pouet',
+                                                          'confirm_password' => 'pouet',
+                                                          'pattern' => '/^(?=.*[^0-9])(?=.*[0-9]).{6,}$/',
+                                                          'password_hint' => 'doit faire au moins 6',
+                                                          ]);
+    $this->assertXPathContentContains('//li', 'doit faire au moins 6');
+  }
+}
+
+
+
+
+class AuthControllerWithNanookPostLoginWithMailAndUnsecurePasswordOthersLogins
+  extends AuthControllerNanookTestCase {
+
+  /** @test */
+  public function withActionLoginShouldForwardToSecurePassword() {
+    $this->postDispatch('/opac/auth/login', ['username' => 'name@server.tld',
+                                             'password' => '1987']);
+
+    $this->assertXPathContentContains('//p', 'Vous vous connectez', $this->_response->getBody());
+  }
+
+
+  /** @test */
+  public function withActionAjaxLoginShouldForwardToSecurePassword() {
+    $this->postDispatch('/opac/auth/ajax-login', ['username' => 'name@server.tld',
+                                                  'password' => '1987']);
+
+    $this->assertXPathContentContains('//p', 'Vous vous connectez', $this->_response->getBody());
+  }
+}
\ No newline at end of file
diff --git a/tests/application/modules/opac/controllers/NoticeAjaxControllerTest.php b/tests/application/modules/opac/controllers/NoticeAjaxControllerTest.php
index 4137cb2d0307e9aca0f912fdc542e78b969ce29e..b33436331fe17be0ad14f2fd6940fc879d228916 100644
--- a/tests/application/modules/opac/controllers/NoticeAjaxControllerTest.php
+++ b/tests/application/modules/opac/controllers/NoticeAjaxControllerTest.php
@@ -645,6 +645,7 @@ class NoticeAjaxControllerResumeAlbumTest extends AbstractControllerTestCase {
 
 abstract class NoticeAjaxControllerExemplairesTestCase extends AbstractControllerTestCase {
   protected
+    $_storm_default_to_volatile=true,
     $_notice,
     $_sigb_exemplaire;
 
@@ -656,7 +657,8 @@ abstract class NoticeAjaxControllerExemplairesTestCase extends AbstractControlle
 
     $this->fixture('Class_IntBib',
                    ['id' => 4,
-                    'comm_sigb' => Class_IntBib::COM_NANOOK]);
+                    'comm_sigb' => Class_IntBib::COM_NANOOK,
+                    'comm_params' => ['url_serveur' => 'http://nanook']]);
 
     $this->fixture('Class_bib',
                    ['id' => 99]);
@@ -705,7 +707,10 @@ abstract class NoticeAjaxControllerExemplairesTestCase extends AbstractControlle
                            ->whenCalled('canConsult')
                            ->answers(false);
 
-    Class_WebService_SIGB_Nanook::setService($mock_sigb_comm);
+    Class_WebService_SIGB_Nanook::setService(['url_serveur' => 'http://nanook',
+                                              'id_bib' => 4,
+                                              'type' => Class_IntBib::COM_NANOOK,],
+                                             $mock_sigb_comm);
   }
 }
 
@@ -1415,7 +1420,7 @@ class NoticeAjaxControllerDisponibiliteNoticeTest extends AbstractControllerTest
 
 
 abstract class NoticeAjaxControllerNoticeWithAvisTestCase extends AbstractControllerTestCase {
-
+  protected $_storm_default_to_volatile = true;
 
   public function setup() {
     parent::setup();
diff --git a/tests/application/modules/opac/controllers/RechercheControllerReservationTest.php b/tests/application/modules/opac/controllers/RechercheControllerReservationTest.php
index f84f21dae803badda27e58e8fb88f59db7028183..1cb371b15aaf553675669fceb365452364f3f269 100644
--- a/tests/application/modules/opac/controllers/RechercheControllerReservationTest.php
+++ b/tests/application/modules/opac/controllers/RechercheControllerReservationTest.php
@@ -104,27 +104,31 @@ class RechercheControllerReservationPickupAjaxActionPostTest extends AbstractCon
 
 
 class RechercheControllerReservationPickupAjaxActionTestWithChosenPickupDispatch extends AbstractControllerTestCase {
-  protected $_json, $_xpath;
+  protected $_json, $_xpath, $_storm_default_to_volatile = true;
 
   public function setUp() {
     parent::setUp();
 
-    $this->jajm = $this->fixture('Class_Users',
-                                 ['id' => 1,
-                                  'login' => 'jajm',
-                                  'password' => 'secret',
-                                  'idabon' => '0000007',
-                                  'int_bib' => $this->fixture('Class_IntBib',
-                                                              ['id' => 1,
-                                                               'comm_sigb' => Class_IntBib::COM_NANOOK,
-                                                               'comm_params' => ['url_serveur' => 'http://bib.valensol.net']])
-                                   ]);
+    $this->jajm = $this
+      ->fixture('Class_Users',
+                ['id' => 1,
+                 'login' => 'jajm',
+                 'password' => 'secret',
+                 'idabon' => '0000007',
+                 'int_bib' => $this->fixture('Class_IntBib',
+                                             ['id' => 1,
+                                              'comm_sigb' => Class_IntBib::COM_NANOOK,
+                                              'comm_params' => ['url_serveur' => 'http://bib.valensol.net']])
+                ]);
 
     $this->nanook = Storm_Test_ObjectWrapper::mock()
       ->whenCalled('isConnected')->answers(true)
       ->whenCalled('reserverExemplaire')->answers(true);
 
-    Class_WebService_SIGB_Nanook::setService($this->nanook);
+    Class_WebService_SIGB_Nanook::setService(['url_serveur' => 'http://bib.valensol.net',
+                                              'id_bib' => 1,
+                                              'type' => Class_IntBib::COM_NANOOK,],
+                                             $this->nanook);
 
     ZendAfi_Auth::getInstance()->logUser($this->jajm);
 
@@ -148,8 +152,9 @@ class RechercheControllerReservationPickupAjaxActionTestWithChosenPickupDispatch
 }
 
 
-class RechercheControllerReservationPickupAjaxActionTestWithPatronLibraryPickup extends AbstractControllerTestCase {
-  protected $_json, $_xpath;
+class RechercheControllerReservationPickupAjaxActionTestWithPatronLibraryPickup
+  extends AbstractControllerTestCase {
+  protected $_json, $_xpath, $_storm_default_to_volatile = true;
 
   public function setUp() {
     parent::setUp();
@@ -171,7 +176,10 @@ class RechercheControllerReservationPickupAjaxActionTestWithPatronLibraryPickup
       ->whenCalled('providesPickupLocations')->answers(false)
       ->whenCalled('getUserAnnexe')->answers('12');
 
-    Class_WebService_SIGB_Nanook::setService($this->nanook);
+    Class_WebService_SIGB_Nanook::setService(['url_serveur' => 'http://bib.valensol.net',
+                                              'id_bib' => 12,
+                                              'type' => Class_IntBib::COM_NANOOK,],
+                                             $this->nanook);
 
     ZendAfi_Auth::getInstance()->logUser($this->jajm);
 
@@ -207,7 +215,7 @@ class RechercheControllerReservationPickupAjaxActionTestWithPatronLibraryPickup
 
 
 class RechercheControllerReservationPickupAjaxActionTestWithItemLibraryPickup extends AbstractControllerTestCase {
-  protected $_json, $_xpath;
+  protected $_json, $_xpath, $_storm_default_to_volatile=true;
 
   public function setUp() {
     parent::setUp();
@@ -229,7 +237,10 @@ class RechercheControllerReservationPickupAjaxActionTestWithItemLibraryPickup ex
       ->whenCalled('reserverExemplaire')->answers(true);
 
 
-    Class_WebService_SIGB_Nanook::setService($this->nanook);
+    Class_WebService_SIGB_Nanook::setService(['url_serveur' => 'http://bib.valensol.net',
+                                              'id_bib' => 1,
+                                              'type' => Class_IntBib::COM_NANOOK,],
+                                             $this->nanook);
 
     ZendAfi_Auth::getInstance()->logUser($this->jajm);
 
diff --git a/tests/library/Class/CommSigbTest.php b/tests/library/Class/CommSigbTest.php
index a9b31874825dff5da983445029ec32359cd176cf..e582ffabe34bf8638c5054c5d58f93f3200458ef 100644
--- a/tests/library/Class/CommSigbTest.php
+++ b/tests/library/Class/CommSigbTest.php
@@ -478,21 +478,24 @@ class CommSigbLocalNanookTest extends CommSigbTestCase {
   public function setUp() {
     parent::setUp();
 
-    $this->bib_pontault = Class_IntBib::getLoader()
-      ->newInstanceWithId(5)
-      ->setCommParams(array("url_serveur" => 'http://192.168.2.3:9080/afi_Nanook-0.7.5/ilsdi/'))
+    $params = ["url_serveur" => 'http://192.168.2.3:9080/afi_Nanook-0.7.5/ilsdi/'];
+    $this->bib_pontault = Class_IntBib::newInstanceWithId(5)
+      ->setCommParams(["url_serveur" => 'http://192.168.2.3:9080/afi_Nanook-0.7.5/ilsdi/'])
       ->setCommSigb(7);
 
 
-    Class_WebService_SIGB_Nanook::setService($this->createMockForService('Nanook'));
+    Class_WebService_SIGB_Nanook::setService(['url_serveur' => 'http://192.168.2.3:9080/afi_Nanook-0.7.5/ilsdi/',
+                                              'id_bib' => 5,
+                                              'type' => Class_IntBib::COM_NANOOK,],
+                                             $this->createMockForService('Nanook'));
   }
 
 
   /** @test */
   public function getModeCommShouldReturnAnArrayWithCommParams() {
-    $this->assertEquals(array("url_serveur" => 'http://192.168.2.3:9080/afi_Nanook-0.7.5/ilsdi/',
-                              "type" => Class_IntBib::COM_NANOOK,
-                              'id_bib' => 5),
+    $this->assertEquals(["url_serveur" => 'http://192.168.2.3:9080/afi_Nanook-0.7.5/ilsdi/',
+                         "type" => Class_IntBib::COM_NANOOK,
+                         'id_bib' => 5],
                         $this->bib_pontault->getModeComm(5));
   }
 }
diff --git a/tests/library/ZendAfi/Auth/Adapter/AuthCommSigbTest.php b/tests/library/ZendAfi/Auth/Adapter/AuthCommSigbTest.php
index fa6a681fb76d6a264d46e3cfd1c5c340e077880d..485d959306a23ac2fe5640e3983096e4fe9b9cfb 100644
--- a/tests/library/ZendAfi/Auth/Adapter/AuthCommSigbTest.php
+++ b/tests/library/ZendAfi/Auth/Adapter/AuthCommSigbTest.php
@@ -74,13 +74,18 @@ abstract class AuthCommSigbWithWebServicesAndAbonneZorkTestCase extends AuthComm
   public function setUp() {
     parent::setUp();
 
-    $this->fixture('Class_IntBib', ['id' => 1, 'comm_sigb' => Class_IntBib::COM_NANOOK]);
+    $comm_params = ['url_serveur' => 'http://localhost:8080/afi_Nanook/ilsdi/',
+                    'id_bib' => 1,
+                    'type' => Class_IntBib::COM_NANOOK];
+    $this->fixture('Class_IntBib', ['id' => 1,
+                                    'comm_sigb' => Class_IntBib::COM_NANOOK,
+                                    'comm_params' => $comm_params]);
     $this->fixture('Class_IntBib', ['id' => 95, 'comm_sigb' => Class_IntBib::COM_ORPHEE]);
     $this->fixture('Class_IntBib', ['id' => 74,
                                     'comm_sigb' => Class_IntBib::COM_OPSYS,
                                     'nom_court' => 'TestingOpsys']);
 
-    Class_WebService_SIGB_Nanook::setService($this->nanook = $this->mock());
+    Class_WebService_SIGB_Nanook::setService($comm_params, $this->nanook = $this->mock());
     Class_WebService_SIGB_Orphee::setService($this->orphee = $this->mock());
     Class_WebService_SIGB_Opsys::setService($this->opsys = $this->mock());
 
@@ -119,7 +124,7 @@ abstract class AuthCommSigbWithWebServicesAndAbonneZorkTestCase extends AuthComm
 
 
   public function tearDown() {
-    Class_WebService_SIGB_Nanook::setService(null);
+    Class_WebService_SIGB_Nanook::reset();
     Class_WebService_SIGB_Orphee::setService(null);
     Class_WebService_SIGB_Opsys::setService(null);