BxCidaasConModule.php 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. <?php defined('BX_DOL') or die('hack attempt');
  2. /**
  3. * Copyright (c) UNA, Inc - https://una.io
  4. * MIT License - https://opensource.org/licenses/MIT
  5. *
  6. * @defgroup CidaasConnect Cidaas Connect
  7. * @ingroup UnaModules
  8. *
  9. * @{
  10. */
  11. require_once(BX_DIRECTORY_PATH_MODULES . 'boonex/cidaas_connect/vendor/autoload.php');
  12. use Cidaas\OAuth2\Client\Provider\GrantType;
  13. use GuzzleHttp\Exception\ClientException;
  14. /**
  15. * Login via the official Cidaas PHP SDK hosted login page.
  16. * @see https://github.com/cidaas/cidaas-sdk-php
  17. */
  18. class BxCidaasConModule extends BxBaseModConnectModule
  19. {
  20. function __construct(&$aModule)
  21. {
  22. parent::__construct($aModule);
  23. }
  24. /**
  25. * Redirect to Cidaas hosted login page.
  26. *
  27. * @return n/a - redirect or HTML page in case of error
  28. */
  29. function actionStart()
  30. {
  31. if (isset($_GET['error'])) {
  32. $this->_oTemplate->getPage(_t($this->_oConfig->sDefaultTitleLangKey), DesignBoxContent(_t($this->_oConfig->sDefaultTitleLangKey), MsgBox(bx_get('error'))));
  33. exit;
  34. }
  35. if (isLogged()) {
  36. $this->_redirect($this->_oConfig->sDefaultRedirectUrl);
  37. }
  38. if (!$this->_oConfig->sBaseUrl || !$this->_oConfig->sClientID || (!$this->_oConfig->bPkce && !$this->_oConfig->sSecret)) {
  39. require_once(BX_DIRECTORY_PATH_INC . 'design.inc.php');
  40. bx_import('BxDolLanguages');
  41. $sCode = MsgBox(_t('_bx_cidaascon_profile_error_api_keys'));
  42. $this->_oTemplate->getPage(_t('_bx_cidaascon'), $sCode);
  43. return;
  44. }
  45. try {
  46. $this->_getProvider()->loginWithBrowser($this->_oConfig->sScope, [
  47. 'state' => $this->_genToken(),
  48. ], $this->_oConfig->bPkce);
  49. } catch (Exception $oException) {
  50. require_once(BX_DIRECTORY_PATH_INC . 'design.inc.php');
  51. $this->_oTemplate->getPage(_t('_Error'), MsgBox($this->_getExceptionMessage($oException)));
  52. return;
  53. }
  54. // SDK stores the PKCE verifier in PHP $_SESSION; persist it in UNA session for the callback.
  55. if ($this->_oConfig->bPkce && !empty($_SESSION['code-verifier']))
  56. BxDolSession::getInstance()->setValue($this->_oConfig->sSessionCodeVerifier, $_SESSION['code-verifier']);
  57. exit;
  58. }
  59. function actionHandle()
  60. {
  61. require_once(BX_DIRECTORY_PATH_INC . 'design.inc.php');
  62. try {
  63. $oProvider = $this->_getProvider();
  64. } catch (Exception $oException) {
  65. $this->_oTemplate->getPage(_t('_Error'), MsgBox($this->_getExceptionMessage($oException)));
  66. return;
  67. }
  68. $aParameters = $oProvider->loginCallback();
  69. if ($this->_getToken() != ($aParameters['state'] ?? '')) {
  70. $this->_oTemplate->getPage(_t('_Error'), MsgBox(_t('_sys_connect_state_invalid')));
  71. return;
  72. }
  73. $sCode = $aParameters['code'] ?? '';
  74. if (!$sCode || !empty($aParameters['error'])) {
  75. $sErrorDescription = !empty($aParameters['error_description']) ? $aParameters['error_description'] : (!empty($aParameters['error']) ? $aParameters['error'] : _t('_error occured'));
  76. $this->_oTemplate->getPage(_t('_Error'), MsgBox($sErrorDescription));
  77. return;
  78. }
  79. if ($this->_oConfig->bPkce) {
  80. $sCodeVerifier = BxDolSession::getInstance()->getUnsetValue($this->_oConfig->sSessionCodeVerifier);
  81. if ($sCodeVerifier)
  82. $_SESSION['code-verifier'] = $sCodeVerifier;
  83. }
  84. try {
  85. $aAuthData = $oProvider->getAccessToken(GrantType::AuthorizationCode, $sCode, '', $this->_oConfig->bPkce)->wait();
  86. if (empty($aAuthData['access_token'])) {
  87. $sErrorDescription = isset($aAuthData['error_description']) ? $aAuthData['error_description'] : _t('_error occured');
  88. $this->_oTemplate->getPage(_t('_Error'), MsgBox($sErrorDescription));
  89. return;
  90. }
  91. $aRemoteProfileInfo = $oProvider->getUserProfile($aAuthData['access_token'])->wait();
  92. } catch (Exception $oException) {
  93. $this->_oTemplate->getPage(_t('_Error'), MsgBox($this->_getExceptionMessage($oException)));
  94. return;
  95. }
  96. if (isset($aRemoteProfileInfo['data']) && is_array($aRemoteProfileInfo['data']))
  97. $aRemoteProfileInfo = $aRemoteProfileInfo['data'];
  98. if (empty($aRemoteProfileInfo['id']) && !empty($aRemoteProfileInfo['sub']))
  99. $aRemoteProfileInfo['id'] = $aRemoteProfileInfo['sub'];
  100. if ($aRemoteProfileInfo && !empty($aRemoteProfileInfo['id'])) {
  101. $iLocalProfileId = $this->_oDb->getProfileId($aRemoteProfileInfo['id']);
  102. if ($iLocalProfileId && $oProfile = BxDolProfile::getInstance($iLocalProfileId)) {
  103. $this->setLogged($oProfile->id(), '', true, true);
  104. }
  105. else {
  106. $this->_createProfile($aRemoteProfileInfo);
  107. }
  108. }
  109. else {
  110. $this->_oTemplate->getPage(_t('_Error'), MsgBox(_t('_sys_connect_profile_error_info')));
  111. }
  112. }
  113. public function serviceGetSafeServices()
  114. {
  115. return array_merge(parent::serviceGetSafeServices(), [
  116. 'Handle' => '',
  117. ]);
  118. }
  119. public function serviceHandle($aRemoteProfileInfo = [])
  120. {
  121. if (!$this->_bIsApi)
  122. return;
  123. if (is_string($aRemoteProfileInfo))
  124. $aRemoteProfileInfo = bx_api_get_browse_params($aRemoteProfileInfo);
  125. if (empty($aRemoteProfileInfo) || !is_array($aRemoteProfileInfo))
  126. return [
  127. bx_api_get_msg(_t('_sys_connect_profile_error_info'))
  128. ];
  129. if (empty($aRemoteProfileInfo['id']) && !empty($aRemoteProfileInfo['sub']))
  130. $aRemoteProfileInfo['id'] = $aRemoteProfileInfo['sub'];
  131. $iProfileId = $this->_oDb->getProfileId($aRemoteProfileInfo['id']);
  132. if ($iProfileId && $oProfile = BxDolProfile::getInstance($iProfileId))
  133. return $this->setLogged($oProfile->id());
  134. else
  135. return $this->_createProfile($aRemoteProfileInfo);
  136. }
  137. /**
  138. * @param $aProfileInfo - remote profile info
  139. * @param $sAlternativeName - suffix to add to NickName to make it unique
  140. * @return profile array info, ready for the local database
  141. */
  142. protected function _convertRemoteFields($aProfileInfo, $sAlternativeName = '')
  143. {
  144. $aProfileFields = $aProfileInfo;
  145. $sEmail = !empty($aProfileInfo['email']) ? $aProfileInfo['email'] : '';
  146. if (!$sEmail && !empty($aProfileInfo['identities'][0]['email']))
  147. $sEmail = $aProfileInfo['identities'][0]['email'];
  148. $sName = !empty($aProfileInfo['preferred_username']) ? $aProfileInfo['preferred_username'] : '';
  149. if (!$sName && !empty($aProfileInfo['name']))
  150. $sName = $aProfileInfo['name'];
  151. if (!$sName && !empty($aProfileInfo['given_name']))
  152. $sName = $aProfileInfo['given_name'];
  153. if (!$sName && $sEmail)
  154. $sName = explode('@', $sEmail)[0];
  155. if (!$sName)
  156. $sName = !empty($aProfileInfo['sub']) ? $aProfileInfo['sub'] : '';
  157. $sFullname = !empty($aProfileInfo['name']) ? $aProfileInfo['name'] : trim((!empty($aProfileInfo['given_name']) ? $aProfileInfo['given_name'] : '') . ' ' . (!empty($aProfileInfo['family_name']) ? $aProfileInfo['family_name'] : ''));
  158. if (!$sFullname)
  159. $sFullname = $sName;
  160. $aProfileFields['name'] = $sName;
  161. $aProfileFields['fullname'] = $sFullname;
  162. $aProfileFields['last_name'] = !empty($aProfileInfo['family_name']) ? $aProfileInfo['family_name'] : '';
  163. $aProfileFields['email'] = $sEmail;
  164. $aProfileFields['picture'] = !empty($aProfileInfo['picture']) ? $aProfileInfo['picture'] : '';
  165. $aProfileFields['allow_view_to'] = getParam('bx_cidaascon_privacy');
  166. return $aProfileFields;
  167. }
  168. /**
  169. * @return BxCidaasConProvider
  170. */
  171. protected function _getProvider()
  172. {
  173. bx_import('Provider', $this->_aModule);
  174. return new BxCidaasConProvider(
  175. $this->_oConfig->sBaseUrl,
  176. $this->_oConfig->sClientID,
  177. $this->_oConfig->sSecret ? $this->_oConfig->sSecret : '',
  178. $this->_oConfig->sPageHandle,
  179. null,
  180. $this->_oConfig->bDebug
  181. );
  182. }
  183. protected function _getExceptionMessage($oException)
  184. {
  185. if ($oException instanceof ClientException && $oException->hasResponse()) {
  186. $aBody = json_decode((string)$oException->getResponse()->getBody(), true);
  187. if (!empty($aBody['error']['error_description']))
  188. return $aBody['error']['error_description'];
  189. if (!empty($aBody['error_description']))
  190. return $aBody['error_description'];
  191. if (!empty($aBody['error']) && is_string($aBody['error']))
  192. return $aBody['error'];
  193. }
  194. $sMessage = $oException->getMessage();
  195. return $sMessage ? $sMessage : _t('_error occured');
  196. }
  197. }
  198. /** @} */