oshipka/oshipka/webapp/default_routes.py

230 lines
9.7 KiB
Python

import urllib
from collections import defaultdict
import requests
from flask import send_from_directory, redirect, request, url_for, session, jsonify, abort, render_template
from flask_login import login_required, current_user
from sqlalchemy import and_
from oshipka.util.strings import random_string_generator
from oshipka.webapp import oshipka_bp, app
from config import MEDIA_DIR, APP_BASE_URL, SECURITY_ENABLED, SSO_BASE_URL, SSO_CLIENT_ID
from sensitive import SSO_CLIENT_SECRET
from oshipka.webapp.views import MODEL_VIEWS, has_permission
@oshipka_bp.route('/media/<model_name>/<int:instance_id>/<column>/<path:filepath>')
def get_media(model_name, instance_id, column, filepath):
model_view = MODEL_VIEWS.get(model_name)
if not model_view:
abort(404)
model = model_view.model
instance = model.query.filter_by(id=instance_id).first()
if not instance:
abort(404)
verb = "{}.read".format(column)
if not has_permission(model_name, verb, action_prefix='column', object_prefix="columns"): # TODO: , instance):
abort(401)
return send_from_directory(MEDIA_DIR, filepath)
if SECURITY_ENABLED:
from flask_security import login_user, roles_required
from oshipka.persistance import User, Role, db
from webapp.models import Permission
app.config['SSO_BASE_URL'] = SSO_BASE_URL
SSO_AUTH_URL = '/oidc/auth'
SSO_TOKEN_URL = '/oidc/token'
SSO_USERINFO_URL = "/endpoints/userinfo"
@app.route('/login')
@oshipka_bp.route('/sso')
def sso():
callback_url = APP_BASE_URL + url_for('oshipka_bp.oidc_callback')
url_to_redicrect_back = request.referrer or url_for('home')
state = url_to_redicrect_back + "|" + random_string_generator()
session['oidc_state'] = state
params = urllib.parse.urlencode({
'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.get('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'
},
verify=False,
)
if response.status_code == 200:
response_json = response.json()
access_token = response_json.get('access_token')
response = requests.get(
SSO_BASE_URL + SSO_USERINFO_URL,
headers={
'Authorization': "Bearer {}".format(access_token)
},
verify=False,
)
if response.status_code == 200:
response_json = response.json()
username = response_json.get('user', {}).get('username')
user = User.query.filter_by(username=username).first()
redirect_uri = url_for('home')
if not user:
user = User(username=username, token=access_token)
db.session.add(user)
db.session.commit()
login_user(user)
if 'oidc_state' in session:
redirect_uri = session['oidc_state'].split('|')[0] or url_for('home')
del session['oidc_state']
return redirect(redirect_uri)
return response.text
def get_permissions_table(query):
permissions = Permission.query.filter(query).all()
permissions_table = dict(
selected_roles=list(),
selected_users=list(),
actions=set(),
table=defaultdict(dict),
)
for p in permissions:
if p.subject in ['user']:
permissions_table['selected_users'].append(p.subject_id)
if p.subject in ['role']:
permissions_table['selected_roles'].append(p.subject_id)
s = "{}_{}".format(p.subject, p.subject_id) if p.subject_id else p.subject
permissions_table["table"][s][p.action] = p.is_allowed
permissions_table["actions"].add(p.action)
permissions_table["actions"] = sorted(permissions_table["actions"])
return permissions_table
@oshipka_bp.route('/permissions', methods=['GET', 'POST'])
@login_required
@roles_required(*['admin'])
def get_permissions():
admin_permissions_table = get_permissions_table(Permission.object == 'admin.permissions')
model_tables = {}
for mv in MODEL_VIEWS:
model_tables[mv] = get_permissions_table(and_(
Permission.action == 'permission.get',
Permission.object == 'models.{}'.format(mv)
))
return render_template('permissions_admin.html',
admin_permissions_table=admin_permissions_table,
model_tables=model_tables,
model_views=[k for k in MODEL_VIEWS.keys() if k not in ['permission']],
users=User.query.all(),
roles=Role.query.all(),
)
def get_model_tables(model_name, instance_id=None):
model_view = MODEL_VIEWS.get(model_name)
if not model_view:
abort(404)
if instance_id:
model_query = and_(Permission.object == 'models.{}'.format(model_name),
Permission.object_id == instance_id)
column_query = and_(Permission.object == 'columns.{}'.format(model_name),
Permission.object_id == instance_id)
else:
model_query = Permission.object == 'models.{}'.format(model_name)
column_query = Permission.object == 'columns.{}'.format(model_name)
model_permissions_table = get_permissions_table(model_query)
column_permissions_table = get_permissions_table(column_query)
return model_permissions_table, column_permissions_table
@app.route("/permissions/<model_name>", methods=['GET', 'POST'])
@login_required
def model_permissions(model_name):
if request.method == "POST":
data = {k: len(request.form.getlist(k)) - 1 for k in request.form.keys()}
for k, v in data.items():
if k in ['csrf_token']:
continue
if k.startswith('users-'):
continue
if k.startswith('roles-'):
continue
_, subject, action = k.split('-')
sub_split, subject_id = subject.split('_'), None
if len(sub_split) == 2:
subject, subject_id = sub_split
if action.startswith('model.') or action.startswith('permission.'):
obj = "models.{}"
elif action.startswith('column.'):
obj = "columns.{}"
else:
obj = "{}"
abort(403)
if not subject_id:
existing_permission = Permission.query.filter_by(
subject=subject, action=action, object=obj.format(model_name)).first()
else:
existing_permission = Permission.query.filter_by(
subject=subject, subject_id=subject_id, action=action, object=obj.format(model_name)).first()
if not existing_permission:
existing_permission = Permission(
subject=subject, subject_id=subject_id, action=action, object=obj.format(model_name),
)
existing_permission.is_allowed = v
db.session.add(existing_permission)
db.session.commit()
return redirect(request.referrer)
model_permissions_table, column_permissions_table = get_model_tables(model_name)
return render_template("permissions_model.html",
model_permissions_table=model_permissions_table,
column_permissions_table=column_permissions_table,
model_name=model_name,
users=User.query.all(),
roles=Role.query.all(),
)
@app.route("/permissions/<model_name>/<int:instance_id>", methods=['GET', 'POST'])
@login_required
def instance_permissions(model_name, instance_id):
model_view = MODEL_VIEWS.get(model_name)
if not model_view:
abort(404)
instance = model_view.model.query.filter_by(id=instance_id).first()
if not instance:
abort(404)
model_permissions_table, column_permissions_table = get_model_tables(model_name, instance_id)
return render_template("permissions_instance.html",
model_permissions_table=model_permissions_table,
column_permissions_table=column_permissions_table,
model_name=model_name,
instance=instance,
)