0013272: add pin column, backend and config
authorPhilipp Schüle <p.schuele@metaways.de>
Wed, 28 Jun 2017 07:43:05 +0000 (09:43 +0200)
committerPhilipp Schüle <p.schuele@metaways.de>
Tue, 4 Jul 2017 07:52:09 +0000 (09:52 +0200)
* uses Tinebase_Auth_Sql for validation
* adds 'login' property to config / only if login => true
 2nd factor is checked during login

https://forge.tine20.org/view.php?id=13272

Change-Id: I7aebec4efacfe6b549f5809ee3e6821edb47e11b
Reviewed-on: http://gerrit.tine20.com/customers/4965
Reviewed-by: Philipp Schüle <p.schuele@metaways.de>
Tested-by: Philipp Schüle <p.schuele@metaways.de>
tests/tine20/Tinebase/AuthTest.php
tests/tine20/Tinebase/Timemachine/ModificationLogTest.php
tine20/Tinebase/Auth/SecondFactor/Tine20.php [new file with mode: 0644]
tine20/Tinebase/Config.php
tine20/Tinebase/Controller.php
tine20/Tinebase/Frontend/Json.php
tine20/Tinebase/Setup/Update/Release10.php
tine20/Tinebase/Setup/setup.xml
tine20/Tinebase/User/Abstract.php
tine20/Tinebase/User/Sql.php

index 7b6000b..299cf0a 100644 (file)
@@ -215,9 +215,23 @@ class Tinebase_AuthTest extends TestCase
     {
         $result = Tinebase_Auth::validateSecondFactor('phil', 'phil', array(
             'active' => true,
-            'provider'              => 'Mock',
+            'provider' => 'Mock',
             'url' => 'https://localhost/validate/check',
         ));
         $this->assertEquals(Tinebase_Auth::SUCCESS, $result);
     }
+
+    /**
+     * @see 0013272: add pin column, backend and config
+     */
+    public function testSecondFactorTine20()
+    {
+        $user = Tinebase_Core::getUser();
+        Tinebase_User::getInstance()->setPin($user, '1234');
+        $result = Tinebase_Auth::validateSecondFactor($user->accountLoginName, '1234', array(
+            'active' => true,
+            'provider' => 'Tine20',
+        ));
+        $this->assertEquals(Tinebase_Auth::SUCCESS, $result);
+    }
 }
index 222bfdc..1de2f7a 100644 (file)
@@ -686,7 +686,8 @@ class Tinebase_Timemachine_ModificationLogTest extends PHPUnit_Framework_TestCas
         $authBackend->setIdentity($replicationUser->accountLoginName);
         $authBackend->setCredential('ssha256Password');
         $authResult = $authBackend->authenticate();
-        $this->assertEquals(Zend_Auth_Result::SUCCESS, $authResult->getCode(), 'changing password did not work');
+        $this->assertEquals(Zend_Auth_Result::SUCCESS, $authResult->getCode(), 'changing password did not work: '
+            . print_r($authResult->getMessages(), true));
 
         // set status to expired
         $mod = $userModifications->getFirstRecord();
diff --git a/tine20/Tinebase/Auth/SecondFactor/Tine20.php b/tine20/Tinebase/Auth/SecondFactor/Tine20.php
new file mode 100644 (file)
index 0000000..9a219bc
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Tine 2.0
+ *
+ * @package     Tinebase
+ * @subpackage  Auth
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @copyright   Copyright (c) 2016-2017 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ */
+class Tinebase_Auth_SecondFactor_Tine20 extends Tinebase_Auth_SecondFactor_Abstract
+{
+    /**
+     * @param $username
+     * @param $password
+     * @return Zend_Auth_Result
+     */
+    public function validate($username, $password)
+    {
+        if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
+            __METHOD__ . '::' . __LINE__ . ' Options: ' . print_r($this->_options, true));
+
+        $instance = new Tinebase_Auth_Sql(
+            Tinebase_Core::getDb(),
+            SQL_TABLE_PREFIX . 'accounts',
+            'login_name',
+            'pin'
+        );
+        $instance->setIdentity($username);
+        $instance->setCredential($password);
+
+        $result = $instance->authenticate();
+        if ($result->isValid()) {
+            return Tinebase_Auth::SUCCESS;
+        } else {
+            if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
+                __METHOD__ . '::' . __LINE__ . ' Auth failure! ' . print_r($result->getMessages(), true));
+            return Tinebase_Auth::FAILURE;
+        }
+    }
+}
index b5eb8c7..c1ab365 100644 (file)
@@ -676,6 +676,7 @@ class Tinebase_Config extends Tinebase_Config_Abstract
          *      'url'                   => 'https://localhost/validate/check',
          *      'allow_self_signed'     => true,
          *      'ignorePeerName'        => true,
+         *      'login'                 => true, // validate during login + show field on login screen
          * )
          */
         self::AUTHENTICATIONSECONDFACTOR => array(
index b50180e..d3b9f3b 100644 (file)
@@ -723,7 +723,7 @@ class Tinebase_Controller extends Tinebase_Controller_Event
         // 2nd factor
         $secondFactorConfig = Tinebase_Config::getInstance()->get(Tinebase_Config::AUTHENTICATIONSECONDFACTOR);
 
-        if ($secondFactorConfig && $secondFactorConfig->active && $accessLog->clienttype === 'JSON-RPC') {
+        if ($secondFactorConfig && $secondFactorConfig->active && $secondFactorConfig->login && $accessLog->clienttype === 'JSON-RPC') {
             $context = $this->getRequestContext();
             if (Tinebase_Auth::validateSecondFactor($user->accountLoginName,
                 $context['otp'],
index efc4d5a..79d8773 100644 (file)
@@ -739,7 +739,7 @@ class Tinebase_Frontend_Json extends Tinebase_Frontend_Json_Abstract
         
         $registryData =  array(
             'modSsl'           => Tinebase_Auth::getConfiguredBackend() == Tinebase_Auth::MODSSL,
-            'secondFactor'     => $secondFactorConfig && $secondFactorConfig->active,
+            'secondFactor'     => $secondFactorConfig && $secondFactorConfig->active && $secondFactorConfig->login,
             'serviceMap'       => $tbFrontendHttp->getServiceMap(),
             'locale'           => array(
                 'locale'   => $locale->toString(),
index f04a54e..c771330 100644 (file)
@@ -1567,8 +1567,8 @@ class Tinebase_Setup_Update_Release10 extends Setup_Update_Abstract
                 </index>'));
         }
 
-        if ($this->_backend->tableExists('cal_events')) {
-            $this->_backend->addForeignKey('cal_events', new Setup_Backend_Schema_Index_Xml('<index>
+        if ($this->_backend->tableExists('cal_resources')) {
+            $this->_backend->addForeignKey('cal_resources', new Setup_Backend_Schema_Index_Xml('<index>
                     <name>cal_resources::container_id--container::id</name>
                     <field>
                         <name>container_id</name>
@@ -1709,4 +1709,26 @@ class Tinebase_Setup_Update_Release10 extends Setup_Update_Abstract
 
         $this->setApplicationVersion('Tinebase', '10.33');
     }
+
+    /**
+     * update to 10.34
+     *
+     * add pin column to accounts
+     */
+    public function update_33()
+    {
+        if (! $this->_backend->columnExists('pin', 'accounts')) {
+            $declaration = new Setup_Backend_Schema_Field_Xml('<field>
+                    <name>pin</name>
+                    <type>text</type>
+                    <length>100</length>
+                    <notnull>false</notnull>
+                </field>');
+            $this->_backend->addCol('accounts', $declaration);
+
+            $this->setTableVersion('accounts', 12);
+        }
+
+        $this->setApplicationVersion('Tinebase', '10.34');
+    }
 }
index cf6a7ac..db5d7a3 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <application>
     <name>Tinebase</name>
-    <version>10.33</version>
+    <version>10.34</version>
     <tables>
         <table>
             <name>applications</name>
 
         <table>
             <name>accounts</name>
-            <version>11</version>
+            <version>12</version>
             <declaration>
                 <field>
                     <name>id</name>
                     <notnull>false</notnull>
                 </field>
                 <field>
+                    <name>pin</name>
+                    <type>text</type>
+                    <length>100</length>
+                    <notnull>false</notnull>
+                </field>
+                <field>
                     <name>login_shell</name>
                     <type>text</type>
                     <length>100</length>
index 3bd0726..24be1de 100644 (file)
@@ -417,7 +417,7 @@ abstract class Tinebase_User_Abstract implements Tinebase_User_Interface
     protected function _generateUserWithSchema2($_account)
     {
         $result = $_account->accountLastName;
-        for ($i=0; $i < strlen($_account->accountFirstName); $i++) {
+        for ($i=0, $iMax = strlen($_account->accountFirstName); $i < $iMax; $i++) {
         
             $userName = strtolower(substr(Tinebase_Helper::replaceSpecialChars($_account->accountFirstName), 0, $i+1) . Tinebase_Helper::replaceSpecialChars($_account->accountLastName));
             if (! $this->nameExists('accountLoginName', $userName)) {
@@ -438,7 +438,7 @@ abstract class Tinebase_User_Abstract implements Tinebase_User_Interface
     protected function _generateUserWithSchema3($_account)
     {
         $result = $_account->accountLastName;
-        for ($i=0; $i < strlen($_account->accountFirstName); $i++) {
+        for ($i=0, $iMax = strlen($_account->accountFirstName); $i < $iMax; $i++) {
         
             $userName = strtolower(substr(Tinebase_Helper::replaceSpecialChars($_account->accountFirstName), 0, $i+1) . '.' . Tinebase_Helper::replaceSpecialChars($_account->accountLastName));
             if (! $this->nameExists('accountLoginName', $userName)) {
@@ -628,6 +628,22 @@ abstract class Tinebase_User_Abstract implements Tinebase_User_Interface
         }
     }
 
+    /**
+     * set PIN
+     *
+     * @param  string  $_userId
+     * @param  string  $_pin
+     * @return array
+     *
+     * TODO move to Tinebase_User_sql and replace with abstract fn here
+     * TODO replicate PIN?
+     */
+    public function setPin($_userId, $_pin)
+    {
+        $userId = $_userId instanceof Tinebase_Model_User ? $_userId->getId() : $_userId;
+        return $this->_updatePasswordProperty($userId, $_pin, 'pin');
+    }
+
     /******************* abstract functions *********************/
     
     /**
index e8010bc..9a5a92e 100644 (file)
@@ -455,28 +455,45 @@ class Tinebase_User_Sql extends Tinebase_User_Abstract
         $userId = $_userId instanceof Tinebase_Model_User ? $_userId->getId() : $_userId;
         $user = $_userId instanceof Tinebase_Model_FullUser ? $_userId : $this->getFullUserById($userId);
         $this->checkPasswordPolicy($_password, $user);
-        
-        $accountsTable = new Tinebase_Db_Table(array('name' => SQL_TABLE_PREFIX . 'accounts'));
-        
-        $accountData['password'] = ($_encrypt) ? Hash_Password::generate('SSHA256', $_password) : $_password;
-        $accountData['last_password_change'] = Tinebase_DateTime::now()->get(Tinebase_Record_Abstract::ISO8601LONG);
-        
+
+        $accountData = $this->_updatePasswordProperty($userId, $_password, 'password', $_encrypt);
+        $this->_setPluginsPassword($userId, $_password, $_encrypt);
+
+        $accountData['id'] = $userId;
+        $oldPassword = new Tinebase_Model_UserPassword(array('id' => $userId), true);
+        $newPassword = new Tinebase_Model_UserPassword($accountData, true);
+        $this->_writeModLog($newPassword, $oldPassword);
+    }
+
+    /**
+     * @param        $_userId
+     * @param        $_password
+     * @param string $_property
+     * @param boolean  $_encrypt
+     * @return array $accountData
+     * @throws Tinebase_Exception_NotFound
+     */
+    protected function _updatePasswordProperty($_userId, $_password, $_property = 'password', $_encrypt = true)
+    {
+        $accountsTable = new Tinebase_Db_Table(array('name' => SQL_TABLE_PREFIX . $this->_tableName));
+
+        $accountData = array();
+        $accountData[$_property] = ($_encrypt) ? Hash_Password::generate('SSHA256', $_password) : $_password;
+        if ($_property === 'password') {
+            $accountData['last_password_change'] = Tinebase_DateTime::now()->get(Tinebase_Record_Abstract::ISO8601LONG);
+        }
+
         $where = array(
-            $accountsTable->getAdapter()->quoteInto($accountsTable->getAdapter()->quoteIdentifier('id') . ' = ?', $userId)
+            $accountsTable->getAdapter()->quoteInto($accountsTable->getAdapter()->quoteIdentifier('id') . ' = ?', $_userId)
         );
-        
+
         $result = $accountsTable->update($accountData, $where);
-        
+
         if ($result != 1) {
             throw new Tinebase_Exception_NotFound('Unable to update password! account not found in authentication backend.');
         }
-        
-        $this->_setPluginsPassword($userId, $_password, $_encrypt);
 
-        $accountData['id'] = $userId;
-        $oldPassword = new Tinebase_Model_UserPassword(array('id' => $userId), true);
-        $newPassword = new Tinebase_Model_UserPassword($accountData, true);
-        $this->_writeModLog($newPassword, $oldPassword);
+        return $accountData;
     }
 
     /**