File indexing completed on 2025-05-04 05:29:10

0001 <?php
0002 
0003 /**
0004  *  ocs-webserver
0005  *
0006  *  Copyright 2016 by pling GmbH.
0007  *
0008  *    This file is part of ocs-webserver.
0009  *
0010  *    This program is free software: you can redistribute it and/or modify
0011  *    it under the terms of the GNU Affero General Public License as
0012  *    published by the Free Software Foundation, either version 3 of the
0013  *    License, or (at your option) any later version.
0014  *
0015  *    This program is distributed in the hope that it will be useful,
0016  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
0017  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0018  *    GNU Affero General Public License for more details.
0019  *
0020  *    You should have received a copy of the GNU Affero General Public License
0021  *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
0022  *
0023  *    Created: 16.12.2016
0024  **/
0025 class Default_Model_OAuth_Ocs implements Default_Model_OAuth_Interface
0026 {
0027     const PREFIX_SEPARATOR = '_';
0028 
0029     /** @var Zend_Db_Adapter_Abstract $_db */
0030     protected $_db;
0031     /** @var null|string $_tableName */
0032     protected $_tableName;
0033     /** @var Zend_Config $config */
0034     protected $config;
0035     /** @var Zend_Session_Namespace $session */
0036     protected $session;
0037     /** @var  array */
0038     protected $memberData;
0039     /** @var  string */
0040     protected $access_token;
0041     /** @var  boolean */
0042     protected $connected;
0043     /** @var  string */
0044     protected $redirect;
0045     private $client_secret;
0046     private $client_id;
0047     private $uri_callback;
0048     private $uri_token;
0049     private $uri_auth;
0050     private $uri_profile;
0051 
0052     /**
0053      * Default_Model_Oauth_Ocs constructor.
0054      *
0055      * @param Zend_Db_Adapter_Abstract|null $dbAdapter
0056      * @param null                          $tableName
0057      * @param Zend_Config                   $config
0058      *
0059      * @throws Zend_Exception
0060      * @throws Zend_Session_Exception
0061      */
0062     public function __construct(Zend_Db_Adapter_Abstract $dbAdapter = null, $tableName = null, Zend_Config $config)
0063     {
0064         $this->_db = $dbAdapter;
0065         if (empty($this->_db)) {
0066             $this->_db = Zend_Db_Table_Abstract::getDefaultAdapter();
0067             if (empty($this->_db)) {
0068                 throw new Zend_Exception('No database adapter present');
0069             }
0070         }
0071 
0072         $this->_tableName = $tableName;
0073 
0074         $this->config = $config;
0075         if (empty($this->config)) {
0076             throw new Zend_Exception('No config present');
0077         }
0078 
0079         // Zend_Registry::get('logger')->debug(__METHOD__ . ' - config: ' . print_r($this->config->toArray(), true));
0080 
0081         $this->uri_auth = $this->config->authorize_url;
0082         $this->uri_token = $this->config->token_url;
0083         $this->uri_callback = $this->config->callback;
0084         $this->client_id = $this->config->client_id;
0085         $this->client_secret = $this->config->client_secret;
0086         $this->uri_profile = $this->config->profile_user_url;
0087 
0088         $this->session = new Zend_Session_Namespace('OCS_AUTH');
0089     }
0090 
0091     /**
0092      * @param null $redirectUrlAfterSuccess
0093      *
0094      * @throws Zend_Cache_Exception
0095      * @throws Zend_Exception
0096      */
0097     public function authStart($redirectUrlAfterSuccess = null)
0098     {
0099         $state_token = $this->generateToken('auth');
0100         $this->saveStateData($state_token, $redirectUrlAfterSuccess);
0101 
0102         $requestUrl = $this->uri_auth . "?client_id={$this->client_id}&redirect_uri=" . urlencode($this->uri_callback)
0103             . "&scope=profile&state={$state_token}";
0104 
0105         Zend_Registry::get('logger')->debug(__METHOD__ . ' - redirectUrl: ' . print_r($requestUrl, true));
0106 
0107         /** @var Zend_Controller_Action_Helper_Redirector $redirection */
0108         $redirection = Zend_Controller_Action_HelperBroker::getStaticHelper('redirector');
0109         $redirection->gotoUrl($requestUrl);
0110     }
0111 
0112     /**
0113      * @param $prefix_state
0114      *
0115      * @return string
0116      */
0117     private function generateToken($prefix_state)
0118     {
0119         $prefix = '';
0120         if (false == empty($prefix_state)) {
0121             $prefix = $prefix_state . self::PREFIX_SEPARATOR;
0122         }
0123 
0124         return $prefix . Local_Tools_UUID::generateUUID();
0125     }
0126 
0127     /**
0128      * @param      $token
0129      * @param null $redirect
0130      *
0131      * @return bool
0132      * @throws Zend_Cache_Exception
0133      * @throws Zend_Exception
0134      */
0135     private function saveStateData($token, $redirect = null)
0136     {
0137         /** @var Zend_Cache_Core $cache */
0138         $cache = Zend_Registry::get('cache');
0139 
0140         return $cache->save(array('redirect' => $redirect), $token, array('auth', 'github'), 120);
0141     }
0142 
0143     /**
0144      * @param array $http_params
0145      *
0146      * @return null|string
0147      * @throws Zend_Exception
0148      */
0149     public function authFinish($http_params)
0150     {
0151         $error = (array_key_exists('error', $http_params)) ? $http_params['error'] : null;
0152         if ($error) {
0153             throw new Zend_Exception('Authentication failed. OAuth provider returned an error: ' . $error);
0154         }
0155 
0156         $request_code = (array_key_exists('code', $http_params)) ? $http_params['code'] : null;
0157         $session_state_token = (array_key_exists('state', $http_params)) ? $http_params['state'] : null;
0158 
0159         $result = $this->isValidStateCode($session_state_token);
0160         if ($result === false) {
0161             $this->connected = false;
0162 
0163             return false;
0164         }
0165 
0166         $this->access_token = $this->requestAccessToken($request_code, $session_state_token);
0167 
0168         if (isset($this->access_token)) {
0169             $this->connected = true;
0170         }
0171 
0172         $this->redirect = $this->getRedirectFromState($session_state_token);
0173 
0174         //        $this->clearStateToken($session_state_token);
0175 
0176         return $this->access_token;
0177     }
0178 
0179     /**
0180      * @param $session_state
0181      *
0182      * @return bool
0183      * @throws Zend_Exception
0184      */
0185     protected function isValidStateCode($session_state)
0186     {
0187         if (empty($session_state)) {
0188             return false;
0189         }
0190 
0191         /** @var Zend_Cache_Backend_Apc $cache */
0192         $cache = Zend_Registry::get('cache');
0193         if (false == $cache->test($session_state)) {
0194             Zend_Registry::get('logger')->err(__METHOD__
0195                 . ' - Authentication failed. OAuth provider send a token that does not match.')
0196             ;
0197 
0198             return false;
0199         }
0200 
0201         return true;
0202     }
0203 
0204     /**
0205      * @param string $code
0206      * @param        $state_token
0207      *
0208      * @return null|string
0209      * @throws Zend_Exception
0210      */
0211     protected function requestAccessToken($code, $state_token)
0212     {
0213         $response = $this->requestHttpAccessToken($code, $state_token);
0214         $data = $this->parseResponse($response);
0215 
0216         Zend_Registry::getInstance()->get('logger')->debug(__METHOD__ . ' - response for post request\n' . print_r($data, true));
0217 
0218         if ($response->getStatus() != 200) {
0219             throw new Zend_Exception('Authentication failed. OAuth provider send error message: ' . $data['error'] . ' : '
0220                 . $data['error_description']);
0221         }
0222 
0223         return (array_key_exists('access_token', $data)) ? $data['access_token'] : null;
0224     }
0225 
0226     /**
0227      * @param $request_code
0228      * @param $state_token
0229      *
0230      * @return Zend_Http_Response
0231      * @throws Zend_Exception
0232      * @throws Zend_Http_Client_Exception
0233      */
0234     protected function requestHttpAccessToken($request_code, $state_token)
0235     {
0236         $httpClient = new Zend_Http_Client($this->uri_token);
0237         $httpClient->setMethod(Zend_Http_Client::POST);
0238         $httpClient->setHeaders('Accept', 'application/json');
0239         $httpClient->setParameterPost(array(
0240             'client_id'     => $this->client_id,
0241             'client_secret' => $this->client_secret,
0242             'code'          => $request_code,
0243             'redirect_uri'  => $this->uri_callback,
0244             'state'         => $state_token,
0245             'grant_type'    => 'authorization_code'
0246         ));
0247 
0248         $response = $httpClient->request();
0249 
0250         Zend_Registry::get('logger')->debug(__METHOD__ . ' - request: \n' . $httpClient->getLastRequest());
0251         Zend_Registry::getInstance()->get('logger')->debug(__METHOD__ . ' - response for post request\n'
0252             . $response->getHeadersAsString())
0253         ;
0254 
0255         return $response;
0256     }
0257 
0258     /**
0259      * @param Zend_Http_Response $response
0260      *
0261      * @return mixed
0262      * @throws Zend_Json_Exception
0263      */
0264     protected function parseResponse(Zend_Http_Response $response)
0265     {
0266         $data = Zend_Json::decode($response->getBody());
0267 
0268         return $data;
0269     }
0270 
0271     /**
0272      * @param $session_state_token
0273      *
0274      * @return mixed|null
0275      * @throws Zend_Exception
0276      */
0277     private function getRedirectFromState($session_state_token)
0278     {
0279         /** @var Zend_Cache_Core $cache */
0280         $cache = Zend_Registry::get('cache');
0281         $data = $cache->load($session_state_token);
0282 
0283         return (is_array($data) AND array_key_exists('redirect', $data)) ? $data['redirect'] : null;
0284     }
0285 
0286     /**
0287      * @return Zend_Auth_Result
0288      * @throws Exception
0289      * @throws Zend_Exception
0290      */
0291     public function authenticate()
0292     {
0293         $userEmail = $this->getUserEmail();
0294 
0295         Zend_Registry::get('logger')->info(__METHOD__ . ' - userEmail: ' . $userEmail);
0296 
0297         $authResult = $this->authenticateUserEmail($userEmail);
0298 
0299         if ($authResult->isValid()) {
0300             $authModel = new Default_Model_Authorization();
0301             $authModel->storeAuthSessionDataByIdentity($this->memberData['member_id']);
0302             $authModel->updateRememberMe(true);
0303             $authModel->updateUserLastOnline('member_id', $this->memberData['member_id']);
0304 
0305             return $authResult;
0306         }
0307 
0308         if ($authResult->getCode() == Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND) {
0309             return $this->createAuthResult(Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND, $userEmail,
0310                 array('A record with the supplied identity could not be found.'));
0311         }
0312 
0313         Zend_Registry::get('logger')->info(__METHOD__ . "\n"
0314             . ' - authentication error : user=>'.$userEmail.': ' . "\n"
0315             . ' - messages : ' . implode(",\n",$authResult->getMessages())
0316         );
0317 
0318         return $authResult;
0319     }
0320 
0321     /**
0322      * @return string
0323      * @throws Zend_Exception
0324      * @throws Zend_Http_Client_Exception
0325      * @throws Zend_Json_Exception
0326      */
0327     public function getUserEmail()
0328     {
0329         $httpClient = new Zend_Http_Client($this->uri_profile);
0330         $httpClient->setHeaders('Authorization', 'Bearer ' . $this->access_token);
0331         $httpClient->setHeaders('Accept', 'application/json');
0332         $response = $httpClient->request();
0333         Zend_Registry::get('logger')->debug(__METHOD__ . ' - last request: \n' . $httpClient->getLastRequest());
0334         Zend_Registry::getInstance()->get('logger')->debug(__METHOD__ . ' - response from post request\n'
0335             . $response->getHeadersAsString())
0336         ;
0337         $data = $this->parseResponse($response);
0338         Zend_Registry::getInstance()->get('logger')->debug(__METHOD__ . ' - parsed response from post request\n' . print_r($data,
0339                 true))
0340         ;
0341         if ($response->getStatus() > 200) {
0342             throw new Zend_Exception('error while request users data');
0343         }
0344         if (isset($data['email'])) {
0345             return $data['email'];
0346         }
0347 
0348         return '';
0349     }
0350 
0351     /**
0352      * @param $userEmail
0353      *
0354      * @return Zend_Auth_Result
0355      * @throws Zend_Exception
0356      */
0357     private function authenticateUserEmail($userEmail)
0358     {
0359         $validator = new Zend_Validate_EmailAddress();
0360         if ($validator->isValid($userEmail)) {
0361             $resultSet = $this->fetchUserByEmail($userEmail);
0362         } else {
0363             $resultSet = $this->fetchUserByUsername($userEmail);
0364         }
0365 
0366         Zend_Registry::get('logger')->info(__METHOD__ . ' - ResultSet: ' . print_r($resultSet, true));
0367 
0368         if (count($resultSet) == 0) {
0369             return $this->createAuthResult(Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND, $userEmail,
0370                 array('A record with the supplied identity could not be found.'));
0371         }
0372 
0373         if (count($resultSet) > 1) {
0374             return $this->createAuthResult(Zend_Auth_Result::FAILURE_IDENTITY_AMBIGUOUS, $userEmail,
0375                 array('More than one record matches the supplied identity.'));
0376         }
0377         $this->memberData = array_shift($resultSet);
0378         Zend_Registry::get('logger')->debug(__METHOD__ . ' - this->memberData: ' . print_r($this->memberData, true));
0379 
0380         return $this->createAuthResult(Zend_Auth_Result::SUCCESS, $userEmail, array('Authentication successful.'));
0381     }
0382 
0383     /**
0384      * @param $userEmail
0385      *
0386      * @return array
0387      * @throws Zend_Exception
0388      */
0389     private function fetchUserByEmail($userEmail)
0390     {
0391         $sql = "
0392             SELECT * 
0393             FROM {$this->_tableName} 
0394             WHERE 
0395             is_active = :active AND 
0396             is_deleted = :deleted AND 
0397             login_method = :login AND 
0398             mail = :mail";
0399 
0400         $this->_db->getProfiler()->setEnabled(true);
0401         $resultSet = $this->_db->fetchAll($sql, array(
0402             'active'  => Default_Model_DbTable_Member::MEMBER_ACTIVE,
0403             'deleted' => Default_Model_DbTable_Member::MEMBER_NOT_DELETED,
0404             'login'   => Default_Model_DbTable_Member::MEMBER_LOGIN_LOCAL,
0405             'mail'    => $userEmail
0406         ));
0407         Zend_Registry::get('logger')->info(__METHOD__ . ' - ResultSet: ' . print_r($resultSet, true));
0408         Zend_Registry::get('logger')->info(__METHOD__ . ' - seconds: ' . $this->_db->getProfiler()->getLastQueryProfile()
0409                                                                                             ->getElapsedSecs())
0410         ;
0411         $this->_db->getProfiler()->setEnabled(false); 
0412 
0413         return $resultSet;
0414     }
0415 
0416     /**
0417      * @param $userEmail
0418      *
0419      * @return array
0420      * @throws Zend_Exception
0421      */
0422     private function fetchUserByUsername($userEmail)
0423     {
0424         $sql = "
0425             SELECT * 
0426             FROM {$this->_tableName} 
0427             WHERE 
0428             is_active = :active AND 
0429             is_deleted = :deleted AND 
0430             login_method = :login AND 
0431             username = :username";
0432 
0433         $this->_db->getProfiler()->setEnabled(true);
0434         $resultSet = $this->_db->fetchAll($sql, array(
0435             'active'   => Default_Model_DbTable_Member::MEMBER_ACTIVE,
0436             'deleted'  => Default_Model_DbTable_Member::MEMBER_NOT_DELETED,
0437             'login'    => Default_Model_DbTable_Member::MEMBER_LOGIN_LOCAL,
0438             'username' => $userEmail
0439         ));
0440         Zend_Registry::get('logger')->info(__METHOD__ . ' - sql take seconds: ' . $this->_db->getProfiler()->getLastQueryProfile()
0441                                                                                             ->getElapsedSecs())
0442         ;
0443         $this->_db->getProfiler()->setEnabled(false);
0444 
0445         return $resultSet;
0446     }
0447 
0448     /**
0449      * @param $code
0450      * @param $identity
0451      * @param $messages
0452      *
0453      * @return Zend_Auth_Result
0454      */
0455     protected function createAuthResult($code, $identity, $messages)
0456     {
0457         return new Zend_Auth_Result($code, $identity, $messages);
0458     }
0459 
0460     /**
0461      * @param $email
0462      *
0463      * @return bool|Zend_Db_Table_Row_Abstract
0464      */
0465     public function findActiveMemberByEmail($email)
0466     {
0467         $modelMember = new Default_Model_Member();
0468         $member = $modelMember->findActiveMemberByIdentity($email);
0469         if (empty($member->member_id)) {
0470             return false;
0471         }
0472 
0473         return $member;
0474     }
0475 
0476     /**
0477      * @param      $access_token
0478      * @param null $username
0479      *
0480      * @return mixed
0481      * @throws Exception
0482      */
0483     public function storeAccessToken($access_token, $username = null)
0484     {
0485         $member_id = Zend_Auth::getInstance()->getIdentity()->member_id;
0486 
0487         $modelToken = new Default_Model_DbTable_MemberToken();
0488         $rowToken = $modelToken->save(array(
0489             'token_member_id'         => $member_id,
0490             'token_provider_name'     => 'ocs_login',
0491             'token_value'             => $access_token,
0492             'token_provider_username' => $username
0493         ));
0494 
0495         return $rowToken;
0496     }
0497 
0498     /**
0499      * @return bool|mixed
0500      */
0501     public function getRedirect()
0502     {
0503         if ($this->redirect) {
0504             $filterRedirect = new Local_Filter_Url_Decrypt();
0505             $redirect = $filterRedirect->filter($this->redirect);
0506             $this->redirect = null;
0507 
0508             return $redirect;
0509         }
0510 
0511         return false;
0512     }
0513 
0514     /**
0515      * @param $token_id
0516      *
0517      * @return string
0518      * @throws Zend_Exception
0519      */
0520     public function authStartWithToken($token_id)
0521     {
0522         $requestUrl = $this->uri_auth . "?client_id={$this->client_id}&redirect_uri=" . urlencode($this->uri_callback)
0523             . "&scope=profile&state={$token_id}&response_type=code";
0524 
0525         Zend_Registry::get('logger')->debug(__METHOD__ . ' - redirectUrl: ' . print_r($requestUrl, true));
0526 
0527         return $requestUrl;
0528     }
0529 
0530     /**
0531      * @return bool
0532      */
0533     public function isConnected()
0534     {
0535         return (boolean)$this->connected;
0536     }
0537 
0538     /**
0539      * @param $token
0540      *
0541      * @return bool
0542      * @throws Zend_Exception
0543      */
0544     protected function clearStateToken($token)
0545     {
0546         /** @var Zend_Cache_Core $cache */
0547         $cache = Zend_Registry::get('cache');
0548 
0549         return $cache->remove($token);
0550     }
0551 
0552 }