230 lines
9.7 KiB
Python
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,
|
|
)
|