From 88af29a5bb971e7c1fd3f3bc71d9024453ab7240 Mon Sep 17 00:00:00 2001
From: Mark <mark@openappstack.net>
Date: Mon, 29 Jun 2020 17:27:38 +0200
Subject: [PATCH] Change from flask-oauthlib to Flask-dance

---
 test/integration_tests/app.py                 | 95 ++++++++++---------
 test/integration_tests/requirements.txt       |  4 +-
 .../test/behave/features/login.feature        |  2 +-
 .../reject_unauthorized_logins.feature        |  4 +-
 .../test/behave/features/remember_me.feature  |  4 +-
 5 files changed, 56 insertions(+), 53 deletions(-)

diff --git a/test/integration_tests/app.py b/test/integration_tests/app.py
index e58a728..71019c0 100644
--- a/test/integration_tests/app.py
+++ b/test/integration_tests/app.py
@@ -1,9 +1,7 @@
-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"]
@@ -17,54 +15,59 @@ 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:
+        descr = sso.error["error_description"]
+        error = sso.error["error"]
+        sso.error = {}
+        return "error: {} - {}".format(error, descr)
+    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")
+        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("/")
+
+@oauth_authorized.connect
+def redirect_to_next_url(blueprint, token):
+    # set OAuth token in the token storage backend
+    blueprint.token = token
 
-@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_error.connect
+def asdasd(blueprint, **error):
+    # set OAuth token 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 065d7e3..40df930 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 93da304..86a132a 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 c95b995..e4b6150 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 81ad49e..d5b3e53 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
-- 
GitLab