-
Notifications
You must be signed in to change notification settings - Fork 215
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #81 from box/auth
Add network and auth subclasses.
- Loading branch information
Showing
26 changed files
with
630 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# coding: utf-8 | ||
|
||
from __future__ import unicode_literals | ||
from boxsdk import OAuth2 | ||
|
||
|
||
class CooperativelyManagedOAuth2Mixin(OAuth2): | ||
""" | ||
Box SDK OAuth2 mixin. | ||
Allows for sharing auth tokens between multiple clients. | ||
""" | ||
def __init__(self, retrieve_tokens=None, *args, **kwargs): | ||
""" | ||
:param retrieve_tokens: | ||
Callback to get the current access/refresh token pair. | ||
:type retrieve_tokens: | ||
`callable` of () => (`unicode`, `unicode`) | ||
""" | ||
self._retrieve_tokens = retrieve_tokens | ||
super(CooperativelyManagedOAuth2Mixin, self).__init__(*args, **kwargs) | ||
|
||
def _get_tokens(self): | ||
""" | ||
Base class override. Get the tokens from the user-specified callback. | ||
""" | ||
return self._retrieve_tokens() | ||
|
||
|
||
class CooperativelyManagedOAuth2(CooperativelyManagedOAuth2Mixin): | ||
""" | ||
Box SDK OAuth2 subclass. | ||
Allows for sharing auth tokens between multiple clients. The retrieve_tokens callback should | ||
return the current access/refresh token pair. | ||
""" | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
# coding: utf-8 | ||
|
||
from __future__ import unicode_literals | ||
from redis import StrictRedis | ||
from redis.lock import Lock | ||
from uuid import uuid4 | ||
from boxsdk import JWTAuth, OAuth2 | ||
|
||
|
||
class RedisManagedOAuth2Mixin(OAuth2): | ||
""" | ||
Box SDK OAuth2 subclass. | ||
Allows for storing auth tokens in redis. | ||
:param unique_id: | ||
An identifier for this auth object. Auth instances which wish to share tokens must use the same ID. | ||
:type unique_id: | ||
`unicode` | ||
:param redis_server: | ||
An instance of a Redis server, configured to talk to Redis. | ||
:type redis_server: | ||
:class:`Redis` | ||
""" | ||
def __init__(self, unique_id=uuid4(), redis_server=None, *args, **kwargs): | ||
self._unique_id = unique_id | ||
self._redis_server = redis_server or StrictRedis() | ||
refresh_lock = Lock(redis=self._redis_server, name='{0}_lock'.format(self._unique_id)) | ||
super(RedisManagedOAuth2Mixin, self).__init__(*args, refresh_lock=refresh_lock, **kwargs) | ||
if self._access_token is None: | ||
self._update_current_tokens() | ||
|
||
def _update_current_tokens(self): | ||
""" | ||
Get the latest tokens from redis and store them. | ||
""" | ||
self._access_token, self._refresh_token = self._redis_server.hvals(self._unique_id) or (None, None) | ||
|
||
@property | ||
def unique_id(self): | ||
""" | ||
Get the unique ID used by this auth instance. Other instances can share tokens with this instance | ||
if they share the ID with this instance. | ||
""" | ||
return self._unique_id | ||
|
||
def _get_tokens(self): | ||
""" | ||
Base class override. | ||
Gets the latest tokens from redis before returning them. | ||
""" | ||
self._update_current_tokens() | ||
return super(RedisManagedOAuth2Mixin, self)._get_tokens() | ||
|
||
def _store_tokens(self, access_token, refresh_token): | ||
""" | ||
Base class override. | ||
Saves the refreshed tokens in redis. | ||
""" | ||
super(RedisManagedOAuth2Mixin, self)._store_tokens(access_token, refresh_token) | ||
self._redis_server.hmset(self._unique_id, {'access': access_token, 'refresh': refresh_token}) | ||
|
||
|
||
class RedisManagedOAuth2(RedisManagedOAuth2Mixin): | ||
""" | ||
OAuth2 subclass which uses Redis to manage tokens. | ||
""" | ||
pass | ||
|
||
|
||
class RedisManagedJWTAuth(RedisManagedOAuth2Mixin, JWTAuth): | ||
""" | ||
JWT Auth subclass which uses Redis to manage access tokens. | ||
""" | ||
def _auth_with_jwt(self, sub, sub_type): | ||
""" | ||
Base class override. Returns the access token in a tuple to match the OAuth2 interface. | ||
""" | ||
return super(RedisManagedJWTAuth, self)._auth_with_jwt(sub, sub_type), None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# coding: utf-8 | ||
|
||
from __future__ import unicode_literals | ||
from boxsdk import OAuth2 | ||
|
||
|
||
class RemoteOAuth2Mixin(OAuth2): | ||
""" | ||
Box SDK OAuth2 mixin. | ||
Allows for storing auth tokens remotely. | ||
""" | ||
def __init__(self, retrieve_access_token=None, *args, **kwargs): | ||
""" | ||
:param retrieve_access_token: | ||
Callback to exchange an existing access token for a new one. | ||
:type retrieve_access_token: | ||
`callable` of `unicode` => `unicode` | ||
""" | ||
self._retrieve_access_token = retrieve_access_token | ||
super(RemoteOAuth2Mixin, self).__init__(*args, **kwargs) | ||
|
||
def _refresh(self, access_token): | ||
""" | ||
Base class override. Ask the remote host for a new token. | ||
""" | ||
self._access_token = self._retrieve_access_token(access_token) | ||
return self._access_token, None | ||
|
||
|
||
class RemoteOAuth2(RemoteOAuth2Mixin): | ||
""" | ||
Box SDK OAuth2 subclass. | ||
Allows for storing auth tokens remotely. The retrieve_access_token callback should | ||
return an access token, presumably acquired from a remote server on which your auth credentials are available. | ||
""" | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
# coding: utf-8 | ||
|
||
from __future__ import unicode_literals | ||
from pprint import pformat | ||
from boxsdk.network.default_network import DefaultNetwork | ||
from boxsdk.util.log import setup_logging | ||
|
||
|
||
class LoggingNetwork(DefaultNetwork): | ||
""" | ||
SDK Network subclass that logs requests and responses. | ||
""" | ||
LOGGER_NAME = 'boxsdk.network' | ||
REQUEST_FORMAT = '\x1b[36m%s %s %s\x1b[0m' | ||
SUCCESSFUL_RESPONSE_FORMAT = '\x1b[32m%s\x1b[0m' | ||
ERROR_RESPONSE_FORMAT = '\x1b[31m%s\n%s\n%s\n\x1b[0m' | ||
|
||
def __init__(self, logger=None): | ||
""" | ||
:param logger: | ||
The logger to use. If you instantiate this class more than once, you should use the same logger | ||
to avoid duplicate log entries. | ||
:type logger: | ||
:class:`Logger` | ||
""" | ||
super(LoggingNetwork, self).__init__() | ||
self._logger = logger or setup_logging(name=self.LOGGER_NAME) | ||
|
||
@property | ||
def logger(self): | ||
return self._logger | ||
|
||
def _log_request(self, method, url, **kwargs): | ||
""" | ||
Logs information about the Box API request. | ||
:param method: | ||
The HTTP verb that should be used to make the request. | ||
:type method: | ||
`unicode` | ||
:param url: | ||
The URL for the request. | ||
:type url: | ||
`unicode` | ||
:param access_token: | ||
The OAuth2 access token used to authorize the request. | ||
:type access_token: | ||
`unicode` | ||
""" | ||
self._logger.info(self.REQUEST_FORMAT, method, url, pformat(kwargs)) | ||
|
||
def _log_response(self, response): | ||
""" | ||
Logs information about the Box API response. | ||
:param response: The Box API response. | ||
""" | ||
if response.ok: | ||
self._logger.info(self.SUCCESSFUL_RESPONSE_FORMAT, response.content) | ||
else: | ||
self._logger.warning( | ||
self.ERROR_RESPONSE_FORMAT, | ||
response.status_code, | ||
response.headers, | ||
pformat(response.content), | ||
) | ||
|
||
def request(self, method, url, access_token, **kwargs): | ||
""" | ||
Base class override. Logs information about an API request and response in addition to making the request. | ||
""" | ||
self._log_request(method, url, **kwargs) | ||
response = super(LoggingNetwork, self).request(method, url, access_token, **kwargs) | ||
self._log_response(response) | ||
return response |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# coding: utf-8 | ||
|
||
from __future__ import unicode_literals | ||
import logging | ||
import sys | ||
|
||
|
||
def setup_logging(stream_or_file=None, debug=False, name=None): | ||
""" | ||
Create a logger for communicating with the user or writing to log files. | ||
By default, creates a root logger that prints to stdout. | ||
:param stream_or_file: | ||
The destination of the log messages. If None, stdout will be used. | ||
:type stream_or_file: | ||
`unicode` or `file` or None | ||
:param debug: | ||
Whether or not the logger will be at the DEBUG level (if False, the logger will be at the INFO level). | ||
:type debug: | ||
`bool` or None | ||
:param name: | ||
The logging channel. If None, a root logger will be created. | ||
:type name: | ||
`unicode` or None | ||
:return: | ||
A logger that's been set up according to the specified parameters. | ||
:rtype: | ||
:class:`Logger` | ||
""" | ||
logger = logging.getLogger(name) | ||
if isinstance(stream_or_file, basestring): | ||
handler = logging.FileHandler(stream_or_file, mode='w') | ||
else: | ||
handler = logging.StreamHandler(stream_or_file or sys.stdout) | ||
logger.addHandler(handler) | ||
logger.setLevel(logging.DEBUG if debug else logging.INFO) | ||
return logger |
Oops, something went wrong.