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:
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' );