@@ -218,7 +218,9 @@ protected function startOAuth2Flow() {
218218 }
219219 }
220220
221- $ login_link = $ this ->oAuth2LoginLink . '? ' . http_build_query ($ params );
221+ $ first_separator = strpos ($ this ->oAuth2LoginLink , '? ' ) === FALSE ? '? ' : '& ' ;
222+
223+ $ login_link = $ this ->oAuth2LoginLink . $ first_separator . http_build_query ($ params );
222224
223225 // redirect to the authorization page, they will redirect back
224226 header ('Location: ' . $ login_link );
@@ -436,7 +438,8 @@ public function getUserByOAuth2Identity($identity, $oauth2_client_id) {
436438 /**
437439 * Retrieves OAuth2 access token from the service and creates new client entry
438440 *
439- * @param string $code OAuth2 code
441+ * @param string $code OAuth2 code
442+ * @return int Internal OAuth2 client id
440443 */
441444 public function getOAuth2ClientIDByCode ($ code ) {
442445 // STEP 2: Get access token
@@ -449,6 +452,34 @@ public function getOAuth2ClientIDByCode($code) {
449452 'client_secret ' => $ this ->oAuth2ClientSecret
450453 );
451454
455+ return $ this ->updateOAuth2Tokens ($ params );
456+ }
457+
458+ /**
459+ * Refreshes access token for existing credentials
460+ *
461+ * @param OAuth2UserCredentials $credentials Existing user credentials
462+ */
463+ public function refreshAccessToken ($ credentials ) {
464+ $ params = array (
465+ 'grant_type ' => 'refresh_token ' ,
466+ 'refresh_token ' => $ credentials ->getRefreshToken (),
467+ 'client_id ' => $ this ->oAuth2ClientID ,
468+ 'client_secret ' => $ this ->oAuth2ClientSecret
469+ );
470+
471+ UserTools::debug ("Refreshing OAuth2 token " );
472+
473+ $ this ->updateOAuth2Tokens ($ params , $ credentials );
474+ }
475+
476+ /**
477+ * Creates or updates OAuth2 tokens and database records
478+ * @param array $params Authorization request parameters
479+ * @param OAuth2UserCredentials|null $current_credentials Current user credentials
480+ * @return int Internal OAuth2 client id
481+ */
482+ private function updateOAuth2Tokens ($ params , $ current_credentials = null ) {
452483 $ ch = curl_init ();
453484 curl_setopt ($ ch , CURLOPT_URL , $ this ->oAuth2AccessTokenURL );
454485 curl_setopt ($ ch , CURLOPT_RETURNTRANSFER , TRUE );
@@ -488,10 +519,16 @@ public function getOAuth2ClientIDByCode($code) {
488519 throw new OAuth2Exception ("OAuth2 access token is not returned " );
489520 }
490521
522+ UserTools::debug ("Result: " . var_export ($ result , TRUE ));
523+
524+ UserTools::debug ("Access token: " . $ access_token );
525+
491526 $ refresh_token = array_key_exists ('refresh_token ' , $ result ) ? $ result ['refresh_token ' ] : null ;
492527 $ expires_in = array_key_exists ('expires_in ' , $ result ) ? $ result ['expires_in ' ] : null ;
493528 $ token_type = array_key_exists ('token_type ' , $ result ) ? $ result ['token_type ' ] : 'bearer ' ;
494529
530+ UserTools::debug ("Refresh token: " . $ refresh_token );
531+
495532 UserTools::debug ("Token type: $ token_type " );
496533
497534 if (strtolower ($ token_type ) != 'bearer ' ) {
@@ -505,43 +542,57 @@ public function getOAuth2ClientIDByCode($code) {
505542
506543 $ access_token_expires = is_null ($ expires_in ) ? null : time () + $ expires_in ;
507544
508- $ oauth2_client_id = null ;
545+ $ oauth2_client_id = $ current_credentials ? $ current_credentials -> getClientId () : null ;
509546 $ current_expires = null ;
510547 $ current_refresh = null ;
511548
512- $ query = 'SELECT oauth2_client_id, UNIX_TIMESTAMP(access_token_expires), refresh_token
513- FROM u_oauth2_clients
514- WHERE module_slug = ? AND access_token = ? ' ;
515- UserTools::debug ($ query );
549+ if (!$ current_credentials ) {
550+ /**
551+ * Let's try reading client info for this access_token from the database
552+ */
553+ $ query = 'SELECT oauth2_client_id, UNIX_TIMESTAMP(access_token_expires), refresh_token
554+ FROM u_oauth2_clients
555+ WHERE module_slug = ? AND access_token = ? ' ;
556+ UserTools::debug ($ query );
516557
517- if ($ stmt = $ db ->prepare ($ query ))
518- {
519- if (!$ stmt ->bind_param ('ss ' , $ module_slug , $ access_token ))
520- {
521- throw new DBBindParamException ($ db , $ stmt );
522- }
523- if (!$ stmt ->execute ())
558+ if ($ stmt = $ db ->prepare ($ query ))
524559 {
525- throw new DBExecuteStmtException ($ db , $ stmt );
560+ if (!$ stmt ->bind_param ('ss ' , $ module_slug , $ access_token ))
561+ {
562+ throw new DBBindParamException ($ db , $ stmt );
563+ }
564+ if (!$ stmt ->execute ())
565+ {
566+ throw new DBExecuteStmtException ($ db , $ stmt );
567+ }
568+ if (!$ stmt ->bind_result ($ oauth2_client_id , $ current_expires , $ current_refresh ))
569+ {
570+ throw new DBBindResultException ($ db , $ stmt );
571+ }
572+
573+ $ stmt ->fetch ();
574+ $ stmt ->close ();
526575 }
527- if (! $ stmt -> bind_result ( $ oauth2_client_id , $ current_expires , $ current_refresh ))
576+ else
528577 {
529- throw new DBBindResultException ($ db, $ stmt );
578+ throw new DBPrepareStmtException ($ db );
530579 }
531-
532- $ stmt ->fetch ();
533- $ stmt ->close ();
534- }
535- else
536- {
537- throw new DBPrepareStmtException ($ db );
538580 }
539581
582+ /**
583+ * If we don't have client_id (for new access_token), let's insert a new one into the database (without identity)
584+ */
540585 if (!$ oauth2_client_id ) {
541586 $ query = 'INSERT INTO u_oauth2_clients
542587 (module_slug, access_token, access_token_expires, refresh_token)
543588 VALUES (?, ?, FROM_UNIXTIME(?), ?) ' ;
544589 UserTools::debug ($ query );
590+ UserTools::debug (var_export ([
591+ "module_slug " => $ module_slug ,
592+ "access_token " => $ access_token ,
593+ "access_token_expires " => $ access_token_expires ,
594+ "refresh_token " => $ refresh_token
595+ ], true ));
545596
546597 if ($ stmt = $ db ->prepare ($ query ))
547598 {
@@ -563,30 +614,65 @@ public function getOAuth2ClientIDByCode($code) {
563614 } else {
564615 throw new DBPrepareStmtException ($ db );
565616 }
566- } else if ($ access_token_expires != $ current_expires
617+ } else {
618+ /**
619+ * Otherwise update the token if we a refreshing the token or expires timestamp / refresh token are updated
620+ */
621+ if ($ current_credentials
622+ || $ access_token_expires != $ current_expires
567623 || $ refresh_token != $ current_refresh ) {
568- $ query = 'UPDATE u_oauth2_clients
569- SET access_token_expires = FROM_UNIXTIME(?), refresh_token = ?
570- WHERE oauth2_client_id = ? ' ;
571- UserTools::debug ($ query );
572624
573- if ($ stmt = $ db ->prepare ($ query ))
574- {
575- if (!$ stmt ->bind_param ('ssi ' ,
576- $ access_token_expires ,
577- $ refresh_token ,
578- $ oauth2_client_id ))
579- {
580- throw new DBBindParamException ($ db , $ stmt );
625+ // if refresh token is in fact passed, update it, otherwise keep the old one
626+ if ($ refresh_token ) {
627+ $ query = 'UPDATE u_oauth2_clients
628+ SET access_token = ?, access_token_expires = FROM_UNIXTIME(?), refresh_token = ?
629+ WHERE oauth2_client_id = ? ' ;
630+ } else {
631+ $ query = 'UPDATE u_oauth2_clients
632+ SET access_token = ?, access_token_expires = FROM_UNIXTIME(?)
633+ WHERE oauth2_client_id = ? ' ;
581634 }
582- if (!$ stmt ->execute ())
635+
636+ UserTools::debug ($ query );
637+ UserTools::debug (var_export ([
638+ "module_slug " => $ module_slug ,
639+ "access_token " => $ access_token ,
640+ "access_token_expires " => $ access_token_expires ,
641+ "refresh_token " => $ refresh_token ,
642+ "oauth2_client_id " => $ oauth2_client_id
643+ ], true ));
644+
645+ if ($ stmt = $ db ->prepare ($ query ))
583646 {
584- throw new DBExecuteStmtException ($ db , $ stmt );
647+ if ($ refresh_token ) {
648+ if (!$ stmt ->bind_param ('sssi ' ,
649+ $ access_token ,
650+ $ access_token_expires ,
651+ $ refresh_token ,
652+ $ oauth2_client_id ))
653+ {
654+ throw new DBBindParamException ($ db , $ stmt );
655+ }
656+ } else {
657+ if (!$ stmt ->bind_param ('ssi ' ,
658+ $ access_token ,
659+ $ access_token_expires ,
660+ $ oauth2_client_id ))
661+ {
662+ throw new DBBindParamException ($ db , $ stmt );
663+ }
664+ }
665+ if (!$ stmt ->execute ())
666+ {
667+ throw new DBExecuteStmtException ($ db , $ stmt );
668+ }
669+
670+ $ stmt ->close ();
671+ } else {
672+ throw new DBPrepareStmtException ($ db );
585673 }
586674
587- $ stmt ->close ();
588- } else {
589- throw new DBPrepareStmtException ($ db );
675+ $ current_credentials ->updateCredentials ($ access_token , $ access_token_expires , $ refresh_token );
590676 }
591677 }
592678
@@ -1003,19 +1089,18 @@ public function getTotalConnectedUsers()
10031089 * This method allows requesting information on behalf of the user from a 3rd party provider.
10041090 * Possibly the most important feature of the whole system.
10051091 *
1006- * @param User $user User to make request for
1092+ * @param UserCredentials $credentials Credentials object representing OAuth2 user
10071093 * @param string $request Request URL
10081094 * @param string $method HTTP method (e.g. GET, POST, PUT, etc)
10091095 * @param array $params Request parameters key->value array
1096+ * @param boolean $refresh_if_unauthorized Whatever to attempt to refresh the token on failure (to avoid recursion)
10101097 *
10111098 * @return array Response data (code=>int, headers=>array(), body=>string)
10121099 */
1013- public function makeOAuth2Request ($ credentials , $ url , $ method = 'GET ' , $ request_params = array (), $ curlopt = array ())
1100+ public function makeOAuth2Request ($ credentials , $ url , $ method = 'GET ' , $ request_params = array (), $ curlopt = array (), $ refresh_if_unauthorized = TRUE )
10141101 {
10151102 $ ch = curl_init ();
10161103
1017- $ separator = strpos ($ url , '? ' ) ? '& ' : '? ' ;
1018-
10191104 if (!is_array ($ request_params )) {
10201105 $ request_params = array ();
10211106 }
@@ -1024,11 +1109,12 @@ public function makeOAuth2Request($credentials, $url, $method = 'GET', $request_
10241109 }
10251110 $ params = array_merge ($ request_params , $ this ->oAuth2ExtraParameters );
10261111
1112+ $ first_separator = strpos ($ url , '? ' ) === FALSE ? '? ' : '& ' ;
1113+
10271114 // always pass access_token as a query string parameter
10281115 if (count ($ params )) {
10291116 if ($ method == 'GET ' ) {
1030- $ url .= $ separator . http_build_query ($ params );
1031- $ separator = '& ' ;
1117+ $ call_url = $ url . $ first_separator . http_build_query ($ params );
10321118 } else if ($ method == 'POST ' ) {
10331119 $ curlopt [CURLOPT_POST ] = TRUE ;
10341120 $ curlopt [CURLOPT_POSTFIELDS ] = $ params ;
@@ -1039,15 +1125,14 @@ public function makeOAuth2Request($credentials, $url, $method = 'GET', $request_
10391125 if ($ this ->oAuth2SendAccessTokenAsHeader ) {
10401126 $ curlopt [CURLOPT_HTTPHEADER ][] = 'Authorization: Bearer ' . $ credentials ->getAccessToken ();
10411127 } else {
1042- $ url .= $ separator . http_build_query (array (
1128+ $ call_url = $ url . $ first_separator . http_build_query (array (
10431129 $ this ->oAuth2AccessTokenParamName => $ credentials ->getAccessToken ()
10441130 ));
1045- $ separator = '& ' ;
10461131 }
10471132
1048- UserTools::debug ("URL: $ url " );
1133+ UserTools::debug ("URL: $ call_url " );
10491134
1050- curl_setopt ($ ch , CURLOPT_URL , $ url );
1135+ curl_setopt ($ ch , CURLOPT_URL , $ call_url );
10511136 curl_setopt ($ ch , CURLOPT_RETURNTRANSFER , TRUE );
10521137 curl_setopt ($ ch , CURLOPT_HEADER , FALSE );
10531138 curl_setopt ($ ch , CURLOPT_SSL_VERIFYPEER , TRUE );
@@ -1060,6 +1145,21 @@ public function makeOAuth2Request($credentials, $url, $method = 'GET', $request_
10601145 $ result = curl_exec ($ ch );
10611146 UserTools::debug ("Request: " . var_export (curl_getinfo ($ ch , CURLINFO_HEADER_OUT ), true ));
10621147 UserTools::debug ("Response: $ result " );
1148+ UserTools::debug ("HTTP Response code: " . curl_getinfo ($ ch , CURLINFO_HTTP_CODE ));
1149+
1150+ // let's see if our access token has expired and try to refresh it
1151+ if ($ result ) {
1152+ $ data = json_decode ($ result , true );
1153+
1154+ if (curl_getinfo ($ ch , CURLINFO_HTTP_CODE ) == 401 || array_key_exists ('code ' , $ data ) && $ data ['code ' ] == 'not_authorized ' ) {
1155+ $ this ->refreshAccessToken ($ credentials );
1156+
1157+ // call thyself without refreshing on failure (to avoid recursion)
1158+
1159+ // @TODO debug why this goes into infinite loop before re-emabling it
1160+ return $ this ->makeOAuth2Request ($ credentials , $ url , $ method , $ request_params , $ curlopt , FALSE );
1161+ }
1162+ }
10631163
10641164 if (curl_getinfo ($ ch , CURLINFO_HTTP_CODE ) != 200 ) {
10651165 throw new OAuth2Exception ("OAuth2 call failed: " . curl_error ($ ch ) . ' (Code: ' . curl_getinfo ($ ch , CURLINFO_HTTP_CODE ) . ') ' );
@@ -1166,6 +1266,27 @@ public function getAccessToken() {
11661266 return $ this ->access_token ;
11671267 }
11681268
1269+ /**
1270+ * Returns OAuth2 refresh token
1271+ *
1272+ * @return string OAuth2 refresh token
1273+ */
1274+ public function getRefreshToken () {
1275+ return $ this ->refresh_token ;
1276+ }
1277+
1278+ public function getClientId () {
1279+ return $ this ->oauth2_client_id ;
1280+ }
1281+
1282+ public function updateCredentials ($ access_token , $ access_token_expires , $ refresh_token ) {
1283+ $ this ->access_token = $ access_token ;
1284+ $ this ->access_token_expires = $ access_token_expires ;
1285+ if ($ refresh_token ) {
1286+ $ this ->refresh_token = $ refresh_token ;
1287+ }
1288+ }
1289+
11691290 public function makeOAuth2Request ($ request , $ method = 'GET ' , $ params = null , $ curlopt = array ()) {
11701291 return $ this ->oauth2_module ->makeOAuth2Request ($ this , $ request , $ method , $ params , $ curlopt );
11711292 }
0 commit comments