From c896a97b938f3aa0c0f324af5a471c7efca75f87 Mon Sep 17 00:00:00 2001 From: Maurice Borgmeier Date: Tue, 26 Mar 2024 13:19:58 +0100 Subject: [PATCH 1/5] Add version constraints so the tests pass, additional tests (including end-to-end), fix https://github.com/fspijkerman/dash-cognito-auth/issues/2 --- .github/workflows/build.yml | 38 +++++++++++ .gitignore | 3 + README.md | 8 ++- app.py | 61 +++++++++--------- requirements-dev.txt | 4 ++ setup.py | 41 +++++++----- tests/conftest.py | 77 +++++++++++++++++++++++ tests/test_auth_flows.py | 102 ++++++++++++++++++++++++++++++ tests/test_end_to_end.py | 121 ++++++++++++++++++++++++++++++++++++ tests/test_import.py | 15 +---- 10 files changed, 407 insertions(+), 63 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 requirements-dev.txt create mode 100644 tests/conftest.py create mode 100644 tests/test_auth_flows.py create mode 100644 tests/test_end_to_end.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..6c058c7 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,38 @@ +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 + strategy: + matrix: + os: [macos-latest, windows-latest, ubuntu-latest] + pythonVersion: ["3.10"] # , "3.11", "3.12" + + fail-fast: true + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.pythonVersion }} + + - name: Install dev dependencies + run: pip install -e ".[dev]" + + - name: Run Tests + run: python setup.py test diff --git a/.gitignore b/.gitignore index 30dbb59..b082762 100644 --- a/.gitignore +++ b/.gitignore @@ -103,3 +103,6 @@ ENV/ # PyCharm .idea/ + +.local/ +.vscode/ \ No newline at end of file diff --git a/README.md b/README.md index 3730ccf..eb2899d 100644 --- a/README.md +++ b/README.md @@ -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) @@ -62,3 +62,9 @@ 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 -e .` +- Run `python setup.py test` to check if the tests run locally \ No newline at end of file diff --git a/app.py b/app.py index ab1c935..f8d16d2 100755 --- a/app.py +++ b/app.py @@ -18,10 +18,9 @@ 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") @@ -29,41 +28,41 @@ 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") diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..25ff063 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,4 @@ +pytest +requests +beautifulsoup4 +python-dotenv \ No newline at end of file diff --git a/setup.py b/setup.py index 292b5c3..eea19f7 100755 --- a/setup.py +++ b/setup.py @@ -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.26.5", + "dash-core-components>=0.28.3", + "dash-html-components>=0.12.0", + "Flask>=0.12.4,<2.2.0", + "Flask-Dance>=0.14.0", + "six>=1.11.0", + "Werkzeug<=2.3.0", + ], + 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, diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..0f8a945 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,77 @@ +from unittest.mock import patch + +import pytest + +from dash import Dash, html +from dotenv import load_dotenv +from flask import Flask, redirect + +from dash_cognito_auth import CognitoOAuth + +load_dotenv() + + +@pytest.fixture +def app(name="dash") -> Dash: + """ + Dash App that has only one H1 Tag with the value "Hello World". + It uses a Flask server with the secret key just_a_test. + """ + + dash_app = Dash(name, server=Flask(name), url_base_pathname="/") + dash_app.layout = html.H1("Hello World") + dash_app.server.config.update( + { + "TESTING": True, + } + ) + dash_app.server.secret_key = "just_a_test" + return dash_app + + +@pytest.fixture +def app_with_auth(app) -> CognitoOAuth: + """ + Dash App wrapped with Cognito Authentication: + - Domain name: test + - Region: eu-central-1 + - App Client Id: testclient + - App Client Secret: testsecret + """ + + auth = CognitoOAuth(app, domain="test", region="eu-central-1") + auth.app.server.config["COGNITO_OAUTH_CLIENT_ID"] = "testclient" + auth.app.server.config["COGNITO_OAUTH_CLIENT_SCRET"] = "testsecret" + + return auth + + +@pytest.fixture +def authorized_app(app) -> CognitoOAuth: + """ + App with Cognito Based authentication that bypasses the authentication/authorization + part, i.e. replaced is_authorized and the authorized endpoint. + + Intended for tests that + """ + + def is_authorized_call(_): + + return True + + def authorized_call(_): + + return redirect("/") + + with patch( + "dash_cognito_auth.cognito_oauth.CognitoOAuth.is_authorized", is_authorized_call + ), patch( + "flask_dance.consumer.oauth2.OAuth2ConsumerBlueprint.authorized", + authorized_call, + ): + + auth = CognitoOAuth(app, domain="test", region="eu-central-1") + auth.app.server.config["COGNITO_OAUTH_CLIENT_ID"] = "testclient" + auth.app.server.config["COGNITO_OAUTH_CLIENT_SCRET"] = "testsecret" + + yield auth diff --git a/tests/test_auth_flows.py b/tests/test_auth_flows.py new file mode 100644 index 0000000..89e45d8 --- /dev/null +++ b/tests/test_auth_flows.py @@ -0,0 +1,102 @@ +""" +Test the various auth flows and ensure that authentication happens. +""" + +from http import HTTPStatus +from urllib.parse import parse_qs, urlparse + + +from flask import Flask + +from dash_cognito_auth.cognito_oauth import CognitoOAuth + + +def test_that_a_redirect_to_cognito_handler_happens_if_not_logged_in( + app_with_auth: CognitoOAuth, +): + """ + If we're not logged in, an unauthenticated request should result in a redirect + to the local cognito endpoint. + """ + + # Arrange + flask_server: Flask = app_with_auth.app.server + client = flask_server.test_client() + + # Act + response = client.get("/") + + # Assert + assert response.status_code == HTTPStatus.FOUND + assert response.headers.get("Location") == "/login/cognito" + + +def test_that_cognito_handler_redirects_to_user_pool_if_not_authenticated( + app_with_auth: CognitoOAuth, +): + """ + If we're not logged in, the Cognito handler should redirect the request to the + Cognito User pool so the client can login an retrieve a JWT. + + We test that all the required scopes etc. are present in the redirect uri + and it's formed according to the expected pattern. + """ + + # Arrange + flask_server: Flask = app_with_auth.app.server + client = flask_server.test_client() + + # Act + response = client.get("/login/cognito") + + # Assert + assert response.status_code == HTTPStatus.FOUND, "We expect a redirect" + + redirect_url = response.headers.get("Location") + parsed = urlparse(redirect_url) + + assert parsed.scheme == "https" + assert ( + parsed.hostname == "test.auth.eu-central-1.amazoncognito.com" + ) # Domain name + region + assert parsed.path == "/oauth2/authorize" + + parsed_qs = parse_qs(parsed.query, strict_parsing=True) + assert "openid" in parsed_qs["scope"][0] + assert "email" in parsed_qs["scope"][0] + assert "profile" in parsed_qs["scope"][0] + + assert parsed_qs["response_type"][0] == "code" + assert parsed_qs["redirect_uri"][0] == "http://localhost/login/cognito/authorized" + assert parsed_qs["client_id"][0] == "testclient" + assert "state" in parsed_qs + + +def test_that_cognito_authorized_response_is_accepted(authorized_app: CognitoOAuth): + """ + After we authenticate, Cognito redirects us to the /login/cognito/authorized + endpoint. Here we test that this redirects us to the login page after the + codes are verified (this is bypassed and left to the end-to-end test). + """ + + # Arrange + flask_server: Flask = authorized_app.app.server + client = flask_server.test_client() + + cognito_redirect_target = ( + "/login/cognito/authorized?code=1e4aa08d-d969-4264-a835-c1b2757b9163" + "&state=vSBCqedXdyXQAOcr" + ) + + # Act + response = client.get(cognito_redirect_target, follow_redirects=False) + + # Assert + assert response.status_code == HTTPStatus.FOUND + assert ( + response.headers.get("Location") == "/" + ), "Should redirect to root after authorization" + + +# Test Cognito Response parsing +# TODO: Test Logout diff --git a/tests/test_end_to_end.py b/tests/test_end_to_end.py new file mode 100644 index 0000000..8d819a9 --- /dev/null +++ b/tests/test_end_to_end.py @@ -0,0 +1,121 @@ +""" +Integration test that authenticates against a real user pool. + +Naturally these are a bit sensitive to the way the Cognito UI is implemented. +""" + +import os + +from http import HTTPStatus + +import requests +import pytest + +from bs4 import BeautifulSoup +from dash import Dash, html +from flask import Flask, session + +from dash_cognito_auth import CognitoOAuth + +# For our end to end test we don't have HTTPS +os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1" + + +@pytest.fixture +def end_to_end_app() -> CognitoOAuth: + """ + Small dash app that's wrapped with the CognitoOAuth and offers a /session-info + endpoint which returns the currently logged in user. + """ + + name = "end-to-end" + + dash_app = Dash(name, server=Flask(name), url_base_pathname="/") + dash_app.layout = html.H1("Hello World") + dash_app.server.config.update( + { + "TESTING": True, + } + ) + dash_app.server.secret_key = "just_a_test" + + auth = CognitoOAuth( + dash_app, + domain=os.environ["COGNITO_DOMAIN"], + region=os.environ["COGNITO_REGION"], + ) + auth.app.server.config["COGNITO_OAUTH_CLIENT_ID"] = os.environ[ + "COGNITO_OAUTH_CLIENT_ID" + ] + auth.app.server.config["COGNITO_OAUTH_CLIENT_SECRET"] = os.environ[ + "COGNITO_OAUTH_CLIENT_SECRET" + ] + + @dash_app.server.route("/session-info") + def session_info(): + return {"email": session["email"]} + + return auth + + +def test_end_to_end(end_to_end_app: CognitoOAuth): + """ + - Request the local webapp + - Follow the redirect to the local authorization endpoint + - Follow the redirect to the Cognito UI + - Parse the Cognito UI + - Log in to Cognito + - Follow the redirect to the authorization endpoint + - Follow the redirect to the app home page (logged in) + - Check the /session-info endpoint to verify the correct user is logged in + """ + + # Arrange + server: Flask = end_to_end_app.app.server + client = server.test_client() + s = requests.session() + + # Act + Assert + + # We're not authenticated, we should be redirected to the local cognito endpoint. + redirect_to_local_cognito = client.get("/") + + # Redirect to the Cognito Login UI + redirect_to_cognito_ui = client.get(redirect_to_local_cognito.location) + + # Get Cognito Login page, extract tokens + cognito_login_ui = s.get(redirect_to_cognito_ui.location, timeout=5) + ui_soup = BeautifulSoup(cognito_login_ui.text, features="html.parser") + + ui_soup.select_one('input[name="_csrf"]') + csrf_token = ui_soup.select_one('input[name="_csrf"]')["value"] + cognito_asf_data = ui_soup.select_one('input[name="cognitoAsfData"]') + login_url = ( + redirect_to_cognito_ui.location.split(".com/")[0] + + ".com" + + ui_soup.select_one('form[name="cognitoSignInForm"]')["action"] + ) + + # Login and catch redirect response + login_response = s.post( + url=login_url, + data={ + "_csrf": csrf_token, + "username": os.environ["COGNITO_USER_NAME"], + "password": os.environ["COGNITO_PASSWORD"], + "cognitoAsfData": cognito_asf_data, + }, + timeout=5, + allow_redirects=False, + ) + + # Use Cognito tokens to log in + post_cognito_auth_redirect = client.get(login_response.headers["location"]) + + # We should now be redirected to the home page which will be displayed + home_page_with_auth = client.get(post_cognito_auth_redirect.location) + assert home_page_with_auth.status_code == HTTPStatus.OK + + # Verify that the logged in users' email matches the one from the env + session_info_response = client.get("/session-info") + assert session_info_response.json["email"] == os.environ["COGNITO_EMAIL"] diff --git a/tests/test_import.py b/tests/test_import.py index 0e16fef..259bac4 100644 --- a/tests/test_import.py +++ b/tests/test_import.py @@ -2,25 +2,12 @@ Test Dash Cognito Auth. """ -import pytest - from dash_cognito_auth import CognitoOAuth -from dash import Dash -from flask import Flask - - -@pytest.fixture -def app(name='dask'): - """Dash App.""" - - return Dash(name, server=Flask(name), - url_base_pathname='/', auth='auth') - def test_init(app): """Test initialisation.""" - auth = CognitoOAuth(app, domain='test', region='eu-west-1') + auth = CognitoOAuth(app, domain="test", region="eu-west-1") assert auth.app is app From 0c1db0b02eb14317eb0fee7031ff3681a66a23d0 Mon Sep 17 00:00:00 2001 From: Maurice Borgmeier Date: Tue, 26 Mar 2024 13:24:16 +0100 Subject: [PATCH 2/5] Try to add missing env vars --- .github/workflows/build.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6c058c7..94cdedb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,6 +15,14 @@ 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] From 412c076f6e0bb0b2103e8535c114b7c0fd81ff4b Mon Sep 17 00:00:00 2001 From: Maurice Borgmeier Date: Tue, 26 Mar 2024 15:29:13 +0100 Subject: [PATCH 3/5] Enable Matrix Tests for Python versions 3.10, 3.11, 3.12 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 94cdedb..1b20a12 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,7 +26,7 @@ jobs: strategy: matrix: os: [macos-latest, windows-latest, ubuntu-latest] - pythonVersion: ["3.10"] # , "3.11", "3.12" + pythonVersion: ["3.10", "3.11", "3.12"] fail-fast: true From 0da31d6b20d139c489e132f8881368a2d6b0bbb9 Mon Sep 17 00:00:00 2001 From: Maurice Borgmeier Date: Tue, 26 Mar 2024 16:27:38 +0100 Subject: [PATCH 4/5] Fix https://github.com/fspijkerman/dash-cognito-auth/issues/3 and adjust version boundaries for dependencies to be more consistent. Require Python 3.10 --- README.md | 29 +++++++++++++++++++++++++++-- dash_cognito_auth/cognito.py | 21 ++++++++------------- dash_cognito_auth/cognito_oauth.py | 20 ++++++++++++-------- requirements.txt | 7 +------ setup.py | 12 ++++++------ 5 files changed, 54 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index eb2899d..7644210 100644 --- a/README.md +++ b/README.md @@ -66,5 +66,30 @@ Steps to try this out yourself: ## Development - Check out the repository -- Run `pip install -e .` -- Run `python setup.py test` to check if the tests run locally \ No newline at end of file +- 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= +COGNITO_EMAIL= +COGNITO_PASSWORD= + +# Connection between the app and the user pool +COGNITO_DOMAIN= +COGNITO_REGION= +COGNITO_OAUTH_CLIENT_ID= +COGNITO_OAUTH_CLIENT_SECRET= +``` + +## Known Limitations + +- Fully Custom Cognito Domains aren't supported at the moment \ No newline at end of file diff --git a/dash_cognito_auth/cognito.py b/dash_cognito_auth/cognito.py index 5d39503..90f6873 100644 --- a/dash_cognito_auth/cognito.py +++ b/dash_cognito_auth/cognito.py @@ -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 " + def make_cognito_blueprint( client_id=None, client_secret=None, @@ -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, @@ -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) diff --git a/dash_cognito_auth/cognito_oauth.py b/dash_cognito_auth/cognito_oauth.py index 66aa32e..b8523a0 100644 --- a/dash_cognito_auth/cognito_oauth.py +++ b/dash_cognito_auth/cognito_oauth.py @@ -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( @@ -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") @@ -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() @@ -52,6 +54,7 @@ def wrap(*args, **kwargs): response = f(*args, **kwargs) return response + return wrap def index_auth_wrapper(self, original_index): @@ -60,4 +63,5 @@ def wrap(*args, **kwargs): return original_index(*args, **kwargs) else: return self.login_request() + return wrap diff --git a/requirements.txt b/requirements.txt index 46e5f66..ecf975e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 . \ No newline at end of file diff --git a/setup.py b/setup.py index eea19f7..8219b86 100755 --- a/setup.py +++ b/setup.py @@ -14,14 +14,14 @@ 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,<2.2.0", - "Flask-Dance>=0.14.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", - "Werkzeug<=2.3.0", ], + python_requires=">=3.10", setup_requires=["pytest-runner", "setuptools_scm"], tests_require=[ "pytest", From b5039f3c4aec1d3f428fe56b9d32096f8d9cdc61 Mon Sep 17 00:00:00 2001 From: Maurice Borgmeier Date: Tue, 26 Mar 2024 16:37:50 +0100 Subject: [PATCH 5/5] Update Pipeline definition to use more recent action versions and reduce the number of OS/Python versions checked --- .github/workflows/build.yml | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1b20a12..a11d688 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,19 +28,31 @@ jobs: 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@v2 + uses: actions/checkout@v4 - name: Install Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.pythonVersion }} - name: Install dev dependencies - run: pip install -e ".[dev]" + run: pip install -r requirements.txt -r requirements-dev.txt - name: Run Tests - run: python setup.py test + run: python -m pytest tests