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.)
This commit is contained in:
parent
8993748934
commit
7e3daaab3d
|
@ -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),
|
|
@ -4,11 +4,7 @@ users:
|
|||
admin:
|
||||
email: admin@spiffworkflow.org
|
||||
password: admin
|
||||
dan:
|
||||
email: dan@spiffworkflow.org
|
||||
password: password
|
||||
|
||||
|
||||
preferred_username: Admin
|
||||
|
||||
groups:
|
||||
admin:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {}
|
||||
return {}
|
||||
|
|
|
@ -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;
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 9.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.9 KiB |
|
@ -2,94 +2,27 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Login Form</title>
|
||||
<link rel="stylesheet" type="text/css" href="css/style.css">
|
||||
<style>
|
||||
body{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color:white;
|
||||
font-family: 'Arial';
|
||||
}
|
||||
.error {
|
||||
margin: 20px auto;
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
.login{
|
||||
width: 382px;
|
||||
overflow: hidden;
|
||||
margin: 20px auto;
|
||||
padding: 80px;
|
||||
background: #000;
|
||||
border-radius: 15px ;
|
||||
|
||||
}
|
||||
h2{
|
||||
text-align: center;
|
||||
color: #277582;
|
||||
padding: 20px;
|
||||
}
|
||||
label{
|
||||
color: #fff;
|
||||
font-size: 17px;
|
||||
}
|
||||
#Uname{
|
||||
width: 300px;
|
||||
height: 30px;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
#Pass{
|
||||
width: 300px;
|
||||
height: 30px;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
padding-left: 8px;
|
||||
|
||||
}
|
||||
#log{
|
||||
width: 300px;
|
||||
height: 30px;
|
||||
border: none;
|
||||
border-radius: 17px;
|
||||
padding-left: 7px;
|
||||
color: blue;
|
||||
|
||||
|
||||
}
|
||||
span{
|
||||
color: white;
|
||||
font-size: 17px;
|
||||
}
|
||||
a{
|
||||
float: right;
|
||||
background-color: grey;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('openid.static', filename='login.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<h2>Login to SpiffWorkflow</h2><br>
|
||||
<header>
|
||||
<img class="logo_small" src="{{ url_for('openid.static', filename='logo_small.png') }}"/>
|
||||
</header>
|
||||
|
||||
<h2>Login</h2>
|
||||
<div class="error">{{error_message}}</div>
|
||||
<div class="login">
|
||||
<form id="login" method="post" action="{{ url_for('openid.form_submit') }}">
|
||||
<label><b>User Name
|
||||
</b>
|
||||
</label>
|
||||
<input type="text" name="Uname" id="Uname" placeholder="Username">
|
||||
<input type="text" class="cds--text-input" name="Uname" id="Uname" placeholder="Username">
|
||||
<br><br>
|
||||
<label><b>Password
|
||||
</b>
|
||||
</label>
|
||||
<input type="Password" name="Pass" id="Pass" placeholder="Password">
|
||||
<input type="Password" class="cds--text-input" name="Pass" id="Pass" placeholder="Password">
|
||||
<br><br>
|
||||
<input type="hidden" name="state" value="{{state}}"/>
|
||||
<input type="hidden" name="response_type" value="{{response_type}}"/>
|
||||
<input type="hidden" name="client_id" value="{{client_id}}"/>
|
||||
<input type="hidden" name="scope" value="{{scope}}"/>
|
||||
<input type="hidden" name="redirect_uri" value="{{redirect_uri}}"/>
|
||||
<input type="submit" name="log" id="log" value="Log In">
|
||||
<input type="submit" name="log" class="cds--btn cds--btn--primary" value="Log In">
|
||||
<br><br>
|
||||
<!-- should maybe add this stuff in eventually, but this is just for testing.
|
||||
<input type="checkbox" id="check">
|
||||
|
|
|
@ -6,7 +6,7 @@ from typing import Union
|
|||
|
||||
import jwt
|
||||
import yaml
|
||||
from flask import current_app
|
||||
from flask import current_app, scaffold
|
||||
from flask import g
|
||||
from flask import request
|
||||
from flask_bpmn.api.api_error import ApiError
|
||||
|
@ -253,6 +253,7 @@ class AuthorizationService:
|
|||
or api_view_function.__name__ in authentication_exclusion_list
|
||||
or api_view_function.__name__ in swagger_functions
|
||||
or module == openid_blueprint
|
||||
or module == scaffold # don't check permissions for static assets
|
||||
):
|
||||
return True
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ class TestFaskOpenId(BaseTest):
|
|||
app: Flask,
|
||||
client: FlaskClient,
|
||||
with_db_and_bpmn_file_cleanup: None,) -> None:
|
||||
response = client.get("/openid/well-known/openid-configuration")
|
||||
response = client.get("/openid/.well-known/openid-configuration")
|
||||
discovered_urls = response.json
|
||||
assert "http://localhost/openid" == discovered_urls["issuer"]
|
||||
assert "http://localhost/openid/auth" == discovered_urls["authorization_endpoint"]
|
||||
|
@ -57,23 +57,3 @@ class TestFaskOpenId(BaseTest):
|
|||
response = client.post("/openid/token", data=data, headers=headers)
|
||||
assert response
|
||||
|
||||
def test_refresh_token_endpoint(self,
|
||||
app: Flask,
|
||||
client: FlaskClient,
|
||||
with_db_and_bpmn_file_cleanup: None,) -> None:
|
||||
pass
|
||||
# Handle a refresh with the following
|
||||
# data provided
|
||||
# "grant_type": "refresh_token",
|
||||
# "refresh_token": refresh_token,
|
||||
# "client_id": open_id_client_id,
|
||||
# "client_secret": open_id_client_secret_key,
|
||||
# Return an json response with:
|
||||
# id - (this users' id)
|
||||
|
||||
def test_logout(self,
|
||||
app: Flask,
|
||||
client: FlaskClient,
|
||||
with_db_and_bpmn_file_cleanup: None,) -> None:
|
||||
pass
|
||||
# It should be possible to logout and be redirected back.
|
||||
|
|
Loading…
Reference in New Issue