-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add permission manager #158
base: master
Are you sure you want to change the base?
Changes from all commits
c828e77
1869264
d10c83e
1eda963
e06f07c
82578fd
814b974
6b51c55
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import flask | ||
blueprint = flask.Blueprint('perm_mgr', __name__, | ||
template_folder='templates', static_folder='static') | ||
|
||
import ruddock.modules.perm_mgr.routes |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
import flask | ||
import sqlalchemy | ||
from ruddock.resources import Permissions | ||
|
||
def fetch_office_permissions(): | ||
"""Returns the office name and permissions of all active offices. | ||
Returns the permissions of all offices that have permissions, | ||
plus the permissions of all active offices (which may be none).""" | ||
query = sqlalchemy.text(""" | ||
SELECT GROUP_CONCAT(permission_id) AS permissions, | ||
office_name, office_id, office_order | ||
FROM office_permissions | ||
NATURAL RIGHT JOIN offices | ||
WHERE is_active=TRUE | ||
GROUP BY office_id | ||
|
||
UNION | ||
|
||
SELECT GROUP_CONCAT(permission_id) AS permissions, | ||
office_name, office_id, office_order | ||
FROM office_permissions | ||
NATURAL JOIN offices | ||
GROUP BY office_id | ||
|
||
ORDER BY | ||
CASE | ||
WHEN permissions IS NULL | ||
THEN 1 | ||
ELSE 0 | ||
END, | ||
permissions ASC, | ||
office_order ASC | ||
""") | ||
|
||
return flask.g.db.execute(query).fetchall() | ||
|
||
def fetch_specific_office_permissions(office_id): | ||
"""Returns the office name and permissions of the specified office. | ||
May be none.""" | ||
if type(office_id) is not int: | ||
raise TypeError("Must pass office id number to fetch office permissions.") | ||
query = sqlalchemy.text(""" | ||
SELECT GROUP_CONCAT(permission_id) AS permissions, | ||
office_name, office_id | ||
FROM office_permissions | ||
NATURAL RIGHT JOIN offices | ||
WHERE office_id = :id | ||
GROUP BY office_id | ||
""") | ||
return flask.g.db.execute(query, id=office_id).fetchone() | ||
|
||
def delete_office_permission(office_id, perm_id): | ||
"""Deletes the specified permission from the office.""" | ||
if type(perm_id) is str or type(perm_id) is unicode: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. man, py3 makes this much nicer; maybe we should consider that someday far in the future There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. later later |
||
perm_id = int(perm_id) | ||
if type(office_id) is not int or type(perm_id) is not int: | ||
raise TypeError("Must pass office id number and permission id to fetch office permissions.") | ||
query = sqlalchemy.text(""" | ||
DELETE FROM office_permissions | ||
WHERE office_id = :o_id | ||
AND permission_id = :p_id | ||
""") | ||
return flask.g.db.execute(query, o_id=office_id, p_id=perm_id) | ||
|
||
def insert_office_permission(office_id, perm_id): | ||
"""Inserts the specified permission for the office.""" | ||
if type(perm_id) is str or type(perm_id) is unicode: | ||
perm_id = int(perm_id) | ||
if type(office_id) is not int or type(perm_id) is not int: | ||
raise TypeError("Must pass office id number and permission id to fetch office permissions.") | ||
query = sqlalchemy.text(""" | ||
INSERT INTO office_permissions VALUES (:o_id, :p_id) | ||
""") | ||
return flask.g.db.execute(query, o_id=office_id, p_id=perm_id) | ||
|
||
|
||
def fetch_user_permissions(): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as before, why does this return a table and also a subset of it? |
||
"""Returns the user's name and permissions of all users. | ||
Returns the permissions of all users who have permissions, | ||
and the permissions of all current members (which may be none)""" | ||
query = sqlalchemy.text(""" | ||
SELECT GROUP_CONCAT(permission_id) AS permissions, name, user_id | ||
FROM user_permissions | ||
NATURAL RIGHT JOIN members_current | ||
NATURAL JOIN users | ||
NATURAL JOIN members_extra | ||
GROUP BY user_id | ||
|
||
UNION | ||
|
||
SELECT GROUP_CONCAT(permission_id) AS permissions, name, user_id | ||
FROM user_permissions | ||
NATURAL JOIN members_extra | ||
GROUP BY user_id | ||
|
||
ORDER BY | ||
CASE | ||
WHEN permissions IS NULL | ||
THEN 1 | ||
ELSE 0 | ||
END, | ||
permissions ASC, | ||
name ASC | ||
""") | ||
|
||
return flask.g.db.execute(query).fetchall() | ||
|
||
def fetch_specific_user_permissions(user_id): | ||
"""Returns the name and permissions of the specified user. | ||
May be none.""" | ||
if type(user_id) is not int: | ||
raise TypeError("Must pass user id number to fetch user permissions.") | ||
query = sqlalchemy.text(""" | ||
SELECT GROUP_CONCAT(permission_id) AS permissions, name, user_id | ||
FROM user_permissions | ||
NATURAL RIGHT JOIN users | ||
NATURAL JOIN members_extra | ||
WHERE user_id = :id | ||
GROUP BY user_id | ||
""") | ||
return flask.g.db.execute(query, id=user_id).fetchone() | ||
|
||
def delete_user_permission(user_id, perm_id): | ||
"""Deletes the specified permission from the user.""" | ||
if type(perm_id) is str or type(perm_id) is unicode: | ||
perm_id = int(perm_id) | ||
if type(user_id) is not int or type(perm_id) is not int: | ||
raise TypeError("Must pass user id number and permission id to fetch user permissions.") | ||
query = sqlalchemy.text(""" | ||
DELETE FROM user_permissions | ||
WHERE user_id = :o_id | ||
AND permission_id = :p_id | ||
""") | ||
return flask.g.db.execute(query, o_id=user_id, p_id=perm_id) | ||
|
||
def insert_user_permission(user_id, perm_id): | ||
"""Inserts the specified permission for the user.""" | ||
if type(perm_id) is str or type(perm_id) is unicode: | ||
perm_id = int(perm_id) | ||
if type(user_id) is not int or type(perm_id) is not int: | ||
raise TypeError("Must pass user id number and permission id to fetch user permissions.") | ||
query = sqlalchemy.text(""" | ||
INSERT INTO user_permissions VALUES (:o_id, :p_id) | ||
""") | ||
return flask.g.db.execute(query, o_id=user_id, p_id=perm_id) | ||
|
||
def decode_perm_string(string, sep=","): | ||
"""Decodes a `sep`-separated string of permissions.""" | ||
if string is None: | ||
return ["None"] | ||
elif type(string) is int: | ||
return get_perm_name(string) | ||
elif type(string) is not str: | ||
raise TypeError("decode_perm_string takes int or str only, received " | ||
+ str(type(string))) | ||
return [get_perm_name(int(x)) for x in string.split(sep)] | ||
|
||
def decode_perm_string_with_id(string, sep=","): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
"""Decodes a `sep`-separated string of permissions into a dictionary | ||
of ID and name.""" | ||
if string is None: | ||
return [{"name": "None", "id": 0}] | ||
elif type(string) is int: | ||
return {"name": get_perm_name(string), "id": string} | ||
elif type(string) is not str: | ||
raise TypeError("decode_perm_string takes int or str only, received " | ||
+ str(type(string))) | ||
return [{"name": get_perm_name(int(x)), "id": x} for x in string.split(sep)] | ||
|
||
def get_perm_name(id): | ||
"""Returns the permission name corresponding to the given ID.""" | ||
if id is None: | ||
return "None" | ||
perm_name = ""; | ||
try: | ||
perm_name = Permissions(id).name.title().replace("_", " ") | ||
except ValueError: | ||
# id is not a valid permission | ||
perm_name = "Invalid permission" | ||
return perm_name | ||
|
||
def get_all_perms(): | ||
"""Gets a list of all permissions possible to assign.""" | ||
x = [] | ||
for p in Permissions: | ||
x.append({"name": get_perm_name(p.value), "id": str(p.value)}) | ||
return x |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import json | ||
import flask | ||
|
||
import datetime | ||
|
||
from ruddock.resources import Permissions | ||
from ruddock.decorators import login_required | ||
from ruddock.modules.perm_mgr import blueprint, helpers | ||
|
||
@blueprint.route('/') | ||
@login_required(Permissions.PERMISSION_MANAGER) | ||
def show_permissions(): | ||
"""Displays a list of permissions for current users and offices.""" | ||
office_perms = [{"name": x["office_name"], | ||
"perms": helpers.decode_perm_string(x['permissions']), | ||
"id": x["office_id"]} | ||
for x in helpers.fetch_office_permissions()] | ||
user_perms = [{"name": x["name"], | ||
"perms": helpers.decode_perm_string(x['permissions']), | ||
"id": x["user_id"]} | ||
for x in helpers.fetch_user_permissions()] | ||
return flask.render_template('perm_list.html', office_perms=office_perms, | ||
user_perms=user_perms) | ||
|
||
@blueprint.route('/edit_user/<int:user_id>') | ||
@login_required(Permissions.PERMISSION_MANAGER) | ||
def edit_user_permissions(user_id): | ||
x = helpers.fetch_specific_user_permissions(user_id) | ||
user_perms = {"name": x["name"], | ||
"perms": helpers.decode_perm_string_with_id(x['permissions']), | ||
"id": x["user_id"]} | ||
all_perms = helpers.get_all_perms() | ||
diff_perms = [p for p in all_perms if not any(p["id"] == u["id"] for u in user_perms["perms"])] | ||
return flask.render_template('edit_user.html', info=user_perms, | ||
all_perms=diff_perms) | ||
|
||
@blueprint.route('/edit_office/<int:office_id>') | ||
@login_required(Permissions.PERMISSION_MANAGER) | ||
def edit_office_permissions(office_id): | ||
x = helpers.fetch_specific_office_permissions(office_id) | ||
office_perms = {"name": x["office_name"], | ||
"perms": helpers.decode_perm_string_with_id(x['permissions']), | ||
"id": x["office_id"]} | ||
all_perms = helpers.get_all_perms() | ||
diff_perms = [p for p in all_perms if not any(p["id"] == o["id"] for o in office_perms["perms"])] | ||
return flask.render_template('edit_office.html', info=office_perms, | ||
all_perms=diff_perms) | ||
|
||
@blueprint.route('/delete_user_perm/<int:user_id>', methods=["POST"]) | ||
@login_required(Permissions.PERMISSION_MANAGER) | ||
def delete_user_perm(user_id): | ||
perm_id = flask.request.form.get("perm_id") | ||
helpers.delete_user_permission(user_id, perm_id) | ||
return flask.redirect(flask.url_for("perm_mgr.edit_user_permissions", user_id=user_id)) | ||
|
||
@blueprint.route('/delete_office_perm/<int:office_id>', methods=["POST"]) | ||
@login_required(Permissions.PERMISSION_MANAGER) | ||
def delete_office_perm(office_id): | ||
perm_id = flask.request.form.get("perm_id") | ||
helpers.delete_office_permission(office_id, perm_id) | ||
return flask.redirect(flask.url_for("perm_mgr.edit_office_permissions", office_id=office_id)) | ||
|
||
@blueprint.route('/add_user_perm/<int:user_id>', methods=["POST"]) | ||
@login_required(Permissions.PERMISSION_MANAGER) | ||
def add_user_perm(user_id): | ||
perm_id = flask.request.form.get("perm_id") | ||
helpers.insert_user_permission(user_id, perm_id) | ||
return flask.redirect(flask.url_for("perm_mgr.edit_user_permissions", user_id=user_id)) | ||
|
||
@blueprint.route('/add_office_perm/<int:office_id>', methods=["POST"]) | ||
@login_required(Permissions.PERMISSION_MANAGER) | ||
def add_office_perm(office_id): | ||
perm_id = flask.request.form.get("perm_id") | ||
helpers.insert_office_permission(office_id, perm_id) | ||
return flask.redirect(flask.url_for("perm_mgr.edit_office_permissions", office_id=office_id)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
{% extends "layout.html" %} | ||
|
||
{% block body %} | ||
<center> | ||
{# Name and subtitle #} | ||
<h2>{{ info["name"] }}</h2> | ||
<h3> | ||
Editing office permissions. | ||
</h3> | ||
|
||
<br> | ||
<table> | ||
<thead> | ||
<tr> | ||
<th>Permissions</th> | ||
<th>Actions</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{% for perm in info["perms"] %} | ||
<tr> | ||
<form action={{ url_for("perm_mgr.delete_office_perm", office_id=info["id"]) }} method="post"> | ||
<input type="hidden" name="perm_id" value={{ perm["id"] }}> | ||
<td> | ||
{% if perm["id"] == 0 %} | ||
<em> | ||
{% endif %} | ||
{{ perm["name"] }} | ||
{% if perm["id"] == 0 %} | ||
</em> | ||
{% endif %} | ||
</td> | ||
<td> | ||
{% if perm["id"] != 0 %} | ||
<input type="submit" value="Delete"> | ||
{% endif %} | ||
</td> | ||
</form> | ||
</tr> | ||
{% endfor %} | ||
<tr> | ||
<form action={{ url_for("perm_mgr.add_office_perm", office_id=info["id"]) }} method="post"> | ||
<td>Add: | ||
<select name="perm_id"> | ||
{% for perm in all_perms %} | ||
<option value={{ perm["id"] }}>{{ perm["name"] }}</option> | ||
{% endfor %} | ||
</select> | ||
</td> | ||
<td> | ||
<input type="submit" value="Submit"> | ||
</td> | ||
</form> | ||
</tr> | ||
</tbody> | ||
</table> | ||
</center> | ||
{% endblock body %} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need the function to do this? Can't we just have it return the permissions of all offices, and also the is_active column?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean I suppose lol, does it matter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ahh, i tried it out with more thorough test data, and now i see it. you wanna show every office that's got permissions, even if they're not active, in case you need to remove them. likewise, you want to show all active offices, even if they have no permissions, in case you want to manage them.
same with users.
if that's the case, lemme suggest the following (you gotta have the extra any_permissions column because there's a bug where you can't sort on expressions involving aliases: https://bugs.mysql.com/bug.php?id=80802)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, it worked fine on my machine (using MariaDB, maybe they fixed it?). I checked and the Ruddock website is using MariaDB also so this might not be needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm also using mariadb; maybe I'm just on an older version. In any case, I think that approach makes it much clearer what you're intending to fetch there.