diff --git a/VERSIONS_WIP/59718 b/VERSIONS_WIP/59718 new file mode 100644 index 0000000000000000000000000000000000000000..d55499ca47dfa14007633fae3361f832a527b11b --- /dev/null +++ b/VERSIONS_WIP/59718 @@ -0,0 +1 @@ + - ticket #59718 : Intégration de l'enregistrement d'applications et d'authentification via OAuth \ No newline at end of file diff --git a/application/modules/api/controllers/UserController.php b/application/modules/api/controllers/UserController.php new file mode 100644 index 0000000000000000000000000000000000000000..2a512d43f495387bf0249695baac0262a64a93c8 --- /dev/null +++ b/application/modules/api/controllers/UserController.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright (c) 2012-2014, 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 Api_UserController extends ZendAfi_Controller_Action { + public function loansAction() { + if (!$this->_request->isSecure()) + return $this->_error($this->_('Protocole HTTP obligatoire')); + + if (!$authorization = $this->_request->getHeader('authorization')) + return $this->_error($this->_('Autorisation non spécifiée')); + + $parts = explode(' ', $authorization); + if ($parts[0] !== 'Bearer') + return $this->_error($this->_('Jeton d\'autorisation non fourni')); + + if (!$token = Class_User_ApiToken::findFirstBy(['token' => $parts[1]])) + return $this->_error($this->_('Jeton d\'autorisation invalide')); + + if (!$user = $token->getUser()) + return $this->_error($this->_('Utilisateur non trouvé')); + + $this->view->loans = (new Class_User_Cards($user))->getLoans(); + } + + + protected function _error($message) { + $this->view->message = $message; + return $this->renderScript('invalid_request.pjson'); + } +} +?> \ No newline at end of file diff --git a/application/modules/api/views/scripts/invalid_request.pjson b/application/modules/api/views/scripts/invalid_request.pjson new file mode 100644 index 0000000000000000000000000000000000000000..153d898f6f513c3f6da36abd64232443fbbe0c02 --- /dev/null +++ b/application/modules/api/views/scripts/invalid_request.pjson @@ -0,0 +1,4 @@ +{ + "error":"invalid_request", + "message":"<?php echo $this->message ?>" +} \ No newline at end of file diff --git a/application/modules/api/views/scripts/user/loans.pjson b/application/modules/api/views/scripts/user/loans.pjson new file mode 100644 index 0000000000000000000000000000000000000000..94d1684f134585f6fea7cbd946598ebe163f13d1 --- /dev/null +++ b/application/modules/api/views/scripts/user/loans.pjson @@ -0,0 +1,3 @@ +{ + "loans": <?php echo $this->loans($this->loans) ?> +} diff --git a/application/modules/opac/controllers/AuthController.php b/application/modules/opac/controllers/AuthController.php index f2cdd2421936a45e905b13bec78f669e131ec2e0..27c66144e3ab8736de027860f28bd0c59f476fd1 100644 --- a/application/modules/opac/controllers/AuthController.php +++ b/application/modules/opac/controllers/AuthController.php @@ -99,7 +99,6 @@ class AuthController extends ZendAfi_Controller_Action { $this->view->redirect = $redirect; $service = $this->_getParam('service',''); $this->view->service = $service; - $this->view->titreAdd($this->view->_('Connexion')); $this->view->title = Class_Users::getIdentity() @@ -138,6 +137,30 @@ class AuthController extends ZendAfi_Controller_Action { } + public function oauthAction() { + $validator = new ZendAfi_Validate_Url(); + if (('code' !== $this->_getParam('response_type')) + || !$this->_getParam('client_id') + || !$this->_getParam('redirect_uri')) { + + throw new Zend_Controller_Action_Exception($this->view->_('Désolé, requête incomplète'), 400); + } + + $this->view->titre = $this->_('Authentifiez-vous pour autoriser "%s" à accéder à votre compte', + $this->_getParam('client_id')); + $preferences = Class_Profil::getCurrentProfil()->getCfgModulesPreferences('auth', 'login'); + $options = ['data' => array_merge( + $preferences, + ['redirect_url' => $this->_getParam('redirect_uri'), + 'id_notice' => 0])]; + $this->view->form = ZendAfi_Form_Login::newWithOptions($options); + + $redirect_uri = $this->_getParam('redirect_uri'); + $strategy = new Auth_Strategy_OAuth($this); + $strategy->processLogin(); + } + + public function popupLoginAction() { $this->view->preferences = $this->_loginPrefFromWidgetOrModule(); $this->view->redirect = $this->_getParam('redirect'); @@ -523,8 +546,9 @@ class AuthController extends ZendAfi_Controller_Action { abstract class Auth_Strategy_Abstract { - protected $redirect_url = ''; - protected $disable_redirect = false; + protected + $redirect_url = '', + $disable_redirect = false; static public function strategyForController($controller) { if ($controller->isCasRequest() && static::isLogged()) @@ -570,6 +594,7 @@ abstract class Auth_Strategy_Abstract { $this->controller->redirect($this->redirect_url); } + public function setDefaultUrl($url) { $this->default_url=$url; } @@ -681,9 +706,9 @@ class Auth_Strategy_Cas_NotLogged extends Auth_Strategy_Cas_Abstract{ } -class Auth_Strategy_Lectura extends Auth_Strategy_Abstract { +class Auth_Strategy_Lectura extends Auth_Strategy_Abstract { public function handlePost() { $this->controller->getHelper('ViewRenderer')->setNoRender(); $response= $this->controller->getResponse(); @@ -705,3 +730,21 @@ class Auth_Strategy_Lectura extends Auth_Strategy_Abstract { } } + + + +class Auth_Strategy_OAuth extends Auth_Strategy_NotLogged { + public function handlePost() { + parent::handlePost(); + if (!$user = Class_Users::getIdentity()) + return $this; + + $request = $this->controller->getRequest(); + + $token = Class_User_ApiToken::findOrCreateForUserAndApplication($user, + $request->getParam('client_id')); + $this->redirect_url = sprintf('%s#token=%s', + $request->getParam('redirect_uri'), + $token->getToken()); + } +} \ No newline at end of file diff --git a/application/modules/opac/views/scripts/auth/oauth.phtml b/application/modules/opac/views/scripts/auth/oauth.phtml new file mode 100644 index 0000000000000000000000000000000000000000..a4574386fb4cb3f83c41e8c7db0f71faa510ac47 --- /dev/null +++ b/application/modules/opac/views/scripts/auth/oauth.phtml @@ -0,0 +1,4 @@ +<?php +echo $this->tag('h1', $this->titre); +echo $this->renderForm($this->form); +?> diff --git a/application/modules/telephone/controllers/AuthController.php b/application/modules/telephone/controllers/AuthController.php index 763316755d0a6cca3875498c46f2e48d5bfb6ffe..5cf1f1ef1749f091d0c728839a200030ffdfd555 100644 --- a/application/modules/telephone/controllers/AuthController.php +++ b/application/modules/telephone/controllers/AuthController.php @@ -55,6 +55,12 @@ class Telephone_AuthController extends AuthController { } + public function oauthAction() { + parent::oauthAction(); + $this->view->form = $this->_getFormLogin(); + } + + public function loginReservationAction() { if (Class_Users::getLoader()->hasIdentity()) { $this->_redirect('/recherche/reservation'); diff --git a/application/modules/telephone/views/scripts/auth/oauth.phtml b/application/modules/telephone/views/scripts/auth/oauth.phtml new file mode 100644 index 0000000000000000000000000000000000000000..7c3b23818cc4ad917f6bfaf461d4c20b6aec6031 --- /dev/null +++ b/application/modules/telephone/views/scripts/auth/oauth.phtml @@ -0,0 +1,4 @@ +<?php +echo $this->tag('h1', $this->titre); +echo $this->form; +?> diff --git a/cosmogramme/sql/patch/patch_316.php b/cosmogramme/sql/patch/patch_316.php index e1578072ceab6f73d7934e8d1db6ec51a1721f40..2ac6816a3b9b30181d89f9fca04416754f059422 100644 --- a/cosmogramme/sql/patch/patch_316.php +++ b/cosmogramme/sql/patch/patch_316.php @@ -3,4 +3,4 @@ try { Zend_Db_Table_Abstract::getDefaultAdapter() ->query('ALTER TABLE codif_section MODIFY id_section int(11) NOT NULL AUTO_INCREMENT'); } catch(Exception $e) {} -?> +?> \ No newline at end of file diff --git a/cosmogramme/sql/patch/patch_326.php b/cosmogramme/sql/patch/patch_326.php new file mode 100644 index 0000000000000000000000000000000000000000..6b5fabaeef2d450e3f06a4a320ac212b7bca832d --- /dev/null +++ b/cosmogramme/sql/patch/patch_326.php @@ -0,0 +1,12 @@ +<?php +Zend_Db_Table_Abstract::getDefaultAdapter() + ->query('CREATE TABLE if not exists `user_api_tokens` ( ' + . 'id int(11) unsigned not null auto_increment,' + . 'user_id int(11) unsigned not null,' + . 'client_id varchar(255) not null,' + . 'token varchar(255) not null,' + . 'primary key (id),' + . 'key (`user_id`),' + . 'key (`token`)' + . ') engine=MyISAM default charset=utf8'); +?> \ No newline at end of file diff --git a/library/Class/User/ApiToken.php b/library/Class/User/ApiToken.php new file mode 100644 index 0000000000000000000000000000000000000000..37dcba1366a9da20db85aa5cc35635126a32364f --- /dev/null +++ b/library/Class/User/ApiToken.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright (c) 2012-2014, 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 User_ApiTokenLoader extends Storm_Model_Loader { + public function findOrCreateForUserAndApplication($user, $client_id) { + if ($token = Class_User_ApiToken::findFirstBy(['role' => 'user', + 'model' => $user, + 'client_id' => $client_id])) + return $token; + + $token = new Class_User_ApiToken(); + $token + ->setUser($user) + ->setClientId($client_id) + ->setToken(md5(uniqid())) + ->save(); + + return $token; + } +} + + +class Class_User_ApiToken extends Storm_Model_Abstract { + protected + $_table_name = 'user_api_tokens', + $_loader_class = 'User_ApiTokenLoader', + $_belongs_to = ['user' => ['model' => 'Class_Users', + 'role' => 'api_token']]; + + +} +?> \ No newline at end of file diff --git a/library/Class/User/Settings.php b/library/Class/User/Settings.php index 8368d29a7e1935d4cc30411ffd08e9aa54ff88f0..747a2ed287a722650d434a42d1d73ec4a42c359c 100644 --- a/library/Class/User/Settings.php +++ b/library/Class/User/Settings.php @@ -31,11 +31,7 @@ class Class_User_Settings { public static function newWith($user) { - $instance = new self(); - $instance - ->setUser($user) - ->setUserSettings(unserialize($user->getSettings())); - return $instance; + return new self($user); } @@ -60,8 +56,14 @@ class Class_User_Settings { } + public function __construct($user) { + $this->setUser($user); + } + + public function setUser($user) { $this->_user = $user; + $this->_user_settings = unserialize($user->getSettings()); return $this; } @@ -72,25 +74,37 @@ class Class_User_Settings { } - public function setBookmarkedDomains($domain_ids) { - $this->_user_settings[self::BOOKMARKED_DOMAINS] = implode('-', $domain_ids); + public function get($name) { + return isset($this->_user_settings[$name]) + ? $this->_user_settings[$name] + : null; + } + + + public function set($name, $value) { + $this->_user_settings[$name] = $value; $this->_user->setSettings(static::serializeSettings($this->_user_settings)); + return $this; + } + + + public function setBookmarkedDomains($domain_ids) { + $this->set(self::BOOKMARKED_DOMAINS, implode('-', $domain_ids)); return $this->_user; } public function setBookmarkedLibraries($libraries_ids) { - $this->_user_settings[self::BOOKMARKED_LIBRARIES] = implode('-', $libraries_ids); - $this->_user->setSettings(static::serializeSettings($this->_user_settings)); + $this->set(self::BOOKMARKED_LIBRARIES, implode('-', $libraries_ids)); return $this->_user; } public function getBookmarkedDomains() { - if(!isset($this->_user_settings[self::BOOKMARKED_DOMAINS])) + if (!$value = $this->get(self::BOOKMARKED_DOMAINS)) return []; - if(!$storm_ids = $this->_getStormIds($this->_user_settings[self::BOOKMARKED_DOMAINS])) + if (!$storm_ids = $this->_getStormIds($value)) return []; return Class_Catalogue::findAllBy(['id_catalogue' => $storm_ids, @@ -102,10 +116,10 @@ class Class_User_Settings { if (!static::isBookmarkLibraryReady()) return $this->_clearBookmarkedLibraries(); - if(!isset($this->_user_settings[self::BOOKMARKED_LIBRARIES])) + if (!$value = $this->get(self::BOOKMARKED_LIBRARIES)) return $this->_initDefaultLibrary(); - if(!$storm_ids = $this->_getStormIds($this->_user_settings[self::BOOKMARKED_LIBRARIES])) + if (!$storm_ids = $this->_getStormIds($value)) return []; return Class_Profil::getCurrentProfil()->isItemAnnexDisplay() @@ -133,9 +147,9 @@ class Class_User_Settings { public function getRedmineLibrary() { - if(!isset($this->_user_settings[self::REDMINE_LIBRARY]) && !$this->_user_settings[self::REDMINE_LIBRARY]) - return $this->getUserLib(); - return Class_Bib::find($this->_user_settings[self::REDMINE_LIBRARY]); + return ($lib_id = $this->get(self::REDMINE_LIBRARY)) + ? Class_Bib::find($lib_id) + : $this->getUserLib(); } @@ -145,19 +159,17 @@ class Class_User_Settings { public function setRedmineLibrary($library_id) { - $this->_user_settings[self::REDMINE_LIBRARY] = $library_id; - $this->_user->setSettings(static::serializeSettings($this->_user_settings)); + $this->set(self::REDMINE_LIBRARY, $library_id); return $this->_user; } public function getAdminSkin() { - if(!isset($this->_user_settings[self::ADMIN_SKIN])) - return new Class_Admin_Skin(); + $admin_skin = $this->get(self::ADMIN_SKIN); - if (!is_a($this->_user_settings[self::ADMIN_SKIN], 'Class_Admin_Skin')) - return new Class_Admin_Skin(); - return new Class_Admin_Skin($this->_user_settings[self::ADMIN_SKIN]->getName(), $this->_user_settings[self::ADMIN_SKIN]->getColor()); + return is_a($admin_skin, 'Class_Admin_Skin') + ? new Class_Admin_Skin($admin_skin->getName(), $admin_skin->getColor()) + : new Class_Admin_Skin(); } @@ -165,8 +177,8 @@ class Class_User_Settings { if(!$datas || !isset($datas[self::ADMIN_SKIN])) return $this->_user; - $this->_user_settings[self::ADMIN_SKIN] = new Class_Admin_Skin($datas[self::ADMIN_SKIN], $datas[self::ADMIN_SKIN_COLOR]); - $this->_user->setSettings(static::serializeSettings($this->_user_settings)); + $this->set(self::ADMIN_SKIN, new Class_Admin_Skin($datas[self::ADMIN_SKIN], + $datas[self::ADMIN_SKIN_COLOR])); return $this->_user; } @@ -175,11 +187,8 @@ class Class_User_Settings { if (!static::isBookmarkLibraryReady()) return $this->_user; - if(!isset($this->_user_settings[self::BOOKMARKED_LIBRARIES])) - $this->_user_settings[self::BOOKMARKED_LIBRARIES] = ''; - - $this->_user_settings[self::BOOKMARKED_LIBRARIES] .= '-' . $id; - $this->_user->setSettings(static::serializeSettings($this->_user_settings)); + $this->set(self::BOOKMARKED_LIBRARIES, + (string)$this->get(self::BOOKMARKED_LIBRARIES) . '-' .$id); return $this->_user; } @@ -188,12 +197,12 @@ class Class_User_Settings { if (!static::isBookmarkLibraryReady()) return $this->_user; - if(!isset($this->_user_settings[self::BOOKMARKED_LIBRARIES])) + if (!$ids = $this->get(self::BOOKMARKED_LIBRARIES)) return $this->_user; - $current_settings = $this->_getStormIds($this->_user_settings[self::BOOKMARKED_LIBRARIES]); - $this->_user_settings[self::BOOKMARKED_LIBRARIES] = implode('-', array_diff($current_settings, [$id])); - $this->_user->setSettings(static::serializeSettings($this->_user_settings)); + $current_settings = $this->_getStormIds($ids); + $this->set(self::BOOKMARKED_LIBRARIES, + implode('-', array_diff($current_settings, [$id]))); return $this->_user; } @@ -216,8 +225,7 @@ class Class_User_Settings { protected function _saveUserWithLib($id) { - $this->_user_settings[self::BOOKMARKED_LIBRARIES] = $id; - $this->_user->setSettings(static::serializeSettings($this->_user_settings)); + $this->set(self::BOOKMARKED_LIBRARIES, $id); $this->_user->save(); } } diff --git a/library/ZendAfi/Controller/Action/Helper/ViewRenderer.php b/library/ZendAfi/Controller/Action/Helper/ViewRenderer.php index bf555b2dc012a5f8f8e6be85e64f24a15decfa75..d7bf0b1d2c1093b0a5d4888fc47ba8c7a8e03872 100644 --- a/library/ZendAfi/Controller/Action/Helper/ViewRenderer.php +++ b/library/ZendAfi/Controller/Action/Helper/ViewRenderer.php @@ -32,7 +32,7 @@ class ZendAfi_Controller_Action_Helper_ViewRenderer extends Zend_Controller_Acti //------------------------------------------------------------------------------- public function __construct() { $options['viewSuffix'] = 'phtml'; - $view=new ZendAfi_Controller_Action_Helper_View(); + $view = new ZendAfi_Controller_Action_Helper_View(); parent::__construct($view, $options); } diff --git a/library/ZendAfi/Controller/Plugin/DefineURLs.php b/library/ZendAfi/Controller/Plugin/DefineURLs.php index f2ec5aa8b27a14182b5527138a8d71e28bfd27d7..9633a15d7570e394a62e30014372ef041eb254f3 100644 --- a/library/ZendAfi/Controller/Plugin/DefineURLs.php +++ b/library/ZendAfi/Controller/Plugin/DefineURLs.php @@ -24,7 +24,8 @@ class ZendAfi_Controller_Plugin_DefineURLs extends Zend_Controller_Plugin_Abstra const PHONE = 'telephone', ADMIN = 'admin', - OPAC = 'opac'; + OPAC = 'opac', + API = 'api'; public function preDispatch(Zend_Controller_Request_Abstract $request) { @@ -48,6 +49,16 @@ class ZendAfi_Controller_Plugin_DefineURLs extends Zend_Controller_Plugin_Abstra protected function updateRequest() { $request = $this->getRequest(); + + if ($request->getModuleName() == static::API) { + Zend_Controller_Action_HelperBroker::removeHelper('ViewRenderer'); + $view = new Zend_View(); + $view->addHelperPath('ZendAfi/View/Helper/Api', 'ZendAfi_View_Helper_Api'); + Zend_Controller_Action_HelperBroker::addHelper(new Zend_Controller_Action_Helper_ViewRenderer($view, ['viewSuffix' => 'pjson'])); + $this->getResponse()->setHeader('Content-Type', 'application/json'); + return $this; + } + $detector = new ZendAfi_Controller_Plugin_DefineURLs_ProfileDetector(); Class_Profil::setCurrentProfil($detector->detectFrom($request)); $profil = Class_Profil::getCurrentProfil(); diff --git a/library/ZendAfi/View/Helper/Api/Loans.php b/library/ZendAfi/View/Helper/Api/Loans.php new file mode 100644 index 0000000000000000000000000000000000000000..c4542a247d7c893166d2905a249ba9d1b61b44d3 --- /dev/null +++ b/library/ZendAfi/View/Helper/Api/Loans.php @@ -0,0 +1,42 @@ +<?php +/** + * Copyright (c) 2012-2014, 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_View_Helper_Api_Loans extends Zend_View_Helper_Abstract { + public function loans($loans) { + return json_encode( + $loans->collect([$this, 'loanToArray']) + ->getArrayCopy() + ); + } + + + public function loanToArray($loan) { + return [ + 'title' => $loan->getTitre(), + 'author' => $loan->getAuteur(), + 'date_due' => implode('-', array_reverse(explode('/', $loan->getDateRetour()))), + 'loaned_by' => $loan->getUserFullName(), + 'library' => $loan->getBibliotheque() + ]; + } +} +?> \ No newline at end of file diff --git a/tests/application/modules/AbstractControllerTestCase.php b/tests/application/modules/AbstractControllerTestCase.php index 3a98f54253c12f266ce628173b06229d03608be0..9c1fbff6fd7a5455fd38702be5c04af00b949414 100644 --- a/tests/application/modules/AbstractControllerTestCase.php +++ b/tests/application/modules/AbstractControllerTestCase.php @@ -219,7 +219,7 @@ abstract class AbstractControllerTestCase extends Zend_Test_PHPUnit_ControllerTe } - public function dispatch($url = null, $throw_exceptions = false) { + public function dispatch($url = null, $throw_exceptions = false, $headers = []) { // redirector should not exit $redirector = Zend_Controller_Action_HelperBroker::getStaticHelper('redirector'); $redirector->setExit(false); @@ -228,7 +228,9 @@ abstract class AbstractControllerTestCase extends Zend_Test_PHPUnit_ControllerTe $json = Zend_Controller_Action_HelperBroker::getStaticHelper('json'); $json->suppressExit = true; - $request = $this->getRequest(); + $request = $this->getRequest(); + $request->setHeaders($headers); + if (null !== $url) { $request->setRequestUri($url); } diff --git a/tests/application/modules/opac/controllers/RechercheControllerTest.php b/tests/application/modules/opac/controllers/RechercheControllerTest.php index 2b3d2fe30fcd1588dfa61d39fc6e8b23a0036131..e2b45d15f0d08cbd375d4a9338d7d4854e86eca4 100644 --- a/tests/application/modules/opac/controllers/RechercheControllerTest.php +++ b/tests/application/modules/opac/controllers/RechercheControllerTest.php @@ -1222,6 +1222,32 @@ class RechercheAvanceeControllerSimpleActionWithDefaultConfigTest extends Recher } + /** @test */ + public function userSettingBookmarkedDomainShouldBeOne() { + $this->assertEquals(1, + (new Class_User_Settings(Class_Users::find(1))) + ->get(Class_User_Settings::BOOKMARKED_DOMAINS)); + } + + + /** @test */ + public function userSettingZorkShouldBeEmpty() { + $this->assertEmpty((new Class_User_Settings(Class_Users::find(1)))->get('zork')); + } + + + /** @test */ + public function userSettingSetZorkToGlubShouldSetIt() { + (new Class_User_Settings(Class_Users::find(1)))->set('zork', 'glub'); + Class_Users::find(1)->save(); + Class_Users::clearCache(); + + $this->assertEquals('glub', + (new Class_User_Settings(Class_Users::find(1))) + ->get('zork')); + } + + /** @test */ public function tagsCloudLevel5ShouldBePresent() { $this->assertXPath('//a[contains(@class,"nuage_niveau5")]', diff --git a/tests/db/UpgradeDBTest.php b/tests/db/UpgradeDBTest.php index 6cdd45f45865bb5b61272f4010563647acbd3e08..7dff59f67e84380d01d5281e165ff96a675bd165 100644 --- a/tests/db/UpgradeDBTest.php +++ b/tests/db/UpgradeDBTest.php @@ -1207,7 +1207,6 @@ class UpgradeDB_315_Test extends UpgradeDBTestCase { class UpgradeDB_316_Test extends UpgradeDBTestCase { - public function prepare() { try { $this->query("ALTER TABLE codif_section MODIFY id_section tinyint(4)"); @@ -1249,7 +1248,7 @@ class UpgradeDB_317_Test extends UpgradeDBTestCase { /** * @test * @dataProvider datas - **/ + */ public function fieldsShouldBecomeMediumtext($key) { $this->assertFieldType('bib_admin_profil', $key, 'mediumtext'); } @@ -1452,3 +1451,34 @@ class UpgradeDB_325_Test extends UpgradeDBTestCase { $this->assertColumn('session_activity_inscriptions', 'session_activity_id'); } } + + + + + +class UpgradeDB_326_Test extends UpgradeDBTestCase { + public function prepare() { + try { + + $this->query('drop table user_api_tokens'); + } catch (Exception $e) {} + } + + public function datas() { + return + [ + ['id', 'int(11) unsigned'], + ['client_id', 'varchar(255)'], + ['token', 'varchar(255)'], + ['user_id', 'int(11) unsigned'], + ]; + } + + /** + * @test + * @dataProvider datas + */ + public function fieldShouldBe($field, $type) { + $this->assertFieldType('user_api_tokens', $field, $type); + } +} diff --git a/tests/scenarios/MobileApplication/UserAccountTest.php b/tests/scenarios/MobileApplication/UserAccountTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ded7d15e8bf6f1ccf1360c395300fb842b0ed0b5 --- /dev/null +++ b/tests/scenarios/MobileApplication/UserAccountTest.php @@ -0,0 +1,342 @@ +<?php +/** + * Copyright (c) 2012-2014, 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 Scenario_MobileApplication_UserAccountTestCase extends AbstractControllerTestCase { + protected + $_storm_default_to_volatile = true; + + public function setUp() { + parent::setUp(); + + $_SERVER['HTTPS'] = 'on'; + + $puppy = $this->fixture('Class_Users', + ['id' => 345, + 'login' => 'puppy', + 'password' => 'opied', + 'role_level' => ZendAfi_Acl_AdminControllerRoles::ABONNE_SIGB, + 'idabon' => '234', + 'id_site' => 1]); + + $this->fixture('Class_User_ApiToken', + ['id' => 1, + 'token' => 'nonos', + 'client_id' => 'My mobile app', + 'user' => $puppy]); + + $potter = new Class_WebService_SIGB_Emprunt('12', new Class_WebService_SIGB_Exemplaire(123)); + $potter + ->setDateRetour('01/01/1974') + ->setAuteur('J.K.R') + ->setBibliotheque('Annecy') + ->getExemplaire()->setTitre('Potter'); + + $alice = new Class_WebService_SIGB_Emprunt('13', new Class_WebService_SIGB_Exemplaire(456)); + $alice + ->setDateRetour(date('d/m/Y', strtotime('tomorrow'))) + ->getExemplaire()->setTitre('Alice'); + + $emprunteur = (new Class_WebService_SIGB_Emprunteur(345, 'puppy')) + ->empruntsAddAll([$potter, $alice]); + + $puppy->setFicheSIGB(['fiche' => $emprunteur]); + $puppy->assertSave(); + + + ZendAfi_Auth::getInstance()->clearIdentity(); + } + + + public function tearDown() { + unset($_SERVER['HTTPS']); + parent::tearDown(); + } +} + + + + +class Scenario_MobileApplication_UserAccountWithTokenTest extends Scenario_MobileApplication_UserAccountTestCase { + protected + $_json; + + public function setUp() { + parent::setUp(); + + $this->dispatch('/api/user/loans', + true, + ["Authorization" => "Bearer nonos" , + "Content-Type" => "application/json"]); + + $this->_json = json_decode($this->_response->getBody(), true); + } + + + /** @test */ + public function responseShouldContainsPotterLoan() { + $this->assertEquals(['title' => 'Potter', + 'author' => 'J.K.R', + 'date_due' => '1974-01-01', + 'loaned_by' => 'puppy', + 'library' => 'Annecy' + ], + $this->_json['loans'][0]); + } + + + /** @test */ + public function responseHeaderContentTypeShouldBeApplicationJson() { + $this->assertArraySubset(['name' => 'Content-Type', + 'value' => 'application/json'], + $this->_response->getHeaders()[0]); + } +} + + + + +class Scenario_MobileApplication_UserAccountWithoutTokenTest extends Scenario_MobileApplication_UserAccountTestCase { + /** @test */ + public function withoutAuthorizationShouldAnswerInvalidRequest() { + $this->dispatch('/api/user/loans', + true, + ["Content-Type" => "application/json"]); + + $this->assertEquals(['error' => 'invalid_request', + 'message' => 'Autorisation non spécifiée'], + json_decode($this->_response->getBody(), true)); + } + + + /** @test */ + public function withWrongAuthorizationTypeShouldAnswerInvalidRequest() { + $this->dispatch('/api/user/loans', + true, + ["Authorization" => 'Catch nonos', + "Content-Type" => "application/json"]); + + $this->assertEquals(['error' => 'invalid_request', + 'message' => 'Jeton d\'autorisation non fourni'], + json_decode($this->_response->getBody(), true)); + } + + + /** @test */ + public function withWrongAuthorizationTokenShouldAnswerInvalidRequest() { + $this->dispatch('/api/user/loans', + true, + ["Authorization" => 'Bearer veget@ble', + "Content-Type" => "application/json"]); + + $this->assertEquals(['error' => 'invalid_request', + 'message' => 'Jeton d\'autorisation invalide'], + json_decode($this->_response->getBody(), true)); + } + + + /** @test */ + public function withLegacyAuthorizationTokenShouldAnswerInvalidRequest() { + $this->fixture('Class_User_ApiToken', + ['id' => 2, + 'token' => 'veget@ble', + 'user_id' => 987]); + + $this->dispatch('/api/user/loans', + true, + ["Authorization" => 'Bearer veget@ble', + "Content-Type" => "application/json"]); + + $this->assertEquals(['error' => 'invalid_request', + 'message' => 'Utilisateur non trouvé'], + json_decode($this->_response->getBody(), true)); + } + + + /** @test */ + public function withoutHttpsShouldAnswerInvalidRequest() { + unset($_SERVER['HTTPS']); + + $this->dispatch('/api/user/loans', + true, + ["Authorization" => "Bearer nonos" , + "Content-Type" => "application/json"]); + + $this->assertEquals(['error' => 'invalid_request', + 'message' => 'Protocole HTTP obligatoire'], + json_decode($this->_response->getBody(), true)); + } +} + + + +class Scenario_MobileApplication_UserAccountOAuthForLoginErrorsTest extends Scenario_MobileApplication_UserAccountTestCase { + public function wrongUrls() { + return [ + ['/auth/oauth/response_type/code/client_id/My%20mobile%20app/redirect_uri/'], + ['/auth/oauth/response_type/code/client_id/My%20mobile%20app/'], + ['/auth/oauth/response_type/code/redirect_uri/http%3A%2F%2Fsomewhere.com'], + ['/auth/oauth/response_type/something/client_id/My%20mobile%20app/redirect_uri/http%3A%2F%2Fsomewhere.com'], + ]; + } + + /** + * @dataProvider wrongUrls + * @test + */ + public function withIncompleUrlShouldError400BadRequest($url) { + try { + $this->dispatch($url, true); + } catch(Zend_Controller_Action_Exception $e) { + $this->assertEquals(400, $e->getCode()); + return; + } + $this->fail('should raise error 400 bad request'); + } +} + + + + +class Scenario_MobileApplication_UserAccountOAuthForLoginTest extends Scenario_MobileApplication_UserAccountTestCase { + public function setUp() { + parent::setUp(); + $this->dispatch('/auth/oauth/response_type/code/client_id/My%20mobile%20app/redirect_uri/' + . urlencode('bokeh://authorize'), + true); + } + + + /** @test */ + public function pageShouldDisplayLoginForm() { + $this->assertXPath('//form//input[@name="username"]'); + } + + + /** @test */ + public function pageShouldContainsMyMobileAppWantsToConnect() { + $this->assertXPathContentContains('//h1', + 'Authentifiez-vous pour autoriser "My mobile app" à accéder à votre compte'); + } +} + + + + +class Scenario_MobileApplication_UserAccountOAuthForLoginOnPhoneTest extends Scenario_MobileApplication_UserAccountTestCase { + public function setUp() { + parent::setUp(); + $_SERVER['HTTP_USER_AGENT'] = 'iPhone'; + Class_Profil::getCurrentProfil() + ->beTelephone() + ->assertSave(); + + $this->dispatch('/auth/oauth/response_type/code/client_id/My%20mobile%20app/redirect_uri/' + . urlencode('bokeh://authorize'), + true); + } + + + public function tearDown() { + unset($_SERVER['HTTP_USER_AGENT']); + parent::tearDown(); + } + + + /** @test */ + public function pageShouldDisplayLoginForm() { + $this->assertXPath('//form//input[@name="username"]'); + } + + + /** @test */ + public function pageShouldContainsMyMobileAppWantsToConnect() { + $this->assertXPathContentContains('//h1', + 'Authentifiez-vous pour autoriser "My mobile app" à accéder à votre compte'); + } +} + + + + +class Scenario_MobileApplication_UserAccountOAuthPostLoginSuccessTest extends Scenario_MobileApplication_UserAccountTestCase { + protected $_auth; + + public function setUp() { + parent::setUp(); + $this->_auth = Storm_Test_ObjectWrapper::mock() + ->whenCalled('authenticateLoginPassword') + ->answers(false) + ->whenCalled('hasIdentity') + ->answers(false) + ->whenCalled('getIdentity') + ->answers(null); + + ZendAfi_Auth::setInstance($this->_auth); + + $this->_auth + ->whenCalled('authenticateLoginPassword') + ->with('puppy', 'opied') + ->willDo( + function() { + $user = new stdClass(); + $user->ID_USER = 345; + $this->_auth->whenCalled('getIdentity')->answers($user); + return true; + }); + } + + + public function tearDown() { + ZendAfi_Auth::setInstance(null); + parent::tearDown(); + } + + + /** @test */ + public function responseShouldRedirectToBokehAuthorizeWithExistingToken() { + $this->postDispatch('/opac/auth/oauth/response_type/code/client_id/My%20mobile%20app/redirect_uri/' . urlencode('bokeh://authorize'), + ['username' => 'puppy', 'password' => 'opied'], true); + + $this->assertRedirectTo('bokeh://authorize#token=nonos'); + } + + + /** @test */ + public function tokenShouldBeCreatedIfNotExists() { + Class_User_ApiToken::deleteBy([]); + + $this->postDispatch('/opac/auth/oauth/response_type/code/client_id/My%20mobile%20bokeh/redirect_uri/' . urlencode('bokeh://authorize'), + ['username' => 'puppy', 'password' => 'opied'], true); + + $token = Class_User_ApiToken::find(1); + $this->assertRedirectTo('bokeh://authorize#token=' . $token->getToken()); + return $token; + } + + + /** + * @depends tokenShouldBeCreatedIfNotExists + * @test + */ + public function tokenClientIdShouldBeMyMobileBokeh($token) { + $this->assertEquals('My mobile bokeh', $token->getClientId()); + } +} +?> \ No newline at end of file