PageRenderTime 27ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/plugins/backupbuddy/destinations/dropbox2/lib/Dropbox/WebAuth.php

https://gitlab.com/mattswann/launch-housing
PHP | 278 lines | 99 code | 25 blank | 154 comment | 19 complexity | b9ce6422a19e716631aac8fb3fb07c1d MD5 | raw file
  1. <?php
  2. namespace Dropbox;
  3. /**
  4. * OAuth 2 "authorization code" flow. (This SDK does not support the "token" flow.)
  5. *
  6. * Use {@link WebAuth::start()} and {@link WebAuth::finish()} to guide your
  7. * user through the process of giving your app access to their Dropbox account.
  8. * At the end, you will have an access token, which you can pass to {@link Client}
  9. * and start making API calls.
  10. *
  11. * Example:
  12. *
  13. * <code>
  14. * use \Dropbox as dbx;
  15. *
  16. * function getWebAuth()
  17. * {
  18. * $appInfo = dbx\AppInfo::loadFromJsonFile(...);
  19. * $clientIdentifier = "my-app/1.0";
  20. * $redirectUri = "https://example.org/dropbox-auth-finish";
  21. * $csrfTokenStore = new dbx\ArrayEntryStore($_SESSION, 'dropbox-auth-csrf-token');
  22. * return new dbx\WebAuth($appInfo, $clientIdentifier, $redirectUri, $csrfTokenStore, ...);
  23. * }
  24. *
  25. * // ----------------------------------------------------------
  26. * // In the URL handler for "/dropbox-auth-start"
  27. *
  28. * $authorizeUrl = getWebAuth()->start();
  29. * header("Location: $authorizeUrl");
  30. *
  31. * // ----------------------------------------------------------
  32. * // In the URL handler for "/dropbox-auth-finish"
  33. *
  34. * try {
  35. * list($accessToken, $userId, $urlState) = getWebAuth()->finish($_GET);
  36. * assert($urlState === null); // Since we didn't pass anything in start()
  37. * }
  38. * catch (dbx\WebAuthException_BadRequest $ex) {
  39. * error_log("/dropbox-auth-finish: bad request: " . $ex->getMessage());
  40. * // Respond with an HTTP 400 and display error page...
  41. * }
  42. * catch (dbx\WebAuthException_BadState $ex) {
  43. * // Auth session expired. Restart the auth process.
  44. * header('Location: /dropbox-auth-start');
  45. * }
  46. * catch (dbx\WebAuthException_Csrf $ex) {
  47. * error_log("/dropbox-auth-finish: CSRF mismatch: " . $ex->getMessage());
  48. * // Respond with HTTP 403 and display error page...
  49. * }
  50. * catch (dbx\WebAuthException_NotApproved $ex) {
  51. * error_log("/dropbox-auth-finish: not approved: " . $ex->getMessage());
  52. * }
  53. * catch (dbx\WebAuthException_Provider $ex) {
  54. * error_log("/dropbox-auth-finish: error redirect from Dropbox: " . $ex->getMessage());
  55. * }
  56. * catch (dbx\Exception $ex) {
  57. * error_log("/dropbox-auth-finish: error communicating with Dropbox API: " . $ex->getMessage());
  58. * }
  59. *
  60. * // We can now use $accessToken to make API requests.
  61. * $client = dbx\Client($accessToken, ...);
  62. * </code>
  63. *
  64. */
  65. class WebAuth extends WebAuthBase
  66. {
  67. /**
  68. * The URI that the Dropbox server will redirect the user to after the user finishes
  69. * authorizing your app. This URI must be HTTPS-based and
  70. * <a href="https://www.dropbox.com/developers/apps">pre-registered with Dropbox</a>,
  71. * though "localhost"-based and "127.0.0.1"-based URIs are allowed without pre-registration
  72. * and can be either HTTP or HTTPS.
  73. *
  74. * @return string
  75. */
  76. function getRedirectUri() { return $this->redirectUri; }
  77. /** @var string */
  78. private $redirectUri;
  79. /**
  80. * A object that lets us save CSRF token string to the user's session. If you're using the
  81. * standard PHP <code>$_SESSION</code>, you can pass in something like
  82. * <code>new ArrayEntryStore($_SESSION, 'dropbox-auth-csrf-token')</code>.
  83. *
  84. * If you're not using $_SESSION, you might have to create your own class that provides
  85. * the same <code>get()</code>/<code>set()</code>/<code>clear()</code> methods as
  86. * {@link ArrayEntryStore}.
  87. *
  88. * @return ValueStore
  89. */
  90. function getCsrfTokenStore() { return $this->csrfTokenStore; }
  91. /** @var object */
  92. private $csrfTokenStore;
  93. /**
  94. * Constructor.
  95. *
  96. * @param AppInfo $appInfo
  97. * See {@link getAppInfo()}
  98. * @param string $clientIdentifier
  99. * See {@link getClientIdentifier()}
  100. * @param null|string $redirectUri
  101. * See {@link getRedirectUri()}
  102. * @param null|ValueStore $csrfTokenStore
  103. * See {@link getCsrfTokenStore()}
  104. * @param null|string $userLocale
  105. * See {@link getUserLocale()}
  106. */
  107. function __construct($appInfo, $clientIdentifier, $redirectUri, $csrfTokenStore, $userLocale = null)
  108. {
  109. parent::__construct($appInfo, $clientIdentifier, $userLocale);
  110. Checker::argStringNonEmpty("redirectUri", $redirectUri);
  111. $this->csrfTokenStore = $csrfTokenStore;
  112. $this->redirectUri = $redirectUri;
  113. }
  114. /**
  115. * Starts the OAuth 2 authorization process, which involves redirecting the user to the
  116. * returned authorization URL (a URL on the Dropbox website). When the user then
  117. * either approves or denies your app access, Dropbox will redirect them to the
  118. * <code>$redirectUri</code> given to constructor, at which point you should
  119. * call {@link finish()} to complete the authorization process.
  120. *
  121. * This function will also save a CSRF token using the <code>$csrfTokenStore</code> given to
  122. * the constructor. This CSRF token will be checked on {@link finish()} to prevent
  123. * request forgery.
  124. *
  125. * See <a href="https://www.dropbox.com/developers/core/docs#oa2-authorize">/oauth2/authorize</a>.
  126. *
  127. * @param string|null $urlState
  128. * Any data you would like to keep in the URL through the authorization process.
  129. * This exact state will be returned to you by {@link finish()}.
  130. *
  131. * @param boolean|null $forceReapprove
  132. * If a user has already approved your app, Dropbox may skip the "approve" step and
  133. * redirect immediately to your callback URL. Setting this to <code>true</code> tells
  134. * Dropbox to never skip the "approve" step.
  135. *
  136. * @return array
  137. * The URL to redirect the user to.
  138. *
  139. * @throws Exception
  140. */
  141. function start($urlState = null, $forceReapprove = false)
  142. {
  143. Checker::argStringOrNull("urlState", $urlState);
  144. $csrfToken = self::encodeCsrfToken(Security::getRandomBytes(16));
  145. $state = $csrfToken;
  146. if ($urlState !== null) {
  147. $state .= "|";
  148. $state .= $urlState;
  149. }
  150. $this->csrfTokenStore->set($csrfToken);
  151. return $this->_getAuthorizeUrl($this->redirectUri, $state, $forceReapprove);
  152. }
  153. private static function encodeCsrfToken($string)
  154. {
  155. return strtr(base64_encode($string), '+/', '-_');
  156. }
  157. /**
  158. * Call this after the user has visited the authorize URL ({@link start()}), approved your app,
  159. * and was redirected to your redirect URI.
  160. *
  161. * See <a href="https://www.dropbox.com/developers/core/docs#oa2-token">/oauth2/token</a>.
  162. *
  163. * @param array $queryParams
  164. * The query parameters on the GET request to your redirect URI.
  165. *
  166. * @return array
  167. * A <code>list(string $accessToken, string $userId, string $urlState)</code>, where
  168. * <code>$accessToken</code> can be used to construct a {@link Client}, <code>$userId</code>
  169. * is the user ID of the user's Dropbox account, and <code>$urlState</code> is the
  170. * value you originally passed in to {@link start()}.
  171. *
  172. * @throws Exception
  173. * Thrown if there's an error getting the access token from Dropbox.
  174. * @throws WebAuthException_BadRequest
  175. * @throws WebAuthException_BadState
  176. * @throws WebAuthException_Csrf
  177. * @throws WebAuthException_NotApproved
  178. * @throws WebAuthException_Provider
  179. */
  180. function finish($queryParams)
  181. {
  182. Checker::argArray("queryParams", $queryParams);
  183. $csrfTokenFromSession = $this->csrfTokenStore->get();
  184. Checker::argStringOrNull("this->csrfTokenStore->get()", $csrfTokenFromSession);
  185. // Check well-formedness of request.
  186. if (!isset($queryParams['state'])) {
  187. throw new WebAuthException_BadRequest("Missing query parameter 'state'.");
  188. }
  189. $state = $queryParams['state'];
  190. Checker::argString("queryParams['state']", $state);
  191. $error = null;
  192. $errorDescription = null;
  193. if (isset($queryParams['error'])) {
  194. $error = $queryParams['error'];
  195. Checker::argString("queryParams['error']", $error);
  196. if (isset($queryParams['error_description'])) {
  197. $errorDescription = $queryParams['error_description'];
  198. Checker::argString("queryParams['error_description']", $errorDescription);
  199. }
  200. }
  201. $code = null;
  202. if (isset($queryParams['code'])) {
  203. $code = $queryParams['code'];
  204. Checker::argString("queryParams['code']", $code);
  205. }
  206. if ($code !== null && $error !== null) {
  207. throw new WebAuthException_BadRequest("Query parameters 'code' and 'error' are both set;".
  208. " only one must be set.");
  209. }
  210. if ($code === null && $error === null) {
  211. throw new WebAuthException_BadRequest("Neither query parameter 'code' or 'error' is set.");
  212. }
  213. // Check CSRF token
  214. if ($csrfTokenFromSession === null) {
  215. throw new WebAuthException_BadState();
  216. }
  217. $splitPos = strpos($state, "|");
  218. if ($splitPos === false) {
  219. $givenCsrfToken = $state;
  220. $urlState = null;
  221. } else {
  222. $givenCsrfToken = substr($state, 0, $splitPos);
  223. $urlState = substr($state, $splitPos + 1);
  224. }
  225. if (!Security::stringEquals($csrfTokenFromSession, $givenCsrfToken)) {
  226. throw new WebAuthException_Csrf("Expected ".Util::q($csrfTokenFromSession) .
  227. ", got ".Util::q($givenCsrfToken) .".");
  228. }
  229. $this->csrfTokenStore->clear();
  230. // Check for error identifier
  231. if ($error !== null) {
  232. if ($error === 'access_denied') {
  233. // When the user clicks "Deny".
  234. if ($errorDescription === null) {
  235. throw new WebAuthException_NotApproved("No additional description from Dropbox.");
  236. } else {
  237. throw new WebAuthException_NotApproved("Additional description from Dropbox: $errorDescription");
  238. }
  239. } else {
  240. // All other errors.
  241. $fullMessage = $error;
  242. if ($errorDescription !== null) {
  243. $fullMessage .= ": ";
  244. $fullMessage .= $errorDescription;
  245. }
  246. throw new WebAuthException_Provider($fullMessage);
  247. }
  248. }
  249. // If everything went ok, make the network call to get an access token.
  250. list($accessToken, $userId) = $this->_finish($code, $this->redirectUri);
  251. return array($accessToken, $userId, $urlState);
  252. }
  253. }