mirror of
https://github.com/robweber/xbmcbackup.git
synced 2024-12-23 06:27:55 +01:00
309 lines
12 KiB
Python
309 lines
12 KiB
Python
"""
|
|
dropbox.session.DropboxSession is responsible for holding OAuth authentication
|
|
info (app key/secret, request key/secret, access key/secret). It knows how to
|
|
use all of this information to craft properly constructed requests to Dropbox.
|
|
|
|
A DropboxSession object must be passed to a dropbox.client.DropboxClient object upon
|
|
initialization.
|
|
|
|
"""
|
|
from __future__ import absolute_import
|
|
|
|
import random
|
|
import sys
|
|
import time
|
|
import urllib
|
|
|
|
try:
|
|
from urlparse import parse_qs
|
|
except ImportError:
|
|
# fall back for Python 2.5
|
|
from cgi import parse_qs
|
|
|
|
from . import rest
|
|
|
|
class OAuthToken(object):
|
|
"""
|
|
A class representing an OAuth token. Contains two fields: ``key`` and
|
|
``secret``.
|
|
"""
|
|
def __init__(self, key, secret):
|
|
self.key = key
|
|
self.secret = secret
|
|
|
|
class BaseSession(object):
|
|
API_VERSION = 1
|
|
|
|
API_HOST = "api.dropbox.com"
|
|
WEB_HOST = "www.dropbox.com"
|
|
API_CONTENT_HOST = "api-content.dropbox.com"
|
|
API_NOTIFICATION_HOST = "api-notify.dropbox.com"
|
|
|
|
def __init__(self, consumer_key, consumer_secret, access_type="auto", locale=None, rest_client=rest.RESTClient):
|
|
"""Initialize a DropboxSession object.
|
|
|
|
Your consumer key and secret are available
|
|
at https://www.dropbox.com/developers/apps
|
|
|
|
Args:
|
|
|
|
- ``access_type``: Either 'auto' (the default), 'dropbox', or
|
|
'app_folder'. You probably don't need to specify this and should
|
|
just use the default.
|
|
- ``locale``: A locale string ('en', 'pt_PT', etc.) [optional]
|
|
The locale setting will be used to translate any user-facing error
|
|
messages that the server generates. At this time Dropbox supports
|
|
'en', 'es', 'fr', 'de', and 'ja', though we will be supporting more
|
|
languages in the future. If you send a language the server doesn't
|
|
support, messages will remain in English. Look for these translated
|
|
messages in rest.ErrorResponse exceptions as e.user_error_msg.
|
|
|
|
"""
|
|
assert access_type in ['dropbox', 'app_folder', 'auto'], "expected access_type of 'dropbox' or 'app_folder'"
|
|
self.consumer_creds = OAuthToken(consumer_key, consumer_secret)
|
|
self.token = None
|
|
self.request_token = None
|
|
self.root = 'sandbox' if access_type == 'app_folder' else access_type
|
|
self.locale = locale
|
|
self.rest_client = rest_client
|
|
|
|
def is_linked(self):
|
|
"""Return whether the DropboxSession has an access token attached."""
|
|
return bool(self.token)
|
|
|
|
def unlink(self):
|
|
"""Remove any attached access token from the DropboxSession."""
|
|
self.token = None
|
|
|
|
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.
|
|
|
|
Args:
|
|
- ``target``: A target url (e.g. '/files') to build upon.
|
|
- ``params``: A dictionary of parameters (name to value). [optional]
|
|
|
|
Returns:
|
|
- The path and parameters components of an API URL.
|
|
"""
|
|
if sys.version_info < (3,) and type(target) == unicode:
|
|
target = target.encode("utf8")
|
|
|
|
target_path = urllib.quote(target)
|
|
|
|
params = params or {}
|
|
params = params.copy()
|
|
|
|
if self.locale:
|
|
params['locale'] = self.locale
|
|
|
|
if params:
|
|
return "/%s%s?%s" % (self.API_VERSION, target_path, urllib.urlencode(params))
|
|
else:
|
|
return "/%s%s" % (self.API_VERSION, target_path)
|
|
|
|
def build_url(self, host, target, params=None):
|
|
"""Build an API URL.
|
|
|
|
This method adds scheme and hostname to the path
|
|
returned from build_path.
|
|
|
|
Args:
|
|
- ``target``: A target url (e.g. '/files') to build upon.
|
|
- ``params``: A dictionary of parameters (name to value). [optional]
|
|
|
|
Returns:
|
|
- The full API URL.
|
|
"""
|
|
return "https://%s%s" % (host, self.build_path(target, params))
|
|
|
|
class DropboxSession(BaseSession):
|
|
|
|
def set_token(self, access_token, access_token_secret):
|
|
"""Attach an access token to the DropboxSession.
|
|
|
|
Note that the access 'token' is made up of both a token string
|
|
and a secret string.
|
|
"""
|
|
self.token = OAuthToken(access_token, access_token_secret)
|
|
|
|
def set_request_token(self, request_token, request_token_secret):
|
|
"""Attach an request token to the DropboxSession.
|
|
|
|
Note that the request 'token' is made up of both a token string
|
|
and a secret string.
|
|
"""
|
|
self.request_token = OAuthToken(request_token, request_token_secret)
|
|
|
|
def build_authorize_url(self, request_token, oauth_callback=None):
|
|
"""Build a request token authorization URL.
|
|
|
|
After obtaining a request token, you'll need to send the user to
|
|
the URL returned from this function so that they can confirm that
|
|
they want to connect their account to your app.
|
|
|
|
Args:
|
|
- ``request_token``: A request token from obtain_request_token.
|
|
- ``oauth_callback``: A url to redirect back to with the authorized
|
|
request token.
|
|
|
|
Returns:
|
|
- An authorization for the given request token.
|
|
"""
|
|
params = {'oauth_token': request_token.key,
|
|
}
|
|
|
|
if oauth_callback:
|
|
params['oauth_callback'] = oauth_callback
|
|
|
|
return self.build_url(self.WEB_HOST, '/oauth/authorize', params)
|
|
|
|
def obtain_request_token(self):
|
|
"""Obtain a request token from the Dropbox API.
|
|
|
|
This is your first step in the OAuth process. You call this to get a
|
|
request_token from the Dropbox server that you can then use with
|
|
DropboxSession.build_authorize_url() to get the user to authorize it.
|
|
After it's authorized you use this token with
|
|
DropboxSession.obtain_access_token() to get an access token.
|
|
|
|
NOTE: You should only need to do this once for each user, and then you
|
|
can store the access token for that user for later operations.
|
|
|
|
Returns:
|
|
- An :py:class:`OAuthToken` object representing the
|
|
request token Dropbox assigned to this app. Also attaches the
|
|
request token as self.request_token.
|
|
"""
|
|
self.token = None # clear any token currently on the request
|
|
url = self.build_url(self.API_HOST, '/oauth/request_token')
|
|
headers, params = self.build_access_headers('POST', url)
|
|
|
|
response = self.rest_client.POST(url, headers=headers, params=params, raw_response=True)
|
|
self.request_token = self._parse_token(response.read())
|
|
return self.request_token
|
|
|
|
def obtain_access_token(self, request_token=None):
|
|
"""Obtain an access token for a user.
|
|
|
|
After you get a request token, and then send the user to the authorize
|
|
URL, you can use the authorized request token with this method to get the
|
|
access token to use for future operations. The access token is stored on
|
|
the session object.
|
|
|
|
Args:
|
|
- ``request_token``: A request token from obtain_request_token. [optional]
|
|
The request_token should have been authorized via the
|
|
authorization url from build_authorize_url. If you don't pass
|
|
a request_token, the fallback is self.request_token, which
|
|
will exist if you previously called obtain_request_token on this
|
|
DropboxSession instance.
|
|
|
|
Returns:
|
|
- An :py:class:`OAuthToken` object with fields ``key`` and ``secret``
|
|
representing the access token Dropbox assigned to this app and
|
|
user. Also attaches the access token as self.token.
|
|
"""
|
|
request_token = request_token or self.request_token
|
|
assert request_token, "No request_token available on the session. Please pass one."
|
|
url = self.build_url(self.API_HOST, '/oauth/access_token')
|
|
headers, params = self.build_access_headers('POST', url, request_token=request_token)
|
|
|
|
response = self.rest_client.POST(url, headers=headers, params=params, raw_response=True)
|
|
self.token = self._parse_token(response.read())
|
|
return self.token
|
|
|
|
def build_access_headers(self, method, resource_url, params=None, request_token=None):
|
|
"""Build OAuth access headers for a future request.
|
|
|
|
Args:
|
|
- ``method``: The HTTP method being used (e.g. 'GET' or 'POST').
|
|
- ``resource_url``: The full url the request will be made to.
|
|
- ``params``: A dictionary of parameters to add to what's already on the url.
|
|
Typically, this would consist of POST parameters.
|
|
|
|
Returns:
|
|
- A tuple of (header_dict, params) where header_dict is a dictionary
|
|
of header names and values appropriate for passing into dropbox.rest.RESTClient
|
|
and params is a dictionary like the one that was passed in, but augmented with
|
|
oauth-related parameters as appropriate.
|
|
"""
|
|
if params is None:
|
|
params = {}
|
|
else:
|
|
params = params.copy()
|
|
|
|
oauth_params = {
|
|
'oauth_consumer_key' : self.consumer_creds.key,
|
|
'oauth_timestamp' : self._generate_oauth_timestamp(),
|
|
'oauth_nonce' : self._generate_oauth_nonce(),
|
|
'oauth_version' : self._oauth_version(),
|
|
}
|
|
|
|
token = request_token if request_token is not None else self.token
|
|
|
|
if token:
|
|
oauth_params['oauth_token'] = token.key
|
|
|
|
self._oauth_sign_request(oauth_params, self.consumer_creds, token)
|
|
|
|
params.update(oauth_params)
|
|
|
|
return {}, params
|
|
|
|
@classmethod
|
|
def _oauth_sign_request(cls, params, consumer_pair, token_pair):
|
|
params.update({'oauth_signature_method' : 'PLAINTEXT',
|
|
'oauth_signature' : ('%s&%s' % (consumer_pair.secret, token_pair.secret)
|
|
if token_pair is not None else
|
|
'%s&' % (consumer_pair.secret,))})
|
|
|
|
@classmethod
|
|
def _generate_oauth_timestamp(cls):
|
|
return int(time.time())
|
|
|
|
@classmethod
|
|
def _generate_oauth_nonce(cls, length=8):
|
|
return ''.join([str(random.randint(0, 9)) for i in range(length)])
|
|
|
|
@classmethod
|
|
def _oauth_version(cls):
|
|
return '1.0'
|
|
|
|
@classmethod
|
|
def _parse_token(cls, s):
|
|
if not s:
|
|
raise ValueError("Invalid parameter string.")
|
|
|
|
params = parse_qs(s, keep_blank_values=False)
|
|
if not params:
|
|
raise ValueError("Invalid parameter string: %r" % s)
|
|
|
|
try:
|
|
key = params['oauth_token'][0]
|
|
except Exception:
|
|
raise ValueError("'oauth_token' not found in OAuth request.")
|
|
|
|
try:
|
|
secret = params['oauth_token_secret'][0]
|
|
except Exception:
|
|
raise ValueError("'oauth_token_secret' not found in "
|
|
"OAuth request.")
|
|
|
|
return OAuthToken(key, secret)
|
|
|
|
# Don't use this class directly.
|
|
class DropboxOAuth2Session(BaseSession):
|
|
|
|
def __init__(self, oauth2_access_token, locale, rest_client=rest.RESTClient):
|
|
super(DropboxOAuth2Session, self).__init__("", "", "auto", locale=locale, rest_client=rest_client)
|
|
self.access_token = oauth2_access_token
|
|
|
|
def build_access_headers(self, method, resource_url, params=None, token=None):
|
|
assert token is None
|
|
headers = {"Authorization": "Bearer " + self.access_token}
|
|
return headers, params
|