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 }