Commit 80bba130 authored by efalcy's avatar efalcy

dev#106075 fix tests

parents 1ab09920 e9dd05b5
Pipeline #9636 passed with stage
in 43 minutes and 50 seconds
......@@ -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;
}
......
......@@ -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);
......
......@@ -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',
......
......@@ -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;
}
......
......@@ -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;
}
}
......
......@@ -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);
}
......
......@@ -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');
}
}
......@@ -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;
......
......@@ -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}
......@@ -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() {