security conditional
This commit is contained in:
parent
517bdc32fe
commit
b1b7b4017b
@ -25,3 +25,4 @@ MAKEDIRS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
APP_BASE_URL = "http://localhost:5000"
|
APP_BASE_URL = "http://localhost:5000"
|
||||||
|
SECURITY_ENABLED = True
|
||||||
|
@ -27,7 +27,7 @@ from tww.lib import solve_query, resolve_timezone, dt_tz_translation, time_ago
|
|||||||
from whooshalchemy import IndexService
|
from whooshalchemy import IndexService
|
||||||
|
|
||||||
from config import SQLALCHEMY_DATABASE_URI, MAKEDIRS, DATABASE_FILE, SEARCH_INDEX_PATH, STATIC_DATA_DIR, MEDIA_DIR, \
|
from config import SQLALCHEMY_DATABASE_URI, MAKEDIRS, DATABASE_FILE, SEARCH_INDEX_PATH, STATIC_DATA_DIR, MEDIA_DIR, \
|
||||||
basepath
|
basepath, SECURITY_ENABLED
|
||||||
from oshipka.util.strings import camel_case_to_snake_case
|
from oshipka.util.strings import camel_case_to_snake_case
|
||||||
from vm_gen.vm_gen import order_from_process_order
|
from vm_gen.vm_gen import order_from_process_order
|
||||||
|
|
||||||
@ -61,11 +61,6 @@ class Datable(object):
|
|||||||
updated_dt = db.Column(db.UnicodeText())
|
updated_dt = db.Column(db.UnicodeText())
|
||||||
|
|
||||||
|
|
||||||
roles_users = db.Table('roles_users',
|
|
||||||
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
|
|
||||||
db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))
|
|
||||||
|
|
||||||
|
|
||||||
class ModelJsonEncoder(JSONEncoder):
|
class ModelJsonEncoder(JSONEncoder):
|
||||||
def default(self, o):
|
def default(self, o):
|
||||||
if isinstance(o, datetime.datetime):
|
if isinstance(o, datetime.datetime):
|
||||||
@ -168,30 +163,36 @@ class ModelController(ModelJsonEncoder):
|
|||||||
return fields
|
return fields
|
||||||
|
|
||||||
|
|
||||||
class Role(db.Model, ModelController, RoleMixin):
|
if SECURITY_ENABLED:
|
||||||
name = db.Column(db.Unicode, unique=True)
|
roles_users = db.Table('roles_users',
|
||||||
description = db.Column(db.Unicode)
|
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
|
||||||
|
db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))
|
||||||
|
|
||||||
|
|
||||||
class User(db.Model, ModelController, UserMixin):
|
class Role(db.Model, ModelController, RoleMixin):
|
||||||
username = db.Column(db.Unicode, unique=True)
|
name = db.Column(db.Unicode, unique=True)
|
||||||
token = db.Column(db.Unicode)
|
description = db.Column(db.Unicode)
|
||||||
|
|
||||||
active = db.Column(db.Boolean(), default=True)
|
|
||||||
|
|
||||||
timezone = db.Column(db.String, default='UTC')
|
|
||||||
tz_offset_seconds = db.Column(db.Integer, default=0)
|
|
||||||
locale = db.Column(db.String(4), default='en')
|
|
||||||
|
|
||||||
name = db.Column(db.Unicode)
|
|
||||||
profile_image_url = db.Column(db.String)
|
|
||||||
|
|
||||||
roles = db.relationship('Role', secondary=roles_users,
|
|
||||||
backref=db.backref('users', lazy='dynamic'))
|
|
||||||
|
|
||||||
|
|
||||||
security = Security()
|
class User(db.Model, ModelController, UserMixin):
|
||||||
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
|
username = db.Column(db.Unicode, unique=True)
|
||||||
|
token = db.Column(db.Unicode)
|
||||||
|
|
||||||
|
active = db.Column(db.Boolean(), default=True)
|
||||||
|
|
||||||
|
timezone = db.Column(db.String, default='UTC')
|
||||||
|
tz_offset_seconds = db.Column(db.Integer, default=0)
|
||||||
|
locale = db.Column(db.String(4), default='en')
|
||||||
|
|
||||||
|
name = db.Column(db.Unicode)
|
||||||
|
profile_image_url = db.Column(db.String)
|
||||||
|
|
||||||
|
roles = db.relationship('Role', secondary=roles_users,
|
||||||
|
backref=db.backref('users', lazy='dynamic'))
|
||||||
|
|
||||||
|
|
||||||
|
security = Security()
|
||||||
|
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
|
||||||
|
|
||||||
|
|
||||||
def register_filters(app):
|
def register_filters(app):
|
||||||
@ -337,7 +338,8 @@ def init_db(app):
|
|||||||
db.init_app(app)
|
db.init_app(app)
|
||||||
csrf.init_app(app)
|
csrf.init_app(app)
|
||||||
migrate.init_app(app, db)
|
migrate.init_app(app, db)
|
||||||
security.init_app(app, user_datastore)
|
if SECURITY_ENABLED:
|
||||||
|
security.init_app(app, user_datastore)
|
||||||
_init_translations(app)
|
_init_translations(app)
|
||||||
|
|
||||||
register_filters(app)
|
register_filters(app)
|
||||||
@ -370,7 +372,7 @@ def populate_static(app):
|
|||||||
sensitive = import_module("sensitive")
|
sensitive = import_module("sensitive")
|
||||||
ordered_model_names = order_from_process_order('csv', STATIC_DATA_DIR)
|
ordered_model_names = order_from_process_order('csv', STATIC_DATA_DIR)
|
||||||
for model_name in ordered_model_names:
|
for model_name in ordered_model_names:
|
||||||
if model_name in ['User', 'Role']:
|
if SECURITY_ENABLED and model_name in ['User', 'Role']:
|
||||||
model = eval(model_name)
|
model = eval(model_name)
|
||||||
else:
|
else:
|
||||||
model = getattr(models, model_name)
|
model = getattr(models, model_name)
|
||||||
@ -385,18 +387,8 @@ def populate_static(app):
|
|||||||
row_updates[key] = sensitive_value
|
row_updates[key] = sensitive_value
|
||||||
if row_updates:
|
if row_updates:
|
||||||
row.update(row_updates)
|
row.update(row_updates)
|
||||||
if model_name == "User":
|
instance = create_model(model, row)
|
||||||
if 'role_names' in row:
|
db.session.add(instance)
|
||||||
role_names = row.pop('role_names')
|
|
||||||
else:
|
|
||||||
role_names = ""
|
|
||||||
user = user_datastore.create_user(**row)
|
|
||||||
for role_name in role_names.split(';'):
|
|
||||||
role = Role.query.filter_by(name=role_name).first()
|
|
||||||
user_datastore.add_role_to_user(user, role)
|
|
||||||
else:
|
|
||||||
instance = create_model(model, row)
|
|
||||||
db.session.add(instance)
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
print("Finished populating")
|
print("Finished populating")
|
||||||
|
|
||||||
|
@ -4,10 +4,9 @@ import requests
|
|||||||
from flask import send_from_directory, redirect, request, url_for, session, jsonify
|
from flask import send_from_directory, redirect, request, url_for, session, jsonify
|
||||||
from flask_security import login_user
|
from flask_security import login_user
|
||||||
|
|
||||||
from oshipka.persistance import User, db
|
|
||||||
from oshipka.util.strings import random_string_generator
|
from oshipka.util.strings import random_string_generator
|
||||||
from oshipka.webapp import oshipka_bp, app
|
from oshipka.webapp import oshipka_bp, app
|
||||||
from config import MEDIA_DIR, APP_BASE_URL
|
from config import MEDIA_DIR, APP_BASE_URL, SECURITY_ENABLED
|
||||||
from sensitive import SSO_CLIENT_ID, SSO_CLIENT_SECRET
|
from sensitive import SSO_CLIENT_ID, SSO_CLIENT_SECRET
|
||||||
|
|
||||||
|
|
||||||
@ -16,70 +15,71 @@ from sensitive import SSO_CLIENT_ID, SSO_CLIENT_SECRET
|
|||||||
def get_media(filepath):
|
def get_media(filepath):
|
||||||
return send_from_directory(MEDIA_DIR, filepath)
|
return send_from_directory(MEDIA_DIR, filepath)
|
||||||
|
|
||||||
|
if SECURITY_ENABLED:
|
||||||
SSO_BASE_URL = 'http://sso.localhost:5008'
|
from oshipka.persistance import User, db
|
||||||
SSO_AUTH_URL = '/oidc/auth'
|
SSO_BASE_URL = 'http://sso.localhost:5008'
|
||||||
SSO_TOKEN_URL = '/oidc/token'
|
SSO_AUTH_URL = '/oidc/auth'
|
||||||
SSO_USERINFO_URL = "/endpoints/userinfo"
|
SSO_TOKEN_URL = '/oidc/token'
|
||||||
|
SSO_USERINFO_URL = "/endpoints/userinfo"
|
||||||
|
|
||||||
|
|
||||||
@app.route('/login')
|
@app.route('/login')
|
||||||
@oshipka_bp.route('/sso')
|
@oshipka_bp.route('/sso')
|
||||||
def sso():
|
def sso():
|
||||||
callback_url = APP_BASE_URL + url_for('oshipka_bp.oidc_callback')
|
callback_url = APP_BASE_URL + url_for('oshipka_bp.oidc_callback')
|
||||||
state = request.referrer or url_for('home')
|
state = request.referrer or url_for('home')
|
||||||
session['oidc_state'] = state
|
session['oidc_state'] = state
|
||||||
params = urllib.parse.urlencode({
|
params = urllib.parse.urlencode({
|
||||||
'redirect_uri': callback_url,
|
'redirect_uri': callback_url,
|
||||||
'client_id': SSO_CLIENT_ID,
|
|
||||||
'state': state,
|
|
||||||
'scope': 'openid',
|
|
||||||
'response_type': 'code',
|
|
||||||
'nonce': random_string_generator(),
|
|
||||||
})
|
|
||||||
return redirect(SSO_BASE_URL + SSO_AUTH_URL + '?' + params)
|
|
||||||
|
|
||||||
|
|
||||||
@oshipka_bp.route('/oidc/callback')
|
|
||||||
def oidc_callback():
|
|
||||||
error = request.args.get('error')
|
|
||||||
if error:
|
|
||||||
return jsonify({"error": "from auth server: {}".format(error)}), 400
|
|
||||||
state = request.args.get('state')
|
|
||||||
session_state = session['oidc_state']
|
|
||||||
if state != session_state:
|
|
||||||
return jsonify({"error": "state is different from session state"}), 400
|
|
||||||
code = request.args.get('code')
|
|
||||||
response = requests.post(
|
|
||||||
SSO_BASE_URL + SSO_TOKEN_URL,
|
|
||||||
data={
|
|
||||||
'code': code,
|
|
||||||
'client_id': SSO_CLIENT_ID,
|
'client_id': SSO_CLIENT_ID,
|
||||||
'client_secret': SSO_CLIENT_SECRET,
|
'state': state,
|
||||||
'grant_type': 'authorization_code'
|
'scope': 'openid',
|
||||||
},
|
'response_type': 'code',
|
||||||
)
|
'nonce': random_string_generator(),
|
||||||
if response.status_code == 200:
|
})
|
||||||
response_json = response.json()
|
return redirect(SSO_BASE_URL + SSO_AUTH_URL + '?' + params)
|
||||||
access_token = response_json.get('access_token')
|
|
||||||
response = requests.get(
|
|
||||||
SSO_BASE_URL + SSO_USERINFO_URL,
|
@oshipka_bp.route('/oidc/callback')
|
||||||
headers={
|
def oidc_callback():
|
||||||
'Authorization': "Bearer {}".format(access_token)
|
error = request.args.get('error')
|
||||||
|
if error:
|
||||||
|
return jsonify({"error": "from auth server: {}".format(error)}), 400
|
||||||
|
state = request.args.get('state')
|
||||||
|
session_state = session['oidc_state']
|
||||||
|
if state != session_state:
|
||||||
|
return jsonify({"error": "state is different from session state"}), 400
|
||||||
|
code = request.args.get('code')
|
||||||
|
response = requests.post(
|
||||||
|
SSO_BASE_URL + SSO_TOKEN_URL,
|
||||||
|
data={
|
||||||
|
'code': code,
|
||||||
|
'client_id': SSO_CLIENT_ID,
|
||||||
|
'client_secret': SSO_CLIENT_SECRET,
|
||||||
|
'grant_type': 'authorization_code'
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
response_json = response.json()
|
response_json = response.json()
|
||||||
username = response_json.get('user', {}).get('username')
|
access_token = response_json.get('access_token')
|
||||||
user = User.query.filter_by(username=username).first()
|
response = requests.get(
|
||||||
redirect_uri = url_for('home')
|
SSO_BASE_URL + SSO_USERINFO_URL,
|
||||||
if not user:
|
headers={
|
||||||
user = User(username=username)
|
'Authorization': "Bearer {}".format(access_token)
|
||||||
db.session.add(user)
|
},
|
||||||
db.session.commit()
|
)
|
||||||
login_user(user)
|
if response.status_code == 200:
|
||||||
if 'oidc_state' in session:
|
response_json = response.json()
|
||||||
redirect_uri = session['oidc_state']
|
username = response_json.get('user', {}).get('username')
|
||||||
del session['oidc_state']
|
user = User.query.filter_by(username=username).first()
|
||||||
return redirect(redirect_uri)
|
redirect_uri = url_for('home')
|
||||||
return response.json()
|
if not user:
|
||||||
|
user = User(username=username)
|
||||||
|
db.session.add(user)
|
||||||
|
db.session.commit()
|
||||||
|
login_user(user)
|
||||||
|
if 'oidc_state' in session:
|
||||||
|
redirect_uri = session['oidc_state']
|
||||||
|
del session['oidc_state']
|
||||||
|
return redirect(redirect_uri)
|
||||||
|
return response.json()
|
||||||
|
@ -257,7 +257,7 @@ def create_view(model_view, view_context_kwargs, is_login_required=False, the_ro
|
|||||||
|
|
||||||
|
|
||||||
class ModelView(object):
|
class ModelView(object):
|
||||||
def __init__(self, app, model, model_acl):
|
def __init__(self, app, model, model_acl=None):
|
||||||
self.app = app
|
self.app = app
|
||||||
self.model = model
|
self.model = model
|
||||||
self.model_acl = model_acl
|
self.model_acl = model_acl
|
||||||
|
@ -6,7 +6,11 @@ from sqlalchemy_utils import ChoiceType
|
|||||||
from werkzeug.security import generate_password_hash
|
from werkzeug.security import generate_password_hash
|
||||||
[%- endif %]
|
[%- endif %]
|
||||||
|
|
||||||
class [[ name ]](db.Model, ModelController[% for inherit in interits %], [[ inherit ]][% endfor %]):
|
[% for inherit_import in inherits_imports %]
|
||||||
|
[[ inherit_import ]]
|
||||||
|
[% endfor %]
|
||||||
|
|
||||||
|
class [[ name ]](db.Model, ModelController[% for inherit in inherits %], [[ inherit ]][% endfor %]):
|
||||||
[%- include "_model_choice_header_py" %]
|
[%- include "_model_choice_header_py" %]
|
||||||
[%- include "_model_searchable_header_py" %]
|
[%- include "_model_searchable_header_py" %]
|
||||||
|
|
||||||
|
@ -6,10 +6,20 @@ Edit the hooks in webapp/routes/[[ name|camel_to_snake ]]_hooks.py instead
|
|||||||
|
|
||||||
from oshipka.webapp import app
|
from oshipka.webapp import app
|
||||||
from oshipka.webapp.views import ModelView
|
from oshipka.webapp.views import ModelView
|
||||||
from webapp.models import [[ name ]], [[ name ]]Acl
|
|
||||||
from webapp.routes.[[ name|camel_to_snake ]]_hooks import *
|
from webapp.routes.[[ name|camel_to_snake ]]_hooks import *
|
||||||
|
[%- if name == "User" %]
|
||||||
|
from webapp.models import [[ name ]]
|
||||||
|
|
||||||
|
|
||||||
|
[[ name|camel_to_snake ]] = ModelView(app, [[name]])
|
||||||
|
[%- else %]
|
||||||
|
from webapp.models import [[ name ]], [[ name ]]Acl
|
||||||
|
|
||||||
|
|
||||||
[[ name|camel_to_snake ]] = ModelView(app, [[name]], [[ name ]]Acl)
|
[[ name|camel_to_snake ]] = ModelView(app, [[name]], [[ name ]]Acl)
|
||||||
|
[%- endif %]
|
||||||
|
|
||||||
|
|
||||||
[% for verb, verb_values in _verbs.items() %]
|
[% for verb, verb_values in _verbs.items() %]
|
||||||
[[ name|camel_to_snake ]].register_verb(view_context=[[ verb ]]_view_context,
|
[[ name|camel_to_snake ]].register_verb(view_context=[[ verb ]]_view_context,
|
||||||
verb="[[ verb ]]",
|
verb="[[ verb ]]",
|
||||||
|
@ -163,24 +163,28 @@ def process_navigation(view_models):
|
|||||||
def process_model(view_model):
|
def process_model(view_model):
|
||||||
template = env.get_template('model_py')
|
template = env.get_template('model_py')
|
||||||
view_model['acls'] = {}
|
view_model['acls'] = {}
|
||||||
for verb, acl in view_model['_verbs'].items():
|
|
||||||
view_model['acls'][verb] = {'authn': acl['is_login_required'], 'authz': acl['the_roles_required']}
|
|
||||||
model = autopep8.fix_code(template.render(**view_model), options=pep_options)
|
|
||||||
_model_name = view_model.get('name')
|
_model_name = view_model.get('name')
|
||||||
|
|
||||||
template_acl = env.get_template('model_acl_py')
|
|
||||||
model_acl = autopep8.fix_code(template_acl.render(**view_model), options=pep_options)
|
|
||||||
|
|
||||||
model_camel = _model_name.split('.yaml')[0]
|
model_camel = _model_name.split('.yaml')[0]
|
||||||
model_snake = camel_case_to_snake_case(_model_name.split('.yaml')[0])
|
model_snake = camel_case_to_snake_case(_model_name.split('.yaml')[0])
|
||||||
|
|
||||||
|
for verb, acl in view_model['_verbs'].items():
|
||||||
|
view_model['acls'][verb] = {'authn': acl['is_login_required'], 'authz': acl['the_roles_required']}
|
||||||
|
model = autopep8.fix_code(template.render(**view_model), options=pep_options)
|
||||||
with open(os.path.join(MODELS_PATH, "_{}.py".format(model_snake)), 'w+') as f:
|
with open(os.path.join(MODELS_PATH, "_{}.py".format(model_snake)), 'w+') as f:
|
||||||
f.write(model)
|
f.write(model)
|
||||||
with open(os.path.join(MODELS_PATH, "_{}_acl.py".format(model_snake)), 'w+') as f:
|
|
||||||
f.write(model_acl)
|
if model_camel not in ['User']:
|
||||||
|
template_acl = env.get_template('model_acl_py')
|
||||||
|
model_acl = autopep8.fix_code(template_acl.render(**view_model), options=pep_options)
|
||||||
|
with open(os.path.join(MODELS_PATH, "_{}_acl.py".format(model_snake)), 'w+') as f:
|
||||||
|
f.write(model_acl)
|
||||||
|
|
||||||
public_model = os.path.join(MODELS_PATH, "{}.py".format(model_snake))
|
public_model = os.path.join(MODELS_PATH, "{}.py".format(model_snake))
|
||||||
if not os.path.exists(public_model):
|
if not os.path.exists(public_model):
|
||||||
with open(public_model, 'w+') as f:
|
with open(public_model, 'w+') as f:
|
||||||
f.write("from webapp.models._{}_acl import {}Acl\n".format(model_snake, model_camel))
|
if model_camel not in ['User']:
|
||||||
|
f.write("from webapp.models._{}_acl import {}Acl\n".format(model_snake, model_camel))
|
||||||
f.write("from webapp.models._{} import {}\n".format(model_snake, model_camel))
|
f.write("from webapp.models._{} import {}\n".format(model_snake, model_camel))
|
||||||
|
|
||||||
|
|
||||||
@ -229,8 +233,12 @@ def main():
|
|||||||
process_routes(view_model)
|
process_routes(view_model)
|
||||||
view_model_name = view_model.get('name', '')
|
view_model_name = view_model.get('name', '')
|
||||||
model_snake_name = camel_case_to_snake_case(view_model_name)
|
model_snake_name = camel_case_to_snake_case(view_model_name)
|
||||||
all_model_imports.append('from webapp.models.{s} import {c}, {c}Acl'.format(
|
if view_model_name not in ['User']:
|
||||||
s=model_snake_name, c=view_model_name))
|
all_model_imports.append('from webapp.models.{s} import {c}, {c}Acl'.format(
|
||||||
|
s=model_snake_name, c=view_model_name))
|
||||||
|
else:
|
||||||
|
all_model_imports.append('from webapp.models.{s} import {c}'.format(
|
||||||
|
s=model_snake_name, c=view_model_name))
|
||||||
process_html_templates(view_model)
|
process_html_templates(view_model)
|
||||||
all_route_imports.append('from webapp.routes.{} import *'.format(
|
all_route_imports.append('from webapp.routes.{} import *'.format(
|
||||||
model_snake_name
|
model_snake_name
|
||||||
|
Loading…
Reference in New Issue
Block a user