lib/Dropbox/WebAuth.php
function finish($queryParams)
    {
        Checker::argArray("queryParams", $queryParams);

        $csrfTokenFromSession = $this->csrfTokenStore->get();
        Checker::argStringOrNull("this->csrfTokenStore->get()", $csrfTokenFromSession);

        // Check well-formedness of request.

        if (!isset($queryParams['state'])) {
            throw new WebAuthException_BadRequest("Missing query parameter 'state'.");
        }
        $state = $queryParams['state'];
        Checker::argString("queryParams['state']", $state);

        $error = null;
        $errorDescription = null;
        if (isset($queryParams['error'])) {
            $error = $queryParams['error'];
            Checker::argString("queryParams['error']", $error);
            if (isset($queryParams['error_description'])) {
                $errorDescription = $queryParams['error_description'];
                Checker::argString("queryParams['error_description']", $errorDescription);
            }
        }

        $code = null;
        if (isset($queryParams['code'])) {
            $code = $queryParams['code'];
            Checker::argString("queryParams['code']", $code);
        }

        if ($code !== null && $error !== null) {
            throw new WebAuthException_BadRequest("Query parameters 'code' and 'error' are both set;".
                                                 " only one must be set.");
        }
        if ($code === null && $error === null) {
            throw new WebAuthException_BadRequest("Neither query parameter 'code' or 'error' is set.");
        }

        // Check CSRF token

        if ($csrfTokenFromSession === null) {
            throw new WebAuthException_BadState();
        }

        $splitPos = strpos($state, "|");
        if ($splitPos === false) {
            $givenCsrfToken = $state;
            $urlState = null;
        } else {
            $givenCsrfToken = substr($state, 0, $splitPos);
            $urlState = substr($state, $splitPos + 1);
        }
        if (!Security::stringEquals($csrfTokenFromSession, $givenCsrfToken)) {
            throw new WebAuthException_Csrf("Expected ".Util::q($csrfTokenFromSession) .
                                           ", got ".Util::q($givenCsrfToken) .".");
        }
        $this->csrfTokenStore->clear();

        // Check for error identifier

        if ($error !== null) {
            if ($error === 'access_denied') {
                // When the user clicks "Deny".
                if ($errorDescription === null) {
                    throw new WebAuthException_NotApproved("No additional description from Dropbox.");
                } else {
                    throw new WebAuthException_NotApproved("Additional description from Dropbox: $errorDescription");
                }
            } else {
                // All other errors.
                $fullMessage = $error;
                if ($errorDescription !== null) {
                    $fullMessage .= ": ";
                    $fullMessage .= $errorDescription;
                }
                throw new WebAuthException_Provider($fullMessage);
            }
        }

        // If everything went ok, make the network call to get an access token.

        list($accessToken, $userId) = $this->_finish($code, $this->redirectUri);
        return array($accessToken, $userId, $urlState);
    }
function finish($queryParams)
    {
        Checker::argArray("queryParams", $queryParams);

        $csrfTokenFromSession = $this->csrfTokenStore->get();
        Checker::argStringOrNull("this->csrfTokenStore->get()", $csrfTokenFromSession);

        // Check well-formedness of request.

        if (!isset($queryParams['state'])) {
            throw new WebAuthException_BadRequest("Missing query parameter 'state'.");
        }
        $state = $queryParams['state'];
        Checker::argString("queryParams['state']", $state);

        $error = null;
        $errorDescription = null;
        if (isset($queryParams['error'])) {
            $error = $queryParams['error'];
            Checker::argString("queryParams['error']", $error);
            if (isset($queryParams['error_description'])) {
                $errorDescription = $queryParams['error_description'];
                Checker::argString("queryParams['error_description']", $errorDescription);
            }
        }

        $code = null;
        if (isset($queryParams['code'])) {
            $code = $queryParams['code'];
            Checker::argString("queryParams['code']", $code);
        }

        if ($code !== null && $error !== null) {
            throw new WebAuthException_BadRequest("Query parameters 'code' and 'error' are both set;".
                                                 " only one must be set.");
        }
        if ($code === null && $error === null) {
            throw new WebAuthException_BadRequest("Neither query parameter 'code' or 'error' is set.");
        }

        // Check CSRF token

        if ($csrfTokenFromSession === null) {
            throw new WebAuthException_BadState();
        }

        $splitPos = strpos($state, "|");
        if ($splitPos === false) {
            $givenCsrfToken = $state;
            $urlState = null;
        } else {
            $givenCsrfToken = substr($state, 0, $splitPos);
            $urlState = substr($state, $splitPos + 1);
        }
        if (!Security::stringEquals($csrfTokenFromSession, $givenCsrfToken)) {
            throw new WebAuthException_Csrf("Expected ".Util::q($csrfTokenFromSession) .
                                           ", got ".Util::q($givenCsrfToken) .".");
        }
        $this->csrfTokenStore->clear();

        // Check for error identifier

        if ($error !== null) {
            if ($error === 'access_denied') {
                // When the user clicks "Deny".
                if ($errorDescription === null) {
                    throw new WebAuthException_NotApproved("No additional description from Dropbox.");
                } else {
                    throw new WebAuthException_NotApproved("Additional description from Dropbox: $errorDescription");
                }
            } else {
                // All other errors.
                $fullMessage = $error;
                if ($errorDescription !== null) {
                    $fullMessage .= ": ";
                    $fullMessage .= $errorDescription;
                }
                throw new WebAuthException_Provider($fullMessage);
            }
        }

        // If everything went ok, make the network call to get an access token.

        list($accessToken, $userId) = $this->_finish($code, $this->redirectUri);
        return array($accessToken, $userId, $urlState);
    }
Call this after the user has visited the authorize URL ({@link start()}), approved your app,
and was redirected to your redirect URI.

See <a href="https://www.dropbox.com/developers/core/docs#oa2-token">/oauth2/token</a>.

@param array $queryParams
The query parameters on the GET request to your redirect URI.

@return array
A `list(string $accessToken, string $userId, string $urlState)`, where
`$accessToken` can be used to construct a {@link Client}, `$userId`
is the user ID of the user's Dropbox account, and `$urlState` is the
value you originally passed in to {@link start()}.

@throws Exception
Thrown if there's an error getting the access token from Dropbox.
@throws WebAuthException_BadRequest
@throws WebAuthException_BadState
@throws WebAuthException_Csrf
@throws WebAuthException_NotApproved
@throws WebAuthException_Provider