All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.dropbox.core.DbxWebAuth Maven / Gradle / Ivy

There is a newer version: 7.0.0
Show newest version
package com.dropbox.core;

import com.dropbox.core.util.StringUtil;
import com.dropbox.core.v2.DbxClientV2;

import static com.dropbox.core.util.StringUtil.jq;

import java.security.SecureRandom;
import java.util.Map;

/*>>> import checkers.nullness.quals.Nullable; */

/**
 * Does the OAuth 2 "authorization code" flow.  (This SDK does not support the "token" flow.)
 *
 * 

* Eventually yields an access token, which * can be used with {@link DbxClientV2} to make Dropbox API calls. You typically only need * to do this for a user when they first use your application. Once you have an access token * for that user, it remains valid for years. *

* *

* Setup: *

*
 * String userLocale = ...
 * {@link DbxRequestConfig} requestConfig = new DbxRequestConfig("text-edit/0.1", userLocale);
 * {@link DbxAppInfo} appInfo = DbxAppInfo.Reader.readFromFile("api.app");
 *
 * // Select a spot in the session for DbxWebAuth to store the CSRF token.
 * javax.servlet.http.HttpServletRequest request = ...
 * javax.servlet.http.HttpSession session = request.getSession(true);
 * String sessionKey = "dropbox-auth-csrf-token";
 * DbxSessionStore csrfTokenStore = new DbxStandardSessionStore(session, sessionKey);
 *
 * String redirectUri = "http://my-server.com/dropbox-auth-finish"
 * DbxWebAuth auth = new DbxWebAuth(requestConfig, appInfo, redirectUri, csrfTokenStore);
 * 
* *

* Part 1 (handler for "http://my-server.com/dropbox-auth-start"). *

*
 * javax.servlet.http.HttpServletResponse response = ...
 *
 * // Start authorization.
 * String authorizePageUrl = auth.{@link #start start}();
 *
 * // Redirect the user to the Dropbox website so they can approve our application.
 * // The Dropbox website will send them back to "http://my-server.com/dropbox-auth-finish"
 * // when they're done.
 * response.sendRedirect(authorizePageUrl);
 * 
* *

* Part 2 (handler for "http://my-server.com/dropbox-auth-finish"). *

*
 * javax.servlet.http.HttpServletResponse response = ...
 *
 * {@link DbxAuthFinish} authFinish;
 * try {
 *     authFinish = auth.{@link #finish finish}(request.getParameterMap());
 * }
 * catch (DbxWebAuth.BadRequestException ex) {
 *     log("On /dropbox-auth-finish: Bad request: " + ex.getMessage());
 *     response.sendError(400);
 *     return;
 * }
 * catch (DbxWebAuth.BadStateException ex) {
 *     // Send them back to the start of the auth flow.
 *     response.sendRedirect("http://my-server.com/dropbox-auth-start");
 *     return;
 * }
 * catch (DbxWebAuth.CsrfException ex) {
 *     log("On /dropbox-auth-finish: CSRF mismatch: " + ex.getMessage());
 *     return;
 * }
 * catch (DbxWebAuth.NotApprovedException ex) {
 *     // When Dropbox asked "Do you want to allow this app to access your
 *     // Dropbox account?", the user clicked "No".
 *     ...
 *     return;
 * }
 * catch (DbxWebAuth.ProviderException ex) {
 *     log("On /dropbox-auth-finish: Auth failed: " + ex.getMessage());
 *     response.sendError(503, "Error communicating with Dropbox.");
 *     return;
 * }
 * catch (DbxException ex) {
 *     log("On /dropbox-auth-finish: Error getting token: " + ex.getMessage());
 *     response.sendError(503, "Error communicating with Dropbox.");
 *     return;
 * }
 * String accessToken = authResponse.accessToken;
 *
 * // Save the access token somewhere (probably in your database) so you
 * // don't need to send the user through the authorization process again.
 * ...
 *
 * // Now use the access token to make Dropbox API calls.
 * {@link DbxClientV2} client = new DbxClientV2(requestConfig, accessToken);
 * ...
 * 
*/ public class DbxWebAuth { private final DbxRequestConfig requestConfig; private final DbxAppInfo appInfo; private final String redirectUri; private final DbxSessionStore csrfTokenStore; /** * @param appInfo * Your application's Dropbox API information (the app key and secret). */ public DbxWebAuth(DbxRequestConfig requestConfig, DbxAppInfo appInfo, String redirectUri, DbxSessionStore csrfTokenStore) { if (requestConfig == null) throw new IllegalArgumentException("'requestConfig' is null"); if (appInfo == null) throw new IllegalArgumentException("'appInfo' is null"); if (redirectUri == null) throw new IllegalArgumentException("'redirectUri' is null"); if (csrfTokenStore == null) throw new IllegalArgumentException("'csrfTokenStore' is null"); this.requestConfig = requestConfig; this.appInfo = appInfo; this.redirectUri = redirectUri; this.csrfTokenStore = csrfTokenStore; } /** * Start authorization. Returns a "authorization URL" on the Dropbox website that gives the * lets the user grant your app access to their Dropbox account. * *

* If they choose to grant access, they will be shown an "authorization code", which they * need to copy/paste back into your app, at which point you can call {@link #finish} to get an * access token. *

*/ public String start(/*@Nullable*/String urlState) { SecureRandom r = new SecureRandom(); byte[] csrfRaw = new byte[16]; r.nextBytes(csrfRaw); String csrfAscii = StringUtil.urlSafeBase64Encode(csrfRaw); String state = csrfAscii; if (urlState != null) { state += "|" + urlState; } this.csrfTokenStore.set(csrfAscii); return DbxWebAuthHelper.getAuthorizeUrl(this.appInfo, this.requestConfig.getUserLocale(), redirectUri, state); } /** * Start authorization. Returns a "authorization URL" on the Dropbox website that gives the * lets the user grant your app access to their Dropbox account. * *

* If they choose to grant access, they will be shown an "authorization code", which they * need to copy/paste back into your app, at which point you can call {@link #finish} to get an * access token. *

*/ public String start() { return start(null); } /** * Call this after the user has visited the authorizaton URL and Dropbox has redirected them * back to you (using the {@code redirectUri} you passed in to {@link #start}. * * @param queryParams * The query parameters on the GET request to your {@code redirectUri}. */ public DbxAuthFinish finish(Map queryParams) throws DbxException, BadRequestException, BadStateException, CsrfException, NotApprovedException, ProviderException { if (queryParams == null) throw new IllegalArgumentException("'queryParams' is null"); // Check well-formedness of request. String state = getParam(queryParams, "state"); if (state == null) throw new BadRequestException("missing 'state' parameter"); String error = getParam(queryParams, "error"); String code = getParam(queryParams, "code"); String errorDescription = getParam(queryParams, "error_description"); if (code == null && error == null) throw new BadRequestException("missing both 'code' and 'error'; one must be present"); if (code != null && error != null) throw new BadRequestException("both 'code' and 'error' are set; only one must be present"); if (code != null && errorDescription != null) throw new BadRequestException("both 'code' and 'error_description' are set"); // Check CSRF token String csrfTokenFromSession = this.csrfTokenStore.get(); if (csrfTokenFromSession == null) throw new BadStateException(); if (csrfTokenFromSession.length() <= 20) throw new AssertionError("CSRF token too short: " + jq(csrfTokenFromSession)); int divPos = state.indexOf('|'); String givenCsrfToken; String givenUrlState; if (divPos < 0) { givenCsrfToken = state; givenUrlState = null; } else { givenCsrfToken = state.substring(0, divPos); givenUrlState = state.substring(divPos + 1); } if (!StringUtil.secureStringEquals(csrfTokenFromSession, givenCsrfToken)) { throw new CsrfException("expecting " + jq(csrfTokenFromSession) + ", got " + jq(givenCsrfToken)); } this.csrfTokenStore.clear(); // Check for error identifier if (error != null) { if (error.equals("access_denied")) { // When the user clicks "Deny" String exceptionMessage; if (errorDescription == null) { exceptionMessage = "No additional description from Dropbox"; } else { exceptionMessage = "Additional description from Dropbox: " + errorDescription; } throw new NotApprovedException(exceptionMessage); } else { // All other errors. String exceptionMessage = error; if (errorDescription != null) { exceptionMessage += ": " + errorDescription; } throw new ProviderException(exceptionMessage); } } assert code != null : "@AssumeAssertion(nullness)"; DbxAuthFinish finish = DbxWebAuthHelper.finish(this.appInfo, this.requestConfig, code, this.redirectUri); return new DbxAuthFinish(finish.getAccessToken(), finish.getUserId(), givenUrlState); } private static /*@Nullable*/String getParam(Map params, String name) throws BadRequestException { String[] v = params.get(name); if (v == null) return null; assert v.length != 0; if (v.length == 1) { return v[0]; } else { throw new BadRequestException("multiple occurrences of '" + name + "' parameter"); } } /** * The base class for authorization redirect errors. You should catch each subclass * individually. */ public static abstract class Exception extends java.lang.Exception { private static final long serialVersionUID = 0L; public Exception(String message) { super(message); } } /** * Thrown when the parameters passed to your redirect URI are not well-formed. * *

* IMPORTANT: The exception's message must not be shown the the user, but may be logged. *

* *

* The recommended action is to show an HTTP 400 error page. *

*/ public static final class BadRequestException extends Exception { private static final long serialVersionUID = 0L; public BadRequestException(String message) { super(message); } } /** * Thrown if all the parameters to your redirect URI are well-formed, but there's no CSRF token * in the session. This probably means that the user's session expired and they must restart * the OAuth 2 process. * *

* IMPORTANT: The exception's message must not be shown the the user, but may be logged. *

* *

* The recommended action is to redirect the user's browser to try the approval process again. *

*/ public static final class BadStateException extends Exception { private static final long serialVersionUID = 0L; public BadStateException() { super("Not expecting Dropbox auth redirect (session doesn't have CSRF token)"); } } /** * Thrown if the given 'state' parameter doesn't contain the expected CSRF token. This request * should be blocked to prevent CSRF attacks. * *

* IMPORTANT: The exception's message must not be shown the the user, but may be logged. *

* *

* The recommended action is to show an HTTP 403 error page. *

*/ public static final class CsrfException extends Exception { private static final long serialVersionUID = 0L; public CsrfException(String message) { super(message); } } /** * Thrown when Dropbox tells us that the user chose not to grant your app access to their * Dropbox account (i.e. the user clicked the "Deny" button). * *

* IMPORTANT: The exception's message must not be shown the the user, but may be logged. *

*/ public static final class NotApprovedException extends Exception { private static final long serialVersionUID = 0L; public NotApprovedException(String message) { super(message); } } /** * Thrown when Dropbox tells us that some other error occurred in the authorization process. * *

* IMPORTANT: The exception's message should not be shown the the user, but should be logged. *

*/ public static final class ProviderException extends Exception { private static final long serialVersionUID = 0L; public ProviderException(String message) { super(message); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy