From 5f034248350b3300516d5ade653000c3cd5c9d8b Mon Sep 17 00:00:00 2001 From: Oliver Eglseder Date: Fri, 9 Mar 2018 17:34:24 +0100 Subject: [PATCH] [RELEASE] Create T3AM Server 1.0.0 --- Classes/SecurityService.php | 111 +++++++++++++++++ Classes/Server.php | 136 +++++++++++++++++++++ Classes/ServerException.php | 25 ++++ Classes/UserRepository.php | 54 ++++++++ Configuration/TCA/tx_t3amserver_client.php | 59 +++++++++ README.md | 36 ++++++ composer.json | 26 ++++ ext_emconf.php | 19 +++ ext_icon.svg | 39 ++++++ ext_localconf.php | 4 + ext_tables.sql | 25 ++++ 11 files changed, 534 insertions(+) create mode 100644 Classes/SecurityService.php create mode 100644 Classes/Server.php create mode 100644 Classes/ServerException.php create mode 100644 Classes/UserRepository.php create mode 100644 Configuration/TCA/tx_t3amserver_client.php create mode 100644 README.md create mode 100644 composer.json create mode 100644 ext_emconf.php create mode 100644 ext_icon.svg create mode 100644 ext_localconf.php create mode 100644 ext_tables.sql diff --git a/Classes/SecurityService.php b/Classes/SecurityService.php new file mode 100644 index 0000000..40221fd --- /dev/null +++ b/Classes/SecurityService.php @@ -0,0 +1,111 @@ +, in2code GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + */ + +use TYPO3\CMS\Core\Database\DatabaseConnection; +use TYPO3\CMS\Core\DataHandling\DataHandler; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Saltedpasswords\Salt\SaltFactory; + +/** + * Class SecurityService + */ +class SecurityService +{ + /** + * @var DatabaseConnection + */ + protected $database; + + /** + * SecurityService constructor. + * + * @SuppressWarnings(PHPMD.Superglobals) + */ + public function __construct() + { + $this->database = $GLOBALS['TYPO3_DB']; + } + + /** + * @param DataHandler $dataHandler + */ + public function processDatamap_beforeStart(DataHandler $dataHandler) + { + if (!empty($dataHandler->datamap['tx_t3amserver_client'])) { + foreach (array_keys($dataHandler->datamap['tx_t3amserver_client']) as $uid) { + if (is_string($uid) && 0 === strpos($uid, 'NEW')) { + $dataHandler->datamap['tx_t3amserver_client'][$uid]['token'] = GeneralUtility::hmac( + GeneralUtility::generateRandomBytes(256), + 'tx_t3amserver_client' + ); + } + } + } + } + + /** + * @param string $token + * @return bool + */ + public function isValid($token) + { + if (!is_string($token)) { + return false; + } + $where = 'token = ' . $this->database->fullQuoteStr($token, 'tx_t3amserver_client'); + return (bool)$this->database->exec_SELECTcountRows('*', 'tx_t3amserver_client', $where); + } + + /** + * @return array + */ + public function createEncryptionKey() + { + $config = array( + 'digest_alg' => 'sha512', + 'private_key_bits' => 1024, + 'private_key_type' => OPENSSL_KEYTYPE_RSA, + ); + + $res = openssl_pkey_new($config); + openssl_pkey_export($res, $privateKey); + $pubKey = openssl_pkey_get_details($res); + + $this->database->exec_INSERTquery('tx_t3amserver_keys', ['crdate' => time(), 'key_value' => $privateKey]); + return ['pubKey' => $pubKey['key'], 'encryptionId' => $this->database->sql_insert_id()]; + } + + public function authUser($user, $password, $encryptionId) + { + $where = 'uid = ' . (int)$encryptionId; + + $keyRow = $this->database->exec_SELECTgetSingleRow('*', 'tx_t3amserver_keys', $where); + if (!is_array($keyRow)) { + return false; + } + $this->database->exec_DELETEquery('tx_t3amserver_keys', $where); + + if (!@openssl_private_decrypt(base64_decode($password), $plainPassword, $keyRow['key_value'])) { + return false; + } + + $userRow = GeneralUtility::makeInstance(UserRepository::class)->getUser($user); + + $saltingInstance = SaltFactory::getSaltingInstance($userRow['password']); + return $saltingInstance->checkPassword($plainPassword, $userRow['password']); + } +} diff --git a/Classes/Server.php b/Classes/Server.php new file mode 100644 index 0000000..26c8b89 --- /dev/null +++ b/Classes/Server.php @@ -0,0 +1,136 @@ +, in2code GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + */ + +use ReflectionException; +use ReflectionMethod; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +/** + * Class Server + */ +class Server +{ + /** + * @var SecurityService + */ + protected $tokenService = null; + + /** + * @var array + */ + protected $routes = [ + 'check/ping' => [Server::class, 'ping'], + 'user/state' => [UserRepository::class, 'getUserState'], + 'user/auth' => [SecurityService::class, 'authUser'], + 'user/get' => [UserRepository::class, 'getUser'], + 'encryption/getKey' => [SecurityService::class, 'createEncryptionKey'], + ]; + + /** + * Server constructor. + */ + public function __construct() + { + $this->tokenService = GeneralUtility::makeInstance(SecurityService::class); + } + + public function handle() + { + try { + $data = $this->dispatch(GeneralUtility::_GET('token'), GeneralUtility::_GET('route')); + $response = ['code' => 1496395280, 'error' => false, 'message' => 'ok', 'data' => $data]; + } catch (ServerException $e) { + $response = ['code' => $e->getCode(), 'error' => true, 'message' => $e->getMessage(), 'data' => []]; + } + + header('Content-Type: application/json'); + echo json_encode($response); + } + + /** + * @param string $token + * @param string $route + * + * @return array + * + * @throws ServerException + */ + protected function dispatch($token, $route) + { + if (!$this->tokenService->isValid($token)) { + throw new ServerException('Access error', 1519999361); + } + + if (!is_string($route) || !isset($this->routes[$route])) { + throw new ServerException('Routing error', 1496395045); + } + + list($class, $action) = $this->routes[$route]; + + try { + $arguments = $this->mapParametersToArguments($class, $action); + $result = call_user_func_array([GeneralUtility::makeInstance($class), $action], $arguments); + } catch (\Exception $exception) { + throw new ServerException('Exception: ' . $exception->getMessage(), 1496395387, $exception); + } + + return $result; + } + + /** + * @param string $class + * @param string $action + * + * @return array + * + * @throws ServerException + */ + protected function mapParametersToArguments($class, $action) + { + $arguments = []; + + try { + $reflectionMethod = new ReflectionMethod($class, $action); + } catch (ReflectionException $e) { + throw new ServerException('Can not examine route target', 1520607184, $e); + } + foreach ($reflectionMethod->getParameters() as $position => $reflectionParameter) { + $parameter = $reflectionParameter->getName(); + $value = GeneralUtility::_GET($parameter); + + if (null === $value && !$reflectionParameter->allowsNull()) { + throw new ServerException('Missing parameter $' . $parameter, 1496395204); + } else { + if (null !== ($type = $reflectionParameter->getType())) { + settype($value, $type); + } + $arguments[$position] = $value; + } + } + return $arguments; + } + + /** + * @return bool + */ + public function ping() + { + return true; + } +} + +GeneralUtility::makeInstance(Server::class)->handle(); diff --git a/Classes/ServerException.php b/Classes/ServerException.php new file mode 100644 index 0000000..f4dd2b2 --- /dev/null +++ b/Classes/ServerException.php @@ -0,0 +1,25 @@ +, in2code GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + */ + +use Exception; + +/** + * Class ServerException + */ +class ServerException extends Exception +{ +} diff --git a/Classes/UserRepository.php b/Classes/UserRepository.php new file mode 100644 index 0000000..f97a443 --- /dev/null +++ b/Classes/UserRepository.php @@ -0,0 +1,54 @@ +connection = $GLOBALS['TYPO3_DB']; + } + + /** + * @param string $user + * + * @return string + */ + public function getUserState($user) + { + $where = 'username = ' . $this->connection->fullQuoteStr($user, 'be_users'); + $whereActive = $where . BackendUtility::deleteClause('be_users') . BackendUtility::BEenableFields('be_users'); + + if ($this->connection->exec_SELECTcountRows('*', 'be_users', $whereActive)) { + return 'okay'; + } + + if ($this->connection->exec_SELECTcountRows('*', 'be_users', $where)) { + return 'deleted'; + } + return 'unknown'; + } + + /** + * @param string $user + * + * @return array + */ + public function getUser($user) + { + $where = 'username = ' . $this->connection->fullQuoteStr($user, 'be_users'); + return $this->connection->exec_SELECTgetSingleRow('*', 'be_users', $where); + } +} diff --git a/Configuration/TCA/tx_t3amserver_client.php b/Configuration/TCA/tx_t3amserver_client.php new file mode 100644 index 0000000..e110b8b --- /dev/null +++ b/Configuration/TCA/tx_t3amserver_client.php @@ -0,0 +1,59 @@ + [ + 'label' => 'identifier', + 'descriptionColumn' => 'instance_notice', + 'sortby' => 'tstamp', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'title' => 'T3AM Client', + 'delete' => 'deleted', + 'versioningWS' => false, + 'hideAtCopy' => true, + 'prependAtCopy' => '[copy]', + 'copyAfterDuplFields' => 'instance_notice', + 'searchFields' => 'token,identifier,instance_notice', + 'rootLevel' => 1, + 'iconfile' => 'EXT:t3am_server/ext_icon.svg', + ], + 'interface' => [ + 'always_description' => 0, + 'showRecordFieldList' => 'token,identifier,instance_notice', + ], + 'types' => [ + '1' => [ + 'showitem' => 'token,identifier,instance_notice', + ], + ], + 'palettes' => [], + 'columns' => [ + 'token' => [ + 'label' => 'token (save the form to generate the token)', + 'config' => [ + 'type' => 'input', + 'size' => 17, + 'max' => 40, + 'readOnly' => true, + ], + ], + 'instance_notice' => [ + 'exclude' => true, + 'label' => 'Instance description', + 'config' => [ + 'type' => 'text', + 'rows' => 5, + 'cols' => 30, + ], + ], + 'identifier' => [ + 'exclude' => true, + 'label' => 'Instance name', + 'config' => [ + 'type' => 'input', + 'size' => 50, + 'max' => 255, + ], + ], + ], +]; diff --git a/README.md b/README.md new file mode 100644 index 0000000..89c3979 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# T3AM Server - TYPO3 Authentication Manager Server + +(Pron.: /tiːm/) + +## What does it do? + +T3AM Server provides a simple yet secure and fast API to check and retrieve backend users as well as validation of their password. +T3AM (client) allows you to log in at any TYPO3 with a single username/password which is managed by a central system (where T3AM Server is installed). + +[T3AM (client)](https://github.com/in2code-de/t3am) is required if you want to use T3AM Server. + +## Installation & Configuration + +Prerequisite: You should have installed T3AM (client) in another TYPO3 instance already! + +1. Get T3AM Server + a) Composer: `composer require in2code/t3am_server` + b) TER download: [extensions.typo3.org](https://extensions.typo3.org/extension/t3am_server) + c) github dowload [https://github.com/in2code-de/t3am_server](https://github.com/in2code-de/t3am_server/releases/latest) +2. Activate T3AM Server in the Extension Manager. +3. Open the List Module and select the root page (ID=0) +4. Create a new T3AM Client and fill in the name and description, save (without closing) and copy the token. +5. Go to your client installation (where you installed T3AM (client)), open the Extension Manager, configure T3AM by pasting the token in the respective field. Add the Server URL. + +Now anyone which has an active backend account on the T3AM Server side can use that account to log into the client system without prior account creation. +If a user gets deleted in T3AM Server (not removed from the database!) the user will be removed from the client system at next login. + +## Features + +* RSA-encrypted transmission for the password +* Detects deleted and disabled users +* Blazing fast + +## Credits + +ext_icon.svg:
Icons made by Smashicons from www.flaticon.com is licensed by CC 3.0 BY
diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..8794019 --- /dev/null +++ b/composer.json @@ -0,0 +1,26 @@ +{ + "name": "in2code/t3am_server", + "description": "T3AM Server - TYPO3 Authentication Manager Server", + "type": "typo3-cms-extension", + "license": "GPL-2.0", + "authors": [ + { + "name": "Oliver Eglseder", + "email": "php@vxvr.de" + } + ], + "minimum-stability": "stable", + "require": { + "php": "^5.5 || ^7.0", + "ext-openssl": "*", + "typo3/cms-core": "^7.6 || ^8.7" + }, + "autoload": { + "psr-4": { + "In2code\\T3AM\\Server\\": "Classes" + } + }, + "conflict": { + "in2code/t3am": "*" + } +} diff --git a/ext_emconf.php b/ext_emconf.php new file mode 100644 index 0000000..1255ce9 --- /dev/null +++ b/ext_emconf.php @@ -0,0 +1,19 @@ + 'T3AM Server', + 'description' => 'T3AM Server - TYPO3 Authentication Manager Server', + 'category' => 'services', + 'state' => 'stable', + 'uploadfolder' => 0, + 'createDirs' => '', + 'clearCacheOnLoad' => 0, + 'author' => 'Oliver Eglseder', + 'author_email' => 'php@vxvr.de', + 'author_company' => 'in2code GmbH', + 'version' => '1.0.0', + 'constraints' => [ + 'depends' => [], + 'conflicts' => [], + 'suggests' => [], + ], +]; diff --git a/ext_icon.svg b/ext_icon.svg new file mode 100644 index 0000000..b44f90f --- /dev/null +++ b/ext_icon.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ext_localconf.php b/ext_localconf.php new file mode 100644 index 0000000..11f8a2b --- /dev/null +++ b/ext_localconf.php @@ -0,0 +1,4 @@ +