diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e5c2a358cd369b570a63ee3eb8c6bf4015c1e5da..7da590064c8a1c08297461f57497145308c192c4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -38,7 +38,7 @@ integration_test_app: extends: .kaniko_build only: changes: - - ./test/integration_tests/**/* + - test/integration_tests/**/* - .gitlab-ci.yml integration_test: @@ -49,7 +49,7 @@ integration_test: extends: .kaniko_build only: changes: - - ./test/integration_tests/test/**/* + - test/integration_tests/test/**/* - .gitlab-ci.yml pylint: @@ -81,7 +81,7 @@ behave-integration: - all - --dangerous-force-http - --dangerous-allow-insecure-redirect-urls - - http://oauth:5000/callback + - http://oauth:5000/login/sso/authorized - name: open.greenhost.net:4567/openappstack/user-panel/backend:master alias: backend - name: ${CI_REGISTRY_IMAGE}/integration_test_app:${CI_COMMIT_REF_NAME} @@ -137,7 +137,7 @@ behave-integration: - /bin/bash user-panel/backend/utils/assign-role.bash ${TESTUSER_USERNAME} ${ROLE} backend:5000 # Wait for 60s for hydra to become available. Then create the oauth2 client object - while [[ $HYDRASTATUS -ne "200" && 60 -ge $TIMER ]]; do HYDRASTATUS=`curl http://hydra:4445/health/ready -o /dev/null -w "%{http_code}"` || TIMER=$TIMER+5 && sleep 5 ; done - - /bin/bash test/create-hydra-client.bash ${KEY} ${SECRET} http://hydra:4445 http://oauth:5000/callback + - /bin/bash test/create-hydra-client.bash ${KEY} ${SECRET} http://hydra:4445 http://oauth:5000/login/sso/authorized - cd test/integration_tests/test/behave/ - > python3 -m behave diff --git a/docker-compose.yml b/docker-compose.yml index 8ed729d76d821a2449a56e26fec6ddc442329f45..260a4125d2335e5b2685f2234e7d014f239e3a59 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,7 +13,7 @@ services: - "4445:4445" # Admin port - "5555:5555" # Port for hydra token user command: - serve all --dangerous-force-http --dangerous-allow-insecure-redirect-urls "http://127.0.0.1:13337/callback, http://localhost:3000/callback" + serve all --dangerous-force-http --dangerous-allow-insecure-redirect-urls "http://127.0.0.1:13337/login/sso/authorized, http://localhost:3000/login/sso/authorized" environment: - URLS_SELF_ISSUER=http://localhost:4444/ - URLS_CONSENT=http://localhost:5001/consent @@ -25,7 +25,8 @@ services: - OIDC_SUBJECT_TYPES_SUPPORTED=public,pairwise - OIDC_SUBJECT_TYPE_PAIRWISE_SALT=youReallyNeedToChangeThis - SERVE_PUBLIC_CORS_DEBUG=true - - LOG_LEVEL="debug" + - LOG_LEVEL=debug + - LOG_LEAK_SENSITIVE_VALUES=true restart: unless-stopped consent: build: consent_provider/ @@ -76,8 +77,8 @@ services: - OAUTHLIB_INSECURE_TRANSPORT=true - FLASK_ENV=development # with this settings run: - # `bash test/create-127.0.0.1-client.bash testapp clientsecret http://localhost:4445 http://127.0.0.1:13337/callback - # to register a corresponding oauth client with hydra + ## `bash test/create-hydra-client.bash testapp clientsecret http://localhost:4445 http://127.0.0.1:13337/login/sso/authorized + ## to register a corresponding oauth client with hydra ports: - "13337:13337" command: flask run --port 13337 --host "0.0.0.0" diff --git a/test/create-hydra-client.bash b/test/create-hydra-client.bash index 1d0fc27e157f2614c55bda53a1200da417c0b9e1..8b4c78fd169d335a52483f820b53db9a1cff75ae 100755 --- a/test/create-hydra-client.bash +++ b/test/create-hydra-client.bash @@ -13,5 +13,5 @@ SCOPES="openid profile email openappstack_roles" curl --header "Content-Type: application/json" \ --request POST \ - --data "{\"client_id\": \"$KEY\", \"client_name\": \"$KEY\", \"client_secret\": \"$SECRET\", \"redirect_uris\": [\"$REDIRECT_URI\"], \"scope\": \"$SCOPES\", \"grant_types\": [\"authorization_code\",\"refresh_token\"], \"response_types\": [\"code\"], \"token_endpoint_auth_method\": \"client_secret_post\"}" \ + --data "{\"client_id\": \"$KEY\", \"client_name\": \"$KEY\", \"client_secret\": \"$SECRET\", \"redirect_uris\": [\"$REDIRECT_URI\"], \"scope\": \"$SCOPES\", \"grant_types\": [\"authorization_code\",\"refresh_token\"], \"response_types\": [\"code\"], \"token_endpoint_auth_method\": \"client_secret_basic\"}" \ $HOST_URL/clients diff --git a/test/integration_tests/app.py b/test/integration_tests/app.py index e58a728047e9987b01eee542955a7b48e8f38bd9..9fdbc42e85d0c67ec846cd1ae7bb1b9950fd0b8f 100644 --- a/test/integration_tests/app.py +++ b/test/integration_tests/app.py @@ -1,14 +1,11 @@ -from flask import Flask, abort, url_for, redirect, request, session, jsonify -from werkzeug import security -from json import dumps +from flask import Flask, url_for, redirect, jsonify +import json from os import environ -from flask_oauthlib.client import OAuth -import uuid +from flask_dance.consumer import OAuth2ConsumerBlueprint, oauth_authorized, oauth_error BASE_URL=environ["BASE_URL"] ACCESS_TOKEN_URL=environ["ACCESS_TOKEN_URL"] -LOGOUT_URL=environ["LOGOUT_URL"] AUTHORIZE_URL=environ["AUTHORIZE_URL"] USERINFO_URL=environ["USERINFO_URL"] KEY=environ["KEY"] @@ -17,54 +14,57 @@ SECRET=environ["SECRET"] app = Flask(__name__) app.secret_key = 'development' -oauth = OAuth(app) - -sso = oauth.remote_app( - "sso", +sso = OAuth2ConsumerBlueprint( + "sso", __name__, + client_id=KEY, + client_secret=SECRET, base_url=BASE_URL, - request_token_url=None, - access_token_url=ACCESS_TOKEN_URL, - authorize_url=AUTHORIZE_URL, - consumer_key=KEY, - request_token_params={'state': lambda: security.gen_salt(10), "scope": "openid profile email openappstack_roles"}, - consumer_secret=SECRET) + token_url=ACCESS_TOKEN_URL, + authorization_url=AUTHORIZE_URL, + ) + +sso.error = {} -@sso.tokengetter -def get_sso_token(): - return session.get('sso_token') +app.register_blueprint(sso, url_prefix="/login") -@app.route('/') -def login(): - return sso.authorize(url_for('callback', _external=True)) +@app.route("/") +def index(): + if sso.error: + errorpage = jsonify(sso.error) + sso.error = {} + return errorpage + elif not sso.token: + return redirect(url_for("sso.login")) + else: + return jsonify(sso.token) -@app.route('/userinfo') -def get_userinfo(): - if "id_token" in session: - # Use the id_token to retrieve user information from the sso server - resp = sso.request(USERINFO_URL) - return jsonify(resp.data) - abort(403) +@app.route("/userinfo") +def info(): + if sso.authorized: + resp = sso.session.get(USERINFO_URL) + assert resp.ok + return jsonify(json.loads(resp.content.decode())) + else: + return redirect("/") -@app.route('/logout') +@app.route("/logout") def logout(): - del session['sso_token'] - return redirect(url_for('login')) + if sso.authorized: + del sso.token + return redirect("/") + else: + return redirect("/") -@app.route('/callback') -def callback(): - # retrieve all sso related values from the response - resp = sso.authorized_response() - if resp is None: - # Response was not authorized. It doesn't contain any authorized values - if "error" in request.args: - return jsonify(request.args) - abort(403) - # The authorized response contains tokens that we can use to authenticate - # In order for the agent to do that we save the tokens in the user session - session['sso_token'] = (resp['access_token'],None) - if "id_token" in resp: - session['id_token'] = resp['id_token'] - return jsonify(resp) +@oauth_authorized.connect +def save_token(blueprint, token): + # set OAuth token in the token storage backend + blueprint.token = token + +@oauth_error.connect +def save_error(blueprint, **error): + # set error in the token storage backend + blueprint.error = error if __name__ == "__main__": app.run() + diff --git a/test/integration_tests/requirements.txt b/test/integration_tests/requirements.txt index 065d7e389ee6f83b5684639db175728a511c694d..40df930d93c0823aea07df942a1b80231bf38ace 100644 --- a/test/integration_tests/requirements.txt +++ b/test/integration_tests/requirements.txt @@ -1,3 +1,3 @@ Flask -Flask-OAuthlib -Werkzeug==0.16.1 # Due to bug: cannot find url_quote https://stackoverflow.com/questions/60192172/importerror-cannot-import-name-filestorage-from-werkzeug. Can be fixed by migrating from flask-oauthlib to oauthlib. +Flask-Dance +blinker diff --git a/test/integration_tests/test/behave/features/login.feature b/test/integration_tests/test/behave/features/login.feature index 93da3043866eb0cff6066964de101c2ca04c8195..86a132aeecc50f22fb5a758687a1d8d53c93685d 100644 --- a/test/integration_tests/test/behave/features/login.feature +++ b/test/integration_tests/test/behave/features/login.feature @@ -11,7 +11,7 @@ Scenario: Open the oAuth application and Login witha valid user And I enter the "password" in the inputfield "input#password" And I click on the button "input#submit" Then I wait on element "input#password" for 1000ms to not exist - And I expect that the path is "/callback" + And I expect that the path is "/" And I expect that element "body" contains the text "access_token" Scenario: Get OpenID Connect userdata for testuser diff --git a/test/integration_tests/test/behave/features/reject_unauthorized_logins.feature b/test/integration_tests/test/behave/features/reject_unauthorized_logins.feature index c95b995944b5ffd4d59cea4033981a9d789f180e..e4b6150bc599cd68e37ad2c57cd1386b61893c93 100644 --- a/test/integration_tests/test/behave/features/reject_unauthorized_logins.feature +++ b/test/integration_tests/test/behave/features/reject_unauthorized_logins.feature @@ -12,7 +12,7 @@ Scenario: Login with a valid user without access to an application And I click on the button "input#submit" Then I wait on element "input#password" for 1000ms to not exist And I expect that element "input#username" does not exist - And I expect that the path is "/callback" + And I expect that the path is "/" And I expect that element "body" contains the text "error" And I expect that element "body" contains the text "Permission denied" And I expect that element "body" contains the text "missing application permission" @@ -25,7 +25,7 @@ Scenario: Login with an invalid user And I click on the button "input#submit" Then I wait on element "input#password" for 1000ms to not exist And I expect that element "input#username" does not exist - And I expect that the path is "/callback" + And I expect that the path is "/" And I expect that element "body" contains the text "error" And I expect that element "body" contains the text "Login denied" And I expect that element "body" contains the text "Invalid username or password" diff --git a/test/integration_tests/test/behave/features/remember_me.feature b/test/integration_tests/test/behave/features/remember_me.feature index 81ad49e2abfc9cad524e7429239fa5a3767eb8b7..d5b3e53cd76e0f4cd2fa91831913a1333e96b564 100644 --- a/test/integration_tests/test/behave/features/remember_me.feature +++ b/test/integration_tests/test/behave/features/remember_me.feature @@ -14,7 +14,7 @@ Scenario: Login with a valid user and remember session And I click on the button "input#submit" Then I wait on element "input#password" for 1000ms to not exist And I expect that element "input#username" does not exist - And I expect that the path is "/callback" + And I expect that the path is "/" And I expect that element "body" contains the text "access_token" Scenario: Login without providing credentials @@ -25,7 +25,7 @@ Scenario: Login without providing credentials And the element "button#continue" is visible When I click on the element "button#continue" Then I wait on element "button#continue" for 1000ms to not exist - And I expect that the path is "/callback" + And I expect that the path is "/" And I expect that element "body" contains the text "access_token" Scenario: Terminate single sign-on session