openrat-cms

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit cac54ffa874b7ef4e2ad59d336b3cd84a2429768
parent 1100345e685fe0beb740364a3bbef165d23beef7
Author: dankert <openrat@jandankert.de>
Date:   Mon,  7 Feb 2022 21:44:42 +0100

New: Authenticate API users with the HTTP authorization header.

Diffstat:
Mmodules/cms/Dispatcher.class.php | 70+++++++++++++++++++++++++++++++++++++++++++++-------------------------
Mmodules/cms/action/LoginAction.class.php | 17-----------------
Mmodules/cms/action/RequestParams.class.php | 29+++++++++++++++++++++++++++--
Mmodules/cms/base/Startup.class.php | 7+++++--
Mmodules/cms/model/BaseObject.class.php | 2+-
5 files changed, 78 insertions(+), 47 deletions(-)

diff --git a/modules/cms/Dispatcher.class.php b/modules/cms/Dispatcher.class.php @@ -8,11 +8,14 @@ namespace cms; use BadMethodCallException; use cms\action\Action; use cms\action\RequestParams; +use cms\auth\Auth; +use cms\auth\InternalAuth; use cms\base\Configuration; use cms\base\DB; use cms\base\DefaultConfig; use cms\base\Startup; use cms\base\Version; +use cms\model\User; use configuration\Config; use configuration\ConfigurationLoader; use database\Database; @@ -99,6 +102,7 @@ class Dispatcher $this->connectToDatabase(); $this->startDatabaseTransaction(); + $this->checkLogin(); try{ @@ -133,10 +137,6 @@ class Dispatcher $result['output']['_id' ] = $this->request->id; - // Yes, closing the session flushes the session data and unlocks other waiting requests. - // Now another request is able to be executed. - Session::close(); - // Ablaufzeit für den Inhalt auf aktuelle Zeit setzen. header('Expires: ' . substr(date('r', time() - date('Z')), 0, -5) . 'GMT', false); @@ -144,8 +144,47 @@ class Dispatcher } + /** + * Clear up after the work is done. + */ + private function clear() { + // Yes, closing the session flushes the session data and unlocks other waiting requests. + // Now another request is able to be executed. + Session::close(); + if ( $this->request->authUser ) { + session_destroy(); + setcookie('or_sid','',time()); + } + } + + + /** + * Make a authentication, if there is a HTTP authorization. + */ + private function checkLogin() { + + if ( $this->request->withAuthorization ) { + $userAuth = new InternalAuth(); + $status = $userAuth->login( $this->request->authUser,$this->request->authPassword,'' ); + if ( ! ($status & Auth::STATUS_SUCCESS) ) + throw new SecurityException('user cannot be authenticated'); + + $user = User::loadWithName( $this->request->authUser,User::AUTH_TYPE_INTERNAL ); + Session::setUser( $user ); + } + } + + + /** + * checks if the request contains a pleasant CSRF token. + * + * @return void + */ private function checkPostToken() { + if ( $this->request->withAuthorization ) + return; // no CSRF token necessary if there are no cookies used for authentication. + if ( Configuration::subset('security')->is('use_post_token',true) && $this->request->isAction && $this->request->getToken() != Session::token() ) { @@ -448,27 +487,8 @@ class Dispatcher if ( ! $updater->isUpdateRequired( DB::get() ) ) return; - - if ( ! $dbConfig->is('auto_update',true)) - throw new \LogicException('DB Update required, but auto-update is disabled. '.Startup::TITLE." ".Startup::VERSION." needs DB-version ".Update::SUPPORTED_VERSION ); - - - try { - $adminDb = new Database( $dbConfig->subset('admin')->getConfig() + $dbConfig->getConfig() ); - $adminDb->id = $dbid; - } catch (\Exception $e) { - - throw new UIException('DATABASE_ERROR_CONNECTION', $e->getMessage(), [], $e); - } - - $updater->update($adminDb); - - // Try to close the PDO connection. PDO doc: - // To close the connection, you need to destroy the object by ensuring that all - // remaining references to it are deleted—you do this by assigning NULL to the variable that holds the object. - // If you don't do this explicitly, PHP will automatically close the connection when your script ends. - $adminDb = null; - unset($adminDb); + Logger::error("Database update required. Please call /status/?upgrade"); + throw new LogicException("Database update required, try calling /status/?upgrade"); } diff --git a/modules/cms/action/LoginAction.class.php b/modules/cms/action/LoginAction.class.php @@ -3,26 +3,9 @@ namespace cms\action; -use cms\auth\Auth; -use cms\auth\AuthRunner; -use cms\auth\InternalAuth; use cms\base\Configuration; -use cms\base\DB; -use cms\base\Startup; -use cms\model\User; use configuration\Config; -use Exception; -use language\Messages; -use logger\Logger; -use openid_connect\OpenIDConnectClient; -use security\Password; -use util\exception\ObjectNotFoundException; -use util\exception\SecurityException; -use util\exception\UIException; -use util\exception\ValidationException; -use util\mail\Mail; use util\Session; -use util\text\TextMessage; // OpenRat Content Management System diff --git a/modules/cms/action/RequestParams.class.php b/modules/cms/action/RequestParams.class.php @@ -2,6 +2,9 @@ namespace cms\action; +use cms\auth\Auth; +use cms\auth\InternalAuth; +use util\exception\SecurityException; use util\exception\ValidationException; use util\json\JSON; use util\mail\Mail; @@ -27,6 +30,10 @@ class RequestParams public $isAction; + public $headers; + public $authUser; + public $authPassword; + private $parameter; /** @@ -34,6 +41,12 @@ class RequestParams */ public $isUIAction; + + /** + * @var bool + */ + public $withAuthorization; + /** * RequestParams constructor. */ @@ -41,6 +54,15 @@ class RequestParams { // Is this a POST request? $this->isAction = @$_SERVER['REQUEST_METHOD'] == 'POST'; + $this->headers = array_change_key_case(getallheaders(), CASE_LOWER); + + if ( @$this->headers['authorization'] ) { + $this->withAuthorization = true; + // Only supporting Basic Auth + // Maybe in the future we will support JWT Bearer tokens... + if ( substr( $this->headers['authorization'],0,6 ) == 'Basic ' ) + list($this->authUser,$this->authPassword) = explode(':',base64_decode( substr( $this->headers['authorization'],6) ) ); + } $this->setParameterStore(); @@ -55,13 +77,16 @@ class RequestParams */ protected function setParameterStore() { - $headers = array_change_key_case(getallheaders(), CASE_LOWER); - $contenttype = trim(explode( ';',@$headers['content-type'])[0]); + $contenttype = trim(explode( ';',@$this->headers['content-type'])[0]); if ( !$this->isAction ) $this->parameter = &$_GET; + // GET method in the HTTP/1.1 spec, section 9.3: + // The GET method means retrieve whatever information ([...]) is identified by the Request-URI. + // so the request body MUST be ignored here. else + // POST requests are NOT idempotent switch( $contenttype ) { // These content-types are known by PHP, so we do NOT have to parse them: case 'application/x-www-form-urlencoded': // the most used form url encoding diff --git a/modules/cms/base/Startup.class.php b/modules/cms/base/Startup.class.php @@ -20,6 +20,7 @@ namespace cms\base; use ErrorException; use logger\Logger; use util\exception\ValidationException; +use util\Session; class Startup { @@ -188,8 +189,10 @@ class Startup { return true; // Aktuelle Datenbankverbindung ist readonly. - $db = DB::get(); - if (isset($db->conf['readonly']) && $db->conf['readonly']) + //$db = DB::get(); + $db = Session::getDatabase(); + + if ($db && isset($db->conf['readonly']) && $db->conf['readonly']) return true; return false; diff --git a/modules/cms/model/BaseObject.class.php b/modules/cms/model/BaseObject.class.php @@ -282,7 +282,7 @@ SQL $sql = Db::sql( <<<SQL SELECT * FROM {{acl}} WHERE objectid={objectid} - --AND ( languageid={languageid} OR languageid IS NULL ) + /*--AND ( languageid={languageid} OR languageid IS NULL )*/ AND ( type = {user} AND userid={userid} OR type = {group} AND $sqlGroupClause OR type = {all}