attachement: switch to uuid + check permission in dl widget (#109)

Also added is_default_accessible() to Thread class as its owner may be a 
Topic with forum access restrictions or public main content (like 
Program)

[MIGRATION] This commit contains a new version of the schema. /!\ This 
migration breaks all attachments
This commit is contained in:
Darks 2022-04-26 23:29:11 +02:00
parent faf5bd184d
commit eb5ce1bd5c
Signed by: Darks
GPG Key ID: 7515644268BE1433
5 changed files with 65 additions and 5 deletions

View File

@ -1,13 +1,16 @@
from werkzeug.utils import secure_filename
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import backref
from app import db
from app.utils.filesize import filesize
from config import V5Config
import os
import uuid
class Attachment(db.Model):
__tablename__ = 'attachment'
id = db.Column(db.Integer, primary_key=True)
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
# Original name of the file
name = db.Column(db.Unicode(64))
@ -24,11 +27,11 @@ class Attachment(db.Model):
@property
def path(self):
return os.path.join(V5Config.DATA_FOLDER, "attachments",
f"{self.id:05}", self.name)
f"{self.id}", self.name)
@property
def url(self):
return f"/fichiers/{self.id:05}/{self.name}"
return f"/fichiers/{self.id}/{self.name}"
def __init__(self, file, comment):

View File

@ -53,6 +53,13 @@ class Thread(db.Model):
return self.owner_program[0]
return None
def is_default_accessible(self):
if self.owner_program != []:
return True
if self.owner_topic != []:
return self.owner_topic[0].forum.is_default_accessible()
return False
def delete(self):
"""Recursively delete thread and all associated contents."""
# Remove reference to top comment

View File

@ -250,6 +250,10 @@ class Member(User):
post = comment.thread.owner_post
return self.can_edit_post(post) and (comment.author == post.author)
def can_access_file(self, file):
"""Whether this member can access the file."""
return self.can_access_post(file.comment)
def update(self, **data):
"""
Update all or part of the user's metadata. The [data] dictionary

View File

@ -13,8 +13,10 @@ License: [BSD](https://opensource.org/licenses/bsd-license.php)
from markdown.extensions import Extension
from markdown.inlinepatterns import InlineProcessor
from uuid import UUID
import xml.etree.ElementTree as etree
from flask import url_for, render_template
from flask_login import current_user
from app.utils.unicode_names import normalize
from app.utils.filters.humanize import humanize
from app.models.poll import Poll
@ -36,7 +38,7 @@ class PCLinkExtension(Extension):
self.md = md
# append to end of inline patterns
PCLINK_RE = r'<([a-z]+): ?(\w+)>'
PCLINK_RE = r'<([a-z]+): ?([\w-]+)>'
pclinkPattern = PCLinksInlineProcessor(PCLINK_RE, self.getConfigs())
pclinkPattern.md = md
md.inlinePatterns.register(pclinkPattern, 'pclink', 135)
@ -125,7 +127,7 @@ def handleUser(content_id, context):
def handleFile(content_id, context):
try:
content_id = int(content_id)
UUID(content_id)
except ValueError:
return "[ID de fichier invalide]"
@ -134,6 +136,16 @@ def handleFile(content_id, context):
if file is None:
return "[Fichier non trouvé]"
# Manage permissions
# TODO: use Guest methods when implemented
if current_user.is_authenticated:
if not current_user.can_access_file(file):
return "[Accès au fichier refusé]"
else:
if not file.comment.thread.is_default_accessible():
return "[Accès au fichier refusé]"
html = render_template('widgets/download_button.html', file=file)
html = html.replace('\n', '') # Needed to avoid lots of <br> due to etree
return etree.fromstring(html)

View File

@ -0,0 +1,34 @@
"""Switched attachment id to UUID
Revision ID: 72df33816b21
Revises: d2227d2479e2
Create Date: 2022-04-26 21:50:05.466388
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '72df33816b21'
down_revision = 'd2227d2479e2'
branch_labels = None
depends_on = None
def upgrade():
# Create uuid-ossp extension, required to use uuid_generate_v4()
op.execute('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";')
# /!\ This operation will break all attachments /!\
op.execute("""ALTER TABLE attachment ALTER COLUMN id DROP DEFAULT,
ALTER COLUMN id SET DATA TYPE UUID USING (uuid_generate_v4()),
ALTER COLUMN id SET DEFAULT uuid_generate_v4();""")
# ### end Alembic commands ###
def downgrade():
# /!\ This operation will break all attachments /!\
op.execute("""ALTER TABLE attachment ALTER COLUMN id DROP DEFAULT,
ALTER COLUMN id SET DATA TYPE integer USING nextval('attachment_id_seq'::regclass),
ALTER COLUMN id SET DEFAULT nextval('attachment_id_seq'::regclass);""")
# ### end Alembic commands ###