Compare commits

...

3 Commits

Author SHA1 Message Date
Lephe 6f98cba65e
review of privileges and forum permissions
* Sorted privileges into categories, similar to the v4.3 style

Added privilege check utilities:
* Forum: is_news(), is_default_accessible() and is_default_postable()
* Member: can_access_forum(), can_post_in_forum(), can_edit_post(),
  and can_delete_post()

Unfortunately current_user is not a Guest when logged out, so one
cannot usually write current_user.can_*() without checking for
authentication first, so the checks are still somewhat verbose.

Reviewed forum permissions; the following permission issues have been
fixed (I have tested most but not all of them prior to fixing):

* app/routes/forum/index.py: Users that were not meant to access a
  forum could still obtain a listing of the topics
* app/routes/forum/topic.py: Users that were not meant to see topics
  could still read them by browsing the URL
* app/routes/forum/topic.py: Authenticated users could post in any
  topic, including ones that they should not have access to
* app/routes/posts/edit.py: Users with edit.posts (eg. mods) could edit
  and delete messages in forums they can't access (eg. creativecalc)

* app/templates/account/user.html: Users with admin panel access would
  see account editing links they can't use (affects developers)
* app/templates/base/navbar/forum.html: The "Forum" tab would list all
  forums including ones the user doesn't have access to
* app/templates/forum/index.html: Users would see every single forum,
  including ones they can't access
* app/template/widgets/thread.html: Anyone would see Edit/Delete links
  on every message, even though most were unusable

Miscellaneous changes:
* app/routes/forum/topic.py: Ordered comments by date as intended,
  which I assume worked by chance until now
* Removed the old assets/privs.txt files which is now superseded by the
  list implemented in app/data/groups.yaml

This commit changes group and forum information, run master.py with:
@> forums update
@> groups update
2021-02-26 18:32:45 +01:00
Lephe d50b58cd24
(random improvements on texts) 2021-02-26 18:31:10 +01:00
Lephe 3ee3794818
master: fix groups not fully updating
Fixes c8661ca50.
2021-02-26 18:31:10 +01:00
28 changed files with 219 additions and 158 deletions

View File

@ -1,10 +1,8 @@
# This file is a list of forums to create when setting up Planète Casio.
#
# * Keys are used as URLs paths and for unique identification.
# * Prefixes represent the privilege category for a forum. Owning privileges
# with this prefix allows the user to post in this forum and all its
# sub-forum regardless of their settings ("forum-root-*" are hyper powerful).
# * For open forums, use the prefix "open".
# * Prefixes are used to identify privileges for each forum, see groups.yaml
# for details.
/:
name: Forum de Planète Casio
@ -104,8 +102,8 @@
prefix: discussion
descr: Sujets hors-sujet et discussion libre.
# Limited-access board
# Prefixes "admin" and "assoc" are reserved for this and require special
# Limited-access boards
# Prefixes "admin" and "creativecalc" are reserved for this and require special
# privileges to list, read and edit topics and messages.
/admin:
@ -116,5 +114,5 @@
/creativecalc:
name: CreativeCalc
prefix: assoc
prefix: creativecalc
descr: Forum privé de l'association CreativeCalc, réservé aux membres.

View File

@ -1,63 +1,109 @@
# LIST OF PRIVILEGES:
#
# Access to specific forums (see forums.yaml for prefix values):
# forum.access.<prefix>
# forum.post.<prefix>
# forum.post-news
# forum.post-anywhere
# -> All forums are readable by default except <admin> and <creativecalc>
# -> All forums are writable by default except <admin>, <creativecalc>,
# children of <news>, and forums with children ("categories")
# -> Use member.can_access_forum(forum) and member.can_post_in_forum(forum)
#
# Access to extended publication methods:
# publish.schedule-posts
# publish.pin-posts
# publish.shared-files
#
# Moderation:
# edit.posts
# edit.tests
# edit.accounts
# edit.trophies
# delete.posts
# delete.tests
# delete.accounts
# delete.shared-files
# move.posts
#
# Shoutbox:
# shoutbox.kick
# shoutbox.ban
#
# Miscellaneous:
# misc.unlimited-pms
# misc.dev-infos
# misc.community-login
# misc.admin-panel
# misc.no-upload-limits
#
# TODO: PRIVILEGES NOT YET IMPLEMENTED:
# The features that these privileges control are not implemented yet, or the
# privilege checks are missing.
#
# publish.*
# edit.tests
# delete.tests delete.shared-files
# move.posts
# shoutbox.*
# misc.unlimited-pms
# misc.community-login
-
name: Administrateur
css: "color: #ee0000;"
descr: "Vous voyez Chuck Norris? Pareil."
privs: access-admin-board access-assoc-board write-news write-anywhere
upload-shared-files delete-shared-files
edit-posts delete-posts scheduled-posting
delete-content move-public-content move-private-content showcase-content
edit-static-content extract-posts
delete-notes delete-tests
shoutbox-kick shoutbox-ban
unlimited-pms footer-statistics community-login
access-admin-panel edit-account delete-account edit-trophies
delete_notification no-upload-limits
privs: forum.access.admin forum.access.creativecalc forum.post-news
forum.post-anywhere
publish.schedule-posts publish.pin-posts publish.shared-files
edit.posts edit.tests edit.accounts edit.trophies
delete.posts delete.tests delete.accounts delete.shared-files
move.posts
shoutbox.kick shoutbox.ban
misc.unlimited-pms misc.dev-infos misc.community-login misc.admin-panel
misc.no-upload-limits
-
name: Modérateur
css: "color: green;"
descr: "Maîtres du kick, ils sont là pour faire respecter un semblant d'ordre."
privs: access-admin-board
edit-posts delete-posts
move-public-content extract-posts
delete-notes delete-tests
shoutbox-kick shoutbox-ban
unlimited-pms no-upload-limits
privs: forum.access.admin
edit.posts edit.tests
delete.posts delete.tests
move.posts
shoutbox.kick shoutbox.ban
misc.unlimited-pms misc.no-upload-limits
-
name: Développeur
css: "color: #4169e1;"
descr: "Les développeurs maintiennent et améliorent le code du site."
privs: access-admin-board
upload-shared-files delete-shared-files
scheduled-posting
edit-static-content
unlimited-pms footer-statistics community-login
access-admin-panel no-upload-limits
privs: forum.access.admin forum.post-anywhere
publish.schedule-posts publish.shared-files
delete.shared-files
misc.unlimited-pms misc.dev-infos misc.community-login misc.admin-panel
-
name: Rédacteur
css: "color: blue;"
descr: "Rédigent les meilleurs articles de la page d'accueil, rien que pour
vous <3"
privs: access-admin-board write-news
upload-shared-files delete-shared-files
scheduled-posting
showcase-content edit-static-content
no-upload-limits
privs: forum.access.admin forum.post-news
publish.schedule-posts publish.pin-posts publish.shared-files
delete.shared-files
misc.no-upload-limits
-
name: Responsable communauté
css: "color: DarkOrange;"
descr: "Anime les pages Twitter et Facebook de Planète Casio et surveille
l'évolution du monde autour de nous !"
privs: access-admin-board write-news
upload-shared-files delete-shared-files
scheduled-posting
showcase-content
privs: forum.access.admin forum.post-news
publish.schedule-posts publish.pin-posts publish.shared-files
delete.shared-files
-
name: Partenaire
css: "color: purple;"
descr: "Membres de l'équipe d'administration des sites partenaires."
privs: write-news
upload-shared-files delete-shared-files
scheduled-posting
privs: forum.post-news
publish.schedule-posts publish.shared-files
delete.shared-files
-
name: Compte communautaire
css: "background:#d8d8d8; border-radius:4px; color:#303030; padding:1px 2px;"
@ -66,13 +112,13 @@
name: Robot
css: "color: #cf25d0;"
descr: "♫ Je suis Nono, le petit robot, l'ami d'Ulysse ♫"
privs: shoutbox-post shoutbox-kick shoutbox-ban
privs: shoutbox.kick shoutbox.ban
-
name: Membre de CreativeCalc
css: "color: #222222;"
descr: "CreativeCalc est l'association qui gère Planète Casio."
privs: access-assoc-board
privs: forum.access.creativecalc
-
name: No login
css: "color: #888888;"
descr: "Compte dont l'accès au site est désactivé."
name: No login
css: "color: #888888;"
descr: "Compte dont l'accès au site est désactivé."

View File

@ -20,7 +20,8 @@ class Forum(db.Model):
lazy=True, foreign_keys=parent_id)
# Other fields populated automatically through relations:
# <topics> List of topics in this exact forum (of type Topic)
# <sub_forums> Children forums
# <topics> List of topics in this exact forum (of type Topic)
# Some configuration
TOPICS_PER_PAGE = 30
@ -36,6 +37,19 @@ class Forum(db.Model):
else:
self.parent = parent
def is_news(self):
"""Whether this forum is a news board."""
return (self.parent is not None) and (self.parent.prefix == "news")
def is_default_accessible(self):
"""Whether this forum can be read without privileges."""
return (self.prefix != "admin") and (self.prefix != "creativecalc")
def is_default_postable(self):
"""Whether this forum can be posted to without privileges."""
return self.is_default_accessible() and (not self.is_news()) and \
(self.sub_forums == [])
def post_count(self):
"""Number of posts in every topic of the forum, without subforums."""
# TODO: optimize this with real ORM

View File

@ -9,6 +9,8 @@ class Topic(Post):
__mapper_args__ = {
'polymorphic_identity': __tablename__,
# Because there is an extra relation to Post (promotion), SQLAlchemy
# cannot guess which Post we inherit from; specify here.
'inherit_condition': id == Post.id
}

View File

@ -20,11 +20,11 @@ import os
class User(UserMixin, db.Model):
""" Website user that performs actions on the post """
""" Any website user, logged in (Member) or not (Guest) """
__tablename__ = 'user'
# User ID, should be used to refer to any user. Thea actual user can either
# User ID, should be used to refer to any user. The actual user can either
# be a guest (with IP as key) or a member (with this ID as key).
id = db.Column(db.Integer, primary_key=True)
# User type (polymorphic discriminator)
@ -55,7 +55,7 @@ class Guest(User):
# ID of the [User] entry
id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True)
# Reusable username, cannot be chosen as the name of a member
# but will be distinguished at rendering time if a member take it later
# but will be distinguished at rendering time if a member takes it later
name = db.Column(db.Unicode(User.NAME_MAXLEN))
def __init__(self, name):
@ -66,7 +66,7 @@ class Guest(User):
class Member(User):
""" Registered user with full access to the website's services """
""" Registered user with full access to the site's features """
__tablename__ = 'member'
__mapper_args__ = {'polymorphic_identity': __tablename__}
@ -116,10 +116,6 @@ class Member(User):
programs = db.relationship('Program')
comments = db.relationship('Comment')
# Displayed title
# title_id = db.Column(db.Integer, db.ForeignKey('title.id'))
# title = db.relationship('Title', foreign_keys=title_id)
# Other fields populated automatically through relations:
# <notifications> List of unseen notifications (of type Notification)
# <polls> Polls created by the member (of class Poll)
@ -152,6 +148,8 @@ class Member(User):
db.session.delete(self)
db.session.commit()
# Privilege checks
def priv(self, priv):
"""Check whether the member has the specified privilege."""
if SpecialPrivilege.query.filter_by(mid=self.id, priv=priv).first():
@ -166,6 +164,35 @@ class Member(User):
sp = SpecialPrivilege.query.filter_by(mid=self.id).all()
return sorted(row.priv for row in sp)
def can_access_forum(self, forum):
"""Whether this member can read the forum's contents."""
return forum.is_default_accessible() or \
self.priv(f"forum.access.{forum.prefix}")
def can_post_in_forum(self, forum):
"""Whether this member can post in the forum."""
return forum.is_default_postable() or \
(forum.is_news() and self.priv("forum.post-news")) or \
self.priv("forum.post.{forum.prefix}") or \
self.priv("forum.post-anywhere")
def can_access_post(self, post):
"""Whether this member can access the post's forum (if any)."""
if post.type == "comment" and post.thread.owner_topic:
return self.can_access_forum(post.thread.owner_post.forum)
# Posts from other types of content are all public
return True
def can_edit_post(self, post):
"""Whether this member can edit the post."""
return self.can_access_post(post) and \
((post.author == self) or self.priv("edit.posts"))
def can_delete_post(self, post):
"""Whether this member can delete the post."""
return self.can_access_post(post) and \
((post.author == self) or self.priv("delete.posts"))
def update(self, **data):
"""
Update all or part of the user's metadata. The [data] dictionary

View File

@ -14,7 +14,7 @@ from config import V5Config
@app.route('/admin/compte/<user_id>/editer', methods=['GET', 'POST'])
@priv_required('access-admin-panel', 'edit-account')
@priv_required('misc.admin-panel', 'edit.accounts')
def adm_edit_account(user_id):
user = Member.query.filter_by(id=user_id).first_or_404()
@ -119,7 +119,7 @@ def adm_edit_account(user_id):
@app.route('/admin/compte/<user_id>/supprimer', methods=['GET', 'POST'])
@priv_required('access-admin-panel', 'delete-account')
@priv_required('misc.admin-panel', 'delete.accounts')
def adm_delete_account(user_id):
user = Member.query.filter_by(id=user_id).first_or_404()

View File

@ -6,7 +6,7 @@ from app.utils.render import render
# TODO: add pagination & moderation tools (deletion)
@app.route('/admin/fichiers', methods=['GET'])
@priv_required('access-admin-panel')
@priv_required('misc.admin-panel')
def adm_attachments():
attachments = Attachment.query.all()

View File

@ -4,7 +4,7 @@ from app import app
from config import V5Config
@app.route('/admin/config', methods=['GET'])
@priv_required('access-admin-panel')
@priv_required('misc.admin-panel')
def adm_config():
config = {k: getattr(V5Config, k) for k in [
"DOMAIN", "DB_NAME", "USE_LDAP", "LDAP_ROOT", "LDAP_ENV",

View File

@ -4,7 +4,7 @@ from app.models.forum import Forum
from app import app, db
@app.route('/admin/forums', methods=['GET'])
@priv_required('access-admin-panel')
@priv_required('misc.admin-panel')
def adm_forums():
main_forum = Forum.query.filter_by(parent=None).first()

View File

@ -10,7 +10,7 @@ import os
@app.route('/admin/groupes', methods=['GET', 'POST'])
@priv_required('access-admin-panel')
@priv_required('misc.admin-panel')
def adm_groups():
# Users with either groups or special privileges
users_groups = Member.query.join(GroupMember)

View File

@ -4,6 +4,6 @@ from app import app
@app.route('/admin', methods=['GET'])
@priv_required('access-admin-panel')
@priv_required('misc.admin-panel')
def adm():
return render('admin/index.html')

View File

@ -6,7 +6,7 @@ from app import app, db
@app.route('/admin/membres', methods=['GET', 'POST'])
@priv_required('access-admin-panel')
@priv_required('misc.admin-panel')
def adm_members():
users = Member.query.all()
users = sorted(users, key = lambda x: x.name)

View File

@ -7,7 +7,7 @@ from app import app, db
@app.route('/admin/trophees', methods=['GET', 'POST'])
@priv_required('access-admin-panel', 'edit-trophies')
@priv_required('misc.admin-panel', 'edit.trophies')
def adm_trophies():
form = TrophyForm()
if request.method == "POST":
@ -31,7 +31,7 @@ def adm_trophies():
@app.route('/admin/trophees/<trophy_id>/editer', methods=['GET', 'POST'])
@priv_required('access-admin-panel', 'edit-trophies')
@priv_required('misc.admin-panel', 'edit.trophies')
def adm_edit_trophy(trophy_id):
trophy = Trophy.query.filter_by(id=trophy_id).first_or_404()
@ -59,7 +59,7 @@ def adm_edit_trophy(trophy_id):
@app.route('/admin/trophees/<trophy_id>/supprimer', methods=['GET', 'POST'])
@priv_required('access-admin-panel', 'edit-trophies')
@priv_required('misc.admin-panel', 'edit.trophies')
def adm_delete_trophy(trophy_id):
trophy = Trophy.query.filter_by(id=trophy_id).first_or_404()

View File

@ -20,21 +20,18 @@ def forum_index():
@app.route('/forum/<forum:f>/', methods=['GET', 'POST'])
@app.route('/forum/<forum:f>/p/<int:page>', methods=['GET', 'POST'])
def forum_page(f, page=1):
if not f.is_default_accessible() and not (
current_user.is_authenticated and current_user.can_access_forum(f)):
abort(403)
if current_user.is_authenticated:
form = TopicCreationForm()
else:
form = AnonymousTopicCreationForm()
# TODO: do not hardcode name of news forums
if form.validate_on_submit() and (
# User can write anywhere
(current_user.is_authenticated and current_user.priv('write-anywhere'))
# Forum is news forum TODO: add good condition to check if it's news
or ("/actus" in f.url and current_user.is_authenticated
and current_user.priv('write-news'))
# Forum is not news and is a leaf:
or ("/actus" not in f.url and not f.sub_forums)) and (
V5Config.ENABLE_GUEST_POST or current_user.is_authenticated):
(V5Config.ENABLE_GUEST_POST and f.is_default_postable()) or \
(current_user.is_authenticated and current_user.can_post_in_forum(f))):
# Manage author
if current_user.is_authenticated:

View File

@ -18,6 +18,10 @@ from datetime import datetime
def forum_topic(f, page):
t, page = page
if not f.is_default_accessible() and not (
current_user.is_authenticated and current_user.can_access_forum(f)):
abort(403)
# Quick n' dirty workaround to converters
if f != t.forum:
abort(404)
@ -27,8 +31,10 @@ def forum_topic(f, page):
else:
form = AnonymousCommentForm()
if form.validate_on_submit() and \
(V5Config.ENABLE_GUEST_POST or current_user.is_authenticated):
if form.validate_on_submit() and (
V5Config.ENABLE_GUEST_POST or \
(current_user.is_authenticated and current_user.can_post_in_forum(f))):
# Manage author
if current_user.is_authenticated:
author = current_user
@ -70,8 +76,8 @@ def forum_topic(f, page):
if page == -1:
page = (t.thread.comments.count() - 1) // Thread.COMMENTS_PER_PAGE + 1
comments = t.thread.comments.paginate(page,
Thread.COMMENTS_PER_PAGE, True)
comments = t.thread.comments.order_by(Comment.date_created.asc()) \
.paginate(page, Thread.COMMENTS_PER_PAGE, True)
# Anti-necropost
last_com = t.thread.comments.order_by(desc(Comment.date_modified)).first()

View File

@ -8,7 +8,6 @@ from flask import redirect, url_for, abort, request
from flask_login import login_required, current_user
@app.route('/post/<int:postid>', methods=['GET','POST'])
# TODO: Allow guest edit of posts
@login_required
def edit_post(postid):
# TODO: Maybe not safe
@ -17,8 +16,8 @@ def edit_post(postid):
p = Post.query.filter_by(id=postid).first_or_404()
# TODO: Check whether privileged user has access to board
if p.author != current_user and not current_user.priv("edit-posts"):
# Check permissions. TODO: Allow guests to edit their posts
if current_user.is_anonymous or not current_user.can_edit_post(p):
abort(403)
if p.type == "comment":
@ -44,8 +43,7 @@ def edit_post(postid):
def delete_post(postid):
p = Post.query.filter_by(id=postid).first_or_404()
# TODO: Check whether privileged user has access to board
if p.author != current_user and not current_user.priv("delete-posts"):
if current_user.is_anonymous or not current_user.can_delete_post(p):
abort(403)
for a in p.attachments:

View File

@ -13,7 +13,7 @@
{% if current_user.is_authenticated %}
{% if current_user == member %}
<div><a href="{{ url_for('edit_account') }}">Modifier le compte</a></div>
{% elif current_user.priv('access-admin-panel') %}
{% elif current_user.priv('edit.accounts') %}
<div><a href="{{ url_for('adm_edit_account', user_id=member.id) }}">Modifier le compte</a></div>
{% endif %}
{% endif %}

View File

@ -1,7 +1,7 @@
<footer>
<p>Planète Casio est un site communautaire non affilié à CASIO. Toute reproduction de Planète Casio, même partielle, est interdite.</p>
<p>Les programmes et autres publications présentes sur Planète Casio restent la propriété de leurs auteurs et peuvent être soumis à des licences ou des copyrights.</p>
{% if current_user.is_authenticated and current_user.priv('footer-statistics') %}
{% if current_user.is_authenticated and current_user.priv('misc.dev-infos') %}
<p>Page générée en {{ g.request_time() }}</p>
{% endif %}
<p>Ceci est un environnement de test. Tout contenu peut être supprimé sans avertissement préalable.</p>

View File

@ -26,7 +26,7 @@
<path fill="#ffffff" d="M12,15.39L8.24,17.66L9.23,13.38L5.91,10.5L10.29,10.13L12,6.09L13.71,10.13L18.09,10.5L14.77,13.38L15.76,17.66M22,9.24L14.81,8.63L12,2L9.19,8.63L2,9.24L7.45,13.97L5.82,21L12,17.27L18.18,21L16.54,13.97L22,9.24Z"></path>
</svg>Topics favoris
</a>
{% if current_user.priv('access-admin-panel') %}
{% if current_user.priv('misc.admin-panel') %}
<a href="{{ url_for('adm') }}">
<svg viewBox="0 0 24 24">
<path fill="#ffffff" d="M3,1H19A1,1 0 0,1 20,2V6A1,1 0 0,1 19,7H3A1,1 0 0,1 2,6V2A1,1 0 0,1 3,1M3,9H19A1,1 0 0,1 20,10V10.67L17.5,9.56L11,12.44V15H3A1,1 0 0,1 2,14V10A1,1 0 0,1 3,9M3,17H11C11.06,19.25 12,21.4 13.46,23H3A1,1 0 0,1 2,22V18A1,1 0 0,1 3,17M8,5H9V3H8V5M8,13H9V11H8V13M8,21H9V19H8V21M4,3V5H6V3H4M4,11V13H6V11H4M4,19V21H6V19H4M17.5,12L22,14V17C22,19.78 20.08,22.37 17.5,23C14.92,22.37 13,19.78 13,17V14L17.5,12M17.5,13.94L15,15.06V17.72C15,19.26 16.07,20.7 17.5,21.06V13.94Z"></path>

View File

@ -10,7 +10,10 @@
<hr>
{% for f in main_forum.sub_forums %}
<a href="{{ url_for('forum_page', f=f) }}">{{ f.name }}</a>
{% if f.is_default_accessible() or
(current_user.is_authenticated and current_user.can_access_forum(f)) %}
<a href="{{ url_for('forum_page', f=f) }}">{{ f.name }}</a>
{% endif %}
{% endfor %}
<hr>

View File

@ -48,10 +48,8 @@
</table>
{% endif %}
{% if (current_user.is_authenticated and current_user.priv('write-anywhere'))
or ("/actus" in f.url and current_user.is_authenticated and current_user.priv('write-news'))
or ("/actus" not in f.url and not f.sub_forums)
and (current_user.is_authenticated or V5Config.ENABLE_GUEST_POST) %}
{% if (V5Config.ENABLE_GUEST_POST and f.is_default_postable())
or (current_user.is_authenticated and current_user.can_post_in_forum(f)) %}
<div class=form>
<h2>Créer un nouveau sujet</h2>
<form action="" method="post" enctype="multipart/form-data">

View File

@ -9,9 +9,9 @@
<p>
Bienvenue sur le forum de Planète Casio ! Vous pouvez créer des
nouveaux sujets ou poster des réponses avec un compte
{% if not current_user.is_authenticated %}
{%- if not current_user.is_authenticated %}
ou en postant en tant qu'invité
{% endif %}
{%- endif -%}
.
</p>
@ -20,22 +20,28 @@
{% else %}
{% for l1 in main_forum.sub_forums %}
<table class=forumlist>
<tr><th>{{ l1.name }}</th><th>Nombre de sujets</th></tr>
{% if l1.is_default_accessible() or
(current_user.is_authenticated and current_user.can_access_forum(l1)) %}
<table class=forumlist>
<tr><th>{{ l1.name }}</th><th>Nombre de sujets</th></tr>
{% if l1.sub_forums == [] %}
<tr><td><a href='/forum{{ l1.url }}'>{{ l1.name }}</a></td>
<td>{{ l1.topics.count() }}</td></tr>
<tr><td>{{ l1.descr }}</td><td></td></tr>
{% endif %}
{% if l1.sub_forums == [] %}
<tr><td><a href='/forum{{ l1.url }}'>{{ l1.name }}</a></td>
<td>{{ l1.topics.count() }}</td></tr>
<tr><td>{{ l1.descr }}</td><td></td></tr>
{% endif %}
{% for l2 in l1.sub_forums %}
<tr><td><a href='/forum{{ l2.url }}'>{{ l2.name }}</td>
<td>{{ l2.topics.count() }}</td></tr>
<tr><td>{{ l2.descr }}</td><td></td></tr>
{% endfor %}
{% for l2 in l1.sub_forums %}
{% if l2.is_default_accessible() or
(current_user.is_authenticated and current_user.can_access_forum(l2)) %}
<tr><td><a href='/forum{{ l2.url }}'>{{ l2.name }}</td>
<td>{{ l2.topics.count() }}</td></tr>
<tr><td>{{ l2.descr }}</td><td></td></tr>
{% endif %}
{% endfor %}
</table>
</table>
{% endif %}
{% endfor %}
{% endif %}

View File

@ -1,6 +1,6 @@
{% extends "base/base.html" %}
{% import "widgets/editor.html" as widget_editor %}
{% import "widgets/thread.html" as widget_thread %}
{% import "widgets/thread.html" as widget_thread with context %}
{% import "widgets/user.html" as widget_user %}
{% import "widgets/pagination.html" as widget_pagination with context %}
@ -25,7 +25,8 @@
</div>
{% endif %}
{% if current_user.is_authenticated or V5Config.ENABLE_GUEST_POST %}
{% if V5Config.ENABLE_GUEST_POST
or (current_user.is_authenticated and current_user.can_post_in_forum(t.forum)) %}
<div class=form>
<h3>Commenter le sujet</h3>
<form action="" method="post" enctype="multipart/form-data">
@ -48,7 +49,7 @@
<div>{{ form.submit(class_='bg-ok') }}</div>
</form>
{% endif %}
</div>
{% endif %}
</section>
{% endblock %}

View File

@ -13,9 +13,14 @@
{% if c.date_created != c.date_modified %}
<div>Modifié le {{ c.date_modified|date }}</div>
{% endif %}
<div><a href="{{ request.path }}#{{ c.id }}">Permalink</a></div>
<div><a href="{{ url_for('edit_post', postid=c.id, r=request.path) }}">Modifier</a></div>
<div><a href="{{ url_for('delete_post', postid=c.id, csrf_token=csrf_token()) }}" onclick="return confirm('Le message sera supprimé')">Supprimer</a></div>
<div><a href="{{ request.path }}#{{ c.id }}">Permalien</a></div>
{# TODO: Let guests edit their posts #}
{% if current_user.is_authenticated and current_user.can_edit_post(c) %}
<div><a href="{{ url_for('edit_post', postid=c.id, r=request.path) }}">Modifier</a></div>
{% endif %}
{% if current_user.is_authenticated and current_user.can_delete_post(c) %}
<div><a href="{{ url_for('delete_post', postid=c.id, csrf_token=csrf_token()) }}" onclick="return confirm('Le message sera supprimé')">Supprimer</a></div>
{% endif %}
</div>
{{ c.text|md }}

View File

@ -13,7 +13,7 @@ def priv_required(*perms):
Example:
@app.route('/admin')
@priv_required('access-admin-board')
@priv_required('forum.access.admin')
def admin_board():
pass

View File

@ -13,7 +13,7 @@ def optional(form, files):
def count(form, files):
if current_user.is_authenticated:
if current_user.priv("no-upload-limits"):
if current_user.priv("misc.no-upload-limits"):
return
if len(files.data) > 100: # 100 files for a authenticated user
raise ValidationError("100 fichiers maximum autorisés")
@ -48,7 +48,7 @@ def size(form, files):
"""There is no global limit to file sizes"""
size = sum([filesize(f) for f in files.data])
if current_user.is_authenticated:
if current_user.priv("no-upload-limits"):
if current_user.priv("misc.no-upload-limits"):
return
if size > 5e6: # 5 Mo per comment for an authenticated user
raise ValidationError("Fichiers trop lourds (max 5Mo)")

View File

@ -1,42 +0,0 @@
# Privileges
Read/write access to forum boards:
access-admin-board Administration board of the forum
access-assoc-board CreativeCalc discussion board
write-news Post articles on the news board
Shared file upload (like /Fr/adpc/img.php for any file):
upload-shared-files Upload files and images on the website server
delete-shared-files Delete files uploaded with upload-shared-files
Post management:
edit-posts Edit any post on the website
delete-posts Remove any post from the website
scheduled-posting Schedule a post or content creation in the future
Content (topic, progs, tutos, etc) management:
delete-content Delete whole topics, program pages, or tutorials
move-public-content Change the section of a page in a public section
move-private-content Change the section of a page in a private section
showcase-content Manage stocky content (post-its)
edit-static-content Edit static content pages
extract-posts Move out-of-topic posts to a new places
Program evaluation:
delete-notes Delete program notes
delete-tests Delete program tests
Shoutbox:
shoutbox-post Write messages in the shoutbox
shoutbox-kick Kick people using the shoutbox
shoutbox-ban Ban people using the shoutbox
Miscellaenous:
unlimited-pms Removes the limit on the number of private messages
footer-statistics View performance statistics in the page footer
community-login Automatically login as a community account
Administration panel:
access-admin-panel Administration panel of website
edit-account Edit details of any account
delete-account Remove member accounts

View File

@ -148,6 +148,8 @@ def update_groups():
print(f"[group] Created {g.name}")
db.session.commit()
def create_common_accounts():
# Clean up common accounts