openrat-cms

# OpenRat Content Management System
git clone http://git.code.weiherhei.de/openrat-cms.git
Log | Files | Refs

commit e4e3229b130c63cab94f0c307a04f07fc5cef03c
parent 7f05efb9ae59a4f5189860d3751b07e9a23c2535
Author: Jan Dankert <develop@jandankert.de>
Date:   Fri, 23 Aug 2019 00:56:00 +0200

Refactoring: Authentication-Token nicht aus Benutzerdaten ermitteln (unsicher), sondern SHA1-Hash in neuer Tabelle speichern. Jedes Gerät erhält seinen eigenen Token, damit diese später einzeln löschbar sind.

Diffstat:
modules/cms-core/action/LoginAction.class.php | 2+-
modules/cms-core/auth/RememberAuth.class.php | 33++++++++++++++++++++-------------
modules/cms-core/model/User.class.php | 39+++++++++++++++++++++++++++++----------
modules/database-update/DbUpdate.class.php | 2+-
modules/database-update/update/DBVersion000021.class.php | 37+++++++++++++++++++++++++++++++++++++
modules/util/Browser.class.php | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/util/require.php | 1+
7 files changed, 149 insertions(+), 25 deletions(-)

diff --git a/modules/cms-core/action/LoginAction.class.php b/modules/cms-core/action/LoginAction.class.php @@ -842,7 +842,7 @@ class LoginAction extends Action { // Cookie setzen $this->setCookie('or_username',$user->name ); - $this->setCookie('or_token' ,$user->loginToken() ); + $this->setCookie('or_token' ,$user->createNewLoginToken() ); } // Anmeldung erfolgreich. diff --git a/modules/cms-core/auth/RememberAuth.class.php b/modules/cms-core/auth/RememberAuth.class.php @@ -16,29 +16,36 @@ class RememberAuth implements Auth public function username() { // Ermittelt den Benutzernamen aus den Login-Cookies. - if ( isset($_COOKIE['or_username']) && - isset($_COOKIE['or_token' ]) && + if ( isset($_COOKIE['or_token' ]) && isset($_COOKIE['or_dbid' ]) ) { - $name = $_COOKIE['or_username']; try { + list( $selector,$token) = array_pad( explode('.',$_COOKIE['or_token']),2,''); $dbid = $_COOKIE['or_dbid']; global $conf; $db = new Database( $conf['database'][$dbid] ); $db->id = $dbid; $db->start(); - Session::setDatabase($db); - - // Jetzt den Benutzer laden und nachschauen, ob der Token stimmt. - $user = User::loadWithName($name); - $token = $user->loginToken(); - - // Stimmt der Token? - if ( $_COOKIE['or_token'] == $token ) - // Token stimmt, Benutzer ist damit angemeldet. - return $name; + + $stmt = $db->sql( <<<SQL + SELECT userid,{{user}}.name as username,token,token_algo FROM {{auth}} + LEFT JOIN {{user}} ON {{auth}}.userid = {{user}}.id + WHERE selector = {selector} AND expires > {now} +SQL + ); + $stmt->setString('selector',$selector); + $stmt->setInt ('now' ,time() ); + + $auth = $stmt->getRow(); + + if ( $auth ) + { + if ( \security\Password::check($token, $auth['token'],$auth['token_algo']) ) + return $auth['username']; + } + } catch( ObjectNotFoundException $e ) { diff --git a/modules/cms-core/model/User.class.php b/modules/cms-core/model/User.class.php @@ -202,21 +202,40 @@ SQL /** * Ermittelt zu diesem Benutzer den Login-Token. */ - function loginToken() + function createNewLoginToken() { - global $conf; - $db = db_connection(); + $selector = Password::randomHexString(48); + $token = Password::randomHexString(48); - $sql = $db->sql( 'SELECT id,mail,name,password_hash FROM {{user}}'. - ' WHERE id={userid}' ); - $sql->setInt( 'userid',$this->userid ); - $row = $sql->getRow(); + $tokenHash = Password::hash($token,Password::ALGO_SHA1); - if ( count($row) == 0 ) - throw new \ObjectNotFoundException(); + $stmt = db()->sql( 'SELECT max(id) FROM {{auth}}'); + $count = $stmt->getOne(); + + $stmt = db()->sql( <<<SQL + INSERT INTO {{auth}} (id,userid,selector,token,token_algo,expires,create_date,platform,name) + VALUES( {id},{userid},{selector},{token},{token_algo},{expires},{create_date},{platform},{name} ) +SQL + ); + $expirationPeriod = Conf()->subset('user')->subset('security')->get('token_expires_after_days',730); + + $stmt->setInt( 'id' ,++$count ); + $stmt->setInt( 'userid' ,$this->userid ); + + $stmt->setString( 'selector' ,$selector ); + $stmt->setString( 'token' ,$tokenHash ); + $stmt->setInt ( 'token_algo' ,Password::ALGO_SHA1 ); + + $stmt->setInt( 'expires' ,time() + $expirationPeriod ); + $stmt->setInt( 'create_date',time() ); + + $browser = new \Browser(); + $stmt->setString( 'platform',$browser->platform ); + $stmt->setString( 'name' ,$browser->name ); + $row = $stmt->getRow(); // Zusammensetzen des Tokens - return sha1( $row['password_hash'].$row['name'].$row['id'].$row['mail'] ); + return $selector.'.'.$token; } diff --git a/modules/database-update/DbUpdate.class.php b/modules/database-update/DbUpdate.class.php @@ -6,7 +6,7 @@ use database\Database; class DbUpdate { // This is the required DB version: - const SUPPORTED_VERSION = 20; + const SUPPORTED_VERSION = 21; // ----------------------------^^----------------------------- const STATUS_UPDATE_PROGRESS = 0; diff --git a/modules/database-update/update/DBVersion000021.class.php b/modules/database-update/update/DBVersion000021.class.php @@ -0,0 +1,37 @@ +<?php + +use database\DbVersion; + +/** + * Authentication tokens. + * + * @author dankert + * + */ +class DBVersion000021 extends DbVersion +{ + /** + * + */ + public function update() + { + $this->addTable('auth'); + + $this->addColumn('auth','selector' ,OR_DB_COLUMN_TYPE_VARCHAR ,255 ,null,OR_DB_COLUMN_NOT_NULLABLE); + $this->addColumn('auth','userid' ,OR_DB_COLUMN_TYPE_INT ,0 ,null,OR_DB_COLUMN_NOT_NULLABLE); + $this->addColumn('auth','token' ,OR_DB_COLUMN_TYPE_VARCHAR ,255 ,null,OR_DB_COLUMN_NOT_NULLABLE); + $this->addColumn('auth','token_algo' ,OR_DB_COLUMN_TYPE_INT ,0 ,0 ,OR_DB_COLUMN_NOT_NULLABLE); + $this->addColumn('auth','expires' ,OR_DB_COLUMN_TYPE_INT ,0 ,null,OR_DB_COLUMN_NOT_NULLABLE); + $this->addColumn('auth','create_date' ,OR_DB_COLUMN_TYPE_INT ,0 ,null,OR_DB_COLUMN_NOT_NULLABLE); + $this->addColumn('auth','platform' ,OR_DB_COLUMN_TYPE_VARCHAR,255 ,null,OR_DB_COLUMN_NOT_NULLABLE); + $this->addColumn('auth','name' ,OR_DB_COLUMN_TYPE_VARCHAR,255 ,null,OR_DB_COLUMN_NOT_NULLABLE); + + + $this->addPrimaryKey ('auth','id'); + + $this->addConstraint ('auth','userid' ,'user' ,'id'); + + $this->addUniqueIndex('auth','selector' ); + } +} + diff --git a/modules/util/Browser.class.php b/modules/util/Browser.class.php @@ -0,0 +1,60 @@ +<?php + +class Browser { + + public $name; + public $platform; + + + + public function Browser() + { + $agent = @$_SERVER['HTTP_USER_AGENT']; + + + if (stripos($agent, 'Opera') || stripos($agent, 'OPR/')) + $this->name = 'Opera'; + elseif (stripos($agent, 'Edge')) + $this->name= 'Microsoft Edge'; + elseif (stripos($agent, 'vivaldi')) + $this->name= 'Vivaldi'; + elseif (stripos($agent, 'netscape')) + $this->name= 'Netscape'; + elseif (stripos($agent, 'Chrome')) + $this->name= 'Google Chrome'; + elseif (stripos($agent, 'Safari')) + $this->name= 'Safari'; + elseif (stripos($agent, 'Firefox')) + $this->name= 'Mozilla Firefox'; + elseif (stripos($agent, 'MSIE') || stripos($agent, 'Trident/7')) + $this->name= 'Internet Explorer'; + else + $this->name= 'Unknown Browser'; + + if (stripos($agent, 'Linux') || stripos($agent, 'linux')) + $this->platform = 'Linux'; + elseif (stripos($agent, 'Windows') || stripos($agent, 'win32')) + $this->platform = 'Windows'; + elseif (stripos($agent, 'android') ) + $this->platform = 'Android'; + elseif (stripos($agent, 'mac os') || stripos($agent, 'cpu os') || stripos($agent, 'iPhone') || stripos($agent, 'OS X')) + $this->platform = 'Android'; + elseif (stripos($agent, 'cros') ) + $this->platform = 'Chrome OS'; + elseif (stripos($agent, 'SymbOS') ) + $this->platform = 'Symbian OS'; + elseif (stripos($agent, 'windows phone') ) + $this->platform = 'Microsoft Windows Phone '; + elseif (stripos($agent, 'nokia') ) + $this->platform = 'Nokia'; + elseif (stripos($agent, 'blackberry') ) + $this->platform = 'Blackberry'; + elseif (stripos($agent, 'openbsd') ) + $this->platform = 'OpenBSD'; + elseif (stripos($agent, 'freebsd') ) + $this->platform = 'FreeBSD'; + else + $this->name= 'Unknown OS'; + + } +} diff --git a/modules/util/require.php b/modules/util/require.php @@ -17,6 +17,7 @@ require_once( __DIR__.'/'.'Mail.class.php' ); if (extension_loaded('ldap') ) require_once( __DIR__.'/'.'Ldap.class.php' ); +require_once( __DIR__.'/'.'Browser.class.php' ); require_once( __DIR__.'/'.'FileUtils.class.php' ); require_once( __DIR__.'/'.'JSON.class.php' ); require_once( __DIR__.'/'.'Less.php' );