From 8ade069dd1e6d11ab760a5ba914e5c585780478e Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 1 Dec 2022 11:42:36 -0500 Subject: [PATCH] A little cleanup of the ui Don't check authorization on static assets Do not require unique username on user table (uniqueness check is on the service and service id composite.) --- .../{ff1c1628337c_.py => 3f049fa4d8ac_.py} | 9 +- .../config/permissions/development.yml | 6 +- src/spiffworkflow_backend/models/user.py | 2 +- .../openid_blueprint/openid_blueprint.py | 79 +++++++----- .../routes/openid_blueprint/static/login.css | 112 ++++++++++++++++++ .../routes/openid_blueprint/static/logo.png | Bin 0 -> 10138 bytes .../openid_blueprint/static/logo_small.png | Bin 0 -> 5000 bytes .../openid_blueprint/templates/login.html | 85 ++----------- .../services/authorization_service.py | 3 +- .../integration/test_openid_blueprint.py | 22 +--- 10 files changed, 177 insertions(+), 141 deletions(-) rename migrations/versions/{ff1c1628337c_.py => 3f049fa4d8ac_.py} (99%) create mode 100644 src/spiffworkflow_backend/routes/openid_blueprint/static/login.css create mode 100644 src/spiffworkflow_backend/routes/openid_blueprint/static/logo.png create mode 100644 src/spiffworkflow_backend/routes/openid_blueprint/static/logo_small.png diff --git a/migrations/versions/ff1c1628337c_.py b/migrations/versions/3f049fa4d8ac_.py similarity index 99% rename from migrations/versions/ff1c1628337c_.py rename to migrations/versions/3f049fa4d8ac_.py index d8da6d3c..dc8675a6 100644 --- a/migrations/versions/ff1c1628337c_.py +++ b/migrations/versions/3f049fa4d8ac_.py @@ -1,8 +1,8 @@ """empty message -Revision ID: ff1c1628337c +Revision ID: 3f049fa4d8ac Revises: -Create Date: 2022-11-28 15:08:52.014254 +Create Date: 2022-11-30 16:49:54.805372 """ from alembic import op @@ -10,7 +10,7 @@ import sqlalchemy as sa # revision identifiers, used by Alembic. -revision = 'ff1c1628337c' +revision = '3f049fa4d8ac' down_revision = None branch_labels = None depends_on = None @@ -79,8 +79,7 @@ def upgrade(): sa.Column('email', sa.String(length=255), nullable=True), sa.PrimaryKeyConstraint('id'), sa.UniqueConstraint('service', 'service_id', name='service_key'), - sa.UniqueConstraint('uid'), - sa.UniqueConstraint('username') + sa.UniqueConstraint('uid') ) op.create_table('message_correlation_property', sa.Column('id', sa.Integer(), nullable=False), diff --git a/src/spiffworkflow_backend/config/permissions/development.yml b/src/spiffworkflow_backend/config/permissions/development.yml index 1acace14..d92daeaf 100644 --- a/src/spiffworkflow_backend/config/permissions/development.yml +++ b/src/spiffworkflow_backend/config/permissions/development.yml @@ -4,11 +4,7 @@ users: admin: email: admin@spiffworkflow.org password: admin - dan: - email: dan@spiffworkflow.org - password: password - - + preferred_username: Admin groups: admin: diff --git a/src/spiffworkflow_backend/models/user.py b/src/spiffworkflow_backend/models/user.py index eb88e5de..ed6d10e6 100644 --- a/src/spiffworkflow_backend/models/user.py +++ b/src/spiffworkflow_backend/models/user.py @@ -30,7 +30,7 @@ class UserModel(SpiffworkflowBaseDBModel): __table_args__ = (db.UniqueConstraint("service", "service_id", name="service_key"),) id = db.Column(db.Integer, primary_key=True) - username = db.Column(db.String(255), nullable=False, unique=True) + username = db.Column(db.String(255), nullable=False, unique=False) # server and service id must be unique, not username. uid = db.Column(db.String(50), unique=True) service = db.Column(db.String(50), nullable=False, unique=False) service_id = db.Column(db.String(255), nullable=False, unique=False) diff --git a/src/spiffworkflow_backend/routes/openid_blueprint/openid_blueprint.py b/src/spiffworkflow_backend/routes/openid_blueprint/openid_blueprint.py index b16ba46a..5c96d62b 100644 --- a/src/spiffworkflow_backend/routes/openid_blueprint/openid_blueprint.py +++ b/src/spiffworkflow_backend/routes/openid_blueprint/openid_blueprint.py @@ -1,23 +1,22 @@ """ Provides the bare minimum endpoints required by SpiffWorkflow to -handle openid authentication -- definitely not a production system. -This is just here to make local development, testing, and -demonstration easier. +handle openid authentication -- definitely not a production ready system. +This is just here to make local development, testing, and demonstration easier. """ import base64 import time -import urllib from urllib.parse import urlencode import jwt import yaml -from flask import Blueprint, render_template, request, current_app, redirect, url_for, g +from flask import Blueprint, render_template, request, current_app, redirect, url_for openid_blueprint = Blueprint( "openid", __name__, template_folder="templates", static_folder="static" ) -MY_SECRET_CODE = ":this_should_be_some_crazy_code_different_all_the_time" +MY_SECRET_CODE = ":this_is_not_secure_do_not_use_in_production" + @openid_blueprint.route("/.well-known/openid-configuration", methods=["GET"]) def well_known(): @@ -26,8 +25,9 @@ def well_known(): host_url = request.host_url.strip('/') return { "issuer": f"{host_url}/openid", - "authorization_endpoint": f"{host_url}{url_for('openid.auth')}", + "authorization_endpoint": f"{host_url}{url_for('openid.auth')}", "token_endpoint": f"{host_url}{url_for('openid.token')}", + "end_session_endpoint": f"{host_url}{url_for('openid.end_session')}", } @@ -40,23 +40,22 @@ def auth(): client_id=request.args.get('client_id'), scope=request.args.get('scope'), redirect_uri=request.args.get('redirect_uri'), - error_message=request.args.get('error_message')) + error_message=request.args.get('error_message', '')) @openid_blueprint.route("/form_submit", methods=["POST"]) def form_submit(): - users = get_users() if request.values['Uname'] in users and request.values['Pass'] == users[request.values['Uname']]["password"]: # Redirect back to the end user with some detailed information state = request.values.get('state') data = { - "state": base64.b64encode(bytes(state, 'UTF-8')), + "state": state, "code": request.values['Uname'] + MY_SECRET_CODE, "session_state": "" } url = request.values.get('redirect_uri') + "?" + urlencode(data) - return redirect(url, code=200) + return redirect(url) else: return render_template('login.html', state=request.values.get('state'), @@ -64,18 +63,19 @@ def form_submit(): client_id=request.values.get('client_id'), scope=request.values.get('scope'), redirect_uri=request.values.get('redirect_uri'), - error_message="Login failed. Please try agian.") + error_message="Login failed. Please try again.") @openid_blueprint.route("/token", methods=["POST"]) def token(): """Url that will return a valid token, given the super secret sauce""" - grant_type=request.values.get('grant_type') - code=request.values.get('code') - redirect_uri=request.values.get('redirect_uri') + grant_type = request.values.get('grant_type') + code = request.values.get('code') + redirect_uri = request.values.get('redirect_uri') """We just stuffed the user name on the front of the code, so grab it.""" user_name, secret_hash = code.split(":") + user_details = get_users()[user_name] """Get authentication from headers.""" authorization = request.headers.get('Authorization') @@ -83,35 +83,50 @@ def token(): authorization = base64.b64decode(authorization).decode('utf-8') client_id, client_secret = authorization.split(":") - base_url = url_for(openid_blueprint) - access_token = "..." - refresh_token = "..." + base_url = request.host_url + "openid" + access_token = user_name + ":" + "always_good_demo_access_token" + refresh_token = user_name + ":" + "always_good_demo_refresh_token" + id_token = jwt.encode({ "iss": base_url, "aud": [client_id, "account"], "iat": time.time(), - "exp": time.time() + 86400 # Exprire after a day. - }) - - {'exp': 1669757386, 'iat': 1669755586, 'auth_time': 1669753049, 'jti': '0ec2cc09-3498-4921-a021-c3b98427df70', - 'iss': 'http://localhost:7002/realms/spiffworkflow', 'aud': 'spiffworkflow-backend', - 'sub': '99e7e4ea-d4ae-4944-bd31-873dac7b004c', 'typ': 'ID', 'azp': 'spiffworkflow-backend', - 'session_state': '8751d5f6-2c60-4205-9be0-2b1005f5891e', 'at_hash': 'O5i-VLus6sryR0grMS2Y4w', 'acr': '0', - 'sid': '8751d5f6-2c60-4205-9be0-2b1005f5891e', 'email_verified': False, 'preferred_username': 'dan'} - + "exp": time.time() + 86400, # Expire after a day. + "sub": user_name, + "preferred_username": user_details.get('preferred_username', user_name) + }, + client_secret, + algorithm="HS256", + ) response = { "access_token": id_token, "id_token": id_token, + "refresh_token": id_token } + return response + + +@openid_blueprint.route("/end_session", methods=["GET"]) +def end_session(): + redirect_url = request.args.get('post_logout_redirect_uri') + id_token_hint = request.args.get('id_token_hint') + return redirect(redirect_url) + @openid_blueprint.route("/refresh", methods=["POST"]) def refresh(): pass + +permission_cache = None + + def get_users(): - with open(current_app.config["PERMISSIONS_FILE_FULLPATH"]) as file: - permission_configs = yaml.safe_load(file) - if "users" in permission_configs: - return permission_configs["users"] + global permission_cache + if not permission_cache: + with open(current_app.config["PERMISSIONS_FILE_FULLPATH"]) as file: + permission_cache = yaml.safe_load(file) + if "users" in permission_cache: + return permission_cache["users"] else: - return {} \ No newline at end of file + return {} diff --git a/src/spiffworkflow_backend/routes/openid_blueprint/static/login.css b/src/spiffworkflow_backend/routes/openid_blueprint/static/login.css new file mode 100644 index 00000000..15b093f6 --- /dev/null +++ b/src/spiffworkflow_backend/routes/openid_blueprint/static/login.css @@ -0,0 +1,112 @@ + body{ + margin: 0; + padding: 0; + background-color:white; + font-family: 'Arial'; + } + header { + width: 100%; + background-color: black; + } + .logo_small { + padding: 5px 20px; + } + .error { + margin: 20px auto; + color: red; + font-weight: bold; + text-align: center; + } + .login{ + width: 400px; + overflow: hidden; + margin: 20px auto; + padding: 50px; + background: #fff; + border-radius: 15px ; + } + h2{ + text-align: center; + color: #277582; + padding: 20px; + } + label{ + color: #fff; + width: 200px; + display: inline-block; + } + #log { + width: 100px; + height: 50px; + border: none; + padding-left: 7px; + background-color:#202020; + color: #DDD; + text-align: left; + } + .cds--btn--primary { + background-color: #0f62fe; + border: 1px solid #0000; + color: #fff; + } + .cds--btn { + align-items: center; + border: 0; + border-radius: 0; + box-sizing: border-box; + cursor: pointer; + display: inline-flex; + flex-shrink: 0; + font-family: inherit; + font-size: 100%; + font-size: .875rem; + font-weight: 400; + justify-content: space-between; + letter-spacing: .16px; + line-height: 1.28572; + margin: 0; + max-width: 20rem; + min-height: 3rem; + outline: none; + padding: calc(0.875rem - 3px) 63px calc(0.875rem - 3px) 15px; + position: relative; + text-align: left; + text-decoration: none; + transition: background 70ms cubic-bezier(0, 0, .38, .9), box-shadow 70ms cubic-bezier(0, 0, .38, .9), border-color 70ms cubic-bezier(0, 0, .38, .9), outline 70ms cubic-bezier(0, 0, .38, .9); + vertical-align: initial; + vertical-align: top; + width: max-content; + } + .cds--btn:hover { + background-color: #0145c5; + } + .cds--btn:focus { + background-color: #01369a; + } + + .cds--text-input { + background-color: #eee; + border: none; + border-bottom: 1px solid #8d8d8d; + color: #161616; + font-family: inherit; + font-size: .875rem; + font-weight: 400; + height: 2.5rem; + letter-spacing: .16px; + line-height: 1.28572; + outline: 2px solid #0000; + outline-offset: -2px; + padding: 0 1rem; + transition: background-color 70ms cubic-bezier(.2,0,.38,.9),outline 70ms cubic-bezier(.2,0,.38,.9); + width: 100%; + } + + span{ + color: white; + font-size: 17px; + } + a{ + float: right; + background-color: grey; + } diff --git a/src/spiffworkflow_backend/routes/openid_blueprint/static/logo.png b/src/spiffworkflow_backend/routes/openid_blueprint/static/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..4cffb07fdf112e035c669c06bd5cbdc9355f43a5 GIT binary patch literal 10138 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 AE&u=k literal 0 HcmV?d00001 diff --git a/src/spiffworkflow_backend/routes/openid_blueprint/static/logo_small.png b/src/spiffworkflow_backend/routes/openid_blueprint/static/logo_small.png new file mode 100644 index 0000000000000000000000000000000000000000..d0ad4499a9f187c829438db52f20575863e526c1 GIT binary patch literal 5000 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 S|CE~m0000 Login Form - - + -

Login to SpiffWorkflow


+
+ +
+ +

Login

{{error_message}}