Skip to content
Snippets Groups Projects
Commit 4831eaef authored by Maarten de Waard's avatar Maarten de Waard :angel:
Browse files

Merge branch '40-replace-flask-oauth-with-oauthlib-in-test-login-logout' into 'master'

Resolve "Replace Flask-oauth with oauthlib in test/login_logout"

Closes #40

See merge request openappstack/single-sign-on!34
parents 85647be3 d5933843
No related branches found
No related tags found
1 merge request!34Resolve "Replace Flask-oauth with oauthlib in test/login_logout"
Pipeline #4798 passed with stages
in 4 minutes and 12 seconds
......@@ -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
......
......@@ -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"
......
......@@ -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
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()
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
......@@ -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
......
......@@ -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"
......@@ -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
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment