From d80444716c64b77f154aed7ec63d2a6af6354232 Mon Sep 17 00:00:00 2001 From: Greg Mundy Date: Wed, 9 Oct 2019 00:07:58 -0400 Subject: [PATCH] feat: add endpoint for retrieving the user detail based on token --- Pipfile | 1 + Pipfile.lock | 17 ++++++++- authserver/api/__init__.py | 1 + authserver/api/home.py | 37 +++++++++++++++++++ authserver/api/oauth2.py | 38 +------------------- authserver/api/static/css/style.css | 8 +++++ authserver/api/static/images/logo.png | Bin 0 -> 2104 bytes authserver/api/static/images/logo_small.png | Bin 0 -> 2151 bytes authserver/api/templates/_formhelpers.html | 20 +++++++++++ authserver/api/templates/login.html | 9 +++-- authserver/api/user.py | 38 +++++++++++++++++++- authserver/app/app.py | 3 +- authserver/db/models/models.py | 1 - 13 files changed, 127 insertions(+), 46 deletions(-) create mode 100644 authserver/api/home.py create mode 100644 authserver/api/static/images/logo.png create mode 100644 authserver/api/static/images/logo_small.png create mode 100644 authserver/api/templates/_formhelpers.html diff --git a/Pipfile b/Pipfile index 0a7205f..dfb9c3c 100644 --- a/Pipfile +++ b/Pipfile @@ -27,6 +27,7 @@ marshmallow-sqlalchemy = "*" bcrypt = "*" flask-cors = "*" webargs = "*" +flask-wtf = "*" [requires] python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock index 216be8e..2adc159 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "b6622fe7fe2111e48ab81391d02a77903273e3da717542e7c0c04e4053dd7dd6" + "sha256": "bbe81dee7c96cac8acca7cbe07c29148bb8655cd528585b300a821109063fa8d" }, "pipfile-spec": 6, "requires": { @@ -175,6 +175,14 @@ "index": "pypi", "version": "==2.4.1" }, + "flask-wtf": { + "hashes": [ + "sha256:5d14d55cfd35f613d99ee7cba0fc3fbbe63ba02f544d349158c14ca15561cc36", + "sha256:d9a9e366b32dcbb98ef17228e76be15702cd2600675668bca23f63a7947fd5ac" + ], + "index": "pypi", + "version": "==0.14.2" + }, "gunicorn": { "hashes": [ "sha256:aa8e0b40b4157b36a5df5e599f45c9c76d6af43845ba3b3b0efe2c70473c2471", @@ -342,6 +350,13 @@ "sha256:e5f4a1f98b52b18a93da705a7458e55afb26f32bff83ff5d19189f92462d65c4" ], "version": "==0.16.0" + }, + "wtforms": { + "hashes": [ + "sha256:0cdbac3e7f6878086c334aa25dc5a33869a3954e9d1e015130d65a69309b3b61", + "sha256:e3ee092c827582c50877cdbd49e9ce6d2c5c1f6561f849b3b068c1b8029626f1" + ], + "version": "==2.2.1" } }, "develop": { diff --git a/authserver/api/__init__.py b/authserver/api/__init__.py index ae9f427..4773fa5 100644 --- a/authserver/api/__init__.py +++ b/authserver/api/__init__.py @@ -4,3 +4,4 @@ from authserver.api.client import client_bp from authserver.api.oauth2 import oauth2_bp from authserver.api.role import role_bp +from authserver.api.home import home_bp diff --git a/authserver/api/home.py b/authserver/api/home.py new file mode 100644 index 0000000..de8040c --- /dev/null +++ b/authserver/api/home.py @@ -0,0 +1,37 @@ +import json +import requests +from flask import Blueprint, render_template, request, redirect, url_for, session +from wtforms import Form, StringField, PasswordField, validators + +from authserver.db import db, User, OAuth2Client + +home_bp = Blueprint('home_ep', __name__, static_folder='static', template_folder='templates', url_prefix='/') + + +class LoginForm(Form): + username = StringField('Username', [validators.DataRequired(), validators.length(min=4, max=40)]) + password = PasswordField('Password', [validators.DataRequired()]) + + +@home_bp.route('/', methods=['GET', 'POST']) +def login(): + form = LoginForm(request.form) + client_id = request.args.get('client_id') + return_to = request.args.get('return_to') + if request.method == 'GET': + if not client_id or not return_to: + return render_template('login.html', form=form) + else: + return render_template('login.html', client_id=client_id, return_to=return_to, form=form) + if form.validate(): + username = form.username.data + password = form.password.data + user = User.query.filter_by(username=username).first() + if user and user.verify_password(password): + session['id'] = user.id + return redirect(return_to) + else: + if not client_id or not return_to: + return redirect(url_for('home_ep.login')) + else: + return redirect(url_for('home_ep.login', client_id=client_id, return_to=return_to)) diff --git a/authserver/api/oauth2.py b/authserver/api/oauth2.py index 5339336..dfd5d96 100644 --- a/authserver/api/oauth2.py +++ b/authserver/api/oauth2.py @@ -29,28 +29,12 @@ def current_user(): return None -@oauth2_bp.route('/login', methods=['GET', 'POST']) -def login(): - client_id = request.args.get('client_id') - return_to = request.args.get('return_to') - if request.method == 'GET': - return render_template('login.html', client_id=client_id, return_to=return_to) - username = request.form.get('username') - password = request.form.get('password') - user = User.query.filter_by(username=username).first() - if user and user.verify_password(password): - session['id'] = user.id - return redirect(return_to) - else: - return redirect(url_for('oauth2_ep.login', client_id=client_id, return_to=return_to)) - - @oauth2_bp.route('/authorize', methods=['GET', 'POST']) def authorize(): user = current_user() if not user: client_id = request.args.get('client_id') - return redirect(url_for('oauth2_ep.login', client_id=client_id, return_to=request.url)) + return redirect(url_for('home_ep.login', client_id=client_id, return_to=request.url)) if request.method == 'GET': try: grant = authorization.validate_consent_request(end_user=user) @@ -91,26 +75,6 @@ def post(self): return authorization.create_endpoint_response('revocation') -class RedirectResource(Resource): - """A User Resource. - - This resource defines an Auth Service user who may have zero or more OAuth 2.0 clients - associated with their accounts. - - """ - - def get(self): - try: - code = request.args.get('code') - except Exception: - code = None - if code: - # foo = requests.get('http://localhost:8000/oauth/token') - # print(foo.status_code) - return {'message': 'ok'}, 200 - - oauth2_api = Api(oauth2_bp) oauth2_api.add_resource(CreateOAuth2TokenResource, '/token') oauth2_api.add_resource(RevokeOAuth2TokenResource, '/revoke') -oauth2_api.add_resource(RedirectResource, '/redirect') diff --git a/authserver/api/static/css/style.css b/authserver/api/static/css/style.css index e69de29..80718dd 100644 --- a/authserver/api/static/css/style.css +++ b/authserver/api/static/css/style.css @@ -0,0 +1,8 @@ +body { + background-color:#333333; + font-family: 'Raleway', sans-serif; +} + +label { + color: #ffffff; +} \ No newline at end of file diff --git a/authserver/api/static/images/logo.png b/authserver/api/static/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..38779e656f9acce55588426c6885481963671163 GIT binary patch literal 2104 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 Rvflm9EzJm~_l#XH{s#^2))N2# literal 0 HcmV?d00001 diff --git a/authserver/api/templates/_formhelpers.html b/authserver/api/templates/_formhelpers.html new file mode 100644 index 0000000..41bd80d --- /dev/null +++ b/authserver/api/templates/_formhelpers.html @@ -0,0 +1,20 @@ +{% macro render_field(field) %} +
+
+
+ +
+
+ {{ field(**kwargs)|safe }} +
+
+
+{% if field.errors %} +
    + {% for error in field.errors %} +
  • {{ error }}
  • + {% endfor %} +
+{% endif %} + +{% endmacro %} \ No newline at end of file diff --git a/authserver/api/templates/login.html b/authserver/api/templates/login.html index fb2547e..66ef722 100644 --- a/authserver/api/templates/login.html +++ b/authserver/api/templates/login.html @@ -1,13 +1,12 @@ {% extends 'base.html' %} {% block content %} -
+{% from "_formhelpers.html" import render_field %} +
- - + {{ render_field(form.username) }}
- - + {{ render_field(form.password) }}

diff --git a/authserver/api/user.py b/authserver/api/user.py index 939f69d..72c1c04 100644 --- a/authserver/api/user.py +++ b/authserver/api/user.py @@ -12,7 +12,7 @@ from webargs import fields, validate from webargs.flaskparser import use_args, use_kwargs -from authserver.db import DataTrust, DataTrustSchema, User, UserSchema, db, OAuth2Client +from authserver.db import DataTrust, DataTrustSchema, User, UserSchema, db, OAuth2Client, OAuth2Token from authserver.utilities import ResponseBody, require_oauth POST_ARGS = { @@ -23,6 +23,41 @@ } +class UserDetailResource(Resource): + """Details of the currently logged in user.""" + + @require_oauth() + def get(self): + try: + token = request.headers.get('authorization').split(' ')[1] + except Exception: + token = None + + if token: + token_details = OAuth2Token.query.filter_by(access_token=token).first() + if token_details: + user_id = token_details.user_id + user = User.query.filter_by(id=user_id).first() + return { + 'id': user.id, + 'username': user.username, + 'firstname': user.firstname, + 'lastname': user.lastname, + 'organization': user.organization, + 'email_address': user.email_address, + 'telephone': user.telephone, + 'active': user.active, + 'data_trust_id': user.data_trust_id, + 'date_created': str(user.date_created), + 'date_last_updated': str(user.date_last_updated) + } + + return { + 'firstname': 'Unknown', + 'lastname': 'Unknown' + } + + class UserResource(Resource): """A User Resource. @@ -177,3 +212,4 @@ def _db_commit(self): user_bp = Blueprint('user_ep', __name__) user_api = Api(user_bp) user_api.add_resource(UserResource, '/users', '/users/') +user_api.add_resource(UserDetailResource, '/user') diff --git a/authserver/app/app.py b/authserver/app/app.py index 59c4785..beeb179 100644 --- a/authserver/app/app.py +++ b/authserver/app/app.py @@ -7,7 +7,7 @@ from flask_sqlalchemy import SQLAlchemy from authserver.api import (client_bp, data_trust_bp, health_api_bp, oauth2_bp, - role_bp, user_bp) + role_bp, user_bp, home_bp) from authserver.config import ConfigurationFactory from authserver.db import db from authserver.utilities import config_oauth @@ -41,6 +41,7 @@ def create_app(environment: str = None): config_oauth(app) CORS(app) migrate = Migrate(app, db) + app.register_blueprint(home_bp) app.register_blueprint(health_api_bp) app.register_blueprint(data_trust_bp) app.register_blueprint(user_bp) diff --git a/authserver/db/models/models.py b/authserver/db/models/models.py index 55a292c..ee476b5 100644 --- a/authserver/db/models/models.py +++ b/authserver/db/models/models.py @@ -93,7 +93,6 @@ def password(self, password: str): def verify_password(self, password: str): try: - print(self.password_hash.encode()) return bcrypt.checkpw(password.encode('utf-8'), self.password_hash.encode('utf-8')) except Exception as e: print(e)