diff --git a/appinfo/info.xml b/appinfo/info.xml index 6e9a420..c563592 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -33,6 +33,10 @@ + + OCA\UserCAS\BackgroundJob\TicketCleanupJob + + OCA\UserCAS\Command\CreateUser OCA\UserCAS\Command\UpdateUser diff --git a/lib/BackgroundJob/TicketCleanupJob.php b/lib/BackgroundJob/TicketCleanupJob.php new file mode 100644 index 0000000..094d696 --- /dev/null +++ b/lib/BackgroundJob/TicketCleanupJob.php @@ -0,0 +1,46 @@ + + * @copyright Vincent Laffargue + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 this library. If not, see . + * + */ + +namespace OCA\UserCAS\BackgroundJob; + +use \OC; +use \OCA\UserCAS\Service\PhpCasTicketManager\PhpCasTicketManager; +use \OCP\BackgroundJob\Job; + +/** + * Class TicketCleanupJob + * + * @package OCA\UserCAS\BackgroundJob + * + * @author Vincent Laffargue + * @copyright Vincent Laffargue + * + * @since 1.8.4 + */ +class TicketCleanupJob extends Job { + + protected function run($argument) { + /* @var $provider IProvider */ + $phpCasTicketManager = new PhpCasTicketManager(); + $phpCasTicketManager->deleteTicketWithoutValideToken(); + } +} diff --git a/lib/Hooks/UserHooks.php b/lib/Hooks/UserHooks.php index 9d27940..7e171fd 100644 --- a/lib/Hooks/UserHooks.php +++ b/lib/Hooks/UserHooks.php @@ -375,13 +375,12 @@ public function postLogin($uid, $password) } /** - * Logout hook method. + * postLogout hook method. * * @return boolean */ public function postLogout() { - if (!$this->appService->isCasInitialized()) { try { diff --git a/lib/Migration/Version104000Date20200401000002.php b/lib/Migration/Version104000Date20200401000002.php new file mode 100644 index 0000000..49a943a --- /dev/null +++ b/lib/Migration/Version104000Date20200401000002.php @@ -0,0 +1,62 @@ + + * @copyright Vincent Laffargue + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 this library. If not, see . + * + */ + +declare(strict_types=1); + +namespace OCA\UserCAS\Migration; + +use Closure; +use OCP\DB\ISchemaWrapper; +use OCP\Migration\SimpleMigrationStep; +use OCP\Migration\IOutput; + +class Version104000Date20200401000002 extends SimpleMigrationStep { + public function name(): string { + return 'Add user_cas_ticket table'; + } + + public function description(): string { + return 'Adds table to store relation ticket <=> token'; + } + + public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + + if (!$schema->hasTable('user_cas_ticket')) { + $table = $schema->createTable('user_cas_ticket'); + $table->addColumn('ticket', 'string', [ + 'notnull' => true, + 'length' => 200, + ]); + $table->addColumn('token', 'string', [ + 'notnull' => true, + 'length' => 200, + ]); + $table->setPrimaryKey(['ticket']); + $table->addUniqueIndex(['token'], 'index_token'); + } + + return $schema; + } +} diff --git a/lib/Service/AppService.php b/lib/Service/AppService.php index 6e0e02a..22ce476 100644 --- a/lib/Service/AppService.php +++ b/lib/Service/AppService.php @@ -25,6 +25,7 @@ use OC\Authentication\Token\IToken; use OCA\UserCAS\Exception\PhpCas\PhpUserCasLibraryNotFoundException; +use OCA\UserCAS\Service\PhpCasTicketManager\PhpCasTicketManager; use \OCP\IConfig; use \OCP\IUserSession; use \OCP\IUserManager; @@ -58,6 +59,11 @@ class AppService */ private $loggingService; + /** + * @var \OCA\UserCAS\Service\PhpCasTicketManager\PhpCasTicketManager $phpCasTicketManager + */ + private $phpCasTicketManager; + /** * @var \OCP\IUserManager $userManager */ @@ -191,6 +197,7 @@ public function __construct($appName, IConfig $config, LoggingService $loggingSe $this->userManager = $userManager; $this->userSession = $userSession; $this->urlGenerator = $urlGenerator; + $this->phpCasTicketManager = new PhpCasTicketManager($config, $userSession->getSession());; $this->casInitialized = FALSE; } @@ -290,6 +297,12 @@ public function init() \phpCAS::setVerbose(TRUE); } + //Ticket not register in phpCAS but needed in order to know what ticket for what user... + //For SingleSignOut + //Warning : function saveTicket must be before \phpCAS::proxy or \phpCAS::client because these functions unset ticket + if (!$this->casDisableSinglesignout) { + $this->phpCasTicketManager->saveTicket(); + } # Initialize client if ($this->casUseProxy) { @@ -303,7 +316,7 @@ public function init() # Handle SingleSignout requests if (!$this->casDisableSinglesignout) { - \phpCAS::setSingleSignoutCallback([$this, 'casSingleSignOut']); + \phpCAS::setSingleSignoutCallback([$this->phpCasTicketManager, 'invalidateTokenByTicket']); \phpCAS::handleLogoutRequests(true, $this->casHandleLogoutServers); } @@ -893,60 +906,6 @@ private function isIpInLocalRange($internalIps) return FALSE; } - /** - * Callback function for CAS singleSignOut call - * - * @author Vincent - * - * @param string $ticket Ticket Object - */ - public function casSingleSignOut($ticket) - { - // Extract the userID from the SAML Request - $decodedLogoutRequest = urldecode($_POST['logoutRequest']); - preg_match( - "|]*>(.*)|", - $decodedLogoutRequest, $tick, PREG_OFFSET_CAPTURE, 3 - ); - $wrappedSamlNameId = preg_replace( - '|]*>|', '', $tick[0][0] - ); - $nameId = preg_replace( - '||', '', $wrappedSamlNameId - ); - - //Kill Session Of UserID: - $this->killSessionUserName($nameId); - } - - /** - * Kill the username's session. - * - * @author Vincent - * @author Felix Rupp - * - * @param string $username The username of the user. - * @return NULL - */ - private function killSessionUserName($username) - { - - if ($this->userManager->userExists($username)) { - - $tokenType = IToken::TEMPORARY_TOKEN; - - $sql = "DELETE FROM oc_authtoken WHERE uid = ? AND type = ? AND password IS NULL;"; - $stmt = \OC::$server->getDatabaseConnection()->prepare($sql); - $stmt->bindParam(1, $username, \PDO::PARAM_STR); - $stmt->bindParam(2, $tokenType, \PDO::PARAM_INT); - - $stmt->execute(); - } - - return NULL; - } - - ## Setters/Getters /** @@ -988,4 +947,12 @@ public function getCasPath() { return $this->casPath; } + + /** + * @return boolean + */ + public function getCasDisableSinglesignout() + { + return $this->casDisableSinglesignout; + } } diff --git a/lib/Service/PhpCasTicketManager/PhpCasTicketManager.php b/lib/Service/PhpCasTicketManager/PhpCasTicketManager.php new file mode 100644 index 0000000..aff05f1 --- /dev/null +++ b/lib/Service/PhpCasTicketManager/PhpCasTicketManager.php @@ -0,0 +1,154 @@ + + * @copyright Vincent Laffargue + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 this library. If not, see . + * + */ + +namespace OCA\UserCAS\Service\PhpCasTicketManager; + +use \OCP\IConfig; +use \OCP\IDBConnection; +use \OCP\IRequest; +use \OCP\ISession; + +/** + * Class PhpCasTicketManager + * + * @package OCA\UserCAS\Service\PhpCasTicketManager + * + * @author Vincent Laffargue + * @copyright Vincent Laffargue + * + * @since 1.8.4 + */ +class PhpCasTicketManager +{ + /** + * @var \OCP\IConfig $appConfig + */ + private $config; + + /** + * @var IDBConnection + */ + private $connection; + + /** + * @var ISession $session + */ + private $session; + + + public function __construct(IConfig $config = NULL, ISession $session=NULL) { + $this->connection = \OC::$server->getDatabaseConnection(); + $this->config = $config; + $this->session = $session; + } + + /** + * This function save the encrypted ticket of phpCAS if present in _GET['ticket']. + * @var casTicket; + */ + public function saveTicket() { + if (isset($_GET['ticket'])) { + $ticket = (isset($_GET['ticket']) ? $_GET['ticket'] : null); + if (preg_match('/^[SP]T-/', $ticket) ) { + if (session_id()=="") + session_start(); + $_SESSION['user_cas_ticket'] = $this->hashTicket($ticket); + } + } + } + + /** + * This function get the encrypted ticket of phpCAS if present in _SESSION['user_cas_ticket']. + * And unset it + * @var casTicket; + */ + private function getUniqueTicket() { + if (session_id()=="") + session_start(); + if (isset($_SESSION['user_cas_ticket'])) { + $ticket = $_SESSION['user_cas_ticket']; + unset($_SESSION['user_cas_ticket']); + return $ticket; + } + return NULL; + } + + /** + * This function save the encrypted ticket of phpCAS with encrypted SessionId. + * @var casTicket; + */ + public function saveTokenTicketDb() { + // Make sure the current sessionId has no leftover tokens + $this->deleteTicket(); + $qb = $this->connection->getQueryBuilder(); + $qb->insert('user_cas_ticket') + ->setValue('ticket', '?') + ->setValue('token', '?') + ->setParameter(0, $this->getUniqueTicket()) + ->setParameter(1, $this->hashTicket($this->session->getId())) + ->execute(); + } + + /** + * This function delete token corresponding at ticket and after the ticket + * @var casTicket; + */ + public function invalidateTokenByTicket($ticket) { + $hashTicket = $this->hashTicket($ticket); + $sql = "DELETE FROM oc_authtoken WHERE token IN(SELECT token FROM oc_user_cas_ticket WHERE ticket = ?)"; + $stmt = $this->connection->prepare($sql); + $stmt->bindParam(1, $hashTicket, \PDO::PARAM_STR); + $stmt->execute(); + + $sql = "DELETE FROM oc_user_cas_ticket WHERE ticket = ?"; + $stmt = $this->connection->prepare($sql); + $stmt->bindParam(1, $hashTicket, \PDO::PARAM_STR); + $stmt->execute(); + } + + /** + * This function delete the ticket of the current SessionId + */ + private function deleteTicket() { + $hashTicket = $this->hashTicket($this->session->getId()); + $sql = "DELETE FROM oc_user_cas_ticket WHERE token = ?"; + $stmt = $this->connection->prepare($sql); + $stmt->bindParam(1, $hashTicket, \PDO::PARAM_STR); + $stmt->execute(); + } + + /** + * Delete all row of the table which no longer have a match with token + */ + public function deleteTicketWithoutValideToken() { + $sql = "DELETE FROM oc_user_cas_ticket ". + "WHERE token NOT IN " . + "(SELECT token FROM oc_authtoken)"; + $this->connection->prepare($sql)->execute(); + } + + private function hashTicket(string $ticket): string { + $secret = $this->config->getSystemValue('secret'); + return hash('sha512', $ticket . $secret); + } +} diff --git a/lib/Service/UserService.php b/lib/Service/UserService.php index e4c30ef..f3fa63b 100644 --- a/lib/Service/UserService.php +++ b/lib/Service/UserService.php @@ -26,6 +26,7 @@ use OCA\UserCAS\Exception\PhpCas\PhpUserCasLibraryNotFoundException; use OCA\UserCas\Service\LoggingService; use OCA\UserCAS\User\UserCasBackendInterface; +use OCA\UserCas\Service\PhpCasTicketManager\PhpCasTicketManager; use \OCP\IConfig; use \OCP\IUserManager; use \OCP\IGroupManager; @@ -81,6 +82,11 @@ class UserService */ private $loggingService; + /** + * @var PhpCasTicketManager $phpCasTicketManager + */ + private $phpCasTicketManager; + /** * UserService constructor. @@ -103,6 +109,7 @@ public function __construct($appName, IConfig $config, IUserManager $userManager $this->groupManager = $groupManager; $this->appService = $appService; $this->loggingService = $loggingService; + $this->phpCasTicketManager = new PhpCasTicketManager($config,$userSession->getSession()); } /** @@ -195,7 +202,13 @@ public function login($request, $uid, $password = '') if ($loginSuccessful) { - return $this->userSession->createSessionToken($request, $this->userSession->getUser()->getUID(), $uid, NULL); + if ($this->userSession->createSessionToken($request, $this->userSession->getUser()->getUID(), $uid, NULL)) { + if (!$this->appService->getCasDisableSinglesignout()) + $this->phpCasTicketManager->saveTokenTicketDb(); + return true; + } else { + return false; + } } $this->loggingService->write(LoggingService::DEBUG, 'phpCAS login function not successful.');