diff --git a/library/Class/IdentityProvider.php b/library/Class/IdentityProvider.php index e3052f9787c3f6202c03f6289c851aa119aeba34..3efdcc7f9c3e25adc96586b8eae98d9785e5181e 100644 --- a/library/Class/IdentityProvider.php +++ b/library/Class/IdentityProvider.php @@ -56,7 +56,8 @@ class Class_IdentityProvider extends Storm_Model_Abstract{ 'nonce', 'logout_url', 'button_login', - 'button_logout'], + 'button_logout', + 'prod_url' ], $_context; @@ -79,8 +80,8 @@ class Class_IdentityProvider extends Storm_Model_Abstract{ public function isAttachable() { $user = Class_Users::getIdentity(); - return !($user && $this->isRemotelyLogged()) - && !$this->isAssociatedTo($user); + return !($user && $this->isRemotelyLogged()); +// && !$this->isAssociatedTo($user); } @@ -212,6 +213,7 @@ class Class_IdentityProvider extends Storm_Model_Abstract{ $config = $this->getConfigAsArray(); $config[$key] = $value; $this->setConfig(json_encode($config)); + return $this; } diff --git a/library/Class/IdentityProvider/Franceconnect.php b/library/Class/IdentityProvider/Franceconnect.php index 334ecf541b90d1bcb38e413a3adeb7047a81d496..39acbf3b071f9dd8d5cc9e54166a32948522ab89 100644 --- a/library/Class/IdentityProvider/Franceconnect.php +++ b/library/Class/IdentityProvider/Franceconnect.php @@ -22,10 +22,10 @@ class Class_IdentityProvider_Franceconnect extends Class_IdentityProvider_Default { protected $_script_logout = '<div id="fconnect-profile" data-fc-logout-url="[URL]"><a href="#">[USERNAME]</a></div> -<script src="https://fcp.integ01.dev-franceconnect.fr/js/franceconnect.js"></script>'; +<script src="[URL_FC]/js/franceconnect.js"></script>'; - protected $_config = ['url' => 'https://fcp.integ01.dev-franceconnect.fr/api/v1', - 'logout_url' => 'https://fcp.integ01.dev-franceconnect.fr/api/v1/logout', + protected $_config = ['url' => '[URL_FC]/api/v1', + 'logout_url' => '[URL_FC]/api/v1/logout', 'button_login' => 'franceconnect', ]; @@ -35,7 +35,16 @@ class Class_IdentityProvider_Franceconnect extends Class_IdentityProvider_Defaul } + public function getFranceConnectUrl() { + return parent::getParam('prod_url') + ? 'https://app.franceconnect.gouv.fr' + :'https://fcp.integ01.dev-franceconnect.fr'; + } + + public function getParam($key) { + $this->_config['url'] = str_replace('[URL_FC]',$this->getFranceConnectUrl(),$this->_config['url']); + $this->_config['logout_url'] = str_replace('[URL_FC]',$this->getFranceConnectUrl(),$this->_config['logout_url']); return 'button_logout' == $key ? $this->_buttonLogout() : parent::getParam($key); @@ -50,6 +59,8 @@ class Class_IdentityProvider_Franceconnect extends Class_IdentityProvider_Defaul 'provider' => $this->_provider->getId()]), $script); + $script = str_replace('[URL_FC]',$this->getFranceConnectUrl(),$script); + if ($user = Class_Users::getIdentity()) return str_replace('[USERNAME]', $user->getNomAff(), $script); diff --git a/library/Class/Testing/PhpCommand.php b/library/Class/Testing/PhpCommand.php index a4137afc850f3e12fcec53ad90326d3d328d4cea..0bea95abc047254d520c631049d2294e5d82decb 100644 --- a/library/Class/Testing/PhpCommand.php +++ b/library/Class/Testing/PhpCommand.php @@ -23,6 +23,7 @@ class Class_Testing_PhpCommand extends Class_Testing_FileSystem { public function __construct() { $this->_known_functions = array_merge($this->_known_functions, ['rand', + 'sha1', 'hash', 'password_hash', 'extension_loaded', diff --git a/library/Class/WebService/Acheteza.php b/library/Class/WebService/Acheteza.php index 81c13dce5e358daec6d738f674c8f4c583b7cfe2..c6b7acb7b74863889173681da2a7844add6e4b12 100644 --- a/library/Class/WebService/Acheteza.php +++ b/library/Class/WebService/Acheteza.php @@ -29,18 +29,11 @@ class Class_WebService_Acheteza extends Class_WebService_IdentityProvider { || (!$pan = $context->getParam('pan'))) return $this; - /* - if (!$this->_checkAccessToken($token, $pan)) + + if (!$this->_checkAccessToken($token, $pan)) return $this; - */ $this->loginWith($pan); - return $this; - - /* - if ($id = $this->_getRemoteId($token)) - $this->loginWith($id); - */ return $this; } diff --git a/library/Class/WebService/IdentityProvider.php b/library/Class/WebService/IdentityProvider.php index 219dcb49962aa23f96bef9e0fec36a2517f30409..868b2a3db46e47eb0999e5edb16999a1b28dcbc6 100644 --- a/library/Class/WebService/IdentityProvider.php +++ b/library/Class/WebService/IdentityProvider.php @@ -23,26 +23,45 @@ abstract class Class_WebService_IdentityProvider { use Trait_SimpleWebClient, Trait_TimeSource; - /** @var ZendAfi_Session_Namespace */ - protected static $_session_namespace; + /** @var array of ZendAfi_Session_Namespace */ + protected static $_session_namespace = [], + $_expiration_delay = 300; /** @var Class_IdentityProvider */ protected $_provider; + /** @return ZendAfi_Session_Namespace */ public static function getSession() { - if (static::$_session_namespace) - return static::$_session_namespace; - static::$_session_namespace = (new ZendAfi_Session_Namespace(md5(BASE_URL . get_called_class()))) - ->setExpirationDelay(300); + $called_class = get_called_class(); + if (isset(static::$_session_namespace[$called_class])) + return static::$_session_namespace[$called_class]; + + static::$_session_namespace[$called_class] = (new ZendAfi_Session_Namespace(md5(BASE_URL . get_called_class()))) + ->setExpirationDelay(static::$_expiration_delay); + + return static::$_session_namespace[$called_class]; + } + - return static::$_session_namespace; + public static function setExpirationDelay($expires_at) { + static::$_session_namespace[get_called_class()]->setExpirationDelay($expires_at); + static::$_expiration_delay = $expires_at; } public static function clearSession() { static::getSession()->unsetAll(); + static::$_session_namespace[get_called_class()] = null; + } + + + public static function clearAllSession() { + foreach (static::$_session_namespace as $namespace) { + static::$_session_namespace[$namespace]->unsetAll(); + static::$_session_namespace[$namespace] = null; + } } diff --git a/library/Class/WebService/OpenId.php b/library/Class/WebService/OpenId.php index 21744ea8c7a6ebf131385bbacbc8019044dba5e8..99d35982061c465d62b4b0b81730a4ae5433b5a6 100644 --- a/library/Class/WebService/OpenId.php +++ b/library/Class/WebService/OpenId.php @@ -23,12 +23,13 @@ require_once 'library/php-jwt/autoload.php'; require_once 'library/phpseclib/autoload.php'; class Class_WebService_OpenId extends Class_WebService_IdentityProvider { + use Trait_StaticPhpCommand; protected $_client_id, $_client_secret = '' , $_url='', - $_nonce = true, + $_nonce, $_authorization_url = '', $_token_url = '', $_userinfo_url = '', @@ -43,6 +44,7 @@ class Class_WebService_OpenId extends Class_WebService_IdentityProvider { public static function getUserId() { + if (!static::isLogged()) return null; @@ -133,12 +135,14 @@ class Class_WebService_OpenId extends Class_WebService_IdentityProvider { public function getUserInfos($code, $state) { + if (isset($this->getSession()->userinfo)) return $this->getSession()->userinfo; if ($state != $this->getState()) return false; + if (!$access_token = $this->_getAndCheckAccessToken($code)) return []; @@ -166,13 +170,15 @@ class Class_WebService_OpenId extends Class_WebService_IdentityProvider { protected function _checkNonce($nonce) { - return $this->getNonce() == $nonce; + return ($this->getNonce() == $nonce); } protected function _getAndCheckAccessToken($code) { - if (isset($this->getSession()->access_token)) + if (isset($this->getSession()->access_token)) { + $this->setExpirationDelay($this->getSession()->expires_in); return $this->getSession()->access_token; + } $http_client = $this->getWebClient(); $post_data = [ @@ -187,17 +193,29 @@ class Class_WebService_OpenId extends Class_WebService_IdentityProvider { 'application/json'); $json = json_decode($response); + if (!isset($json->access_token)|| !isset($json->id_token)) return ; $this->getSession()->access_token = $json->access_token; + if (isset($json->expires_in)) { + $this->setExpirationDelay($json->expires_in); + $this->getSession()->expires_in = $json->expires_in; + } + + $this->getSession()->token = $json->id_token; - if (!$this->_nonce) + if (!$this->_nonce) { return $json->access_token; + } + if (! $certifs = $this->_getCertificates()) $certifs = $this->_client_secret; + \Firebase\JWT\JWT::$leeway = 2; + + $response =\Firebase\JWT\JWT::decode($json->id_token, $certifs, ['HS256','RS256']); @@ -208,7 +226,6 @@ class Class_WebService_OpenId extends Class_WebService_IdentityProvider { if (!$this->_checkNonce($response->nonce)) return ; - $this->getSession()->token = $json->id_token; return $json->access_token; } @@ -238,7 +255,7 @@ class Class_WebService_OpenId extends Class_WebService_IdentityProvider { && ($state = $this->getSession()->state)) return $state; - return $this->_getRandomToken(); + return $this->getSession()->state = $this->_getRandomToken(); } @@ -252,7 +269,7 @@ class Class_WebService_OpenId extends Class_WebService_IdentityProvider { protected function _getRandomToken(){ - return sha1(mt_rand(0, mt_getrandmax())); + return $this->getPhpCommand()->sha1(mt_rand(0, mt_getrandmax())); } @@ -278,8 +295,8 @@ class Class_WebService_OpenId extends Class_WebService_IdentityProvider { 'scope' => 'openid', 'state' => $this->getState()]; - if ($this->_nonce) - $params['nonce'] = $this->getNonce(); +// if ($this->_nonce) + $params['nonce'] = $this->getNonce(); return $this->_authorization_url . '?' . http_build_query($params); } diff --git a/library/ZendAfi/Form/Admin/IdentityProvider.php b/library/ZendAfi/Form/Admin/IdentityProvider.php index 436c83113b5023034f0d5f80b76f81de35320b39..8282bdac1ec425174f8ad7e2721353e3c3716df2 100644 --- a/library/ZendAfi/Form/Admin/IdentityProvider.php +++ b/library/ZendAfi/Form/Admin/IdentityProvider.php @@ -25,7 +25,9 @@ class ZendAfi_Form_Admin_IdentityProvider extends ZendAfi_Form { parent::init(); Class_ScriptLoader::getInstance() - ->addJQueryBackEnd('formSelectToggleVisibilityForElement("#type", $("#url, #url_api, #button_logout, #logout_url").closest("tr"), ["default", "acheteza"]);'); + ->addJQueryBackEnd('formSelectToggleVisibilityForElement("#type", $("#url, #url_api, #button_logout, #logout_url").closest("tr"), ["default", "acheteza"]);') + ->addJQueryBackEnd('formSelectToggleVisibilityForElement("#type", $("#prod_url").closest("tr"), ["franceconnect"]);'); + $this ->addElement('text', @@ -46,14 +48,14 @@ class ZendAfi_Form_Admin_IdentityProvider extends ZendAfi_Form { ->addElement('text', 'client_id', - ['label' => $this->_('client_id'), + ['label' => $this->_('Identifiant client'), 'size' => 50, 'required' => true, 'allowEmpty' => false]) ->addElement('text', 'client_secret', - ['label' => $this->_('client_secret'), + ['label' => $this->_('Clé secrète'), 'size' => 50, 'required' => true, 'allowEmpty' => false]) @@ -82,6 +84,11 @@ class ZendAfi_Form_Admin_IdentityProvider extends ZendAfi_Form { ['label' => $this->_('Url de logout'), 'title' => $this->_('Url de logout')]) + ->addElement('checkbox', + 'prod_url', + ['label' => $this->_('En production'), + 'title' => $this->_('En production')]) + ->addUniqDisplayGroup('provider'); } } diff --git a/library/ZendAfi/Session/Namespace.php b/library/ZendAfi/Session/Namespace.php index 851dad44001b9d5744d29a32f498190d385dfcd6..157bab066ecf6559be68b34b53a4ffdac71765e8 100644 --- a/library/ZendAfi/Session/Namespace.php +++ b/library/ZendAfi/Session/Namespace.php @@ -30,7 +30,7 @@ class ZendAfi_Session_Namespace extends Zend_Session_Namespace { public function __construct($namespace = 'Default', $singleInstance = false) { parent::__construct($namespace, $singleInstance); if ($this->_isExpired()) - $this->unsetAll(); + $this->unsetAll(); } @@ -45,10 +45,19 @@ class ZendAfi_Session_Namespace extends Zend_Session_Namespace { public function setExpirationDelay($seconds) { $this->_expiration_delay = (int)$seconds; + + parent::__set(static::EXPIRATION_VAR_NAME, + $this->getTimeSource()->time() + $this->_expiration_delay); + return $this; } + public function getExpirationDelay() { + return $this->_expiration_delay; + } + + protected function _isExpired() { $expires_at = $this->__get(static::EXPIRATION_VAR_NAME); return isset($expires_at) && $this->getTimeSource()->time() > $expires_at; diff --git a/public/opac/css/global.css b/public/opac/css/global.css index 9d1a4f995cf64af8c3baffb35e6a57a4fded6e19..0d021851a4eab7e0d3299ba6fc2abe44897d482c 100644 --- a/public/opac/css/global.css +++ b/public/opac/css/global.css @@ -3817,3 +3817,4 @@ a[href*="bookmarked-searches/notify"] img { /* identity provider widget */ .boite.identity_provider .contenu {overflow:visible} .boite.identity_provider #fconnect-profile {margin: 20px 10px} +.boite.identity_provider #fconnect-access {position: relative!important} diff --git a/tests/scenarios/IdentityProvider/IdentityProviderAuthenticationTest.php b/tests/scenarios/IdentityProvider/IdentityProviderAuthenticationTest.php index 510c3692aec4736526862a8fcca686d31983cfae..ee3a6311080fa63d8058cb2440f4200a9351a125 100644 --- a/tests/scenarios/IdentityProvider/IdentityProviderAuthenticationTest.php +++ b/tests/scenarios/IdentityProvider/IdentityProviderAuthenticationTest.php @@ -278,20 +278,19 @@ abstract class IdentityProviderAuthenticationCallbackAuthenticationTestCase 'iss'=>'http://franceconnect.gouv.fr', 'sub'=>'YWxhY3JpdMOp', 'idp'=> 'FC', - 'nonce'=>'1234' + 'nonce'=>'1234', ], '8888'); $response_token = json_encode(['access_token' => 1111, 'token_type' => 'Bearer', - 'expires_in' => '60', + 'expires_in' => 60, 'id_token' => $token]); $web_client = $this->mock(); $web_client ->whenCalled('open_url') ->with('https://fcp.integ01.dev-franceconnect.fr/api/v1/.well-known/openid-configuration') ->answers(null) - ->whenCalled('open_url') ->with('https://fcp.integ01.dev-franceconnect.fr/api/v1/userinfo?schema=openid', ['headers' => 'Authorization: Bearer 1111']) @@ -324,9 +323,28 @@ abstract class IdentityProviderAuthenticationCallbackAuthenticationTestCase Class_WebService_OpenId::clearSession(); parent::tearDown(); } + + + } +class IdentityProviderAuthenticationOpenIdFCTest extends IdentityProviderAuthenticationCallbackAuthenticationTestCase { + + /** @test */ + public function successSigbShouldSetExpirationDelay() { + $time_source = new TimeSourceForTest('2020-02-17 10:21:38'); + Class_WebService_OpenId::setPhpCommand($this->mock()->whenCalled('sha1')->answers('mystate')); + + ZendAfi_Auth::getInstance()->clearIdentity(); + $this->postDispatch('/auth/login/provider/1/code/1233/state/mystate', + ['username' => 'name@server.tld', + 'password' => '1987']); + + $this->assertEquals(60,Class_WebService_IdentityProvider::getSession()->getExpirationDelay()); + } +} + class IdentityProviderAuthenticationConnectedOpenidUserTest @@ -376,6 +394,48 @@ class IdentityProviderAuthenticationConnectedOpenidUserTest } +class IdentityProviderAuthenticationConnectedBokehNotConnectedOpenidUserTest + extends IdentityProviderAuthenticationCallbackAuthenticationTestCase { + + public function setUp() { + parent::setUp(); + ZendAfi_Auth::getInstance()->logUser(Class_Users::find(33)); + Class_WebService_OpenId::getSession()->userinfo = null; + + $simple_widgets = ['modules' => ['1' => ['division' => '1', + 'type_module' => 'IDENTITY_PROVIDER', + 'preferences' => []], + ]]; + + $this->fixture('Class_User_Identity', + ['id' => 1, + 'provider_id' => 1, + 'user_id' => 33, + 'identifier' => 1232 + ]); + + Class_Profil::getCurrentProfil() + ->setCfgAccueil($simple_widgets) + ->save(); + + $this->dispatch('/opac/index'); + } + + + /** @test */ + public function buttonLoginFCShouldBePresent() { + $this->assertXPath('//button[contains(@data-url,"/identity-providers/authenticate/id")]', $this->_response->getBody()); + } + + + /** @test */ + public function titleShouldBeCompteAssocie() { + $this->assertXPathContentContains('//div[@class="titre"]//h1','Associer votre compte',$this->_response->getBody()); + } + +} + + class IdentityProviderAuthenticationLoggedUserTest extends IdentityProviderAuthenticationCallbackAuthenticationTestCase { @@ -428,6 +488,32 @@ class IdentityProviderAuthenticationShouldDisplayButtonWithFCUrl } + + /** @test */ + public function authenticationToFranceConnectProdShouldRedirectToFranceConnectAuthorize() { + Class_IdentityProvider::find(1)->setConfigValue( 'prod_url',1)->save(); + $web_client = $this->mock(); + $web_client + ->whenCalled('open_url') + ->with('https://app.franceconnect.gouv.fr/api/v1/.well-known/openid-configuration') + ->answers(null); + Class_WebService_OpenId::setWebClient($web_client); + + + $_SERVER['HTTP_REFERER'] = 'referer'; + $this->_state = base64_encode('http://bokeh.org/mycurrenturl'); + $session = Class_WebService_OpenId::getSession(); + $session->state = $this->_state; + $session->nonce = $this->_nonce; + + $this->dispatch('/identity-providers/authenticate/id/1/redirect/'.urlencode('http://bokeh.org/mycurrenturl'));; + + $this->assertRedirectTo('https://app.franceconnect.gouv.fr/api/v1/authorize?response_type=code&client_id=1234&redirect_uri='.urlencode('http://localhost'.BASE_URL.'/auth/login/provider/1').'&scope=openid&state='.urlencode($this->_state) . "&nonce=" . $this->_nonce); + } + + + + public function tearDown() { unset($_SERVER['HTTP_REFERER']); parent::tearDown(); @@ -547,7 +633,7 @@ class IdentityProviderAuthenticationDissociateUserTest /** @test */ public function sessionShouldBeCleared() { - $this->assertEmpty(Class_WebService_OpenId::getSession()->getIterator()); + $this->assertNull(Class_WebService_OpenId::getSession()->userinfo); } } @@ -569,7 +655,8 @@ class IdentityProviderAuthenticationCallbackAuthentication $this->fixture('Class_IdentityProvider', ['id' => 12, 'label' => 'FranceConnect', - 'type' => 'franceconnect']); + 'type' => 'franceconnect', + ]); } @@ -617,11 +704,16 @@ class IdentityProviderAuthenticationCallbackAuthentication /** @test */ - public function logoutShouldRemoveOpenIdInSession() { + public function logoutShouldRemoveOpenIdTokeAndStateInSession() { Class_WebService_OpenId::getSession()->userinfo = '999'; + Class_WebService_OpenId::getSession()->token = '999'; + Class_WebService_OpenId::getSession()->state = '999'; ZendAfi_Auth::getInstance()->logUser(Class_Users::find(33)); + $this->dispatch('/auth/logout'); - $this->assertEmpty(Class_WebService_OpenId::getSession()->getIterator()); + $this->assertNull(Class_WebService_OpenId::getSession()->userinfo); + $this->assertNull(Class_WebService_OpenId::getSession()->token); + $this->assertNull(Class_WebService_OpenId::getSession()->state); } @@ -635,7 +727,7 @@ class IdentityProviderAuthenticationCallbackAuthentication ZendAfi_Auth::getInstance()->logUser(Class_Users::find(33)); $this->dispatch('/auth/logout/provider/1'); - $this->assertEmpty(Class_WebService_OpenId::getSession()->getIterator()); + $this->assertNull(Class_WebService_OpenId::getSession()->userinfo); $this->assertRedirectTo('https://fcp.integ01.dev-franceconnect.fr/api/v1/logout?id_token_hint=mytoken&state=6&post_logout_redirect_uri='.urlencode(Class_Url::absolute([], null, true))); $this->assertEmpty(Class_Users::getIdentity()); } @@ -670,7 +762,7 @@ class IdentityProviderAuthenticationAbonneListTest /** @test */ - public function withioutAssociatedFSMessageShouldBeDisplayed() { + public function withoutAssociatedFSMessageShouldBeDisplayed() { $this->dispatch('/abonne/associated-providers'); $this->assertXPathContentContains('//p', 'Votre compte n\'est pas associé ', $this->_response->getBody()); } @@ -869,12 +961,12 @@ class IdentityProviderAuthenticationAuthLoginWithPanAchetezaTest ->whenCalled('isError')->answers(false) ->whenCalled('getBody')->answers('{"success":"true"}')) - /* - ->whenCalled('open_url') - ->with('http://api.crm.server.com/api/v2.3/me', - ['headers' => ['Authorization: Bearer MYACCESSTOKEN']]) - ->answers(json_encode(['id' => '95fbcb97-6461-48de-8923-ef4f1de30409'])) - */ + + ->whenCalled('open_url') + ->with('http://api.crm.server.com/api/v2.3/me', + ['headers' => ['Authorization: Bearer MYACCESSTOKEN']]) + ->answers(json_encode(['id' => '95fbcb97-6461-48de-8923-ef4f1de30409'])) + ; $this->dispatch('/auth/login/provider/5?access_token=MYACCESSTOKEN&pan=8839'); @@ -898,6 +990,13 @@ class IdentityProviderAuthenticationAuthLoginWithPanAchetezaTest public function pageShouldDisplayConnectedToAcheteza() { $this->assertXPathContentContains('//p', 'Vous êtes connecté à Acheteza'); } + + + /** @test */ + public function logoutBokehShouldLogoutAcheteza() { + $this->dispatch('/auth/logout'); + $this->assertRedirect('/'); + } } @@ -1071,6 +1170,17 @@ class IdentityProviderAuthenticationAuthLoginSigbAchetezaTest } + /** @test */ + public function logoutShouldCleanSession() { + Class_WebService_Acheteza::getSession()->id = '999'; + ZendAfi_Auth::getInstance()->logUser(Class_Users::find(33)); + $this->dispatch('/auth/logout'); + $this->assertNull(Class_WebService_Acheteza::getSession()->userinfo); + $this->assertNull(Class_WebService_Acheteza::getSession()->token); + $this->assertNull(Class_WebService_Acheteza::getSession()->state); + } + + /** @test */ public function logoutProviderShouldRedirectToProviderLogoutUrl() { ZendAfi_Auth::getInstance()->logUser(Class_Users::find(33)); @@ -1105,11 +1215,28 @@ class IdentityProviderAuthenticationExpiringSessionAchetezaTest /** @test */ public function notLoggedRemoteIdShouldBeCleared() { - ZendAfi_Session_Namespace::setTimeSource(new TimeSourceForTest('2020-02-17 10:20:38')); - Class_WebService_Acheteza::loginWith(9763); - // jump to future past expiration time of 300s - ZendAfi_Session_Namespace::setTimeSource(new TimeSourceForTest('2020-02-17 10:55:38')); + $time_source = new TimeSourceForTest('2020-02-17 10:21:38'); + $_SESSION[md5(BASE_URL . 'Class_WebService_Acheteza')] = ['id' => 9763, + 'expires_at' => $time_source->time()-10]; + ZendAfi_Session_Namespace::setTimeSource($time_source); $this->dispatch('/opac/index/index'); $this->assertNull(Class_IdentityProvider::find(5)->getRemoteUserId()); } + + + /** @test */ + public function notLoggedRemoteIdShouldBeNotClearedIfNotExpired() { + $time_source = new TimeSourceForTest('2020-02-17 10:21:38'); + $_SESSION[md5(BASE_URL . 'Class_WebService_Acheteza')] = ['id' => 9763, + 'expires_at' => $time_source->time()+10 ]; + + ZendAfi_Session_Namespace::setTimeSource($time_source); + $this->dispatch('/opac/index/index'); + $this->assertEquals(9763,Class_IdentityProvider::find(5)->getRemoteUserId()); + } + + public function tearDown() { + unset($_SESSION[md5(BASE_URL . 'Class_WebService_Acheteza')]); + parent::tearDown(); + } } \ No newline at end of file