From 3a3e427695f3f6754297a2dde335358518d3373a Mon Sep 17 00:00:00 2001 From: citrons <citrons> Date: Fri, 25 Jun 2021 05:33:00 +0000 Subject: permissions table --- apioforum/db.py | 19 ++++++++++++++++++- apioforum/templates/view_forum.html | 1 + 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/apioforum/db.py b/apioforum/db.py index 7dd635e..06682d6 100644 --- a/apioforum/db.py +++ b/apioforum/db.py @@ -117,7 +117,24 @@ CREATE VIEW most_recent_posts AS CREATE VIEW number_of_posts AS SELECT thread, count(*) AS num_replies FROM posts GROUP BY thread; """, - +""" +CREATE TABLE role_config ( + role TEXT NOT NULL, + forum NOT NULL REFERENCES forums(id), + id INTEGER PRIMARY KEY, + + p_create_threads INT NOT NULL DEFAULT 1, + p_reply_threads INT NOT NULL DEFAULT 1, + p_view_threads INT NOT NULL DEFAULT 1, + p_delete_threads INT NOT NULL DEFAULT 0, + p_lock_threads INT NOT NULL DEFAULT 0, + p_approve INT NOT NULL DEFAULT 0, + p_create_subforum INT NOT NULL DEFAULT 0 +); + +INSERT INTO role_config (role,forum) SELECT "approved",id FROM forums; +INSERT INTO role_config (role,forum) SELECT "other",id FROM forums; +""", ] def init_db(): diff --git a/apioforum/templates/view_forum.html b/apioforum/templates/view_forum.html index 96c51bb..fce051f 100644 --- a/apioforum/templates/view_forum.html +++ b/apioforum/templates/view_forum.html @@ -10,6 +10,7 @@ {%block content%} {% if forum.description %} {{forum.description|md|safe}} +<hr/> {% endif %} {% if subforums %} -- cgit v1.2.3 From eb340b12ae9844d5ff2e4a927753c4d92d3f56e0 Mon Sep 17 00:00:00 2001 From: citrons <citrons> Date: Tue, 29 Jun 2021 19:53:24 +0000 Subject: role config, UI, and things --- apioforum/db.py | 13 ++++- apioforum/forum.py | 23 ++++++++ apioforum/roles.py | 48 ++++++++++++++++ apioforum/static/style.css | 4 ++ apioforum/templates/edit_permissions.html | 94 +++++++++++++++++++++++++++++++ apioforum/templates/view_forum.html | 2 +- 6 files changed, 181 insertions(+), 3 deletions(-) create mode 100644 apioforum/roles.py create mode 100644 apioforum/templates/edit_permissions.html diff --git a/apioforum/db.py b/apioforum/db.py index 06682d6..b5cba39 100644 --- a/apioforum/db.py +++ b/apioforum/db.py @@ -123,11 +123,14 @@ CREATE TABLE role_config ( forum NOT NULL REFERENCES forums(id), id INTEGER PRIMARY KEY, + inherit INT NOT NULL DEFAULT 0, + p_create_threads INT NOT NULL DEFAULT 1, p_reply_threads INT NOT NULL DEFAULT 1, p_view_threads INT NOT NULL DEFAULT 1, - p_delete_threads INT NOT NULL DEFAULT 0, - p_lock_threads INT NOT NULL DEFAULT 0, + p_manage_threads INT NOT NULL DEFAULT 0, + p_vote INT NOT NULL DEFAULT 1, + p_create_polls INT NOT NULL DEFAULT 1, p_approve INT NOT NULL DEFAULT 0, p_create_subforum INT NOT NULL DEFAULT 0 ); @@ -135,6 +138,12 @@ CREATE TABLE role_config ( INSERT INTO role_config (role,forum) SELECT "approved",id FROM forums; INSERT INTO role_config (role,forum) SELECT "other",id FROM forums; """, +""" +CREATE TRIGGER default_role_config AFTER INSERT ON forums BEGIN + INSERT INTO role_config (role,forum) VALUES ("approved",new.id); + INSERT INTO role_config (role,forum) VALUES ("other",new.id); +END; +""" ] def init_db(): diff --git a/apioforum/forum.py b/apioforum/forum.py index 7d6f0f0..69d7650 100644 --- a/apioforum/forum.py +++ b/apioforum/forum.py @@ -8,6 +8,7 @@ from flask import ( from .db import get_db from .mdrender import render +from .roles import forum_perms, overridden_perms from sqlite3 import OperationalError import datetime @@ -118,6 +119,28 @@ def create_thread(forum_id): return render_template("create_thread.html") +@bp.route("/<int:forum_id>/roles",methods=("GET","POST")) +def edit_roles(forum_id): + db = get_db() + forum = db.execute("SELECT * FROM forums WHERE id = ?",(forum_id,)).fetchone() + role_configs = db.execute( + "SELECT * FROM role_config WHERE forum = ? ORDER BY ID ASC", + (forum_id,)).fetchall() + overridden = {} + for c in role_configs: + overridden[c['id']] = overridden_perms(forum_id,c['role']) + + return render_template("edit_permissions.html", + forum=forum, + role_configs=role_configs, + other_roles=["the","test","placeholder"], + overridden=overridden + ) + +@bp.route("/<int:forum_id>/roles/new/<role_name>",methods=["POST"]) +def add_role(forum_id,role_name): + db.execute + @bp.route("/search") def search(): db = get_db() diff --git a/apioforum/roles.py b/apioforum/roles.py new file mode 100644 index 0000000..f364b04 --- /dev/null +++ b/apioforum/roles.py @@ -0,0 +1,48 @@ + +from .db import get_db + +permissions = [ + "p_create_threads", + "p_reply_threads", + "p_manage_threads", + "p_view_threads", + "p_vote", + "p_create_polls", + "p_approve", + "p_create_subforum" +] + +def get_role_config(forum_id, role): + db = get_db() + return db.execute(""" + SELECT * FROM role_config + WHERE forum = ? AND role = ?; + """, (forum_id,role)).fetchone() + +def overridden_perms(forum_id, role): + db = get_db() + p = {} + for perm in permissions: + p[perm] = False + ancestors = db.execute(""" + WITH RECURSIVE fs AS + (SELECT * FROM forums WHERE id = ? + UNION ALL + SELECT forums.* FROM forums, fs WHERE fs.parent=forums.id) + SELECT * FROM fs; + """,(forum_id,)).fetchall()[1:] + for ancestor in ancestors: + config = get_role_config(ancestor['id'], role) + if config and config['inherit']: + for perm in permissions: + p[perm] = p[perm] or not config[perm] + return p + +def forum_perms(forum_id, role): + role_config = get_role_config(forum_id, role) + if not role_config: + role_config = get_role_config(forum_id, "other") + p = {} + overridden = overridden_perms(forum_id, role) + for perm in permissions: + p[perm] = role_config[perm] and not overridden[perm] diff --git a/apioforum/static/style.css b/apioforum/static/style.css index 4403f18..09df395 100644 --- a/apioforum/static/style.css +++ b/apioforum/static/style.css @@ -181,6 +181,10 @@ blockquote { border-left: 3px solid grey; } +label { user-select: none; } + +fieldset { margin-bottom: 15px; } + .search-form { display: inline-block; } diff --git a/apioforum/templates/edit_permissions.html b/apioforum/templates/edit_permissions.html new file mode 100644 index 0000000..a32ceda --- /dev/null +++ b/apioforum/templates/edit_permissions.html @@ -0,0 +1,94 @@ +{% extends 'base.html' %} +{% from 'common.html' import tag %} +{% block header %}<h1>{% block title %}role permissions for '{{forum.name}}'{% endblock %}</h1>{% endblock %} +{% block content %} +<p> + each user has a role in this forum. + the permissions associated with different roles can be configured here. +</p> +<p> + there are three special roles: "bureaucrat", "approved", and "other". + bureaucrats are automatically granted every permission. + everyone by default has the "other" role. + an assigned role is inherited by all subforæ unless overridden. +</p> +<p> + if a role's permissions are set to inherit, + permissions disabled for a role are disabled for that role in all subforæ. +</p> +<form method="post" id="role_config"> + +{% set show_footnote = False %} +{% for role_config in role_configs %} + <fieldset> + <legend id="config_{{role_config.role}}">{{role_config.role}}</legend> + {% macro perm(p, description, tooltip) %} + <input + type="checkbox" + id="{{role_config.role}}_{{p}}" + name="{{role_config.role}}_{{p}}" + {% if role_config[p] %}checked{% endif %} + /> + <label for="{{role_config.role}}_{{p}}" title="{{tooltip}}"> + {{- description -}} + {%- if overridden[role_config.id][p] -%} + * + {%- set show_footnote = True -%} + {%- endif -%} + </label> + <br/> + {% endmacro %} + {{perm("p_create_threads","create threads", + "allow users with the role to create a thread in the forum")}} + {{perm("p_reply_threads","reply to threads", + "allow users with the role to create a post within a thread")}} + {{perm("p_view_threads","view threads", + "allow users with the role to view threads in the forum")}} + {{perm("p_manage_threads","configure others' threads", + "allow users with the role to delete, lock, or modify the title/tags for others' threads")}} + {{perm("p_create_polls","create polls", + "allow users with the role to create poll threads")}} + {{perm("p_vote","vote", + "allow users with the role to vote on poll threads")}} + {{perm("p_create_subforum","create subforæ", + "allow users with the role to create subforæ in this forum. they will automatically become a bureaucrat in this subforum.")}} + {% if role_config.role != "other" %} + {{perm("p_approve","approve others", + "allow users with the role to assign the 'approved' role to those with the 'other' role")}} + {% endif %} + <hr/> + <input + type="checkbox" + id="{{role_config.role}}_inherit" + name="{{role_config.role}}_inherit" + {% if role_config.inherit %}checked{% endif %} + /> + <label for="{{role_config.role}}_inherit">inherit</label> + </fieldset> +{% endfor %} + +{% if show_footnote %} + <p>* disabled in inherited permissions from parent forum</p> +{% endif %} +</form> + +{% if other_roles %} + <fieldset> + <legend>roles from parent foræ</legend> + <ul> + {% for role in other_roles %} + <li>{{role}} + <form action="{{url_for('forum.add_role',forum_id=forum.id,role_name=role)}}" method="POST" style="display:inline"> + <input type="submit" value="add" /> + </form> + </li> + {% endfor %} + </ul> + </fieldset> +{% endif %} + +<p>confirm changes?</p> +<input type="submit" value="confirm" form="role_config"> +<a href="{{url_for('forum.view_forum',forum_id=forum.id)}}">cancel</a> + +{% endblock %} diff --git a/apioforum/templates/view_forum.html b/apioforum/templates/view_forum.html index fce051f..d3d09e1 100644 --- a/apioforum/templates/view_forum.html +++ b/apioforum/templates/view_forum.html @@ -14,7 +14,7 @@ {% endif %} {% if subforums %} -<h2>subforae</h2> +<h2>subforæ</h2> <div class="forum-list"> {% for subforum in subforums %} <div class="listing"> -- cgit v1.2.3 From 338d67854d5eca63b6596fb309589755012c4ca2 Mon Sep 17 00:00:00 2001 From: citrons <citrons> Date: Fri, 16 Jul 2021 09:46:44 +0000 Subject: committing what I have so that ubq can work on it --- apioforum/db.py | 12 +----- apioforum/forum.py | 10 ++--- apioforum/roles.py | 40 ++++------------- apioforum/static/style.css | 2 + apioforum/templates/edit_permissions.html | 71 +++++++++++++------------------ 5 files changed, 46 insertions(+), 89 deletions(-) diff --git a/apioforum/db.py b/apioforum/db.py index b5cba39..5ffd5d9 100644 --- a/apioforum/db.py +++ b/apioforum/db.py @@ -123,8 +123,6 @@ CREATE TABLE role_config ( forum NOT NULL REFERENCES forums(id), id INTEGER PRIMARY KEY, - inherit INT NOT NULL DEFAULT 0, - p_create_threads INT NOT NULL DEFAULT 1, p_reply_threads INT NOT NULL DEFAULT 1, p_view_threads INT NOT NULL DEFAULT 1, @@ -135,14 +133,8 @@ CREATE TABLE role_config ( p_create_subforum INT NOT NULL DEFAULT 0 ); -INSERT INTO role_config (role,forum) SELECT "approved",id FROM forums; -INSERT INTO role_config (role,forum) SELECT "other",id FROM forums; -""", -""" -CREATE TRIGGER default_role_config AFTER INSERT ON forums BEGIN - INSERT INTO role_config (role,forum) VALUES ("approved",new.id); - INSERT INTO role_config (role,forum) VALUES ("other",new.id); -END; +INSERT INTO role_config (role,forum) VALUES ("approved",1); +INSERT INTO role_config (role,forum) VALUES ("other",1); """ ] diff --git a/apioforum/forum.py b/apioforum/forum.py index 69d7650..4b7522c 100644 --- a/apioforum/forum.py +++ b/apioforum/forum.py @@ -126,20 +126,16 @@ def edit_roles(forum_id): role_configs = db.execute( "SELECT * FROM role_config WHERE forum = ? ORDER BY ID ASC", (forum_id,)).fetchall() - overridden = {} - for c in role_configs: - overridden[c['id']] = overridden_perms(forum_id,c['role']) return render_template("edit_permissions.html", forum=forum, role_configs=role_configs, other_roles=["the","test","placeholder"], - overridden=overridden ) -@bp.route("/<int:forum_id>/roles/new/<role_name>",methods=["POST"]) -def add_role(forum_id,role_name): - db.execute +@bp.route("/<int:forum_id>/roles/new",methods=["POST"]) +def add_role(forum_id): + return "placeholder" @bp.route("/search") def search(): diff --git a/apioforum/roles.py b/apioforum/roles.py index f364b04..71efcbd 100644 --- a/apioforum/roles.py +++ b/apioforum/roles.py @@ -14,35 +14,13 @@ permissions = [ def get_role_config(forum_id, role): db = get_db() - return db.execute(""" - SELECT * FROM role_config - WHERE forum = ? AND role = ?; - """, (forum_id,role)).fetchone() -def overridden_perms(forum_id, role): - db = get_db() - p = {} - for perm in permissions: - p[perm] = False - ancestors = db.execute(""" - WITH RECURSIVE fs AS - (SELECT * FROM forums WHERE id = ? - UNION ALL - SELECT forums.* FROM forums, fs WHERE fs.parent=forums.id) - SELECT * FROM fs; - """,(forum_id,)).fetchall()[1:] - for ancestor in ancestors: - config = get_role_config(ancestor['id'], role) - if config and config['inherit']: - for perm in permissions: - p[perm] = p[perm] or not config[perm] - return p - -def forum_perms(forum_id, role): - role_config = get_role_config(forum_id, role) - if not role_config: - role_config = get_role_config(forum_id, "other") - p = {} - overridden = overridden_perms(forum_id, role) - for perm in permissions: - p[perm] = role_config[perm] and not overridden[perm] + fid = forum_id + the = None + while the == None and fid != None: + the = db.execute(""" + SELECT * FROM role_config + WHERE forum = ? AND role = ?; + """, (fid,role)).fetchone() + fid = db.execute(""" + """).fetchone()['parent'] diff --git a/apioforum/static/style.css b/apioforum/static/style.css index 09df395..3813d63 100644 --- a/apioforum/static/style.css +++ b/apioforum/static/style.css @@ -195,6 +195,8 @@ fieldset { margin-bottom: 15px; } border: 1px solid black; } +.role-input { width: 12ch; } + .breadcrumbs { list-style: none; } diff --git a/apioforum/templates/edit_permissions.html b/apioforum/templates/edit_permissions.html index a32ceda..e79c0c7 100644 --- a/apioforum/templates/edit_permissions.html +++ b/apioforum/templates/edit_permissions.html @@ -3,22 +3,20 @@ {% block header %}<h1>{% block title %}role permissions for '{{forum.name}}'{% endblock %}</h1>{% endblock %} {% block content %} <p> - each user has a role in this forum. - the permissions associated with different roles can be configured here. + each user has a role in the forum. + a user may be assigned a role in the forum. + otherwise, the user's role is the same as the parent forum. + everyone's role is "other" by default. </p> <p> - there are three special roles: "bureaucrat", "approved", and "other". - bureaucrats are automatically granted every permission. - everyone by default has the "other" role. - an assigned role is inherited by all subforæ unless overridden. -</p> -<p> - if a role's permissions are set to inherit, - permissions disabled for a role are disabled for that role in all subforæ. + here a set of permissions may be associated with any role. + if a role does not have any permissions configured for this forum, + the permissions set for the role in closest ancestor forum are used. + if no permissions are set for the role in any ancestor forum, + the permissions for the role "other" are used. </p> <form method="post" id="role_config"> -{% set show_footnote = False %} {% for role_config in role_configs %} <fieldset> <legend id="config_{{role_config.role}}">{{role_config.role}}</legend> @@ -31,10 +29,6 @@ /> <label for="{{role_config.role}}_{{p}}" title="{{tooltip}}"> {{- description -}} - {%- if overridden[role_config.id][p] -%} - * - {%- set show_footnote = True -%} - {%- endif -%} </label> <br/> {% endmacro %} @@ -51,41 +45,36 @@ {{perm("p_vote","vote", "allow users with the role to vote on poll threads")}} {{perm("p_create_subforum","create subforæ", - "allow users with the role to create subforæ in this forum. they will automatically become a bureaucrat in this subforum.")}} + "allow users with the role to create subforæ in this forum. " + + "they will automatically become a bureaucrat in this subforum.")}} {% if role_config.role != "other" %} {{perm("p_approve","approve others", "allow users with the role to assign the 'approved' role to those with the 'other' role")}} {% endif %} - <hr/> - <input - type="checkbox" - id="{{role_config.role}}_inherit" - name="{{role_config.role}}_inherit" - {% if role_config.inherit %}checked{% endif %} - /> - <label for="{{role_config.role}}_inherit">inherit</label> </fieldset> {% endfor %} -{% if show_footnote %} - <p>* disabled in inherited permissions from parent forum</p> -{% endif %} </form> -{% if other_roles %} - <fieldset> - <legend>roles from parent foræ</legend> - <ul> - {% for role in other_roles %} - <li>{{role}} - <form action="{{url_for('forum.add_role',forum_id=forum.id,role_name=role)}}" method="POST" style="display:inline"> - <input type="submit" value="add" /> - </form> - </li> - {% endfor %} - </ul> - </fieldset> -{% endif %} +<fieldset> + <legend>add role</legend> + <ul> + {% for role in other_roles %} + <li>{{role}} + <form action="{{url_for('forum.add_role',forum_id=forum.id)}}" method="POST" style="display:inline"> + <input type="hidden" value="{{role}}" name="role" /> + <input type="submit" value="add" /> + </form> + </li> + {% endfor %} + <li> + <form action="{{url_for('forum.add_role',forum_id=forum.id,role_name=role)}}" method="POST" style="display:inline"> + <input type="text" name="role" class="role-input" placeholder="role name"/> + <input type="submit" value="add" /> + </form> + </li> + </ul> +</fieldset> <p>confirm changes?</p> <input type="submit" value="confirm" form="role_config"> -- cgit v1.2.3 From 96fcef98d7bc0fd8940959077c009016aae56fd0 Mon Sep 17 00:00:00 2001 From: citrons <citrons> Date: Sun, 18 Jul 2021 06:11:26 +0000 Subject: role config UI --- apioforum/db.py | 6 +++++ apioforum/forum.py | 43 ++++++++++++++++++++++++++++--- apioforum/roles.py | 41 +++++++++++++++++++++++++++++ apioforum/templates/edit_permissions.html | 28 ++++++++++---------- 4 files changed, 102 insertions(+), 16 deletions(-) diff --git a/apioforum/db.py b/apioforum/db.py index 5ffd5d9..d94a707 100644 --- a/apioforum/db.py +++ b/apioforum/db.py @@ -135,6 +135,12 @@ CREATE TABLE role_config ( INSERT INTO role_config (role,forum) VALUES ("approved",1); INSERT INTO role_config (role,forum) VALUES ("other",1); +""", +""" +CREATE TABLE role_assignments ( + user NOT NULL REFERENCES users(username), + forum NOT NULL REFERENCES forums(id) +); """ ] diff --git a/apioforum/forum.py b/apioforum/forum.py index 4b7522c..ed7e2b7 100644 --- a/apioforum/forum.py +++ b/apioforum/forum.py @@ -8,7 +8,8 @@ from flask import ( from .db import get_db from .mdrender import render -from .roles import forum_perms, overridden_perms +from .roles import get_forum_roles +from .roles import permissions as role_permissions from sqlite3 import OperationalError import datetime @@ -127,15 +128,51 @@ def edit_roles(forum_id): "SELECT * FROM role_config WHERE forum = ? ORDER BY ID ASC", (forum_id,)).fetchall() + if request.method == "POST": + for config in role_configs: + if 'roleconfig_' + config['role'] in request.form: + for p in role_permissions: + permission_setting =\ + f"perm_{config['role']}_{p}" in request.form + db.execute(f""" + UPDATE role_config SET {p} = ? + WHERE forum = ? AND role = ?; + """, + (permission_setting,forum_id, config['role'])) + db.commit() + flash('roles sucessfully enroled') + return redirect(url_for('forum.view_forum',forum_id=forum_id)) + + role_config_roles = [c['role'] for c in role_configs] + other_roles = [role for role in get_forum_roles(forum_id) if not role in role_config_roles] + return render_template("edit_permissions.html", forum=forum, role_configs=role_configs, - other_roles=["the","test","placeholder"], + other_roles=other_roles ) @bp.route("/<int:forum_id>/roles/new",methods=["POST"]) def add_role(forum_id): - return "placeholder" + name = request.form['role'].strip() + if not all(c in (" ","-","_") or c.isalnum() for c in name) \ + or len(name) > 32: + flash("role name must contain no special characters") + return redirect(url_for('forum.edit_roles',forum_id=forum_id)) + if name == "bureaucrat": + flash("cannot configure permissions for bureaucrat") + return redirect(url_for('forum.edit_roles',forum_id=forum_id)) + + db = get_db() + + existing_config = db.execute(""" + SELECT * FROM role_config WHERE forum = ? AND role = ? + """,(forum_id,name)).fetchone() + if not existing_config: + db.execute("INSERT INTO role_config (forum,role) VALUES (?,?)", + (forum_id,name)) + db.commit() + return redirect(url_for('forum.edit_roles',forum_id=forum_id)) @bp.route("/search") def search(): diff --git a/apioforum/roles.py b/apioforum/roles.py index 71efcbd..ae193a7 100644 --- a/apioforum/roles.py +++ b/apioforum/roles.py @@ -23,4 +23,45 @@ def get_role_config(forum_id, role): WHERE forum = ? AND role = ?; """, (fid,role)).fetchone() fid = db.execute(""" + SELECT * FROM forums WHERE id = ? + """(fid,)).fetchone()['parent'] + if the == None: + if role == "other": + raise(RuntimeError( + "unable to find permissions for role 'other', " + + "which should have associated permissions in all contexts.")) + else: + return get_role_config(forum_id, "other") + return the + +def get_user_role(forum_id, user): + db = get_db() + + fid = forum_id + the = None + while the == None and fid != None: + the = db.execute(""" + SELECT * FROM role_assignments + WHERE forum = ? AND user = ?; + """, (fid,role)).fetchone() + fid = db.execute(""" + SELECT * FROM forums WHERE id = ? """).fetchone()['parent'] + return the['role'] if the != None else 'other' + +def get_forum_roles(forum_id): + db = get_db() + + ancestors = db.execute(""" + WITH RECURSIVE fs AS + (SELECT * FROM forums WHERE id = ? + UNION ALL + SELECT forums.* FROM forums, fs WHERE fs.parent=forums.id) + SELECT * FROM fs; + """,(forum_id,)).fetchall() + configs = [] + for a in ancestors: + configs += db.execute(""" + SELECT * FROM role_config WHERE forum = ? + """,(a['id'],)).fetchall() + return set(r['role'] for r in configs) diff --git a/apioforum/templates/edit_permissions.html b/apioforum/templates/edit_permissions.html index e79c0c7..1e4e848 100644 --- a/apioforum/templates/edit_permissions.html +++ b/apioforum/templates/edit_permissions.html @@ -9,11 +9,9 @@ everyone's role is "other" by default. </p> <p> - here a set of permissions may be associated with any role. + here, a set of permissions may be associated with any role. if a role does not have any permissions configured for this forum, the permissions set for the role in closest ancestor forum are used. - if no permissions are set for the role in any ancestor forum, - the permissions for the role "other" are used. </p> <form method="post" id="role_config"> @@ -23,11 +21,11 @@ {% macro perm(p, description, tooltip) %} <input type="checkbox" - id="{{role_config.role}}_{{p}}" - name="{{role_config.role}}_{{p}}" + id="perm_{{role_config.role}}_{{p}}" + name="perm_{{role_config.role}}_{{p}}" {% if role_config[p] %}checked{% endif %} /> - <label for="{{role_config.role}}_{{p}}" title="{{tooltip}}"> + <label for="perm_{{role_config.role}}_{{p}}" title="{{tooltip}}"> {{- description -}} </label> <br/> @@ -47,15 +45,23 @@ {{perm("p_create_subforum","create subforæ", "allow users with the role to create subforæ in this forum. " + "they will automatically become a bureaucrat in this subforum.")}} + <input type="hidden" name="roleconfig_{{role_config.role}}" value="present"/> {% if role_config.role != "other" %} {{perm("p_approve","approve others", "allow users with the role to assign the 'approved' role to those with the 'other' role")}} {% endif %} </fieldset> {% endfor %} - +{% if role_configs %} + <p>confirm changes?</p> + <p> + <input type="submit" value="confirm"> + <a href="{{url_for('forum.view_forum',forum_id=forum.id)}}">cancel</a> + </p> +{% endif %} </form> + <fieldset> <legend>add role</legend> <ul> @@ -68,16 +74,12 @@ </li> {% endfor %} <li> - <form action="{{url_for('forum.add_role',forum_id=forum.id,role_name=role)}}" method="POST" style="display:inline"> - <input type="text" name="role" class="role-input" placeholder="role name"/> + <form action="{{url_for('forum.add_role',forum_id=forum.id)}}" method="POST" style="display:inline"> + <input type="text" name="role" class="role-input" placeholder="role name" maxlength="32"/> <input type="submit" value="add" /> </form> </li> </ul> </fieldset> -<p>confirm changes?</p> -<input type="submit" value="confirm" form="role_config"> -<a href="{{url_for('forum.view_forum',forum_id=forum.id)}}">cancel</a> - {% endblock %} -- cgit v1.2.3 From 3ea7198a166ac161df82a35c135a59bbd67ee645 Mon Sep 17 00:00:00 2001 From: citrons <citrons> Date: Sat, 31 Jul 2021 10:16:24 +0000 Subject: "just commit" - ubq --- apioforum/__init__.py | 2 ++ apioforum/forum.py | 42 ++++++++++++++++++++++++++----------- apioforum/roles.py | 9 ++++++++ apioforum/templates/view_forum.html | 10 +++++++-- 4 files changed, 49 insertions(+), 14 deletions(-) diff --git a/apioforum/__init__.py b/apioforum/__init__.py index 30dd813..7c99c0c 100644 --- a/apioforum/__init__.py +++ b/apioforum/__init__.py @@ -48,6 +48,8 @@ def create_app(): return dict(path_for_next=p) app.jinja_env.globals.update(forum_path=forum.forum_path) + from .roles import has_permission, is_bureaucrat, + app.jinja_env.globals.update(has_permission=has_permission,is_bureaucrat=is_bureaucrat) from .mdrender import render @app.template_filter('md') diff --git a/apioforum/forum.py b/apioforum/forum.py index ed7e2b7..5c6f5bf 100644 --- a/apioforum/forum.py +++ b/apioforum/forum.py @@ -8,8 +8,7 @@ from flask import ( from .db import get_db from .mdrender import render -from .roles import get_forum_roles -from .roles import permissions as role_permissions +from .roles import get_forum_roles,has_permission,is_bureaucrat from sqlite3 import OperationalError import datetime @@ -32,10 +31,30 @@ def forum_path(forum_id): ancestors.reverse() return ancestors -@bp.route("/<int:forum_id>") -def view_forum(forum_id): +def forum_route(relative_path, **kwargs): + def decorator(f): + path = "/<int:forum_id>" + if relative_path != "": + path += "/" + relative_path + + @bp.route(path, **kwargs) + def wrapper(forum_id, *args, **kwargs): + db = get_db() + forum = db.execute("SELECT * FROM forums WHERE id = ?", + (forum_id,)).fetchone() + if forum == None: + abort(404) + return f(forum, *args, **kwargs) + +def requires_permission(permission): + def decorator(f): + def wrapper(forum, *args, **kwargs): + if not has_permission(forum['id'], g.user, permission): + abort(403) + +@forum_route("") +def view_forum(forum): db = get_db() - forum = db.execute("SELECT * FROM forums WHERE id = ?",(forum_id,)).fetchone() threads = db.execute( """SELECT threads.id, threads.title, threads.creator, threads.created, @@ -49,7 +68,7 @@ def view_forum(forum_id): INNER JOIN number_of_posts ON number_of_posts.thread = threads.id WHERE threads.forum = ? ORDER BY threads.updated DESC; - """,(forum_id,)).fetchall() + """,(forum['id'],)).fetchall() thread_tags = {} #todo: somehow optimise this for thread in threads: @@ -66,7 +85,7 @@ def view_forum(forum_id): WHERE parent = ? GROUP BY forums.id ORDER BY name ASC - """,(forum_id,)).fetchall() + """,(forum['id'],)).fetchall() subforums = [] for s in subforums_rows: a={} @@ -75,7 +94,6 @@ def view_forum(forum_id): a['updated'] = datetime.datetime.fromisoformat(a['updated']) subforums.append(a) - return render_template("view_forum.html", forum=forum, subforums=subforums, @@ -83,10 +101,10 @@ def view_forum(forum_id): thread_tags=thread_tags, ) -@bp.route("/<int:forum_id>/create_thread",methods=("GET","POST")) -def create_thread(forum_id): +@forum_route("create_thread",methods=("GET","POST")) +def create_thread(forum): db = get_db() - forum = db.execute("SELECT * FROM forums WHERE id = ?",(forum_id,)).fetchone() + forum = db.execute("SELECT * FROM forums WHERE id = ?",(forum['id'],)).fetchone() if forum is None: flash("that forum doesn't exist") return redirect(url_for('index')) @@ -106,7 +124,7 @@ def create_thread(forum_id): cur = db.cursor() cur.execute( "INSERT INTO threads (title,creator,created,updated,forum) VALUES (?,?,current_timestamp,current_timestamp,?);", - (title,g.user,forum_id) + (title,g.user,forum['id']) ) thread_id = cur.lastrowid cur.execute( diff --git a/apioforum/roles.py b/apioforum/roles.py index ae193a7..ab273c8 100644 --- a/apioforum/roles.py +++ b/apioforum/roles.py @@ -65,3 +65,12 @@ def get_forum_roles(forum_id): SELECT * FROM role_config WHERE forum = ? """,(a['id'],)).fetchall() return set(r['role'] for r in configs) + +def has_permission(forum_id, user, permission): + role = get_user_role(forum_id, user) if user != None else "other" + config = get_role_config(forum_id, role) + return config[permission] + +def is_bureaucrat(forum_id, user): + if user == None: return False + return get_user_role(forum_id, user) == "bureaucrat" diff --git a/apioforum/templates/view_forum.html b/apioforum/templates/view_forum.html index d3d09e1..98d2110 100644 --- a/apioforum/templates/view_forum.html +++ b/apioforum/templates/view_forum.html @@ -8,10 +8,11 @@ {%endblock%} {%block content%} -{% if forum.description %} {{forum.description|md|safe}} -<hr/> +{% if is_bureaucrat(forum.id, g.user) %} + <p><a class="actionbutton" href="{{url_for('forum.edit_roles')}}">role/permission settings</a></p> {% endif %} +<hr/> {% if subforums %} <h2>subforæ</h2> @@ -43,6 +44,8 @@ {% else %} <p>please log in to create a new thread</p> {% endif %} + +{% if has_permission(forum.id, g.user, "p_view_threads") %} <div class="thread-list"> {%for thread in threads%} <div class="listing"> @@ -80,5 +83,8 @@ </div> {%endfor%} </div> +{% else %} +<p>you do not have permission to view threads in this forum</p> +{% endif %} {%endblock%} -- cgit v1.2.3 From fd04b1ea444b2b77cb56ed7a67b8ac2225cfa6bd Mon Sep 17 00:00:00 2001 From: ubq323 <ubq323> Date: Sat, 31 Jul 2021 19:52:57 +0000 Subject: fix typos and syntax errors mainly, i think --- apioforum/__init__.py | 2 +- apioforum/db.py | 3 ++- apioforum/forum.py | 8 ++++++-- apioforum/roles.py | 6 +++--- apioforum/templates/view_forum.html | 2 +- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/apioforum/__init__.py b/apioforum/__init__.py index 7c99c0c..f28471f 100644 --- a/apioforum/__init__.py +++ b/apioforum/__init__.py @@ -48,7 +48,7 @@ def create_app(): return dict(path_for_next=p) app.jinja_env.globals.update(forum_path=forum.forum_path) - from .roles import has_permission, is_bureaucrat, + from .roles import has_permission, is_bureaucrat app.jinja_env.globals.update(has_permission=has_permission,is_bureaucrat=is_bureaucrat) from .mdrender import render diff --git a/apioforum/db.py b/apioforum/db.py index d94a707..cfb5646 100644 --- a/apioforum/db.py +++ b/apioforum/db.py @@ -139,7 +139,8 @@ INSERT INTO role_config (role,forum) VALUES ("other",1); """ CREATE TABLE role_assignments ( user NOT NULL REFERENCES users(username), - forum NOT NULL REFERENCES forums(id) + forum NOT NULL REFERENCES forums(id), + role TEXT NOT NULL ); """ ] diff --git a/apioforum/forum.py b/apioforum/forum.py index 5c6f5bf..1c9b4ed 100644 --- a/apioforum/forum.py +++ b/apioforum/forum.py @@ -8,10 +8,10 @@ from flask import ( from .db import get_db from .mdrender import render -from .roles import get_forum_roles,has_permission,is_bureaucrat - +from .roles import get_forum_roles,has_permission,is_bureaucrat, permissions as role_permissions from sqlite3 import OperationalError import datetime +import functools bp = Blueprint("forum", __name__, url_prefix="/") @@ -38,6 +38,7 @@ def forum_route(relative_path, **kwargs): path += "/" + relative_path @bp.route(path, **kwargs) + @functools.wraps(f) def wrapper(forum_id, *args, **kwargs): db = get_db() forum = db.execute("SELECT * FROM forums WHERE id = ?", @@ -46,8 +47,11 @@ def forum_route(relative_path, **kwargs): abort(404) return f(forum, *args, **kwargs) + return decorator + def requires_permission(permission): def decorator(f): + @functools.wraps(f) def wrapper(forum, *args, **kwargs): if not has_permission(forum['id'], g.user, permission): abort(403) diff --git a/apioforum/roles.py b/apioforum/roles.py index ab273c8..6d20316 100644 --- a/apioforum/roles.py +++ b/apioforum/roles.py @@ -24,7 +24,7 @@ def get_role_config(forum_id, role): """, (fid,role)).fetchone() fid = db.execute(""" SELECT * FROM forums WHERE id = ? - """(fid,)).fetchone()['parent'] + """,(fid,)).fetchone()['parent'] if the == None: if role == "other": raise(RuntimeError( @@ -43,10 +43,10 @@ def get_user_role(forum_id, user): the = db.execute(""" SELECT * FROM role_assignments WHERE forum = ? AND user = ?; - """, (fid,role)).fetchone() + """,(fid,user)).fetchone() fid = db.execute(""" SELECT * FROM forums WHERE id = ? - """).fetchone()['parent'] + """,(fid,)).fetchone()['parent'] return the['role'] if the != None else 'other' def get_forum_roles(forum_id): diff --git a/apioforum/templates/view_forum.html b/apioforum/templates/view_forum.html index 98d2110..c5666c8 100644 --- a/apioforum/templates/view_forum.html +++ b/apioforum/templates/view_forum.html @@ -10,7 +10,7 @@ {%block content%} {{forum.description|md|safe}} {% if is_bureaucrat(forum.id, g.user) %} - <p><a class="actionbutton" href="{{url_for('forum.edit_roles')}}">role/permission settings</a></p> + <p><a class="actionbutton" href="{{url_for('forum.edit_roles',forum_id=forum.id)}}">role/permission settings</a></p> {% endif %} <hr/> -- cgit v1.2.3 From 76cb3a6be912e55ffe7f6e7c221000f57cff6d4a Mon Sep 17 00:00:00 2001 From: citrons <citrons> Date: Thu, 5 Aug 2021 11:28:53 +0000 Subject: make many of the permissions do things. somewhat functional menus for role configuration and assignment. big brother thoughtcrime message deletion. mark roles next to usernames on posts. other things I may have forgot --- apioforum/__init__.py | 7 +- apioforum/db.py | 4 ++ apioforum/forum.py | 98 ++++++++++++++++++++++----- apioforum/roles.py | 2 + apioforum/static/style.css | 38 +++++++++-- apioforum/templates/common.html | 45 ++++++++++--- apioforum/templates/delete_thread.html | 18 +++++ apioforum/templates/edit_permissions.html | 9 +-- apioforum/templates/role_assignment.html | 53 +++++++++++++++ apioforum/templates/view_forum.html | 27 +++++--- apioforum/templates/view_thread.html | 9 ++- apioforum/thread.py | 107 +++++++++++++++++++----------- 12 files changed, 331 insertions(+), 86 deletions(-) create mode 100644 apioforum/templates/delete_thread.html create mode 100644 apioforum/templates/role_assignment.html diff --git a/apioforum/__init__.py b/apioforum/__init__.py index f28471f..1d96d8c 100644 --- a/apioforum/__init__.py +++ b/apioforum/__init__.py @@ -48,8 +48,11 @@ def create_app(): return dict(path_for_next=p) app.jinja_env.globals.update(forum_path=forum.forum_path) - from .roles import has_permission, is_bureaucrat - app.jinja_env.globals.update(has_permission=has_permission,is_bureaucrat=is_bureaucrat) + from .roles import has_permission, is_bureaucrat, get_user_role + app.jinja_env.globals.update( + has_permission=has_permission, + is_bureaucrat=is_bureaucrat, + get_user_role=get_user_role) from .mdrender import render @app.template_filter('md') diff --git a/apioforum/db.py b/apioforum/db.py index cfb5646..d501159 100644 --- a/apioforum/db.py +++ b/apioforum/db.py @@ -127,6 +127,7 @@ CREATE TABLE role_config ( p_reply_threads INT NOT NULL DEFAULT 1, p_view_threads INT NOT NULL DEFAULT 1, p_manage_threads INT NOT NULL DEFAULT 0, + p_delete_posts INT NOT NULL DEFAULT 0, p_vote INT NOT NULL DEFAULT 1, p_create_polls INT NOT NULL DEFAULT 1, p_approve INT NOT NULL DEFAULT 0, @@ -142,6 +143,9 @@ CREATE TABLE role_assignments ( forum NOT NULL REFERENCES forums(id), role TEXT NOT NULL ); +""", +""" +ALTER TABLE posts ADD COLUMN deleted NOT NULL DEFAULT 0; """ ] diff --git a/apioforum/forum.py b/apioforum/forum.py index 1c9b4ed..9d84a69 100644 --- a/apioforum/forum.py +++ b/apioforum/forum.py @@ -3,12 +3,12 @@ from flask import ( Blueprint, render_template, request, - g, redirect, url_for, flash + g, redirect, url_for, flash, abort ) from .db import get_db from .mdrender import render -from .roles import get_forum_roles,has_permission,is_bureaucrat, permissions as role_permissions +from .roles import get_forum_roles,has_permission,is_bureaucrat,get_user_role, permissions as role_permissions from sqlite3 import OperationalError import datetime import functools @@ -55,6 +55,17 @@ def requires_permission(permission): def wrapper(forum, *args, **kwargs): if not has_permission(forum['id'], g.user, permission): abort(403) + return f(forum, *args, **kwargs) + return wrapper + return decorator + +def requires_bureaucrat(f): + @functools.wraps(f) + def wrapper(forum, *args, **kwargs): + if not is_bureaucrat(forum['id'], g.user): + abort(403) + return f(forum, *args, **kwargs) + return wrapper @forum_route("") def view_forum(forum): @@ -66,7 +77,8 @@ def view_forum(forum): most_recent_posts.created as mrp_created, most_recent_posts.author as mrp_author, most_recent_posts.id as mrp_id, - most_recent_posts.content as mrp_content + most_recent_posts.content as mrp_content, + most_recent_posts.deleted as mrp_deleted FROM threads INNER JOIN most_recent_posts ON most_recent_posts.thread = threads.id INNER JOIN number_of_posts ON number_of_posts.thread = threads.id @@ -142,13 +154,13 @@ def create_thread(forum): return render_template("create_thread.html") -@bp.route("/<int:forum_id>/roles",methods=("GET","POST")) -def edit_roles(forum_id): +@forum_route("roles",methods=("GET","POST")) +@requires_bureaucrat +def edit_roles(forum): db = get_db() - forum = db.execute("SELECT * FROM forums WHERE id = ?",(forum_id,)).fetchone() role_configs = db.execute( "SELECT * FROM role_config WHERE forum = ? ORDER BY ID ASC", - (forum_id,)).fetchall() + (forum['id'],)).fetchall() if request.method == "POST": for config in role_configs: @@ -160,13 +172,13 @@ def edit_roles(forum_id): UPDATE role_config SET {p} = ? WHERE forum = ? AND role = ?; """, - (permission_setting,forum_id, config['role'])) + (permission_setting,forum['id'], config['role'])) db.commit() flash('roles sucessfully enroled') - return redirect(url_for('forum.view_forum',forum_id=forum_id)) + return redirect(url_for('forum.view_forum',forum_id=forum['id'])) role_config_roles = [c['role'] for c in role_configs] - other_roles = [role for role in get_forum_roles(forum_id) if not role in role_config_roles] + other_roles = [role for role in get_forum_roles(forum['id']) if not role in role_config_roles] return render_template("edit_permissions.html", forum=forum, @@ -174,27 +186,79 @@ def edit_roles(forum_id): other_roles=other_roles ) -@bp.route("/<int:forum_id>/roles/new",methods=["POST"]) -def add_role(forum_id): +@forum_route("roles/new",methods=["POST"]) +def add_role(forum): name = request.form['role'].strip() if not all(c in (" ","-","_") or c.isalnum() for c in name) \ or len(name) > 32: flash("role name must contain no special characters") - return redirect(url_for('forum.edit_roles',forum_id=forum_id)) + return redirect(url_for('forum.edit_roles',forum_id=forum['id'])) if name == "bureaucrat": flash("cannot configure permissions for bureaucrat") - return redirect(url_for('forum.edit_roles',forum_id=forum_id)) + return redirect(url_for('forum.edit_roles',forum_id=forum['id'])) db = get_db() existing_config = db.execute(""" SELECT * FROM role_config WHERE forum = ? AND role = ? - """,(forum_id,name)).fetchone() + """,(forum['id'],name)).fetchone() if not existing_config: db.execute("INSERT INTO role_config (forum,role) VALUES (?,?)", - (forum_id,name)) + (forum['id'],name)) db.commit() - return redirect(url_for('forum.edit_roles',forum_id=forum_id)) + return redirect(url_for('forum.edit_roles',forum_id=forum['id'])) + +@forum_route("role",methods=["GET","POST"]) +@requires_permission("p_approve") +def view_user_role(forum): + if request.method == "POST": + return redirect(url_for( 'forum.edit_user_role', + username=request.form['user'],forum_id=forum['id'])) + else: + return render_template("role_assignment.html",forum=forum) + +@forum_route("role/<username>",methods=["GET","POST"]) +@requires_permission("p_approve") +def edit_user_role(forum, username): + db = get_db() + if request.method == "POST": + user = db.execute("SELECT * FROM users WHERE username = ?;",(username,)).fetchone() + if user == None: + return redirect(url_for('forum.edit_user_role', + username=username,forum_id=forum['id'])) + role = request.form['role'] + if role not in get_forum_roles(forum['id']) and role != "" and role != "bureaucrat": + flash("no such role") + return redirect(url_for('forum.edit_user_role', + username=username,forum_id=forum['id'])) + if not is_bureaucrat(forum['id'],g.user) and role != "approved" and role != "": + abort(403) + existing = db.execute("SELECT * FROM role_assignments WHERE user = ?;",(username,)).fetchone() + if existing: + if role == "": + db.execute("DELETE FROM role_assignments WHERE user = ?;",(username,)) + else: + db.execute("UPDATE role_assignments SET role = ? WHERE user = ?;",(role,username)) + db.commit() + elif role != "": + db.execute("INSERT INTO role_assignments (user,role) VALUES (?,?);",(username,role)) + db.commit() + flash("role assigned assignedly") + return redirect(url_for('forum.view_forum',forum_id=forum['id'])) + else: + user = db.execute("SELECT * FROM users WHERE username = ?;",(username,)).fetchone() + if user == None: + return render_template("role_assignment.html", + forum=forum,user=username,invalid_user=True) + role = get_user_role(forum['id'], username) + if is_bureaucrat(forum['id'], g.user): + roles = get_forum_roles(forum['id']) + roles.remove("other") + roles.add("bureaucrat") + else: + roles = ["approved"] + return render_template("role_assignment.html", + forum=forum,user=username,role=role,forum_roles=roles) @bp.route("/search") def search(): diff --git a/apioforum/roles.py b/apioforum/roles.py index 6d20316..bda6704 100644 --- a/apioforum/roles.py +++ b/apioforum/roles.py @@ -5,6 +5,7 @@ permissions = [ "p_create_threads", "p_reply_threads", "p_manage_threads", + "p_delete_posts", "p_view_threads", "p_vote", "p_create_polls", @@ -68,6 +69,7 @@ def get_forum_roles(forum_id): def has_permission(forum_id, user, permission): role = get_user_role(forum_id, user) if user != None else "other" + if role == "bureaucrat": return True config = get_role_config(forum_id, role) return config[permission] diff --git a/apioforum/static/style.css b/apioforum/static/style.css index 3813d63..931ac9a 100644 --- a/apioforum/static/style.css +++ b/apioforum/static/style.css @@ -17,10 +17,10 @@ body { font-family: sans-serif; word-wrap: break-word; } .post:last-of-type { border-bottom: 1px solid black; } .post-heading { font-size: smaller; } -.post-heading,a.username { +.post-heading,.username { color: hsl(0,0%,25%); } -a.username { +.username { font-weight: bold; text-decoration: underline; } @@ -34,6 +34,23 @@ a.username { .post-anchor-link { color: hsl(0,0%,25%); } +.deleted-post { + color:white; + background-color: hsl(0,0%,15%) !important; + border-left: 1px solid darkgray; + border-right: 1px solid darkgray; + border-top: 1px solid darkgray; +} +.deleted-post > .post-heading > * { + color: hsl(0,0%,85%); +} +.deleted-post > .post-heading > .post-heading-b > .post-anchor-link { + color: hsl(0,0%,60%); +} +.deleted-post > .post-heading > .post-heading-a > .username { + color: hsl(0,0%,80%); +} + .thread-top-bar, .user-top-bar { margin-bottom: 4px; } @@ -79,7 +96,16 @@ dt { font-weight: bold } img { max-width: 100% } -nav#navbar { float: right; padding: 5px; margin: 2px; border: 1px solid black; display:flex; align-items: center; flex-wrap: wrap } +nav#navbar { + float: right; + padding: 5px; + margin: 2px; + margin-bottom: 20px; + border: 1px solid black; + display:flex; + align-items: center; + flex-wrap: wrap; +} nav#navbar p { margin-left: 15px; margin-top: 0; margin-bottom: 0; margin-right: 10px; padding: 0 } nav#navbar p:first-of-type { margin-left:0.5em } nav#navbar a { color: blue; text-decoration: none } @@ -185,6 +211,8 @@ label { user-select: none; } fieldset { margin-bottom: 15px; } +.warning { color: red; font-weight: bold } + .search-form { display: inline-block; } @@ -195,7 +223,9 @@ fieldset { margin-bottom: 15px; } border: 1px solid black; } -.role-input { width: 12ch; } +.role-input, .name-input { width: 12ch; } + +.thing-id { color: darkgray; font-size: smaller; font-weight: normal; } .breadcrumbs { list-style: none; diff --git a/apioforum/templates/common.html b/apioforum/templates/common.html index b0bf713..9e60e81 100644 --- a/apioforum/templates/common.html +++ b/apioforum/templates/common.html @@ -6,28 +6,55 @@ {{url_for('thread.view_thread', thread_id=post.thread)}}#post_{{post.id}} {%- endmacro %} -{% macro disp_post(post, buttons=False) %} -<div class="post" id="post_{{post.id}}"> +{% macro disp_post(post, buttons=False, forum=None) %} +<div class="post {% if post.deleted %}deleted-post{% endif %}" id="post_{{post.id}}"> <div class="post-heading"> <span class="post-heading-a"> - {{disp_user(post.author)}} + {% if not post.deleted %} + {{disp_user(post.author)}} + {% else %} + <span class="username">big brother</span> + {% endif %} + + {% if forum != None %} + {% set role = get_user_role(forum, post.author) %} + {% if post.deleted %} + <span class="user-role"> + (bureaucrat) + </span> + {% elif role != "other" %} + <span class="user-role"> + ({{ role }}) + </span> + {% endif %} + {% endif %} + {{ts(post.created)}} + {% if post.edited %} (edited {{ts(post.updated)}}) {% endif %} </span> <span class="post-heading-b"> - {% if buttons and post.author == g.user %} - <a class="actionbutton" - href="{{url_for('thread.edit_post',post_id=post.id)}}">edit</a> - <a class="actionbutton" - href="{{url_for('thread.delete_post',post_id=post.id)}}">delete</a> + {% if buttons and not post.deleted %} + {% if post.author == g.user %} + <a class="actionbutton" + href="{{url_for('thread.edit_post',post_id=post.id)}}">edit</a> + {% endif %} + {% if post.author == g.user or (forum and has_permission(forum, g.user, "p_delete_posts")) %} + <a class="actionbutton" + href="{{url_for('thread.delete_post',post_id=post.id)}}">delete</a> + {% endif %} {% endif %} <a class="post-anchor-link" href="{{post_url(post)}}">#{{post.id}}</a> </span> </div> <div class="post-content"> - {{ post.content|md|safe }} + {% if not post.deleted %} + {{ post.content|md|safe }} + {% else %} + this post never existed. + {% endif %} </div> </div> {% endmacro %} diff --git a/apioforum/templates/delete_thread.html b/apioforum/templates/delete_thread.html new file mode 100644 index 0000000..aaf1de3 --- /dev/null +++ b/apioforum/templates/delete_thread.html @@ -0,0 +1,18 @@ +{% from 'common.html' import ts %} +{% extends 'base.html' %} +{% block header %} +<h1>{% block title %}delete thread '{{thread.title}}'{% endblock %}</h1> +{% endblock %} + +{% block content %} + +<form method="post"> +<p>deleting thread created {{ts(thread.created)}} ago with {{post_count}} posts</p> +{% if post_count > 50 %} +<p class="warning">thread contains more than 50 posts!</p> +{% endif %} +<p>confirm delete?</p> +<input type="submit" value="delete"> +<a href="{{url_for('thread.view_thread',thread_id=thread.id)}}">cancel</a> +</form> +{% endblock %} diff --git a/apioforum/templates/edit_permissions.html b/apioforum/templates/edit_permissions.html index 1e4e848..f91c710 100644 --- a/apioforum/templates/edit_permissions.html +++ b/apioforum/templates/edit_permissions.html @@ -1,5 +1,4 @@ {% extends 'base.html' %} -{% from 'common.html' import tag %} {% block header %}<h1>{% block title %}role permissions for '{{forum.name}}'{% endblock %}</h1>{% endblock %} {% block content %} <p> @@ -37,11 +36,13 @@ {{perm("p_view_threads","view threads", "allow users with the role to view threads in the forum")}} {{perm("p_manage_threads","configure others' threads", - "allow users with the role to delete, lock, or modify the title/tags for others' threads")}} + "allow users with the role to modify the title/tags for others' threads or lock it to prevent new posts")}} + {{perm("p_delete_posts","delete others' posts and threads", + "allow users with the role to delete others' posts and threads")}} {{perm("p_create_polls","create polls", - "allow users with the role to create poll threads")}} + "allow users with the role to add a poll to a thread")}} {{perm("p_vote","vote", - "allow users with the role to vote on poll threads")}} + "allow users with the role to vote in polls")}} {{perm("p_create_subforum","create subforæ", "allow users with the role to create subforæ in this forum. " + "they will automatically become a bureaucrat in this subforum.")}} diff --git a/apioforum/templates/role_assignment.html b/apioforum/templates/role_assignment.html new file mode 100644 index 0000000..d56c060 --- /dev/null +++ b/apioforum/templates/role_assignment.html @@ -0,0 +1,53 @@ +{% extends 'base.html' %} +{% block header %}<h1>{% block title %}configure user role in '{{forum.name}}'{% endblock %}</h1>{% endblock %} +{% block content %} +<p> + each user has a role in the forum. + here, a user may be assigned a role in the forum. + otherwise, the user's role is the same as the parent forum. + everyone's role is "other" by default. +</p> +{% if not is_bureaucrat(forum.id, g.user) %} + <p> + you are only allowed to approve members in this forum. + </p> +{% endif %} +<form method="post" action="{{url_for('forum.view_user_role',forum_id=forum.id)}}"> + <label for="user">role settings for user: </label> + <input type="text" class="name-input" id="user" name="user" value="{{user}}"/> + <input type="submit" value="view"/> +</form> + +{% if invalid_user %} + <p>requested user does not exist.</p> + <p> + <a href="{{url_for('forum.view_forum',forum_id=forum.id)}}">cancel</a> + </p> +{% elif user %} +<hr/> +<form method="post"> + <p>{{user}}'s role in this forum is "{{role}}"</p> + {% if role == "other" or is_bureaucrat(forum.id, g.user) %} + <label for="role">assign role: </label> + <select name="role" id="role" value=""> + <option value="">(no assigned role)</option> + {% for role in forum_roles %} + <option value="{{role}}">{{role}}</option> + {% endfor %} + </select> + {% else %} + <p>you do not have permission to change the role of this user</p> + {% endif %} + <p>confirm changes?</p> + <p> + <input type="submit" value="confirm"> + <a href="{{url_for('forum.view_forum',forum_id=forum.id)}}">cancel</a> + </p> +</form> +{% else %} +<p> + <a href="{{url_for('forum.view_forum',forum_id=forum.id)}}">cancel</a> +</p> +{% endif %} + +{% endblock %} diff --git a/apioforum/templates/view_forum.html b/apioforum/templates/view_forum.html index c5666c8..a3563be 100644 --- a/apioforum/templates/view_forum.html +++ b/apioforum/templates/view_forum.html @@ -1,7 +1,7 @@ {% extends 'base.html' %} {% from 'common.html' import ts, tag, disp_user, post_url, forum_breadcrumb %} {% block header %} -<h1>{% block title %}{{forum.name}}{%endblock%}</h1> +<h1>{% block title %}{{forum.name}}{% endblock %} <span class="thing-id">#{{forum.id}}</span></h1> {% if forum.id != 1 %} {{ forum_breadcrumb(forum) }} {% endif %} @@ -9,11 +9,15 @@ {%block content%} {{forum.description|md|safe}} -{% if is_bureaucrat(forum.id, g.user) %} - <p><a class="actionbutton" href="{{url_for('forum.edit_roles',forum_id=forum.id)}}">role/permission settings</a></p> -{% endif %} -<hr/> - +<p> + {% if is_bureaucrat(forum.id, g.user) %} + <a class="actionbutton" href="{{url_for('forum.edit_roles',forum_id=forum.id)}}">role/permission settings</a> + <a class="actionbutton" href="{{url_for('forum.view_user_role',forum_id=forum.id)}}">assign roles</a> + {% endif %} + {% if not is_bureaucrat(forum.id, g.user) and has_permission(forum.id, g.user, "p_approve") %} + <a class="actionbutton" href="{{url_for('forum.view_user_role',forum_id=forum.id)}}">approve users</a> + {% endif %} +</p> {% if subforums %} <h2>subforæ</h2> <div class="forum-list"> @@ -67,7 +71,7 @@ {{ ts(thread.created) }} </div> </div> - {#{% if thread.mrp_id %}#} + {% if not thread.mrp_deleted %} <div class="listing-caption"> {{ disp_user(thread.mrp_author) }} <span class="thread-preview-ts"> @@ -79,7 +83,14 @@ </a> </span> </div> - {#{% endif %}#} + {% else %} + <div class="listing-caption"> + <a class="thread-preview-post" + href="{{url_for('thread.view_thread',thread_id=thread.id)}}#post_{{thread.mrp_id}}"> + latest post + </a> + </div> + {% endif %} </div> {%endfor%} </div> diff --git a/apioforum/templates/view_thread.html b/apioforum/templates/view_thread.html index dd41d87..da8df74 100644 --- a/apioforum/templates/view_thread.html +++ b/apioforum/templates/view_thread.html @@ -1,16 +1,19 @@ {% from 'common.html' import disp_post,tag,thread_breadcrumb %} {% extends 'base.html' %} {% block header %} -<h1>{%block title %}{{thread.title}}{% endblock %}</h1> +<h1>{%block title %}{{thread.title}}{% endblock %} <span class="thing-id">#{{thread.id}}</span></h1> {{ thread_breadcrumb(thread) }} {% endblock %} {%block content%} <div class="thread-top-bar"> <span class="thread-top-bar-a"> - {% if g.user == thread.creator %} + {% if g.user == thread.creator or has_permission(thread.forum, g.user, "p_manage_threads") %} <a class="actionbutton" href="{{url_for('thread.config_thread',thread_id=thread.id)}}">configure thread</a> {% endif %} + {% if has_permission(thread.forum, g.user, "p_delete_posts") %} + <a class="actionbutton" href="{{url_for('thread.delete_thread',thread_id=thread.id)}}">delete thread</a> + {% endif %} </span> <span class="thread-top-bar-b"> @@ -22,7 +25,7 @@ <div class="posts"> {% for post in posts %} - {{ disp_post(post, True) }} + {{ disp_post(post, buttons=True, forum=thread.forum) }} {% endfor %} </div> {% if g.user %} diff --git a/apioforum/thread.py b/apioforum/thread.py index 4bb3c86..1291adf 100644 --- a/apioforum/thread.py +++ b/apioforum/thread.py @@ -5,6 +5,7 @@ from flask import ( url_for, flash ) from .db import get_db +from .roles import has_permission bp = Blueprint("thread", __name__, url_prefix="/thread") @@ -17,66 +18,94 @@ def view_thread(thread_id): thread = db.execute("SELECT * FROM threads WHERE id = ?;",(thread_id,)).fetchone() if thread is None: abort(404) - else: - posts = db.execute( - "SELECT * FROM posts WHERE thread = ? ORDER BY created ASC;", - (thread_id,) - ).fetchall() - tags = db.execute( - """SELECT tags.* FROM tags - INNER JOIN thread_tags ON thread_tags.tag = tags.id - WHERE thread_tags.thread = ? - ORDER BY tags.id""",(thread_id,)).fetchall() - return render_template("view_thread.html",posts=posts,thread=thread,tags=tags) + if not has_permission(thread['forum'], g.user, "p_view_threads"): + abort(403) + posts = db.execute( + "SELECT * FROM posts WHERE thread = ? ORDER BY created ASC;", + (thread_id,) + ).fetchall() + tags = db.execute( + """SELECT tags.* FROM tags + INNER JOIN thread_tags ON thread_tags.tag = tags.id + WHERE thread_tags.thread = ? + ORDER BY tags.id""",(thread_id,)).fetchall() + return render_template("view_thread.html",posts=posts,thread=thread,tags=tags) @bp.route("/<int:thread_id>/create_post", methods=("POST",)) def create_post(thread_id): if g.user is None: flash("you need to log in before you can post") - return redirect(url_for('thread.view_thread',thread_id=thread_id)) + db = get_db() + content = request.form['content'] + thread = db.execute("SELECT * FROM threads WHERE id = ?;",(thread_id,)).fetchone() + if len(content.strip()) == 0: + flash("you cannot post an empty message") + elif not thread: + flash("that thread does not exist") + elif not has_permission(thread['forum'], g.user, "p_reply_threads"): + flash("you do not have permission to do this") + elif not has_permission(thread['forum'], g.user, "p_view_threads"): + flash("you do not have permission to do this") else: - db = get_db() - content = request.form['content'] - thread = db.execute("SELECT * FROM threads WHERE id = ?;",(thread_id,)).fetchone() - if len(content.strip()) == 0: - flash("you cannot post an empty message") - elif not thread: - flash("that thread does not exist") - else: - cur = db.cursor() - cur.execute( - "INSERT INTO posts (thread,author,content,created) VALUES (?,?,?,current_timestamp);", - (thread_id,g.user,content) - ) - post_id = cur.lastrowid - cur.execute( - "UPDATE threads SET updated = current_timestamp WHERE id = ?;", - (thread_id,) - ) - db.commit() - flash("post posted postfully") - return redirect(post_jump(thread_id, post_id)) + cur = db.cursor() + cur.execute( + "INSERT INTO posts (thread,author,content,created) VALUES (?,?,?,current_timestamp);", + (thread_id,g.user,content) + ) + post_id = cur.lastrowid + cur.execute( + "UPDATE threads SET updated = current_timestamp WHERE id = ?;", + (thread_id,) + ) + db.commit() + flash("post posted postfully") + return redirect(post_jump(thread_id, post_id)) + return redirect(url_for('thread.view_thread',thread_id=thread_id)) @bp.route("/delete_post/<int:post_id>", methods=["GET","POST"]) def delete_post(post_id): db = get_db() post = db.execute("SELECT * FROM posts WHERE id = ?",(post_id,)).fetchone() + thread = db.execute("SELECT * FROM threads WHERE id = ?",(post['thread'],)).fetchone() if post is None: flash("that post doesn't exist") return redirect("/") - if post['author'] != g.user: - flash("you can only delete posts that you created") + if post['author'] != g.user and not has_permission(thread['forum'], g.user, "p_delete_posts"): + flash("you do not have permission to do that") return redirect(url_for("thread.view_thread",thread_id=post["thread"])) if request.method == "POST": - # todo: don't actually delete, just mark as deleted or something (and wipe content) - # so that you can have a "this post was deleted" thing - db.execute("DELETE FROM posts WHERE id = ?",(post_id,)) + db.execute(""" + UPDATE posts SET + content = '', + deleted = 1 + WHERE id = ?""",(post_id,)) db.commit() flash("post deleted deletedly") return redirect(url_for("thread.view_thread",thread_id=post["thread"])) else: return render_template("delete_post.html",post=post) +@bp.route("/delete_thread/<int:thread_id>", methods=["GET","POST"]) +def delete_thread(thread_id): + db = get_db() + thread = db.execute("SELECT * FROM threads WHERE id = ?",(thread_id,)).fetchone() + if thread is None: + flash("that thread doesn't exist") + return redirect("/") + if not has_permission(thread['forum'], g.user, "p_delete_posts"): + flash("you do not have permission to do that") + return redirect(url_for("thread.view_thread",thread_id=post["thread"])) + if request.method == "POST": + db.execute("DELETE FROM posts WHERE thread = ?",(thread_id,)) + db.execute("DELETE FROM threads WHERE id = ?",(thread_id,)) + db.commit() + flash("thread deleted deletedly") + return redirect(url_for("forum.view_forum",forum_id=thread['forum'])) + else: + count = db.execute("SELECT num_replies FROM number_of_posts WHERE thread = ?", + (thread_id,)).fetchone()[0] + return render_template("delete_thread.html",thread=thread,post_count=count) + @bp.route("/edit_post/<int:post_id>",methods=["GET","POST"]) def edit_post(post_id): @@ -117,7 +146,7 @@ def config_thread(thread_id): err = None if g.user is None: err = "you need to be logged in to do that" - elif g.user != thread['creator']: + elif g.user != thread['creator'] and not has_permission(thread['forum'], g.user, "g_manage_threads"): err = "you can only configure threads that you own" if err is not None: -- cgit v1.2.3 From 6d7246a9496015a00538c00689d43fad241fbcca Mon Sep 17 00:00:00 2001 From: citrons <citrons> Date: Thu, 5 Aug 2021 21:47:30 +0000 Subject: fix role assignment UI --- apioforum/forum.py | 21 +++++++++++++-------- apioforum/templates/role_assignment.html | 29 ++++++++++++++--------------- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/apioforum/forum.py b/apioforum/forum.py index 9d84a69..f86629d 100644 --- a/apioforum/forum.py +++ b/apioforum/forum.py @@ -235,13 +235,11 @@ def edit_user_role(forum, username): abort(403) existing = db.execute("SELECT * FROM role_assignments WHERE user = ?;",(username,)).fetchone() if existing: - if role == "": - db.execute("DELETE FROM role_assignments WHERE user = ?;",(username,)) - else: - db.execute("UPDATE role_assignments SET role = ? WHERE user = ?;",(role,username)) - db.commit() - elif role != "": - db.execute("INSERT INTO role_assignments (user,role) VALUES (?,?);",(username,role)) + db.execute("DELETE FROM role_assignments WHERE user = ?;",(username,)) + if role != "": + db.execute( + "INSERT INTO role_assignments (user,role,forum) VALUES (?,?,?);", + (username,role,forum['id'])) db.commit() flash("role assigned assignedly") return redirect(url_for('forum.view_forum',forum_id=forum['id'])) @@ -250,6 +248,12 @@ def edit_user_role(forum, username): if user == None: return render_template("role_assignment.html", forum=forum,user=username,invalid_user=True) + r = db.execute( + "SELECT role FROM role_assignments WHERE user = ?;",(username,)).fetchone() + if not r: + assigned_role = "" + else: + assigned_role = r[0] role = get_user_role(forum['id'], username) if is_bureaucrat(forum['id'], g.user): roles = get_forum_roles(forum['id']) @@ -258,7 +262,8 @@ def edit_user_role(forum, username): else: roles = ["approved"] return render_template("role_assignment.html", - forum=forum,user=username,role=role,forum_roles=roles) + forum=forum,user=username,role=role, + assigned_role=assigned_role,forum_roles=roles) @bp.route("/search") def search(): diff --git a/apioforum/templates/role_assignment.html b/apioforum/templates/role_assignment.html index d56c060..b212606 100644 --- a/apioforum/templates/role_assignment.html +++ b/apioforum/templates/role_assignment.html @@ -18,36 +18,35 @@ <input type="submit" value="view"/> </form> +{% set can_change = not invalid_user and user %} {% if invalid_user %} <p>requested user does not exist.</p> - <p> - <a href="{{url_for('forum.view_forum',forum_id=forum.id)}}">cancel</a> - </p> {% elif user %} <hr/> -<form method="post"> +<form method="post" id="role-form"> <p>{{user}}'s role in this forum is "{{role}}"</p> - {% if role == "other" or is_bureaucrat(forum.id, g.user) %} - <label for="role">assign role: </label> - <select name="role" id="role" value=""> + {% set can_change = role == "other" or is_bureaucrat(forum.id, g.user) %} + {% if can_change %} + <label for="role">assigned role: </label> + <select name="role" id="role" autocomplete="off"> <option value="">(no assigned role)</option> {% for role in forum_roles %} - <option value="{{role}}">{{role}}</option> + <option value="{{role}}" + {% if role == assigned_role %}selected{% endif %}> + {{role}} + </option> {% endfor %} </select> {% else %} <p>you do not have permission to change the role of this user</p> {% endif %} - <p>confirm changes?</p> - <p> - <input type="submit" value="confirm"> - <a href="{{url_for('forum.view_forum',forum_id=forum.id)}}">cancel</a> - </p> </form> -{% else %} +{% endif %} + +{% if can_change %}<p>confirm changes?</p>{% endif %} <p> +{% if can_change %}<input type="submit" value="confirm" form="role-form">{% endif %} <a href="{{url_for('forum.view_forum',forum_id=forum.id)}}">cancel</a> </p> -{% endif %} {% endblock %} -- cgit v1.2.3 From 52c63cddb3f7860862af6a2185a728baf7593cc7 Mon Sep 17 00:00:00 2001 From: citrons <citrons> Date: Fri, 6 Aug 2021 01:35:37 +0000 Subject: fix roles even more; forum creation and configuration --- apioforum/db.py | 3 ++ apioforum/forum.py | 78 ++++++++++++++++++++++++++++++-- apioforum/roles.py | 9 +++- apioforum/static/style.css | 2 +- apioforum/templates/common.html | 4 ++ apioforum/templates/edit_forum.html | 27 +++++++++++ apioforum/templates/role_assignment.html | 2 +- apioforum/templates/view_forum.html | 20 ++++++-- apioforum/templates/view_unlisted.html | 24 ++++++++++ apioforum/user.py | 7 ++- 10 files changed, 161 insertions(+), 15 deletions(-) create mode 100644 apioforum/templates/edit_forum.html create mode 100644 apioforum/templates/view_unlisted.html diff --git a/apioforum/db.py b/apioforum/db.py index d501159..899c6b4 100644 --- a/apioforum/db.py +++ b/apioforum/db.py @@ -146,6 +146,9 @@ CREATE TABLE role_assignments ( """, """ ALTER TABLE posts ADD COLUMN deleted NOT NULL DEFAULT 0; +""", +""" +ALTER TABLE forums ADD COLUMN unlisted NOT NULL DEFAULT 0; """ ] diff --git a/apioforum/forum.py b/apioforum/forum.py index f86629d..410bee5 100644 --- a/apioforum/forum.py +++ b/apioforum/forum.py @@ -98,7 +98,7 @@ def view_forum(forum): subforums_rows = db.execute(""" SELECT max(threads.updated) as updated, forums.* FROM forums LEFT OUTER JOIN threads ON threads.forum=forums.id - WHERE parent = ? + WHERE parent = ? AND unlisted = 0 GROUP BY forums.id ORDER BY name ASC """,(forum['id'],)).fetchall() @@ -109,12 +109,19 @@ def view_forum(forum): if a['updated'] is not None: a['updated'] = datetime.datetime.fromisoformat(a['updated']) subforums.append(a) + + bureaucrats = db.execute(""" + SELECT user FROM role_assignments + WHERE role = 'bureaucrat' AND forum = ? + """,(forum['id'],)).fetchall() + bureaucrats = [b[0] for b in bureaucrats] return render_template("view_forum.html", forum=forum, subforums=subforums, threads=threads, thread_tags=thread_tags, + bureaucrats=bureaucrats ) @forum_route("create_thread",methods=("GET","POST")) @@ -232,15 +239,18 @@ def edit_user_role(forum, username): return redirect(url_for('forum.edit_user_role', username=username,forum_id=forum['id'])) if not is_bureaucrat(forum['id'],g.user) and role != "approved" and role != "": + # only bureaucrats can assign arbitrary roles abort(403) - existing = db.execute("SELECT * FROM role_assignments WHERE user = ?;",(username,)).fetchone() + existing = db.execute( + "SELECT * FROM role_assignments WHERE user = ? AND forum = ?;", + (username,forum['id'])).fetchone() if existing: - db.execute("DELETE FROM role_assignments WHERE user = ?;",(username,)) + db.execute("DELETE FROM role_assignments WHERE user = ? AND forum = ?;",(username,forum['id'])) if role != "": db.execute( "INSERT INTO role_assignments (user,role,forum) VALUES (?,?,?);", (username,role,forum['id'])) - db.commit() + db.commit() flash("role assigned assignedly") return redirect(url_for('forum.view_forum',forum_id=forum['id'])) else: @@ -249,7 +259,8 @@ def edit_user_role(forum, username): return render_template("role_assignment.html", forum=forum,user=username,invalid_user=True) r = db.execute( - "SELECT role FROM role_assignments WHERE user = ?;",(username,)).fetchone() + "SELECT role FROM role_assignments WHERE user = ? AND forum = ?;", + (username,forum['id'])).fetchone() if not r: assigned_role = "" else: @@ -265,6 +276,63 @@ def edit_user_role(forum, username): forum=forum,user=username,role=role, assigned_role=assigned_role,forum_roles=roles) +def forum_config_page(forum, create=False): + db = get_db() + if request.method == "POST": + name = request.form["name"] + desc = request.form["description"] + unlisted = "unlisted" in request.form + if len(name) > 100 or len(name.strip()) == 0: + flash("invalid name") + return redirect(url_for('forum.edit_forum',forum_id=forum['id'])) + elif len(desc) > 6000: + flash("invalid description") + return redirect(url_for('forum.edit_forum',forum_id=forum['id'])) + if not create: + db.execute("UPDATE forums SET name = ?, description = ?, unlisted = ? WHERE id = ?", + (name,desc,forum['id'])) + fid = forum['id'] + else: + cur = db.cursor() + cur.execute( + "INSERT INTO forums (name,description,parent,unlisted) VALUES (?,?,?,?)", + (name,desc,forum['id'],unlisted)) + new = cur.lastrowid + # creator becomes bureaucrat of new forum + db.execute("INSERT INTO role_assignments (role,user,forum) VALUES (?,?,?)", + ("bureaucrat",g.user,new)) + fid = new + db.commit() + return redirect(url_for('forum.view_forum',forum_id=fid)) + else: + if create: + name = "" + desc = "" + else: + name = forum['name'] + desc = forum['description'] + cancel_link = url_for('forum.view_forum',forum_id=forum['id']) + return render_template("edit_forum.html",create=create, + name=name,description=desc,cancel_link=cancel_link) + +@forum_route("edit",methods=["GET","POST"]) +@requires_bureaucrat +def edit_forum(forum): + return forum_config_page(forum) + +@forum_route("create",methods=["GET","POST"]) +@requires_permission("p_create_subforum") +def create_forum(forum): + return forum_config_page(forum,create=True) + +@forum_route("unlisted") +@requires_bureaucrat +def view_unlisted(forum): + db = get_db() + unlisted = db.execute( + "SELECT * FROM forums WHERE unlisted = 1 AND parent = ?",(forum['id'],)) + return render_template('view_unlisted.html',forum=forum,unlisted=unlisted) + @bp.route("/search") def search(): db = get_db() diff --git a/apioforum/roles.py b/apioforum/roles.py index bda6704..ae47e31 100644 --- a/apioforum/roles.py +++ b/apioforum/roles.py @@ -40,11 +40,16 @@ def get_user_role(forum_id, user): fid = forum_id the = None - while the == None and fid != None: - the = db.execute(""" + while fid != None: + r = db.execute(""" SELECT * FROM role_assignments WHERE forum = ? AND user = ?; """,(fid,user)).fetchone() + # the user's role is equal to the role assignnment of the closest + # ancestor unless the user's role is "bureaucrat" in any ancestor + # in which case, the users role is "bureaucrat" + if the == None or (r and r['role'] == "bureaucrat"): + the = r fid = db.execute(""" SELECT * FROM forums WHERE id = ? """,(fid,)).fetchone()['parent'] diff --git a/apioforum/static/style.css b/apioforum/static/style.css index 931ac9a..2ed2e7a 100644 --- a/apioforum/static/style.css +++ b/apioforum/static/style.css @@ -188,7 +188,7 @@ nav#navbar .links { display: flex; } } .actionbutton { color:blue } -.new-post-box { +.new-post-box, .forum-desc-box { height:20em; resize:vertical; width:100%; diff --git a/apioforum/templates/common.html b/apioforum/templates/common.html index 9e60e81..7144667 100644 --- a/apioforum/templates/common.html +++ b/apioforum/templates/common.html @@ -67,6 +67,10 @@ <span class="tag" style="color: {{the_tag.text_colour}}; background-color: {{the_tag.bg_colour}}">{{the_tag.name}}</span> {%- endmacro %} +{% macro ab(name,href) -%} +<a class="actionbutton" href="{{href}}">{{name}}</a> +{%- endmacro %} + {% macro breadcrumb() %} <nav aria-label="Breadcrumb"> <ol class="breadcrumbs"> diff --git a/apioforum/templates/edit_forum.html b/apioforum/templates/edit_forum.html new file mode 100644 index 0000000..c8027e7 --- /dev/null +++ b/apioforum/templates/edit_forum.html @@ -0,0 +1,27 @@ +{% extends 'base.html' %} +{% block header %} +<h1>{% block title %}{%if create %}create{% else %}edit{%endif%} forum{% endblock %}</h1> +{% endblock %} + +{% block content %} +<form method="POST"> + <label for="name">forum name</label> + <input name="name" id="name" value="{{name}}" placeholder="apioforum" required maxlength="100"/> + <br> + <label for="description">forum description (markdown enabled)</label> + <textarea + name="description" + id="description" + class="forum-desc-box" + placeholder="this is a forum for discussing bees" + maxlength="6000" + required + >{{description}}</textarea> + <input type="checkbox" id="unlisted" name="unlisted"/> + <label for="unlisted">unlisted?</label> + <p> + <input type="submit" value="confirm"> + <a href="{{cancel_link}}">cancel</a> + </p> +</form> +{% endblock %} diff --git a/apioforum/templates/role_assignment.html b/apioforum/templates/role_assignment.html index b212606..74dc3cd 100644 --- a/apioforum/templates/role_assignment.html +++ b/apioforum/templates/role_assignment.html @@ -29,7 +29,7 @@ {% if can_change %} <label for="role">assigned role: </label> <select name="role" id="role" autocomplete="off"> - <option value="">(no assigned role)</option> + <option value="" {% if not assigned_role %}selected{% endif %}>(no assigned role)</option> {% for role in forum_roles %} <option value="{{role}}" {% if role == assigned_role %}selected{% endif %}> diff --git a/apioforum/templates/view_forum.html b/apioforum/templates/view_forum.html index a3563be..863f91c 100644 --- a/apioforum/templates/view_forum.html +++ b/apioforum/templates/view_forum.html @@ -1,5 +1,5 @@ {% extends 'base.html' %} -{% from 'common.html' import ts, tag, disp_user, post_url, forum_breadcrumb %} +{% from 'common.html' import ts, tag, disp_user, post_url, forum_breadcrumb, ab %} {% block header %} <h1>{% block title %}{{forum.name}}{% endblock %} <span class="thing-id">#{{forum.id}}</span></h1> {% if forum.id != 1 %} @@ -9,13 +9,25 @@ {%block content%} {{forum.description|md|safe}} +{% if bureaucrats|length > 0 %} + <p> + bureaucrats in this forum: + {% for b in bureaucrats %} + {{disp_user(b)}} + {% endfor %} + </p> +{% endif %} <p> {% if is_bureaucrat(forum.id, g.user) %} - <a class="actionbutton" href="{{url_for('forum.edit_roles',forum_id=forum.id)}}">role/permission settings</a> - <a class="actionbutton" href="{{url_for('forum.view_user_role',forum_id=forum.id)}}">assign roles</a> + {{ab("forum settings",url_for('forum.edit_forum',forum_id=forum.id))}} + {{ab("role/permission settings",url_for('forum.edit_roles',forum_id=forum.id))}} + {{ab("assign roles",url_for('forum.view_user_role',forum_id=forum.id))}} + {% endif %} + {% if has_permission(forum.id, g.user, "p_create_subforum") %} + {{ab("create subforum",url_for('forum.create_forum',forum_id=forum.id))}} {% endif %} {% if not is_bureaucrat(forum.id, g.user) and has_permission(forum.id, g.user, "p_approve") %} - <a class="actionbutton" href="{{url_for('forum.view_user_role',forum_id=forum.id)}}">approve users</a> + {{ab("approve users",url_for('forum.view_user_role',forum_id=forum.id))}} {% endif %} </p> {% if subforums %} diff --git a/apioforum/templates/view_unlisted.html b/apioforum/templates/view_unlisted.html new file mode 100644 index 0000000..c0fd074 --- /dev/null +++ b/apioforum/templates/view_unlisted.html @@ -0,0 +1,24 @@ +{% extends 'base.html' %} +{% from 'common.html' import forum_breadcrumb %} +{% block header %} +<h1>{% block title %}unlisted subforæ in '{{forum.name}}'{% endblock %}</h1> +{% if forum.id != 1 %} + {{ forum_breadcrumb(forum) }} +{% endif %} +{% endblock %} + +{% block content %} +<form method="POST"> + {% if unlisted %} + <ul> + {% for f in unlisted %} + <li> + <a href="{{url_for('forum.view_forum',forum_id=f.id)}}">{{f.name}}</a> + </li> + {% endfor %} + </ul> + {% else %} + <p>there are no unlisted subforæ in '{{forum.name}}'</p> + {% endif %} +</form> +{% endblock %} diff --git a/apioforum/user.py b/apioforum/user.py index 9f4bc5b..bbdd060 100644 --- a/apioforum/user.py +++ b/apioforum/user.py @@ -16,8 +16,11 @@ def view_user(username): user = db.execute("SELECT * FROM users WHERE username = ?;",(username,)).fetchone() if user is None: abort(404) - posts = db.execute( - "SELECT * FROM posts WHERE author = ? ORDER BY created DESC LIMIT 25;",(username,)).fetchall() + posts = db.execute(""" + SELECT * FROM posts + WHERE author = ? AND deleted = 0 + ORDER BY created DESC + LIMIT 25;""",(username,)).fetchall() return render_template("view_user.html", user=user, posts=posts) @bp.route("/<username>/edit", methods=["GET","POST"]) -- cgit v1.2.3 From 6d2a72726f95ba762ede5c25aff8b73573fb77c0 Mon Sep 17 00:00:00 2001 From: citrons <citrons> Date: Fri, 6 Aug 2021 04:25:55 +0000 Subject: fix forum edit page --- apioforum/forum.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apioforum/forum.py b/apioforum/forum.py index cae4eab..d177b16 100644 --- a/apioforum/forum.py +++ b/apioforum/forum.py @@ -332,7 +332,7 @@ def forum_config_page(forum, create=False): return redirect(url_for('forum.edit_forum',forum_id=forum['id'])) if not create: db.execute("UPDATE forums SET name = ?, description = ?, unlisted = ? WHERE id = ?", - (name,desc,forum['id'])) + (name,desc,unlisted,forum['id'])) fid = forum['id'] else: cur = db.cursor() -- cgit v1.2.3 From bd7a53ba3daf8853707d6df511cc1e31d2a850a3 Mon Sep 17 00:00:00 2001 From: citrons <citrons> Date: Fri, 6 Aug 2021 04:54:23 +0000 Subject: fix thread edit page --- apioforum/templates/config_thread.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apioforum/templates/config_thread.html b/apioforum/templates/config_thread.html index 7403614..383cc9c 100644 --- a/apioforum/templates/config_thread.html +++ b/apioforum/templates/config_thread.html @@ -29,7 +29,7 @@ <a href="{{url_for('thread.view_thread',thread_id=thread.id)}}">cancel</a> </form> -{% if thread.poll is none and has_permission(thread.forum, g.user, "p_create_polls" %} +{% if thread.poll is none and has_permission(thread.forum, g.user, "p_create_polls") %} <h2>create poll</h2> <form method="post" action="{{url_for('thread.create_poll',thread_id=thread.id)}}"> <fieldset> -- cgit v1.2.3 From a4d99164de42603b83b2a7ac6e594e5925108a32 Mon Sep 17 00:00:00 2001 From: citrons <citrons> Date: Fri, 6 Aug 2021 22:54:59 +0000 Subject: logged out users are considered to have no permissions unless in a specific instance login is not required in which case they are treated as having the role "other". --- apioforum/roles.py | 5 +++-- apioforum/templates/view_forum.html | 2 +- apioforum/thread.py | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/apioforum/roles.py b/apioforum/roles.py index ae47e31..1e9b206 100644 --- a/apioforum/roles.py +++ b/apioforum/roles.py @@ -72,8 +72,9 @@ def get_forum_roles(forum_id): """,(a['id'],)).fetchall() return set(r['role'] for r in configs) -def has_permission(forum_id, user, permission): - role = get_user_role(forum_id, user) if user != None else "other" +def has_permission(forum_id, user, permission, login_required=True): + if user == None and login_required: return False + role = get_user_role(forum_id, user) if user else "other" if role == "bureaucrat": return True config = get_role_config(forum_id, role) return config[permission] diff --git a/apioforum/templates/view_forum.html b/apioforum/templates/view_forum.html index ff1af9b..a4ffac6 100644 --- a/apioforum/templates/view_forum.html +++ b/apioforum/templates/view_forum.html @@ -76,7 +76,7 @@ please log in to create a new thread {% endif %} -{% if has_permission(forum.id, g.user, "p_view_threads") %} +{% if has_permission(forum.id, g.user, "p_view_threads", login_required=False) %} <div class="thread-list"> {%for thread in threads%} <div class="listing"> diff --git a/apioforum/thread.py b/apioforum/thread.py index 0b0804e..a3a122a 100644 --- a/apioforum/thread.py +++ b/apioforum/thread.py @@ -21,7 +21,7 @@ def view_thread(thread_id): thread = db.execute("SELECT * FROM threads WHERE id = ?;",(thread_id,)).fetchone() if thread is None: abort(404) - if not has_permission(thread['forum'], g.user, "p_view_threads"): + if not has_permission(thread['forum'], g.user, "p_view_threads", False): abort(403) posts = db.execute(""" SELECT * FROM posts -- cgit v1.2.3 From 7e5abdf79fb8358ed28d69241a929e3af4841d5b Mon Sep 17 00:00:00 2001 From: citrons <citrons> Date: Sat, 7 Aug 2021 00:24:53 +0000 Subject: random CSS thing that is peeving me --- apioforum/static/style.css | 6 +++++- apioforum/templates/edit_forum.html | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apioforum/static/style.css b/apioforum/static/style.css index 62d643c..280749b 100644 --- a/apioforum/static/style.css +++ b/apioforum/static/style.css @@ -196,7 +196,10 @@ nav#navbar .links { display: flex; } content: "]"; color: grey; } -.actionbutton { color:blue } +.actionbutton { + color:blue; + white-space: nowrap; +} .new-post-box, .forum-desc-box { height:20em; @@ -241,6 +244,7 @@ fieldset { margin-bottom: 15px; } font-size: .75rem; padding: 1px 3px; border: 1px solid black; + white-space: nowrap; } .md table { diff --git a/apioforum/templates/edit_forum.html b/apioforum/templates/edit_forum.html index c8027e7..32bfaf1 100644 --- a/apioforum/templates/edit_forum.html +++ b/apioforum/templates/edit_forum.html @@ -17,7 +17,7 @@ maxlength="6000" required >{{description}}</textarea> - <input type="checkbox" id="unlisted" name="unlisted"/> + <input type="checkbox" id="unlisted" name="unlisted" {% if unlisted %}checked{% endif %}/> <label for="unlisted">unlisted?</label> <p> <input type="submit" value="confirm"> -- cgit v1.2.3 From 2cbb238d80533cf06b1b6a7e1353843a0f583dea Mon Sep 17 00:00:00 2001 From: citrons <citrons> Date: Sat, 7 Aug 2021 01:16:20 +0000 Subject: admin users are automatically bureaucrats --- apioforum/forum.py | 1 + apioforum/roles.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/apioforum/forum.py b/apioforum/forum.py index d177b16..bbc43fe 100644 --- a/apioforum/forum.py +++ b/apioforum/forum.py @@ -1,5 +1,6 @@ # view threads in a forum # currently there is only ever one forum however +# ^ aha we never removed this. we should keep it. it is funny. from flask import ( Blueprint, render_template, request, diff --git a/apioforum/roles.py b/apioforum/roles.py index 1e9b206..bd913a0 100644 --- a/apioforum/roles.py +++ b/apioforum/roles.py @@ -37,6 +37,10 @@ def get_role_config(forum_id, role): def get_user_role(forum_id, user): db = get_db() + user = db.execute('SELECT * FROM users WHERE username = ?', + (user,)).fetchone() + if user == None: return "other" + if user['admin']: return "bureaucrat" fid = forum_id the = None -- cgit v1.2.3 From 09f38fd67bae05c7998ab9573d5842d02df247d8 Mon Sep 17 00:00:00 2001 From: citrons <citrons> Date: Sat, 7 Aug 2021 23:22:51 +0000 Subject: one is now able to remove a role configuration --- apioforum/forum.py | 6 +++++- apioforum/templates/edit_permissions.html | 10 ++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/apioforum/forum.py b/apioforum/forum.py index bbc43fe..270b328 100644 --- a/apioforum/forum.py +++ b/apioforum/forum.py @@ -214,7 +214,11 @@ def edit_roles(forum): if request.method == "POST": for config in role_configs: - if 'roleconfig_' + config['role'] in request.form: + if 'delete_' + config['role'] in request.form: + db.execute( + "DELETE FROM role_config WHERE forum = ? AND role = ?", + (forum['id'],config['role'])) + elif 'roleconfig_' + config['role'] in request.form: for p in role_permissions: permission_setting =\ f"perm_{config['role']}_{p}" in request.form diff --git a/apioforum/templates/edit_permissions.html b/apioforum/templates/edit_permissions.html index f91c710..c92c9a9 100644 --- a/apioforum/templates/edit_permissions.html +++ b/apioforum/templates/edit_permissions.html @@ -16,7 +16,7 @@ {% for role_config in role_configs %} <fieldset> - <legend id="config_{{role_config.role}}">{{role_config.role}}</legend> + <legend id="config_{{role_config.role}}">{{role_config.role}}</legend> {% macro perm(p, description, tooltip) %} <input type="checkbox" @@ -46,11 +46,17 @@ {{perm("p_create_subforum","create subforæ", "allow users with the role to create subforæ in this forum. " + "they will automatically become a bureaucrat in this subforum.")}} - <input type="hidden" name="roleconfig_{{role_config.role}}" value="present"/> {% if role_config.role != "other" %} {{perm("p_approve","approve others", "allow users with the role to assign the 'approved' role to those with the 'other' role")}} {% endif %} + <input type="hidden" name="roleconfig_{{role_config.role}}" value="present"/> + + {% if forum.id != 1 or role_config.role != "other" %} + <hr/> + <input type="checkbox" name="delete_{{role_config.role}}" id="delete_{{role_config.role}}"/> + <label for="delete_{{role_config.role}}">remove</label> + {% endif %} </fieldset> {% endfor %} {% if role_configs %} -- cgit v1.2.3 From 5e46fa10619a8b4d0e7c635658aa36ca47c8a624 Mon Sep 17 00:00:00 2001 From: citrons <citrons> Date: Sat, 7 Aug 2021 23:43:47 +0000 Subject: fix the issue in which everything is broken --- apioforum/roles.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apioforum/roles.py b/apioforum/roles.py index bd913a0..d8e59ba 100644 --- a/apioforum/roles.py +++ b/apioforum/roles.py @@ -35,10 +35,10 @@ def get_role_config(forum_id, role): return get_role_config(forum_id, "other") return the -def get_user_role(forum_id, user): +def get_user_role(forum_id, username): db = get_db() user = db.execute('SELECT * FROM users WHERE username = ?', - (user,)).fetchone() + (username,)).fetchone() if user == None: return "other" if user['admin']: return "bureaucrat" @@ -48,7 +48,7 @@ def get_user_role(forum_id, user): r = db.execute(""" SELECT * FROM role_assignments WHERE forum = ? AND user = ?; - """,(fid,user)).fetchone() + """,(fid,username)).fetchone() # the user's role is equal to the role assignnment of the closest # ancestor unless the user's role is "bureaucrat" in any ancestor # in which case, the users role is "bureaucrat" -- cgit v1.2.3 From 242f8cd10f51caa271bb4a5bdbab24f06edaf157 Mon Sep 17 00:00:00 2001 From: citrons <citrons> Date: Sun, 8 Aug 2021 00:02:25 +0000 Subject: p_create_threads actually does the --- apioforum/forum.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apioforum/forum.py b/apioforum/forum.py index 270b328..ce0215c 100644 --- a/apioforum/forum.py +++ b/apioforum/forum.py @@ -168,6 +168,7 @@ def view_forum(forum): ) @forum_route("create_thread",methods=("GET","POST")) +@requires_permission("p_create_threads") def create_thread(forum): db = get_db() forum = db.execute("SELECT * FROM forums WHERE id = ?",(forum['id'],)).fetchone() -- cgit v1.2.3 From 591ca58043f0c4cb9c148eb058d0c0663ff87c5a Mon Sep 17 00:00:00 2001 From: citrons <citrons> Date: Sun, 8 Aug 2021 00:05:33 +0000 Subject: UI reflects p_create_threads --- apioforum/templates/view_forum.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apioforum/templates/view_forum.html b/apioforum/templates/view_forum.html index a4ffac6..290bb23 100644 --- a/apioforum/templates/view_forum.html +++ b/apioforum/templates/view_forum.html @@ -70,7 +70,7 @@ <h2>threads</h2> <p> -{% if g.user %} +{% if has_permission(forum.id, g.user, "p_create_threads") %} <a class="actionbutton" href="{{url_for('forum.create_thread',forum_id=forum.id)}}">create new thread</a> {% else %} please log in to create a new thread -- cgit v1.2.3 From 761e87bd3f5942199dc24ea93f455284ccb608fb Mon Sep 17 00:00:00 2001 From: citrons <citrons> Date: Sun, 8 Aug 2021 00:07:46 +0000 Subject: UI does not say "login required" when logged in --- apioforum/templates/view_forum.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apioforum/templates/view_forum.html b/apioforum/templates/view_forum.html index 290bb23..0994752 100644 --- a/apioforum/templates/view_forum.html +++ b/apioforum/templates/view_forum.html @@ -72,8 +72,10 @@ <p> {% if has_permission(forum.id, g.user, "p_create_threads") %} <a class="actionbutton" href="{{url_for('forum.create_thread',forum_id=forum.id)}}">create new thread</a> -{% else %} +{% elif has_permission(forum.id, g.user "p_create_threads", login_required=False) %} please log in to create a new thread +{% else %} +you do not have permission to create threads in this forum {% endif %} {% if has_permission(forum.id, g.user, "p_view_threads", login_required=False) %} -- cgit v1.2.3 From 6ccb87c7c0e735664061154ff6ca6980fa332083 Mon Sep 17 00:00:00 2001 From: citrons <citrons> Date: Sun, 8 Aug 2021 00:15:59 +0000 Subject: fix things --- apioforum/templates/config_thread.html | 5 +++-- apioforum/templates/view_forum.html | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apioforum/templates/config_thread.html b/apioforum/templates/config_thread.html index 383cc9c..0795ccc 100644 --- a/apioforum/templates/config_thread.html +++ b/apioforum/templates/config_thread.html @@ -29,7 +29,8 @@ <a href="{{url_for('thread.view_thread',thread_id=thread.id)}}">cancel</a> </form> -{% if thread.poll is none and has_permission(thread.forum, g.user, "p_create_polls") %} +{% if has_permission(thread.forum, g.user, "p_create_polls") %} +{% if thread.poll is none %} <h2>create poll</h2> <form method="post" action="{{url_for('thread.create_poll',thread_id=thread.id)}}"> <fieldset> @@ -49,7 +50,7 @@ <form action="{{url_for('thread.delete_poll',thread_id=thread.id)}}" method="post"> <input type="submit" value="confirm: delete poll"> </form> - +{% endif %} {% endif %} {% endblock %} diff --git a/apioforum/templates/view_forum.html b/apioforum/templates/view_forum.html index 0994752..c42b7b8 100644 --- a/apioforum/templates/view_forum.html +++ b/apioforum/templates/view_forum.html @@ -72,7 +72,7 @@ <p> {% if has_permission(forum.id, g.user, "p_create_threads") %} <a class="actionbutton" href="{{url_for('forum.create_thread',forum_id=forum.id)}}">create new thread</a> -{% elif has_permission(forum.id, g.user "p_create_threads", login_required=False) %} +{% elif has_permission(forum.id, g.user, "p_create_threads", login_required=False) %} please log in to create a new thread {% else %} you do not have permission to create threads in this forum -- cgit v1.2.3 From 8ae879fdfe18997a82535ed64acc40fe281d759c Mon Sep 17 00:00:00 2001 From: citrons <citrons> Date: Sun, 8 Aug 2021 00:35:40 +0000 Subject: one's role in a forum is now viewable --- apioforum/templates/view_forum.html | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apioforum/templates/view_forum.html b/apioforum/templates/view_forum.html index c42b7b8..0eada1a 100644 --- a/apioforum/templates/view_forum.html +++ b/apioforum/templates/view_forum.html @@ -21,6 +21,11 @@ </p> {% endif %} + {% set role = get_user_role(forum.id, g.user) %} + {% if role != "other" %} + <p>your role in this forum: {{role}}</p> + {% endif %} + <p>available tags: {% for the_tag in avail_tags %} {{tag(the_tag)}} -- cgit v1.2.3 From 9a1373022ea968aa84324eb6db8e3f3a001297d1 Mon Sep 17 00:00:00 2001 From: citrons <citrons> Date: Sun, 8 Aug 2021 00:47:28 +0000 Subject: fix typo --- apioforum/thread.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apioforum/thread.py b/apioforum/thread.py index a3a122a..e9deea8 100644 --- a/apioforum/thread.py +++ b/apioforum/thread.py @@ -314,7 +314,7 @@ def config_thread(thread_id): err = None if g.user is None: err = "you need to be logged in to do that" - elif g.user != thread['creator'] and not has_permission(thread['forum'], g.user, "g_manage_threads"): + elif g.user != thread['creator'] and not has_permission(thread['forum'], g.user, "p_manage_threads"): err = "you can only configure threads that you own" if err is not None: -- cgit v1.2.3 From 4588c1526d6cb73b85f10e2c177d2686ebc9e26c Mon Sep 17 00:00:00 2001 From: citrons <citrons> Date: Sun, 8 Aug 2021 01:07:13 +0000 Subject: thread configuration cannot occur if one is not able to view the thread. unlisted forums are completely invisible to those without view permissions --- apioforum/forum.py | 5 +++++ apioforum/thread.py | 2 ++ 2 files changed, 7 insertions(+) diff --git a/apioforum/forum.py b/apioforum/forum.py index ce0215c..108f0ba 100644 --- a/apioforum/forum.py +++ b/apioforum/forum.py @@ -83,6 +83,11 @@ def requires_bureaucrat(f): @forum_route("") def view_forum(forum): + # user should not be able to see anything about the forum if it is unlisted + # and the user does not have permission to see things + if forum['unlisted'] and not has_permission(forum['id'], g.user, "p_view_threads"): + abort(403) + db = get_db() threads = db.execute( """SELECT diff --git a/apioforum/thread.py b/apioforum/thread.py index e9deea8..3c054d7 100644 --- a/apioforum/thread.py +++ b/apioforum/thread.py @@ -314,6 +314,8 @@ def config_thread(thread_id): err = None if g.user is None: err = "you need to be logged in to do that" + elif not has_permission(thread['forum'], g.user, "p_view_threads"): + err = "you do not have permission to do that" elif g.user != thread['creator'] and not has_permission(thread['forum'], g.user, "p_manage_threads"): err = "you can only configure threads that you own" -- cgit v1.2.3 From 0cd850265b567a53921da70bf6d07e4330500f34 Mon Sep 17 00:00:00 2001 From: citrons <citrons> Date: Sun, 8 Aug 2021 01:16:59 +0000 Subject: in the forum settings page, the "unlisted?" box is checked if the forum is already unlisted --- apioforum/forum.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apioforum/forum.py b/apioforum/forum.py index 108f0ba..084c75d 100644 --- a/apioforum/forum.py +++ b/apioforum/forum.py @@ -361,12 +361,14 @@ def forum_config_page(forum, create=False): if create: name = "" desc = "" + unlisted = False else: name = forum['name'] desc = forum['description'] + unlisted = forum['unlisted'] cancel_link = url_for('forum.view_forum',forum_id=forum['id']) return render_template("edit_forum.html",create=create, - name=name,description=desc,cancel_link=cancel_link) + name=name,description=desc,unlisted=unlisted,cancel_link=cancel_link) @forum_route("edit",methods=["GET","POST"]) @requires_bureaucrat -- cgit v1.2.3 From 947c8168f1ce5df05fabc93975049b3ee49ad499 Mon Sep 17 00:00:00 2001 From: citrons <citrons> Date: Mon, 9 Aug 2021 00:23:56 +0000 Subject: view forum permission --- apioforum/db.py | 3 +++ apioforum/forum.py | 44 +++++++++++++++---------------- apioforum/roles.py | 17 +++++++++--- apioforum/templates/edit_forum.html | 2 -- apioforum/templates/edit_permissions.html | 2 ++ apioforum/templates/role_assignment.html | 4 +++ 6 files changed, 43 insertions(+), 29 deletions(-) diff --git a/apioforum/db.py b/apioforum/db.py index 5c3d2eb..c0c8c7e 100644 --- a/apioforum/db.py +++ b/apioforum/db.py @@ -204,6 +204,9 @@ ALTER TABLE posts ADD COLUMN deleted NOT NULL DEFAULT 0; """ ALTER TABLE forums ADD COLUMN unlisted NOT NULL DEFAULT 0; """, +""" +ALTER TABLE role_config ADD COLUMN p_view_forum INT NOT NULL DEFAULT 1; +""" ] def init_db(): diff --git a/apioforum/forum.py b/apioforum/forum.py index 084c75d..2931df9 100644 --- a/apioforum/forum.py +++ b/apioforum/forum.py @@ -10,6 +10,7 @@ from flask import ( from .db import get_db from .mdrender import render from .roles import get_forum_roles,has_permission,is_bureaucrat,get_user_role, permissions as role_permissions +from .permissions import is_admin from sqlite3 import OperationalError import datetime import functools @@ -63,11 +64,11 @@ def forum_route(relative_path, **kwargs): return decorator -def requires_permission(permission): +def requires_permission(permission, login_required=True): def decorator(f): @functools.wraps(f) def wrapper(forum, *args, **kwargs): - if not has_permission(forum['id'], g.user, permission): + if not has_permission(forum['id'],g.user,permission,login_required): abort(403) return f(forum, *args, **kwargs) return wrapper @@ -75,6 +76,7 @@ def requires_permission(permission): def requires_bureaucrat(f): @functools.wraps(f) + @requires_permission("p_view_forum") def wrapper(forum, *args, **kwargs): if not is_bureaucrat(forum['id'], g.user): abort(403) @@ -82,12 +84,8 @@ def requires_bureaucrat(f): return wrapper @forum_route("") +@requires_permission("p_view_forum", login_required=False) def view_forum(forum): - # user should not be able to see anything about the forum if it is unlisted - # and the user does not have permission to see things - if forum['unlisted'] and not has_permission(forum['id'], g.user, "p_view_threads"): - abort(403) - db = get_db() threads = db.execute( """SELECT @@ -154,7 +152,8 @@ def view_forum(forum): a.update(s) if a['updated'] is not None: a['updated'] = datetime.datetime.fromisoformat(a['updated']) - subforums.append(a) + if has_permission(a['id'],g.user,"p_view_forum",login_required=False): + subforums.append(a) bureaucrats = db.execute(""" SELECT user FROM role_assignments @@ -174,6 +173,7 @@ def view_forum(forum): @forum_route("create_thread",methods=("GET","POST")) @requires_permission("p_create_threads") +@requires_permission("p_view_forum") def create_thread(forum): db = get_db() forum = db.execute("SELECT * FROM forums WHERE id = ?",(forum['id'],)).fetchone() @@ -247,6 +247,7 @@ def edit_roles(forum): ) @forum_route("roles/new",methods=["POST"]) +@requires_bureaucrat def add_role(forum): name = request.form['role'].strip() if not all(c in (" ","-","_") or c.isalnum() for c in name) \ @@ -334,7 +335,6 @@ def forum_config_page(forum, create=False): if request.method == "POST": name = request.form["name"] desc = request.form["description"] - unlisted = "unlisted" in request.form if len(name) > 100 or len(name.strip()) == 0: flash("invalid name") return redirect(url_for('forum.edit_forum',forum_id=forum['id'])) @@ -342,14 +342,14 @@ def forum_config_page(forum, create=False): flash("invalid description") return redirect(url_for('forum.edit_forum',forum_id=forum['id'])) if not create: - db.execute("UPDATE forums SET name = ?, description = ?, unlisted = ? WHERE id = ?", - (name,desc,unlisted,forum['id'])) + db.execute("UPDATE forums SET name = ?, description = ? WHERE id = ?", + (name,desc,forum['id'])) fid = forum['id'] else: cur = db.cursor() cur.execute( - "INSERT INTO forums (name,description,parent,unlisted) VALUES (?,?,?,?)", - (name,desc,forum['id'],unlisted)) + "INSERT INTO forums (name,description,parent) VALUES (?,?,?)", + (name,desc,forum['id'])) new = cur.lastrowid # creator becomes bureaucrat of new forum db.execute("INSERT INTO role_assignments (role,user,forum) VALUES (?,?,?)", @@ -361,14 +361,12 @@ def forum_config_page(forum, create=False): if create: name = "" desc = "" - unlisted = False else: name = forum['name'] desc = forum['description'] - unlisted = forum['unlisted'] cancel_link = url_for('forum.view_forum',forum_id=forum['id']) return render_template("edit_forum.html",create=create, - name=name,description=desc,unlisted=unlisted,cancel_link=cancel_link) + name=name,description=desc,cancel_link=cancel_link) @forum_route("edit",methods=["GET","POST"]) @requires_bureaucrat @@ -380,13 +378,13 @@ def edit_forum(forum): def create_forum(forum): return forum_config_page(forum,create=True) -@forum_route("unlisted") -@requires_bureaucrat -def view_unlisted(forum): - db = get_db() - unlisted = db.execute( - "SELECT * FROM forums WHERE unlisted = 1 AND parent = ?",(forum['id'],)) - return render_template('view_unlisted.html',forum=forum,unlisted=unlisted) +#@forum_route("unlisted") +#def view_unlisted(forum): +# if not is_admin: abort(403) # why doesn't this fucking work +# db = get_db() +# unlisted = db.execute( +# "SELECT * FROM forums WHERE unlisted = 1 AND parent = ?",(forum['id'],)) +# return render_template('view_unlisted.html',forum=forum,unlisted=unlisted) @bp.route("/search") def search(): diff --git a/apioforum/roles.py b/apioforum/roles.py index d8e59ba..aa1d239 100644 --- a/apioforum/roles.py +++ b/apioforum/roles.py @@ -1,5 +1,6 @@ from .db import get_db +from .permissions import is_admin permissions = [ "p_create_threads", @@ -10,7 +11,8 @@ permissions = [ "p_vote", "p_create_polls", "p_approve", - "p_create_subforum" + "p_create_subforum", + "p_view_forum" ] def get_role_config(forum_id, role): @@ -76,9 +78,16 @@ def get_forum_roles(forum_id): """,(a['id'],)).fetchall() return set(r['role'] for r in configs) -def has_permission(forum_id, user, permission, login_required=True): - if user == None and login_required: return False - role = get_user_role(forum_id, user) if user else "other" +def has_permission(forum_id, username, permission, login_required=True): + db = get_db() + forum = db.execute("SELECT * FROM forums WHERE id = ?",(forum_id,)).fetchone() + user = db.execute('SELECT * FROM users WHERE username = ?', + (username,)).fetchone() if username else None + + if forum['unlisted'] and not (user and user['admin']): return False + if username == None and login_required: return False + + role = get_user_role(forum_id, username) if username else "other" if role == "bureaucrat": return True config = get_role_config(forum_id, role) return config[permission] diff --git a/apioforum/templates/edit_forum.html b/apioforum/templates/edit_forum.html index 32bfaf1..f165676 100644 --- a/apioforum/templates/edit_forum.html +++ b/apioforum/templates/edit_forum.html @@ -17,8 +17,6 @@ maxlength="6000" required >{{description}}</textarea> - <input type="checkbox" id="unlisted" name="unlisted" {% if unlisted %}checked{% endif %}/> - <label for="unlisted">unlisted?</label> <p> <input type="submit" value="confirm"> <a href="{{cancel_link}}">cancel</a> diff --git a/apioforum/templates/edit_permissions.html b/apioforum/templates/edit_permissions.html index c92c9a9..59c9093 100644 --- a/apioforum/templates/edit_permissions.html +++ b/apioforum/templates/edit_permissions.html @@ -29,6 +29,8 @@ </label> <br/> {% endmacro %} + {{perm("p_view_forum","view the forum", + "allow users with the role to see the forum in listings and view information about it")}} {{perm("p_create_threads","create threads", "allow users with the role to create a thread in the forum")}} {{perm("p_reply_threads","reply to threads", diff --git a/apioforum/templates/role_assignment.html b/apioforum/templates/role_assignment.html index 74dc3cd..8309506 100644 --- a/apioforum/templates/role_assignment.html +++ b/apioforum/templates/role_assignment.html @@ -1,4 +1,5 @@ {% extends 'base.html' %} +{% from 'common.html' import ab %} {% block header %}<h1>{% block title %}configure user role in '{{forum.name}}'{% endblock %}</h1>{% endblock %} {% block content %} <p> @@ -12,6 +13,9 @@ you are only allowed to approve members in this forum. </p> {% endif %} + +{# <p>{{ab("role assignment list",url_for("forum.role_list_select",forum_id=forum.id))}}</p> #} + <form method="post" action="{{url_for('forum.view_user_role',forum_id=forum.id)}}"> <label for="user">role settings for user: </label> <input type="text" class="name-input" id="user" name="user" value="{{user}}"/> -- cgit v1.2.3 From 2d812c9c01bdc94e56500c0c0ffe187117e7696a Mon Sep 17 00:00:00 2001 From: citrons <citrons> Date: Mon, 9 Aug 2021 20:00:42 +0000 Subject: fix bug in deleting threads without permission --- apioforum/thread.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apioforum/thread.py b/apioforum/thread.py index 3c054d7..2fc9dca 100644 --- a/apioforum/thread.py +++ b/apioforum/thread.py @@ -250,7 +250,7 @@ def delete_thread(thread_id): return redirect("/") if not has_permission(thread['forum'], g.user, "p_delete_posts"): flash("you do not have permission to do that") - return redirect(url_for("thread.view_thread",thread_id=post["thread"])) + return redirect(url_for("thread.view_thread",thread_id=thread_id)) if request.method == "POST": db.execute("DELETE FROM posts WHERE thread = ?",(thread_id,)) db.execute("DELETE FROM threads WHERE id = ?",(thread_id,)) -- cgit v1.2.3