Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
218 changes: 96 additions & 122 deletions dropbox/oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,20 +44,15 @@ class OAuth2FlowNoRedirectResult(object):

def __init__(self, access_token, account_id, user_id, refresh_token, expiration, scope):
"""
Args:
access_token (str): Token to be used to authenticate later
requests.
refresh_token (str): Token to be used to acquire new access token
when existing one expires
expiration (int, datetime): Either the number of seconds from now that the token expires
in or the datetime at which the token expires
account_id (str): The Dropbox user's account ID.
user_id (str): Deprecated (use account_id instead).
refresh_token (str): Token to be used to acquire new access token
when existing one expires
expiration (int, datetime): Either the number of seconds from now that the token expires
in or the datetime at which the token expires
scope (list): list of scopes to request in base oauth flow.
:param str access_token: Token to be used to authenticate later requests.
:param str account_id: The Dropbox user's account ID.
:param str user_id: Deprecated (use :attr:`account_id` instead).
:param str refresh_token: Token to be used to acquire new access token when existing one
expires.
:param expiration: Either the number of seconds from now that the token expires in or the
datetime at which the token expires.
:type expiration: int or datetime
:param list scope: List of scopes to request in base oauth flow.
"""
self.access_token = access_token
if not expiration:
Expand All @@ -84,17 +79,15 @@ def __repr__(self):

class OAuth2FlowResult(OAuth2FlowNoRedirectResult):
"""
Authorization information for an OAuth2Flow with redirect.
Authorization information for an :class:`OAuth2Flow` with redirect.
"""

def __init__(self, access_token, account_id, user_id, url_state, refresh_token,
expires_in, scope):
"""
Same as OAuth2FlowNoRedirectResult but with url_state.
Same as :class:`OAuth2FlowNoRedirectResult` but with url_state.

Args:
url_state (str): The url state that was set by
:meth:`DropboxOAuth2Flow.start`.
:param str url_state: The url state that was set by :meth:`DropboxOAuth2Flow.start`.
"""
super(OAuth2FlowResult, self).__init__(
access_token=access_token,
Expand Down Expand Up @@ -234,9 +227,8 @@ def _finish(self, code, redirect_uri, code_verifier):
def build_path(self, target, params=None):
"""Build the path component for an API URL.

This method urlencodes the parameters, adds them
to the end of the target url, and puts a marker for the API
version in front.
This method urlencodes the parameters, adds them to the end of the target url, and puts a
marker for the API version in front.

:param str target: A target url (e.g. '/files') to build upon.
:param dict params: Optional dictionary of parameters (name to value).
Expand All @@ -263,8 +255,7 @@ def build_path(self, target, params=None):
def build_url(self, target, params=None, host=API_HOST):
"""Build an API URL.

This method adds scheme and hostname to the path
returned from build_path.
This method adds scheme and hostname to the path returned from build_path.

:param str target: A target url (e.g. '/files') to build upon.
:param dict params: Optional dictionary of parameters (name to value).
Expand All @@ -279,7 +270,8 @@ class DropboxOAuth2FlowNoRedirect(DropboxOAuth2FlowBase):
OAuth 2 authorization helper for apps that can't provide a redirect URI
(such as the command-line example apps).

See examples under example/oauth
See examples under `example/oauth <https://github.com/dropbox/dropbox-sdk-python/tree/master/
example/oauth>`_

"""

Expand All @@ -290,33 +282,31 @@ def __init__(self, consumer_key, consumer_secret=None, locale=None, token_access

:param str consumer_key: Your API app's "app key".
:param str consumer_secret: Your API app's "app secret".
:param str locale: The locale of the user of your application. For
example "en" or "en_US". Some API calls return localized data and
error messages; this setting tells the server which locale to use.
By default, the server uses "en_US".
:param str locale: The locale of the user of your application. For example "en" or "en_US".
Some API calls return localized data and error messages; this setting tells the server
which locale to use. By default, the server uses "en_US".
:param str token_access_type: the type of token to be requested.
From the following enum:

* legacy - creates one long-lived token with no expiration
* online - create one short-lived token with an expiration
* offline - create one short-lived token with an expiration with a refresh token

:param list scope: list of scopes to request in base oauth flow. If left blank,
will default to all scopes for app.
:param str include_granted_scopes: which scopes to include from previous grants
:param list scope: list of scopes to request in base oauth flow.
If left blank, will default to all scopes for app.
:param str include_granted_scopes: which scopes to include from previous grants.
From the following enum:

* user - include user scopes in the grant
* team - include team scopes in the grant
* Note: if this user has never linked the app, include_granted_scopes must be None
* *Note*: if this user has never linked the app, include_granted_scopes must be None

:param bool use_pkce: Whether or not to use Sha256 based PKCE. PKCE should be only use on
client apps which doesn't call your server. It is less secure than non-PKCE flow but
can be used if you are unable to safely retrieve your app secret.
:param Optional[float] timeout: Maximum duration in seconds that
client will wait for any single packet from the
server. After the timeout the client will give up on
connection. If `None`, client will wait forever. Defaults
client will wait for any single packet from the server. After the timeout the client
will give up on connection. If `None`, client will wait forever. Defaults
to 100 seconds.
"""
super(DropboxOAuth2FlowNoRedirect, self).__init__(
Expand All @@ -334,10 +324,9 @@ def start(self):
"""
Starts the OAuth 2 authorization process.

:return: The URL for a page on Dropbox's website. This page will let
the user "approve" your app, which gives your app permission to
access the user's Dropbox account. Tell the user to visit this URL
and approve your app.
:return: The URL for a page on Dropbox's website. This page will let the user "approve"
your app, which gives your app permission to access the user's Dropbox account.
Tell the user to visit this URL and approve your app.
"""
return self._get_authorize_url(None, None, self.token_access_type,
scope=self.scope,
Expand All @@ -346,27 +335,27 @@ def start(self):

def finish(self, code):
"""
If the user approves your app, they will be presented with an
"authorization code". Have the user copy/paste that authorization code
into your app and then call this method to get an access token.
If the user approves your app, they will be presented with an "authorization code".
Have the user copy/paste that authorization code into your app and then call this method to
get an access token.

:param str code: The authorization code shown to the user when they
approved your app.
:rtype: OAuth2FlowNoRedirectResult
:rtype: :class:`OAuth2FlowNoRedirectResult`
:raises: The same exceptions as :meth:`DropboxOAuth2Flow.finish()`.
"""
return self._finish(code, None, self.code_verifier)


class DropboxOAuth2Flow(DropboxOAuth2FlowBase):
"""
OAuth 2 authorization helper. Use this for web apps.
OAuth 2 authorization helper. Use this for web apps.

OAuth 2 has a two-step authorization process. The first step is having the
user authorize your app. The second involves getting an OAuth 2 access
token from Dropbox.
OAuth 2 has a two-step authorization process. The first step is having the user authorize your
app. The second involves getting an OAuth 2 access token from Dropbox.

See examples under example/oauth
See examples under `example/oauth <https://github.com/dropbox/dropbox-sdk-python/tree/master/
example/oauth>`_

"""

Expand All @@ -379,41 +368,38 @@ def __init__(self, consumer_key, redirect_uri, session,

:param str consumer_key: Your API app's "app key".
:param str consumer_secret: Your API app's "app secret".
:param str redirect_uri: The URI that the Dropbox server will redirect
the user to after the user finishes authorizing your app. This URI
must be HTTPS-based and pre-registered with the Dropbox servers,
though localhost URIs are allowed without pre-registration and can
be either HTTP or HTTPS.
:param dict session: A dict-like object that represents the current
user's web session (will be used to save the CSRF token).
:param str csrf_token_session_key: The key to use when storing the CSRF
token in the session (for example: "dropbox-auth-csrf-token").
:param str locale: The locale of the user of your application. For
example "en" or "en_US". Some API calls return localized data and
error messages; this setting tells the server which locale to use.
By default, the server uses "en_US".
:param str token_access_type: the type of token to be requested.
:param str redirect_uri: The URI that the Dropbox server will redirect the user to after the
user finishes authorizing your app. This URI must be HTTPS-based and pre-registered
with the Dropbox servers, though localhost URIs are allowed without pre-registration and
can be either HTTP or HTTPS.
:param dict session: A dict-like object that represents the current user's web session
(Will be used to save the CSRF token).
:param str csrf_token_session_key: The key to use when storing the CSRF token in the session
(For example: "dropbox-auth-csrf-token").
:param str locale: The locale of the user of your application. For example "en" or "en_US".
Some API calls return localized data and error messages; this setting tells the server
which locale to use. By default, the server uses "en_US".
:param str token_access_type: The type of token to be requested.
From the following enum:

* legacy - creates one long-lived token with no expiration
* online - create one short-lived token with an expiration
* offline - create one short-lived token with an expiration with a refresh token

:param list scope: list of scopes to request in base oauth flow. If left blank,
:param list scope: List of scopes to request in base oauth flow. If left blank,
will default to all scopes for app.
:param str include_granted_scopes: which scopes to include from previous grants
:param str include_granted_scopes: Which scopes to include from previous grants.
From the following enum:

* user - include user scopes in the grant
* team - include team scopes in the grant
* Note: if this user has never linked the app, include_granted_scopes must be None
* *Note*: If this user has never linked the app, :attr:`include_granted_scopes` must \
be `None`

:param bool use_pkce: Whether or not to use Sha256 based PKCE
:param Optional[float] timeout: Maximum duration in seconds that
client will wait for any single packet from the
server. After the timeout the client will give up on
connection. If `None`, client will wait forever. Defaults
to 100 seconds.
:param Optional[float] timeout: Maximum duration in seconds that client will wait for any
single packet from the server. After the timeout the client will give up on connection.
If `None`, client will wait forever. Defaults to 100 seconds.
"""

super(DropboxOAuth2Flow, self).__init__(
Expand All @@ -434,24 +420,19 @@ def start(self, url_state=None):
"""
Starts the OAuth 2 authorization process.

This function builds an "authorization URL". You should redirect your
user's browser to this URL, which will give them an opportunity to
grant your app access to their Dropbox account. When the user
completes this process, they will be automatically redirected to the
``redirect_uri`` you passed in to the constructor.

This function will also save a CSRF token to
``session[csrf_token_session_key]`` (as provided to the constructor).
This CSRF token will be checked on :meth:`finish()` to prevent request
forgery.

:param str url_state: Any data that you would like to keep in the URL
through the authorization process. This exact value will be
returned to you by :meth:`finish()`.
:return: The URL for a page on Dropbox's website. This page will let
the user "approve" your app, which gives your app permission to
access the user's Dropbox account. Tell the user to visit this URL
and approve your app.
This function builds an "authorization URL". You should redirect your user's browser to this
URL, which will give them an opportunity to grant your app access to their Dropbox
account. When the user completes this process, they will be automatically redirected to
the :attr:`redirect_uri` you passed in to the constructor. This function will also save
a CSRF token to :attr:`session[csrf_token_session_key]`
(as provided to the constructor). This CSRF token will be checked on :meth:`finish()`
to prevent request forgery.

:param str url_state: Any data that you would like to keep in the URL through the
authorization process. This exact value will be returned to you by :meth:`finish()`.
:return: The URL for a page on Dropbox's website. This page will let the user "approve" your
app, which gives your app permission to access the user's Dropbox account. Tell the user
to visit this URL and approve your app.
"""
csrf_token = base64.urlsafe_b64encode(os.urandom(16)).decode('ascii')
state = csrf_token
Expand All @@ -466,23 +447,19 @@ def start(self, url_state=None):

def finish(self, query_params):
"""
Call this after the user has visited the authorize URL (see
:meth:`start()`), approved your app and was redirected to your redirect
URI.

:param dict query_params: The query parameters on the GET request to
your redirect URI.
:rtype: OAuth2FlowResult
:raises: :class:`BadRequestException` If the redirect URL was missing
parameters or if the given parameters were not valid.
:raises: :class:`BadStateException` If there's no CSRF token in the
session.
:raises: :class:`CsrfException` If the ``state`` query parameter
doesn't contain the CSRF token from the user's session.
:raises: :class:`NotApprovedException` If the user chose not to
approve your app.
:raises: :class:`ProviderException` If Dropbox redirected to your
redirect URI with some unexpected error identifier and error message.
Call this after the user has visited the authorize URL (see :meth:`start()`), approved your
app and was redirected to your redirect URI.

:param dict query_params: The query parameters on the GET request to your redirect URI.
:rtype: class:`OAuth2FlowResult`
:raises: :class:`BadRequestException` If the redirect URL was missing parameters or if the
given parameters were not valid.
:raises: :class:`BadStateException` If there's no CSRF token in the session.
:raises: :class:`CsrfException` If the :attr:`state` query parameter doesn't contain the
CSRF token from the user's session.
:raises: :class:`NotApprovedException` If the user chose not to approve your app.
:raises: :class:`ProviderException` If Dropbox redirected to your redirect URI with some
unexpected error identifier and error message.
"""
# Check well-formedness of request.

Expand Down Expand Up @@ -553,8 +530,7 @@ def finish(self, query_params):

class BadRequestException(Exception):
"""
Thrown if the redirect URL was missing parameters or if the
given parameters were not valid.
Thrown if the redirect URL was missing parameters or if the given parameters were not valid.

The recommended action is to show an HTTP 400 error page.
"""
Expand All @@ -563,19 +539,18 @@ class BadRequestException(Exception):

class BadStateException(Exception):
"""
Thrown if all the parameters are correct, but there's no CSRF token in the
session. This probably means that the session expired.
Thrown if all the parameters are correct, but there's no CSRF token in the session. This
probably means that the session expired.

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


class CsrfException(Exception):
"""
Thrown if the given 'state' parameter doesn't contain the CSRF token from
the user's session. This is blocked to prevent CSRF attacks.
Thrown if the given 'state' parameter doesn't contain the CSRF token from the user's session.
This is blocked to prevent CSRF attacks.

The recommended action is to respond with an HTTP 403 error page.
"""
Expand All @@ -591,11 +566,10 @@ class NotApprovedException(Exception):

class ProviderException(Exception):
"""
Dropbox redirected to your redirect URI with some unexpected error
identifier and error message.
Dropbox redirected to your redirect URI with some unexpected error identifier and error message.

The recommended action is to log the error, tell the user something went
wrong, and let them try again.
The recommended action is to log the error, tell the user something went wrong, and let them try
again.
"""
pass

Expand All @@ -621,11 +595,11 @@ def _safe_equals(a, b):

def _params_to_urlencoded(params):
"""
Returns a application/x-www-form-urlencoded ``str`` representing the
key/value pairs in ``params``.
Returns a application/x-www-form-urlencoded :class:`str` representing the key/value pairs in
:attr:`params`.

Keys are values are ``str()``'d before calling ``urllib.urlencode``, with
the exception of unicode objects which are utf8-encoded.
Keys are values are ``str()``'d before calling :meth:`urllib.urlencode`, with the exception of
unicode objects which are utf8-encoded.
"""
def encode(o):
if isinstance(o, six.binary_type):
Expand Down