Skip to content

Commit

Permalink
Merge pull request #5 from MauriceBrg/ft-more-tests
Browse files Browse the repository at this point in the history
Tests, Pipelines, Bugfixes
  • Loading branch information
fspijkerman authored Mar 31, 2024
2 parents 14aba09 + b5039f3 commit 652ce5b
Show file tree
Hide file tree
Showing 13 changed files with 473 additions and 90 deletions.
58 changes: 58 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Dash-Cognito-Auth

on:
push:
branches: ["*"]
# pull_request:
# branches: [master]

# Trigger on release, this will cause the upload to Pypi
release:
types:
- created

jobs:
cross_platform_tests:
runs-on: ${{ matrix.os }}
environment: end-2-end-tests
env:
COGNITO_DOMAIN: ${{ secrets.COGNITO_DOMAIN }}
COGNITO_EMAIL: ${{ secrets.COGNITO_EMAIL }}
COGNITO_OAUTH_CLIENT_ID: ${{ secrets.COGNITO_OAUTH_CLIENT_ID }}
COGNITO_OAUTH_CLIENT_SECRET: ${{ secrets.COGNITO_OAUTH_CLIENT_SECRET }}
COGNITO_PASSWORD: ${{ secrets.COGNITO_PASSWORD }}
COGNITO_REGION: ${{ secrets.COGNITO_REGION }}
COGNITO_USER_NAME: ${{ secrets.COGNITO_USER_NAME }}
strategy:
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
pythonVersion: ["3.10", "3.11", "3.12"]

# MacOS / Windows Runners consume more minutes, we restrict
# those to the minimum supported Python version
exclude:
- os: macos-latest
pythonVersion: 3.11
- os: macos-latest
pythonVersion: 3.12
- os: windows-latest
pythonVersion: 3.11
- os: windows-latest
pythonVersion: 3.12

fail-fast: true

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.pythonVersion }}

- name: Install dev dependencies
run: pip install -r requirements.txt -r requirements-dev.txt

- name: Run Tests
run: python -m pytest tests
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,6 @@ ENV/

# PyCharm
.idea/

.local/
.vscode/
33 changes: 32 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ server.config.update({
'COGNITO_OAUTH_CLIENT_SECRET': ...,
})

app = Dash(__name__, server=server, url_base_pathname='/', auth='auth')
app = Dash(__name__, server=server, url_base_pathname='/')

additional_scopes = [...]
auth = CognitoOAuth(app, domain='mydomain', region='eu-west-1', authorized_emails, additional_scopes)
Expand Down Expand Up @@ -62,3 +62,34 @@ Steps to try this out yourself:
prompting a Cognito login, that means you're already authenticated -- try
using an incognito window in this case if you want to see the login
experience for a new user.

## Development

- Check out the repository
- Run `pip install -r requirements.txt` to install the package
- Run `pip install -r requirements-dev.txt` to install additional dependencies for running the tests
- Run the tests locally
- Use `python -m pytest tests --ignore-glob "*end_to_end*"` to exclude the integration / end to end tests that require a [Cognito Setup](#integration-tests)
- Use `python -m pytest tests` to run all tests


## Integration Tests

There are integration tests against a Cognito User Pool + App Client, if you want to run those - either create a `.env` file with this content or set the environment variables with the same name.

```shell
# Credentials for the user in the user pool
COGNITO_USER_NAME=<username>
COGNITO_EMAIL=<email-that-must-match>
COGNITO_PASSWORD=<password>
# Connection between the app and the user pool
COGNITO_DOMAIN=<just-the-prefix>
COGNITO_REGION=<aws-region-of-the-cognito-userpool>
COGNITO_OAUTH_CLIENT_ID=<app-client-id>
COGNITO_OAUTH_CLIENT_SECRET=<app-client-secret>
```

## Known Limitations

- Fully Custom Cognito Domains aren't supported at the moment
61 changes: 30 additions & 31 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,52 +18,51 @@
app = Dash(
__name__,
server=server,
url_base_pathname='/',
auth='auth',
url_base_pathname="/",
)
app.config['suppress_callback_exceptions']=True
app.config["suppress_callback_exceptions"] = True

# configure google oauth using environment variables
server.secret_key = os.environ.get("FLASK_SECRET_KEY", "supersekrit")
server.config["COGNITO_OAUTH_CLIENT_ID"] = os.environ["COGNITO_OAUTH_CLIENT_ID"]
server.config["COGNITO_OAUTH_CLIENT_SECRET"] = os.environ["COGNITO_OAUTH_CLIENT_SECRET"]

# allow for insecure transport for local testing (remove in prod)
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"

auth = CognitoOAuth(
app, domain=os.environ["COGNITO_DOMAIN"], region=os.environ["COGNITO_REGION"]
)

auth = CognitoOAuth(app, domain='sbphnk', region='eu-west-1')

@server.route("/")
def MyDashApp():
return app.index()


app.layout = html.Div(children=[
html.H1(children="Private Dash App"),

html.Div(id='placeholder', style={'display':'none'}),
html.Div(id='welcome'),

dcc.Graph(
id='example-graph',
figure={
'data': [
{'x': [1, 2, 3], 'y': [4, 1, 2], 'type': 'bar', 'name': 'SF'},
{'x': [1, 2, 3], 'y': [2, 4, 6], 'type': 'bar', 'name': 'Montreal'},
],
'layout': {
'title': 'Dash Data Visualization'
}
}
)
])

@app.callback(
Output('welcome', 'children'),
[Input('placeholder', 'value')]
app.layout = html.Div(
children=[
html.H1(children="Private Dash App"),
html.Div(id="placeholder", style={"display": "none"}),
html.Div(id="welcome"),
dcc.Graph(
id="example-graph",
figure={
"data": [
{"x": [1, 2, 3], "y": [4, 1, 2], "type": "bar", "name": "SF"},
{"x": [1, 2, 3], "y": [2, 4, 6], "type": "bar", "name": "Montreal"},
],
"layout": {"title": "Dash Data Visualization"},
},
),
]
)


@app.callback(Output("welcome", "children"), [Input("placeholder", "value")])
def on_load(value):
return "Welcome, {}!".format(session['email'])
return "Welcome, {}!".format(session["email"])


if __name__ == '__main__':
app.run_server(host='localhost')
if __name__ == "__main__":
app.run_server(host="localhost")
21 changes: 8 additions & 13 deletions dash_cognito_auth/cognito.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
from __future__ import unicode_literals

from flask_dance.consumer import OAuth2ConsumerBlueprint
from functools import partial
from flask.globals import LocalProxy, _lookup_app_object

try:
from flask import _app_ctx_stack as stack
except ImportError:
from flask import _request_ctx_stack as stack
from flask.globals import LocalProxy
from flask import g


__maintainer__ = "Frank Spijkerman <fspijkerman@schubergphilis.com>"


def make_cognito_blueprint(
client_id=None,
client_secret=None,
Expand Down Expand Up @@ -65,9 +61,9 @@ def make_cognito_blueprint(
client_id=client_id,
client_secret=client_secret,
scope=scope,
base_url=f'https://{domain}.auth.{region}.amazoncognito.com',
authorization_url=f'https://{domain}.auth.{region}.amazoncognito.com/oauth2/authorize',
token_url=f'https://{domain}.auth.{region}.amazoncognito.com/oauth2/token',
base_url=f"https://{domain}.auth.{region}.amazoncognito.com",
authorization_url=f"https://{domain}.auth.{region}.amazoncognito.com/oauth2/authorize",
token_url=f"https://{domain}.auth.{region}.amazoncognito.com/oauth2/token",
redirect_url=redirect_url,
redirect_to=redirect_to,
login_url=login_url,
Expand All @@ -80,10 +76,9 @@ def make_cognito_blueprint(

@cognito_bp.before_app_request
def set_applocal_session():
ctx = stack.top
ctx.cognito_oauth = cognito_bp.session
g.cognito_oauth = cognito_bp.session

return cognito_bp


cognito = LocalProxy(partial(_lookup_app_object, "cognito_oauth"))
cognito = LocalProxy(lambda: g.cognito_oauth)
20 changes: 12 additions & 8 deletions dash_cognito_auth/cognito_oauth.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
from oauthlib.oauth2.rfc6749.errors import InvalidGrantError, TokenExpiredError, OAuth2Error
from oauthlib.oauth2.rfc6749.errors import InvalidGrantError, TokenExpiredError
from flask import (
redirect,
url_for,
Response,
abort,
session,
)
from .cognito import (
make_cognito_blueprint,
cognito
)
from .cognito import make_cognito_blueprint, cognito

from .auth import Auth


class CognitoOAuth(Auth):
"""
Wraps a Dash App and adds Cognito based OAuth2 authentication.
"""

def __init__(self, app, domain, region, additional_scopes=None):
super(CognitoOAuth, self).__init__(app)
cognito_bp = make_cognito_blueprint(
Expand All @@ -23,7 +24,8 @@ def __init__(self, app, domain, region, additional_scopes=None):
"openid",
"email",
"profile",
] + (additional_scopes if additional_scopes else [])
]
+ (additional_scopes if additional_scopes else []),
)
app.server.register_blueprint(cognito_bp, url_prefix="/login")

Expand All @@ -36,7 +38,7 @@ def is_authorized(self):
resp = cognito.get("/oauth2/userInfo")
assert resp.ok, resp.text

session['email'] = resp.json().get('email')
session["email"] = resp.json().get("email")
return True
except (InvalidGrantError, TokenExpiredError):
return self.login_request()
Expand All @@ -52,6 +54,7 @@ def wrap(*args, **kwargs):

response = f(*args, **kwargs)
return response

return wrap

def index_auth_wrapper(self, original_index):
Expand All @@ -60,4 +63,5 @@ def wrap(*args, **kwargs):
return original_index(*args, **kwargs)
else:
return self.login_request()

return wrap
4 changes: 4 additions & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pytest
requests
beautifulsoup4
python-dotenv
7 changes: 1 addition & 6 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1 @@
dash>=0.41.0
dash-core-components>=0.46.0
dash-html-components>=0.15.0
Flask>=1.0.2
Flask-Dance>=1.2.0
six
-e .
41 changes: 24 additions & 17 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,34 @@
setup(
name="dash-cognito-auth",
description="Dash Cognito Auth",
long_description=open('README.md', 'rt').read().strip(),
long_description_content_type='text/markdown',
author="Frank Spijkerman", author_email='fspijkerman@gmail.com',
long_description=open("README.md", "rt").read().strip(),
long_description_content_type="text/markdown",
author="Frank Spijkerman",
author_email="fspijkerman@gmail.com",
url="https://github.com/fspijkerman/dash-cognito-auth",
license='MIT',
package='dash_cognito_auth',
packages=['dash_cognito_auth'],
license="MIT",
package="dash_cognito_auth",
packages=["dash_cognito_auth"],
install_requires=[
'dash>=0.26.5',
'dash-core-components>=0.28.3',
'dash-html-components>=0.12.0',
'Flask>=0.12.4',
'Flask-Dance>=0.14.0',
'six>=1.11.0',
"dash>=0.41.0",
"dash-core-components>=0.46.0",
"dash-html-components>=0.15.0",
"Flask>=1.0.2",
"Flask-Dance>=1.2.0",
"six>=1.11.0",
],
python_requires=">=3.10",
setup_requires=["pytest-runner", "setuptools_scm"],
tests_require=[
"pytest",
"requests",
"beautifulsoup4",
"python-dotenv",
],
setup_requires=['pytest-runner', 'setuptools_scm'],
tests_require=['pytest'],
classifiers=[
'License :: OSI Approved :: MIT License',
'Programming Language :: Python',
'Programming Language :: Python :: Implementation :: CPython',
"License :: OSI Approved :: MIT License",
"Programming Language :: Python",
"Programming Language :: Python :: Implementation :: CPython",
],
use_scm_version=True,
zip_safe=False,
Expand Down
Loading

0 comments on commit 652ce5b

Please sign in to comment.