Skip to content
Merged
Show file tree
Hide file tree
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
97 changes: 64 additions & 33 deletions dash_auth/oauth.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from __future__ import absolute_import
import datetime
import flask
from flask_seasurf import SeaSurf
import json
import os
from textwrap import dedent
import itsdangerous

from .auth import Auth

Expand All @@ -14,7 +14,13 @@ class OAuthBase(Auth):
# Name of the cookie containing the OAuth2 access token
TOKEN_COOKIE_NAME = 'oauth_token'

def __init__(self, app, app_url, client_id=None):
def __init__(
self,
app,
app_url,
client_id=None,
secret_key=None,
salt=None):
Auth.__init__(self, app)

self.config = {
Expand All @@ -24,7 +30,31 @@ def __init__(self, app, app_url, client_id=None):
self._app = app
self._app_url = app_url
self._oauth_client_id = client_id
self._access_codes = self.create_access_codes()

if secret_key is None and app.server.secret_key is None:
raise Exception(dedent('''
app.server.secret_key is missing.
Generate a secret key in your Python session
with the following commands:

>>> import os
>>> import base64
>>> base64.b64encode(os.urandom(30)).decode('utf-8')

and assign it to the property app.server.secret_key
(where app is your dash app instance).
Note that you should not do this dynamically:
you should create a key and then assign the value of
that key in your code.
'''))

if salt is None:
raise Exception(dedent('''
salt is missing. The salt parameter needs to a string that
is unique to this individual Dash app.
'''))

self._signer = itsdangerous.TimestampSigner(secret_key, salt=salt)

app.server.add_url_rule(
'{}_dash-login'.format(app.config['routes_pathname_prefix']),
Expand Down Expand Up @@ -52,36 +82,33 @@ def __init__(self, app, app_url, client_id=None):
with open(os.path.join(_current_path, 'login.js'), 'r') as f:
self.login_bundle = f.read()

def create_access_codes(self):
token = SeaSurf()._generate_token()
new_access_codes = {
'access_granted': token,
'expiration': (
datetime.datetime.now() + datetime.timedelta(
seconds=self.config['permissions_cache_expiry']
)
def access_token_is_valid(self):
if self.AUTH_COOKIE_NAME not in flask.request.cookies:
return False

access_token = flask.request.cookies[self.AUTH_COOKIE_NAME]

try:
self._signer.unsign(
access_token,
max_age=self.config['permissions_cache_expiry']
)
}
self._access_codes = new_access_codes
return self._access_codes
return True
except itsdangerous.SignatureExpired:
# Check access in case the user is valid but the token has expired
return False
except itsdangerous.BadSignature:
# Access tokens in previous versions of `dash-auth`
# weren't generated with itsdangerous
# and will raise `BadSignature`
return False

def is_authorized(self):
if self.TOKEN_COOKIE_NAME not in flask.request.cookies:
return False

oauth_token = flask.request.cookies[self.TOKEN_COOKIE_NAME]

if (datetime.datetime.now() > self._access_codes['expiration']):
self.create_access_codes()

if self.AUTH_COOKIE_NAME not in flask.request.cookies:
return self.check_view_access(oauth_token)

access_cookie = flask.request.cookies[self.AUTH_COOKIE_NAME]

# If there access was previously declined,
# check access again in case it has changed
if access_cookie != self._access_codes['access_granted']:
if not self.access_token_is_valid():
return self.check_view_access(oauth_token)

return True
Expand Down Expand Up @@ -118,12 +145,16 @@ def wrap(*args, **kwargs):
# Python 3
if isinstance(response, str):
response = flask.Response(response)
self.set_cookie(
response,
name=self.AUTH_COOKIE_NAME,
value=self._access_codes['access_granted'],
max_age=(60 * 60 * 24 * 7), # 1 week
)

# grant a new access token if expired, missing, or invalid
if not self.access_token_is_valid():
access_token = self._signer.sign('access')
self.set_cookie(
response,
name=self.AUTH_COOKIE_NAME,
value=access_token,
max_age=(60 * 60 * 24 * 7), # 1 week
)
return response
return wrap

Expand Down
7 changes: 6 additions & 1 deletion dash_auth/plotly_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@ def __init__(self, app, app_name, sharing, app_url):
Returns:
None
"""
super(PlotlyAuth, self).__init__(app, app_url)
super(PlotlyAuth, self).__init__(
app,
app_url,
secret_key=api_requests.credential('plotly_api_key'),
salt=app_name
)

self._fid = create_or_overwrite_dash_app(
app_name, sharing, app_url
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
'flask-seasurf',
'plotly',
'dash>=0.18.3',
'retrying'
'retrying',
'itsdangerous'
],
include_package_data=True,
url='https://plot.ly/dash',
Expand Down
1 change: 1 addition & 0 deletions usage_plotly_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
'private',
'http://localhost:8050'
)
server = app.server


app.layout = html.Div([
Expand Down