added user and group API and models

This commit is contained in:
2019-04-04 16:05:36 +02:00
parent cfa12717e0
commit 8b7b2f489c
12 changed files with 337 additions and 79 deletions

View File

@@ -19,6 +19,7 @@ coverage = "*"
flask-testing = "*" flask-testing = "*"
flask-pyoidc = "*" flask-pyoidc = "*"
python-jose = "*" python-jose = "*"
flask-jwt-extended = "*"
[dev-packages] [dev-packages]

93
Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "e688fa75b0dde0fed147314666df07f60d9a4abf5fb8646ce3f2b85adf744a36" "sha256": "bde392334efe0c90dfcb180d0fc07549827e819343af266b4fa43be0bc1c0596"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@@ -38,10 +38,10 @@
}, },
"apispec": { "apispec": {
"hashes": [ "hashes": [
"sha256:9300142aa93e0c020e6b223a196cd2103ac4a61bcceea7dba894c0959b72e327", "sha256:6746a57f1395fc201d06b051e43e8a7fdd23138607ebbeabdfcc6477bc3cc956",
"sha256:bcfe21887ba7c6e94c4be00f10564478a0d9109bb8e574aae97442909fd69b31" "sha256:f0a5ddaee255eebeda8e84659028c3523b7801c7023f9364972035d36b23d734"
], ],
"version": "==1.1.0" "version": "==1.1.1"
}, },
"asn1crypto": { "asn1crypto": {
"hashes": [ "hashes": [
@@ -208,6 +208,13 @@
"index": "pypi", "index": "pypi",
"version": "==3.2.4" "version": "==3.2.4"
}, },
"flask-jwt-extended": {
"hashes": [
"sha256:68035dba637fd58c0f0b308e49103cba7f6977aa301d958ddbd7b811a91c6dec"
],
"index": "pypi",
"version": "==3.18.0"
},
"flask-login": { "flask-login": {
"hashes": [ "hashes": [
"sha256:c815c1ac7b3e35e2081685e389a665f2c74d7e077cb93cecabaea352da4752ec" "sha256:c815c1ac7b3e35e2081685e389a665f2c74d7e077cb93cecabaea352da4752ec"
@@ -309,9 +316,9 @@
}, },
"mako": { "mako": {
"hashes": [ "hashes": [
"sha256:4e02fde57bd4abb5ec400181e4c314f56ac3e49ba4fb8b0d50bba18cb27d25ae" "sha256:04092940c0df49b01f43daea4f5adcecd0e50ef6a4b222be5ac003d5d84b2843"
], ],
"version": "==1.0.7" "version": "==1.0.8"
}, },
"markupsafe": { "markupsafe": {
"hashes": [ "hashes": [
@@ -348,10 +355,10 @@
}, },
"marshmallow": { "marshmallow": {
"hashes": [ "hashes": [
"sha256:01412e979b45c003aeb3632718780b15b01566ae0182cc9232434b30f6b85e1b", "sha256:0e497a6447ffaad55578138ca512752de7a48d12f444996ededc3d6bf8a09ca2",
"sha256:8a1a2e13c6a621f4970faf21e5d9b146e451e779d0f334a96eae4fcdef53455f" "sha256:e21a4dea20deb167c723e0ffb13f4cf33bcbbeb8a334e92406a3308cedea2826"
], ],
"version": "==2.19.1" "version": "==2.19.2"
}, },
"oic": { "oic": {
"hashes": [ "hashes": [
@@ -389,36 +396,36 @@
}, },
"pycryptodomex": { "pycryptodomex": {
"hashes": [ "hashes": [
"sha256:0bda549e20db1eb8e29fb365d10acf84b224d813b1131c828fc830b2ce313dcd", "sha256:00e0a7c992756c8d6d63f2cf992276ca62e702629bfcb54f57a192624c22c3d9",
"sha256:1210c0818e5334237b16d99b5785aa0cee815d9997ee258bd5e2936af8e8aa50", "sha256:04689a34f5cf54bd049371c6b16ca44ed4c4202f3f9d58ff25e75016808d076e",
"sha256:2090dc8cd7843eae75bd504b9be86792baa171fc5a758ea3f60188ab67ca95cf", "sha256:088689b91b8dc0df52710e88ef447c6ca087c1f82398b8a41537c2a907f03e6a",
"sha256:22e6784b65dfdd357bf9a8a842db445192b227103e2c3137a28c489c46742135", "sha256:1607d36788ff97b43abb896f7f81a2eed1bdc86e982e953e36b9afeb01942c9c",
"sha256:2edb8c3965a77e3092b5c5c1233ffd32de083f335202013f52d662404191ac79", "sha256:184356bb5039fc24c337dbe485b406425d6f0ee2659d59c313e69703063bf518",
"sha256:310fe269ac870135ff610d272e88dcb594ee58f40ac237a688d7c972cbca43e8", "sha256:21cde61813059437206f745b620068d22cfe5059134eefe858ba7e4b639c410f",
"sha256:456136b7d459f000794a67b23558351c72e21f0c2d4fcaa09fc99dae7844b0ef", "sha256:27a7968b231c1dec2a3d4cc66621c97ee623abda65fc1df392aa19a764f2e803",
"sha256:463e49a9c5f1fa7bd36aff8debae0b5c487868c1fb66704529f2ad7e92f0cc9f", "sha256:42c089411f55663c08e04c5bb8ce51d18002534ae76e8d2e1dd560b3d44dec5c",
"sha256:4a33b2828799ef8be789a462e6645ea6fe2c42b0df03e6763ccbfd1789c453e6", "sha256:42e20e201267726c2d0687886d08f3e97192e0fa456535bb62274dd4b190dc0a",
"sha256:5ff02dff1b03929e6339226b318aa59bd0b5c362f96e3e0eb7f3401d30594ed3", "sha256:47ac3a9e87e559d1bfd60ebb5a81eac66f080ec5593c77d9e33b811bee929c87",
"sha256:6b1db8234b8ee2b30435d9e991389c2eeae4d45e09e471ffe757ba1dfae682bb", "sha256:73f72f7670d2182077fc36420e651ddc6eaa7c8478bf586719f051ecafec533e",
"sha256:6eb67ee02de143cd19e36a52bd3869a9dc53e9184cd6bed5c39ff71dee2f6a45", "sha256:84182dd94089287269a93f4d49226915b3aab386cba587dd8ec68da01bfa3e25",
"sha256:6f42eea5afc7eee29494fdfddc6bb7173953d4197d9200e4f67096c2a24bc21b", "sha256:898c311e6fc8df2c4aba67c554276e5e798f8fb65d1854e26494f3d809314b91",
"sha256:87bc8082e2de2247df7d0b161234f8edb1384294362cc0c8db9324463097578b", "sha256:96352d121e5a750b006e2a357d0a9913f12c6652ca07a5e61c74f63d8be0394f",
"sha256:8df93d34bc0e3a28a27652070164683a07d8a50c628119d6e0f7710f4d01b42f", "sha256:9a18a7078cfdf61e020f0705c6b7188513973af68a26df42b7aaaa395b5e3e2f",
"sha256:989952c39e8fef1c959f0a0f85656e29c41c01162e33a3f5fd8ce71e47262ae9", "sha256:9ecce5ab26737dde0beeff894344d56758e0711f2883619f10625d6a46d8116c",
"sha256:a4a203077e2f312ec8677dde80a5c4e6fe5a82a46173a8edc8da668602a3e073", "sha256:a4bac3507bcefd43e26a92182bcde27c4e37dbe4385b12ba04025a8bf93be5e7",
"sha256:a793c1242dffd39f585ae356344e8935d30f01f6be7d4c62ffc87af376a2f5f9", "sha256:ae044ff318656aaacdfce4d91c369ead871d170d03dfd44ee37e46734f06d24e",
"sha256:b70fe991564e178af02ccf89435a8f9e8d052707a7c4b95bf6027cb785da3175", "sha256:c8186e82d2854738ae7c18c9683bf023aa13d564b5c845a4cfd83063cf44e182",
"sha256:b83594196e3661cb78c97b80a62fbfbba2add459dfd532b58e7a7c62dd06aab4", "sha256:d2bc76e6edbcc8b32465c7e47f045398bb857390aca0782bfc6985919cf9d27c",
"sha256:ba27725237d0a3ea66ec2b6b387259471840908836711a3b215160808dffed0f", "sha256:d89db6097c644f814c9f8995a6a6e4b13dffcc82dcdb6ca06dc8a12545d36319",
"sha256:d1ab8ad1113cdc553ca50c4d5f0142198c317497364c0c70443d69f7ad1c9288", "sha256:deb56cc3b9381b0d5a2060edb6e2ea31351c4b569abf52216d05c91a16214ac3",
"sha256:dce039a8a8a318d7af83cae3fd08d58cefd2120075dfac0ae14d706974040f63", "sha256:e3fe77a6558d21481d4c1290462eb3c9498bad46ce4cd979ff9dd2dd76b5700f",
"sha256:e3213037ea33c85ab705579268cbc8a4433357e9fb99ec7ce9fdcc4d4eec1d50", "sha256:f04359d03506c4a8b0a7d178afda8dca78405bf6f8e8f2080d5206fc7733631a",
"sha256:ec8d8023d31ef72026d46e9fb301ff8759eff5336bcf3d1510836375f53f96a9", "sha256:f066c42bbc84c658d81aaecf36a14b71ef89a0353b17dc5c50f50fd7fb92fc15",
"sha256:ece65730d50aa57a1330d86d81582a2d1587b2ca51cb34f586da8551ddc68fee", "sha256:f26d76c5430efc44b7a3ca4f5f661f3cb314a33bc2a9656f89f44bf73514446c",
"sha256:ed21fc515e224727793e4cc3fb3d00f33f59e3a167d3ad6ac1475ab3b05c2f9e", "sha256:fab723651dfb40d25d8557bc0230133b29cf12835875cd56f77138ee467408cb",
"sha256:eec1132d878153d61a05424f35f089f951bd6095a4f6c60bdd2ef8919d44425e" "sha256:feb7673ee7981cee624c9cdb3446a7d909e99e7296eedef973e766a2bd5128ff"
], ],
"version": "==3.7.3" "version": "==3.8.0"
}, },
"pyjwkest": { "pyjwkest": {
"hashes": [ "hashes": [
@@ -500,10 +507,10 @@
}, },
"sqlalchemy": { "sqlalchemy": {
"hashes": [ "hashes": [
"sha256:781fb7b9d194ed3fc596b8f0dd4623ff160e3e825dd8c15472376a438c19598b" "sha256:d5432832f91d200c3d8b473a266d59442d825f9ea744c467e68c5d9a9479fbce"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.3.1" "version": "==1.3.2"
}, },
"sqlalchemy-migrate": { "sqlalchemy-migrate": {
"hashes": [ "hashes": [
@@ -543,10 +550,10 @@
}, },
"werkzeug": { "werkzeug": {
"hashes": [ "hashes": [
"sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c", "sha256:0a73e8bb2ff2feecfc5d56e6f458f5b99290ef34f565ffb2665801ff7de6af7a",
"sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b" "sha256:7fad9770a8778f9576693f0cc29c7dcc36964df916b83734f4431c0e612a7fbc"
], ],
"version": "==0.14.1" "version": "==0.15.2"
} }
}, },
"develop": {} "develop": {}

View File

@@ -3,8 +3,10 @@
Backend base module Backend base module
""" """
from flask import Flask import jwt
from flask import Flask, jsonify
from flask_httpauth import HTTPTokenAuth, HTTPBasicAuth, MultiAuth from flask_httpauth import HTTPTokenAuth, HTTPBasicAuth, MultiAuth
from flask_jwt_extended import JWTManager, decode_token
from flask_login import LoginManager from flask_login import LoginManager
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
@@ -16,9 +18,32 @@ db = SQLAlchemy(app)
login_manager = LoginManager() login_manager = LoginManager()
login_manager.init_app(app) login_manager.init_app(app)
jwt_auth = HTTPTokenAuth() # flask_jwt_extended: to be used usually by API
jwt_extended = JWTManager(app)
#
jwt_auth = HTTPTokenAuth('Bearer')
@jwt_auth.verify_token
def verify_token(token):
"""This function (and HTTPTokenAuth('Bearer')) has been defined to be used together with MultiAuth. For API calls
solely using JWT authentication, jwt_required of flask_jwt_extended should be used directly."""
app.logger.info(token)
try:
decoded = decode_token(token)
except jwt.exceptions.DecodeError as e:
app.logger.warn("Could not verify token: {}".format(str(e)))
return False
except jwt.exceptions.ExpiredSignatureError as e:
app.logger.warn("Could not verify token: {}".format(str(e)))
return False
app.logger.info(decoded)
return True
basic_auth = HTTPBasicAuth() basic_auth = HTTPBasicAuth()
multi_auth = MultiAuth(basic_auth, jwt_auth) multi_auth = MultiAuth(basic_auth, jwt_auth)
from backend.auth import oidc_auth, auth_bp from backend.auth import oidc_auth, auth_bp
oidc_auth.init_app(app) oidc_auth.init_app(app)
@@ -26,11 +51,13 @@ oidc_auth.init_app(app)
# oidc_multi_auth = MultiAuth(oidc_auth, jwt_auth) <- can't work as OIDCAuthentication not implementing HTTPAuth # oidc_multi_auth = MultiAuth(oidc_auth, jwt_auth) <- can't work as OIDCAuthentication not implementing HTTPAuth
from .serve_frontend import fe_bp from .serve_frontend import fe_bp
from .api import auth_api_bp, api_bp from .api import auth_api_bp, api_v1, api_bp
app.register_blueprint(auth_bp) app.register_blueprint(auth_bp)
app.register_blueprint(auth_api_bp) app.register_blueprint(auth_api_bp)
app.register_blueprint(api_bp) app.register_blueprint(api_bp)
app.register_blueprint(fe_bp) app.register_blueprint(fe_bp)
# Fix flask-restplus by duck typing error handlers
jwt_extended._set_error_handler_callbacks(api_v1)

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from flask import Blueprint from flask import Blueprint
from flask_restplus import Api from flask_restplus import Api, Namespace
api_authorizations = { api_authorizations = {
'apikey': { 'apikey': {
@@ -25,7 +25,15 @@ api_bp = Blueprint('api', __name__, url_prefix='/api')
api_v1 = Api(api_bp, prefix="/v1", version='0.1', title='Vue Test API', api_v1 = Api(api_bp, prefix="/v1", version='0.1', title='Vue Test API',
description='The Vue Test API', doc='/v1/doc/', authorizations=api_authorizations, security='bearerAuth') description='The Vue Test API', doc='/v1/doc/', authorizations=api_authorizations, security='bearerAuth')
api_user = Namespace('user', description="User management namespace", authorizations=api_authorizations)
api_v1.add_namespace(api_user)
auth_api_bp = Blueprint('auth_api', __name__, url_prefix='/api/auth') auth_api_bp = Blueprint('auth_api', __name__, url_prefix='/api/auth')
user_api_bp = Blueprint('user_api', __name__, url_prefix='/api/user')
group_api_bp = Blueprint('group_api', __name__, url_prefix='/api/group')
from .example_api import * from .example_api import *
from .auth_api import * from .auth_api import *
from .user_api import *
from .group_api import *

View File

@@ -5,10 +5,14 @@ For example: listing of available auth providers or registration of users.
Login through API does not start a new session, but instead returns JWT. Login through API does not start a new session, but instead returns JWT.
""" """
import base64
import json
import flask import flask
from datetime import datetime, timedelta from datetime import datetime, timedelta
import jwt import jwt
from flask import request, jsonify, current_app, url_for from flask import request, jsonify, current_app, url_for, Response, session, redirect, make_response
from flask_jwt_extended import create_access_token, create_refresh_token, jwt_refresh_token_required, get_jwt_identity
from functools import wraps from functools import wraps
from random import randint from random import randint
@@ -22,14 +26,6 @@ from backend.auth import AUTH_PROVIDERS, oidc_auth
from backend.models.user_model import User, Group from backend.models.user_model import User, Group
def create_jwt(user: User, validity_min=30):
return jwt.encode({
'sub': user.email,
'iat': datetime.utcnow(),
'exp': datetime.utcnow() + timedelta(minutes=validity_min)},
current_app.config['SECRET_KEY'])
@auth_api_bp.route('/providers', methods=('GET',)) @auth_api_bp.route('/providers', methods=('GET',))
def get_auth_providers(): def get_auth_providers():
providers = dict() providers = dict()
@@ -65,8 +61,11 @@ def login():
if not user: if not user:
return jsonify({'message': 'Invalid credentials', 'authenticated': False}), 401 return jsonify({'message': 'Invalid credentials', 'authenticated': False}), 401
token = create_jwt(user) token = {
return jsonify({'token': token.decode('UTF-8')}) 'access_token': create_access_token(identity=user.email, fresh=True),
'refresh_token': create_refresh_token(identity=user.email)
}
return jsonify(token), 200
def check_and_create_groups(groups: Iterable[str]): def check_and_create_groups(groups: Iterable[str]):
@@ -112,17 +111,38 @@ def create_or_retrieve_user_from_userinfo(userinfo):
@auth_api_bp.route('/oidc', methods=['GET']) @auth_api_bp.route('/oidc', methods=['GET'])
@auth_api_bp.route('/oidc/<redirect_url>', methods=['GET'])
@oidc_auth.oidc_auth() @oidc_auth.oidc_auth()
def oidc(): def oidc(redirect_url=None):
user = create_or_retrieve_user_from_userinfo(flask.session['userinfo']) user = create_or_retrieve_user_from_userinfo(flask.session['userinfo'])
#return jsonify(user.to_dict())
return user.toJSON()
if user is None: if user is None:
return "Could not authenticate: could not find or create user.", 401 return "Could not authenticate: could not find or create user.", 401
if current_app.config.get("AUTH_RETURN_EXTERNAL_JWT", False): if current_app.config.get("AUTH_RETURN_EXTERNAL_JWT", False):
token = jwt.encode(flask.session['id_token'], current_app.config['SECRET_KEY']) token = jwt.encode(flask.session['id_token'], current_app.config['SECRET_KEY'])
else: else:
token = create_jwt(user) token = json.dumps({
return token 'access_token': create_access_token(identity=user.email, fresh=True),
'refresh_token': create_refresh_token(identity=user.email)
})
if redirect_url is None:
redirect_url = request.headers.get("Referer")
if redirect_url is None:
redirect_url = request.args.get('redirect_url')
if redirect_url is None:
redirect_url = "/"
app.logger.info("Token: {}".format(token))
response = make_response(redirect(redirect_url))
response.set_cookie('tokens', base64.b64encode(token.encode('utf-8')))
return response
@app.route('/refresh', methods=['POST'])
@jwt_refresh_token_required
def refresh():
"""Refresh token endpoint. This will generate a new access token from
the refresh token, but will mark that access token as non-fresh,
as we do not actually verify a password in this endpoint."""
current_user = get_jwt_identity()
new_token = create_access_token(identity=current_user, fresh=False)
ret = {'access_token': new_token}
return jsonify(ret), 200

40
api/group_api.py Normal file
View File

@@ -0,0 +1,40 @@
# Copyright (c) 2019. Tobias Kurze
"""
This module provides functions related to authentication through the API.
For example: listing of available auth providers or registration of users.
Login through API does not start a new session, but instead returns JWT.
"""
import flask
from datetime import datetime, timedelta
import jwt
from flask import request, jsonify, current_app, url_for
from flask_jwt_extended import jwt_required
from functools import wraps
from random import randint
from flask_login import logout_user, login_user
from typing import Iterable
from werkzeug.routing import BuildError
from backend import db, app
from backend.api import auth_api_bp, group_api_bp
from backend.auth import AUTH_PROVIDERS, oidc_auth
from backend.models.user_model import User, Group
@group_api_bp.route('/<id>', methods=['GET'])
@jwt_required
def get_group():
user = create_or_retrieve_user_from_userinfo(flask.session['userinfo'])
return jsonify(user.to_dict())
if user is None:
return "Could not authenticate: could not find or create user.", 401
if current_app.config.get("AUTH_RETURN_EXTERNAL_JWT", False):
token = jwt.encode(flask.session['id_token'], current_app.config['SECRET_KEY'])
else:
token = create_jwt(user)
return token

58
api/user_api.py Normal file
View File

@@ -0,0 +1,58 @@
# Copyright (c) 2019. Tobias Kurze
"""
This module provides functions related to authentication through the API.
For example: listing of available auth providers or registration of users.
Login through API does not start a new session, but instead returns JWT.
"""
import flask
import jwt
from flask import request, jsonify, current_app, url_for
from flask_jwt_extended import get_jwt_identity, jwt_optional, jwt_required
from flask_restplus import Resource, fields
from backend import db, app, jwt_auth
from backend.api import user_api_bp, api_bp, api_user
from backend.auth import oidc_auth
from backend.models.user_model import User, Group
user = api_user.model('User', {
'id': fields.String(required=True, description='The user\'s identifier'),
'first_name': fields.String(required=True, description='The user\'s first name'),
})
@api_user.route('/')
class UserList(Resource):
"""
This is a test class.
"""
#@jwt_auth.login_required
@jwt_required
@api_user.doc('users')
@api_user.marshal_list_with(user)
def get(self):
"""
just a test!
:return: Hello: World
"""
current_user = get_jwt_identity()
app.logger.info(current_user)
return User.get_all()
@api_user.route('/<id>')
@api_user.param('id', 'The user identifier')
@api_user.response(404, 'User not found')
class UserResource(Resource):
@jwt_auth.login_required
@api_user.doc('get_user')
@api_user.marshal_with(user)
def get(self, id):
"""Fetch a user given its identifier"""
user = User.get_by_id(id)
if user is not None:
return user
api_user.abort(404)
# api_user.add_resource(UserResource, '/')

BIN
app.db

Binary file not shown.

View File

@@ -4,7 +4,7 @@ OIDC login auth module
""" """
import flask import flask
from flask import jsonify from flask import jsonify, redirect, url_for
from flask_login import login_user from flask_login import login_user
from flask_pyoidc.flask_pyoidc import OIDCAuthentication from flask_pyoidc.flask_pyoidc import OIDCAuthentication
from flask_pyoidc.user_session import UserSession from flask_pyoidc.user_session import UserSession
@@ -15,17 +15,28 @@ from . import auth_bp
from .oidc_config import PROVIDER_NAME, OIDC_PROVIDERS from .oidc_config import PROVIDER_NAME, OIDC_PROVIDERS
OIDCAuthentication.oidc_auth_orig = OIDCAuthentication.oidc_auth
OIDCAuthentication.oidc_logout_orig = OIDCAuthentication.oidc_logout
def oidc_auth_default_provider(self): def oidc_auth_default_provider(self):
"""monkey patch oidc_auth"""
return self.oidc_auth_orig(PROVIDER_NAME) return self.oidc_auth_orig(PROVIDER_NAME)
OIDCAuthentication.oidc_auth_orig = OIDCAuthentication.oidc_auth def oidc_logout_default_provider(self):
"""monkey patch oidc_logout"""
return self.oidc_logout_orig(PROVIDER_NAME)
OIDCAuthentication.oidc_auth = oidc_auth_default_provider OIDCAuthentication.oidc_auth = oidc_auth_default_provider
OIDCAuthentication.oidc_logout = oidc_logout_default_provider
oidc_auth = OIDCAuthentication(OIDC_PROVIDERS) oidc_auth = OIDCAuthentication(OIDC_PROVIDERS)
def create_or_retrieve_user_from_userinfo(userinfo): def create_or_retrieve_user_from_userinfo(userinfo):
"""Updates and returns ar creates a user from userinfo (part of OIDC token)."""
try: try:
email = userinfo["email"] email = userinfo["email"]
except KeyError: except KeyError:
@@ -34,6 +45,7 @@ def create_or_retrieve_user_from_userinfo(userinfo):
if user is not None: if user is not None:
app.logger.info("user found") app.logger.info("user found")
#TODO: update user!
return user return user
user = User(email=email, first_name=userinfo.get("given_name", ""), user = User(email=email, first_name=userinfo.get("given_name", ""),
@@ -57,3 +69,9 @@ def oidc():
return jsonify(id_token=user_session.id_token, return jsonify(id_token=user_session.id_token,
access_token=flask.session['access_token'], access_token=flask.session['access_token'],
userinfo=user_session.userinfo) userinfo=user_session.userinfo)
@auth_bp.route('/oidc_logout', methods=['GET'])
def oidc_logout():
oidc_auth.oidc_logout()
return redirect('/')

42
auth/utils.py Normal file
View File

@@ -0,0 +1,42 @@
import flask_jwt_extended
from flask_jwt_extended import jwt_optional, get_jwt_identity
from functools import wraps
from backend import jwt_auth
from backend.models.user_model import User
def requires_permission_level(permission_level):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if flask_jwt_extended.verify_jwt_in_request():
current_user_id = get_jwt_identity()
user = User.get_by_identifier(current_user_id)
if user is not None:
if user.has_permission(permission_level):
#for g in user.groups:
# if g.permissions
#TODO
pass
else:
pass
# return FALSE
#if not session.get('email'):
# return redirect(url_for('users.login'))
#user = User.find_by_email(session['email'])
#elif not user.allowed(access_level):
# return redirect(url_for('users.profile', message="You do not have access to that page. Sorry!"))
return f(*args, **kwargs)
return decorated_function
return decorator
def require_jwt():
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
return jwt_auth.login_required(jwt_optional(f(*args, **kwargs)))
return decorated_function
return decorator

View File

@@ -64,9 +64,11 @@ class Config():
#ASSETS_DEBUG = True #ASSETS_DEBUG = True
JWT_SECRET = "abcxyz" #JWT_SECRET = "abcxyz"
JWT_ALGORITHM = "HS256" #JWT_ALGORITHM = "HS256"
JWT_EXP_DELTA_SECONDS = 5 * 60 #JWT_EXP_DELTA_SECONDS = 5 * 60
JWT_SECRET_KEY = "abcxyz"
AUTH_RETURN_EXTERNAL_JWT = False AUTH_RETURN_EXTERNAL_JWT = False

View File

@@ -46,23 +46,38 @@ user_group_table = db.Table('user_group',
primary_key=True)) primary_key=True))
# This is the association table for the many-to-many relationship between
# groups and permissions.
group_permission_table = db.Table('group_permission',
db.Column('group_id', db.Integer,
db.ForeignKey('group.id',
onupdate="CASCADE",
ondelete="CASCADE"),
primary_key=True),
db.Column('permission_id', db.Integer,
db.ForeignKey('permission.id',
onupdate="CASCADE",
ondelete="CASCADE"),
primary_key=True))
class User(UserMixin, db.Model): class User(UserMixin, db.Model):
""" """
Example user model representation. Example user model representation.
""" """
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
social_id = db.Column(db.String(64), nullable=True, unique=True) social_id = db.Column(db.Unicode(63), nullable=True, unique=True)
nickname = db.Column(db.String(64), index=True, unique=True) nickname = db.Column(db.Unicode(63), index=True, unique=True)
first_name = db.Column(db.String(64), index=True, nullable=True) first_name = db.Column(db.Unicode(63), index=True, nullable=True)
last_name = db.Column(db.String(64), index=True, nullable=True) last_name = db.Column(db.Unicode(63), index=True, nullable=True)
email = db.Column(db.String(120), nullable=False, index=True, unique=True) email = db.Column(db.String(120), nullable=False, index=True, unique=True)
lang = db.Column(db.String(16), index=False, unique=False) lang = db.Column(db.Unicode(32), index=False, unique=False)
timezone = db.Column(db.String(32), index=False, unique=False) timezone = db.Column(db.Unicode(63), index=False, unique=False)
posts = db.relationship('Post', backref='author', lazy='dynamic') posts = db.relationship('Post', backref='author', lazy='dynamic')
example_data_item = db.relationship('ExampleDataItem', backref='owner') example_data_item = db.relationship('ExampleDataItem', backref='owner')
example_data_item_id = db.Column(db.ForeignKey(ExampleDataItem.id)) example_data_item_id = db.Column(db.ForeignKey(ExampleDataItem.id))
about_me = db.Column(db.String(140)) about_me = db.Column(db.Unicode(255))
role = db.Column(db.String(64)) role = db.Column(db.Unicode(63))
groups = db.relationship('Group', secondary=user_group_table, back_populates='users') groups = db.relationship('Group', secondary=user_group_table, back_populates='users')
password = db.Column(db.String(255), nullable=True) password = db.Column(db.String(255), nullable=True)
registered_on = db.Column(db.DateTime, nullable=False, default=datetime.utcnow()) registered_on = db.Column(db.DateTime, nullable=False, default=datetime.utcnow())
@@ -110,6 +125,16 @@ class User(UserMixin, db.Model):
User.email == identifier, User.email == identifier,
User.id == identifier)).first() User.id == identifier)).first()
@staticmethod
@login_manager.user_loader
def get_by_id(identifier):
"""
Find user by ID.
:param identifier:
:return:
"""
return User.query.filter(User.id == identifier).first()
@staticmethod @staticmethod
def get_all(): def get_all():
""" """
@@ -396,8 +421,9 @@ class Group(db.Model):
super(Group, self).__init__(**kwargs) super(Group, self).__init__(**kwargs)
id = db.Column(db.Integer, autoincrement=True, primary_key=True) id = db.Column(db.Integer, autoincrement=True, primary_key=True)
name = db.Column(db.Unicode(64), unique=True, nullable=False) name = db.Column(db.Unicode(63), unique=True, nullable=False)
users = db.relationship('User', secondary=user_group_table, back_populates='groups') users = db.relationship('User', secondary=user_group_table, back_populates='groups')
permissions = db.relationship('Permission', secondary=group_permission_table, back_populates='groups')
@staticmethod @staticmethod
def get_by_name(name): def get_by_name(name):
@@ -417,3 +443,12 @@ class Group(db.Model):
def toJSON(self): def toJSON(self):
return json.dumps(self.to_dict(), default=lambda o: o.__dict__, return json.dumps(self.to_dict(), default=lambda o: o.__dict__,
sort_keys=True, indent=4) sort_keys=True, indent=4)
class Permission(db.Model):
"""Table containing permissions associated with groups."""
id = db.Column(db.Integer, autoincrement=True, primary_key=True)
name = db.Column(db.Unicode(63), unique=True, nullable=False)
description = db.Column(db.Unicode(255))
groups = db.relationship(Group, secondary=group_permission_table,
back_populates='permissions')