Short description
This patch add an "attach" acl entry, that control whether or not an user can attach files to a specific page.
Why?
MoinMoin offers two features that help prevent abuse of an open wiki:
ACLs can be used to make pages read-only or restricted to a specific group of users (for example, you might want to secure the FrontPage of your wiki, while keeping open the other pages).
- Subscription enable the administrator to monitor activity on the wiki and to react in case of abuse.
Unfortunately, it's not that easy when it comes to attachments.
- You can enable or disable attachement for the whole wiki.
- If attachments are enabled, any user with write access can add an attachment.
- Currently, people subscribing to a page get no notification of added attachments. Moreover, a notification will not tell you what is the content of the attachment.
It is therefore much more difficult to prevent abuse of attachements than texts.
This added ACL entry would enable a site administrator to restrict adding attachments to trusted users (as attachments are much more difficult to monitor), while keeping the wiki open to add or change pages.
Restricting access to Know users is not enough to prevent abuse. Bots are able to register to a wiki and the add attachments.
This feature will be useful to all user hosting an open wiki on the internet.
Questions
Shouldn't the patch rather check "write" AND "attach" rights instead of only "attach" rights?
Or use some "readattach", "writeattach", "deleteattach" rights?
BTW, when UnifyPagesAndAttachments gets implemented, files will just have normal ACLs, like pages do.
My idea was to use attach as a special case of write. The write right being limited to pages and the attach right to attachments. It seems to me logicial that way as the page is not really updated when you add an attachement. It therefore reflected the apparent wiki structure. The "write" + "attach" option would be slightly less intuitive. The "readattach", "writeattach", "deleteattach" option seems to be a little complex. However, I'd be happy if any of these options makes it into MoinMoin. Having files with their own ACL would also be very nice. -- JeanPhilippeGuérard 2007-09-12 18:04:50
Patch to moin-1.6
# HG changeset patch # User Jean-Philippe Guérard <jean-philippe.guerard@tigreraye.org> # Date 1178928781 -7200 # Node ID 4352478d65d7c8ad72391bbee2a0c739c1b81c8e # Parent dc9a3809af61aa74bdb4861f1ab7d02f8b730c0e Implementation of an attach ACL entry. diff -r dc9a3809af61 -r 4352478d65d7 MoinMoin/_tests/test_security.py --- a/MoinMoin/_tests/test_security.py Mon May 07 22:50:51 2007 +0200 +++ b/MoinMoin/_tests/test_security.py Sat May 12 02:13:01 2007 +0200 @@ -200,6 +200,7 @@ class AclTestCase(unittest.TestCase): acl_rights = [ "Admin1,Admin2:read,write,delete,revert,admin " "Admin3:read,write,admin " + "Admin4:read,write,attach,admin " "JoeDoe:read,write " "name with spaces,another one:read,write " "CamelCase,extended name:read,write " @@ -215,6 +216,7 @@ class AclTestCase(unittest.TestCase): ('Admin1', ('read', 'write', 'admin', 'revert', 'delete')), ('Admin2', ('read', 'write', 'admin', 'revert', 'delete')), ('Admin3', ('read', 'write', 'admin')), + ('Admin4', ('read', 'write', 'attach', 'admin')), ('JoeDoe', ('read', 'write')), ('SomeGuy', ('read',)), # Extended names or mix of extended and CamelCase diff -r dc9a3809af61 -r 4352478d65d7 MoinMoin/action/AttachFile.py --- a/MoinMoin/action/AttachFile.py Mon May 07 22:50:51 2007 +0200 +++ b/MoinMoin/action/AttachFile.py Sat May 12 02:13:01 2007 +0200 @@ -347,7 +347,7 @@ def _build_filelist(request, pagename, s viewlink += ' | <a href="%(baseurl)s/%(urlpagename)s?action=%(action)s&do=install&target=%(urlfile)s">%(label_install)s</a>' % parmdict elif (zipfile.is_zipfile(os.path.join(attach_dir, file).encode(config.charset)) and mt.minor == 'zip' and request.user.may.read(pagename) and request.user.may.delete(pagename) - and request.user.may.write(pagename)): + and request.user.may.attach(pagename)): viewlink += ' | <a href="%(baseurl)s/%(urlpagename)s?action=%(action)s&do=unzip&target=%(urlfile)s">%(label_unzip)s</a>' % parmdict @@ -477,7 +477,7 @@ def send_uploadform(pagename, request): request.write('<p>%s</p>' % _('You are not allowed to view this page.')) return - writeable = request.user.may.write(pagename) + writeable = request.user.may.attach(pagename) # First send out the upload new attachment form on top of everything else. # This avoids usability issues if you have to scroll down a lot to upload @@ -544,14 +544,14 @@ def execute(pagename, request): elif 'do' not in request.form: upload_form(pagename, request) elif request.form['do'][0] == 'savedrawing': - if request.user.may.write(pagename): + if request.user.may.attach(pagename): save_drawing(pagename, request) request.emit_http_headers() request.write("OK") else: msg = _('You are not allowed to save a drawing on this page.') elif request.form['do'][0] == 'upload': - if request.user.may.write(pagename): + if request.user.may.attach(pagename): if 'file' in request.form: do_upload(pagename, request) else: @@ -589,7 +589,7 @@ def execute(pagename, request): else: msg = _('You are not allowed to get attachments from this page.') elif request.form['do'][0] == 'unzip': - if request.user.may.delete(pagename) and request.user.may.read(pagename) and request.user.may.write(pagename): + if request.user.may.delete(pagename) and request.user.may.read(pagename) and request.user.may.attach(pagename): unzip_file(pagename, request) else: msg = _('You are not allowed to unzip attachments of this page.') @@ -732,7 +732,7 @@ def move_file(request, pagename, new_pag _ = request.getText newpage = Page(request, new_pagename) - if newpage.exists(includeDeleted=1) and request.user.may.write(new_pagename) and request.user.may.delete(pagename): + if newpage.exists(includeDeleted=1) and request.user.may.attach(new_pagename) and request.user.may.delete(pagename): new_attachment_path = os.path.join(getAttachDir(request, new_pagename, create=1), new_attachment).encode(config.charset) attachment_path = os.path.join(getAttachDir(request, pagename), diff -r dc9a3809af61 -r 4352478d65d7 MoinMoin/config/multiconfig.py --- a/MoinMoin/config/multiconfig.py Mon May 07 22:50:51 2007 +0200 +++ b/MoinMoin/config/multiconfig.py Sat May 12 02:13:01 2007 +0200 @@ -188,10 +188,10 @@ class DefaultConfig: DesktopEdition = False # All acl_rights_* lines must use unicode! - acl_rights_default = u"Trusted:read,write,delete,revert Known:read,write,delete,revert All:read,write" + acl_rights_default = u"Trusted:read,write,attach,delete,revert Known:read,write,delete,revert All:read,write" acl_rights_before = u"" acl_rights_after = u"" - acl_rights_valid = ['read', 'write', 'delete', 'revert', 'admin'] + acl_rights_valid = ['read', 'write', 'attach', 'delete', 'revert', 'admin'] acl_hierarchic = False actions_excluded = [] # ['DeletePage', 'AttachFile', 'RenamePage', 'test', ] diff -r dc9a3809af61 -r 4352478d65d7 MoinMoin/security/__init__.py --- a/MoinMoin/security/__init__.py Mon May 07 22:50:51 2007 +0200 +++ b/MoinMoin/security/__init__.py Sat May 12 02:13:01 2007 +0200 @@ -228,7 +228,7 @@ class AccessControlList: cfg.acl_rights_default It is is ONLY used when no other ACLs are given. - Default: "Known:read,write,delete All:read,write", + Default: "Trusted:read,write,attach,delete,revert Known:read,write,delete,revert All:read,write", cfg.acl_rights_before When the page has ACL entries, this will be inserted BEFORE @@ -243,7 +243,7 @@ class AccessControlList: cfg.acl_rights_valid These are the acceptable (known) rights (and the place to extend, if necessary). - Default: ["read", "write", "delete", "admin"] + Default: ["read", "write", "attach", "delete", "admin"] ''' special_users = ["All", "Known", "Trusted"] # order is important diff -r dc9a3809af61 -r 4352478d65d7 wikiconfig.py --- a/wikiconfig.py Mon May 07 22:50:51 2007 +0200 +++ b/wikiconfig.py Sat May 12 02:13:01 2007 +0200 @@ -15,7 +15,7 @@ class Config(DefaultConfig): data_underlay_dir = os.path.join(moinmoin_dir, 'wiki', 'underlay') DesktopEdition = True # give all local users full powers - acl_rights_default = u"All:read,write,delete,revert,admin" + acl_rights_default = u"All:read,write,attach,delete,revert,admin" surge_action_limits = None # no surge protection sitename = u'MoinMoin DesktopEdition' logo_string = u'<img src="/moin_static160/common/moinmoin.png" alt="MoinMoin Logo">'
Patch to moin-1.7
# HG changeset patch # User Jean-Philippe Guérard <jean-philippe.guerard@tigreraye.org> # Date 1178929623 -7200 # Node ID 0c2f60b3c01fb32b8079718b51358023f39fe378 # Parent a63c473a100db0f52c8edea3ddfa9dfcb0749e64 Add an attach ACL entry. diff -r a63c473a100d -r 0c2f60b3c01f MoinMoin/_tests/test_security.py --- a/MoinMoin/_tests/test_security.py Sat May 05 15:26:15 2007 +0200 +++ b/MoinMoin/_tests/test_security.py Sat May 12 02:27:03 2007 +0200 @@ -199,6 +199,7 @@ class TestAcl(unittest.TestCase): acl_rights = [ "Admin1,Admin2:read,write,delete,revert,admin " "Admin3:read,write,admin " + "Admin4:read,write,attach,admin " "JoeDoe:read,write " "name with spaces,another one:read,write " "CamelCase,extended name:read,write " @@ -214,6 +215,7 @@ class TestAcl(unittest.TestCase): ('Admin1', ('read', 'write', 'admin', 'revert', 'delete')), ('Admin2', ('read', 'write', 'admin', 'revert', 'delete')), ('Admin3', ('read', 'write', 'admin')), + ('Admin4', ('read', 'write', 'attach', 'admin')), ('JoeDoe', ('read', 'write')), ('SomeGuy', ('read',)), # Extended names or mix of extended and CamelCase diff -r a63c473a100d -r 0c2f60b3c01f MoinMoin/action/AttachFile.py --- a/MoinMoin/action/AttachFile.py Sat May 05 15:26:15 2007 +0200 +++ b/MoinMoin/action/AttachFile.py Sat May 12 02:27:03 2007 +0200 @@ -347,7 +347,7 @@ def _build_filelist(request, pagename, s viewlink += ' | <a href="%(baseurl)s/%(urlpagename)s?action=%(action)s&do=install&target=%(urlfile)s">%(label_install)s</a>' % parmdict elif (zipfile.is_zipfile(os.path.join(attach_dir, file).encode(config.charset)) and mt.minor == 'zip' and request.user.may.read(pagename) and request.user.may.delete(pagename) - and request.user.may.write(pagename)): + and request.user.may.attach(pagename)): viewlink += ' | <a href="%(baseurl)s/%(urlpagename)s?action=%(action)s&do=unzip&target=%(urlfile)s">%(label_unzip)s</a>' % parmdict @@ -477,7 +477,7 @@ def send_uploadform(pagename, request): request.write('<p>%s</p>' % _('You are not allowed to view this page.')) return - writeable = request.user.may.write(pagename) + writeable = request.user.may.attach(pagename) # First send out the upload new attachment form on top of everything else. # This avoids usability issues if you have to scroll down a lot to upload @@ -544,14 +544,14 @@ def execute(pagename, request): elif 'do' not in request.form: upload_form(pagename, request) elif request.form['do'][0] == 'savedrawing': - if request.user.may.write(pagename): + if request.user.may.attach(pagename): save_drawing(pagename, request) request.emit_http_headers() request.write("OK") else: msg = _('You are not allowed to save a drawing on this page.') elif request.form['do'][0] == 'upload': - if request.user.may.write(pagename): + if request.user.may.attach(pagename): if 'file' in request.form: do_upload(pagename, request) else: @@ -589,7 +589,7 @@ def execute(pagename, request): else: msg = _('You are not allowed to get attachments from this page.') elif request.form['do'][0] == 'unzip': - if request.user.may.delete(pagename) and request.user.may.read(pagename) and request.user.may.write(pagename): + if request.user.may.delete(pagename) and request.user.may.read(pagename) and request.user.may.attach(pagename): unzip_file(pagename, request) else: msg = _('You are not allowed to unzip attachments of this page.') @@ -732,7 +732,7 @@ def move_file(request, pagename, new_pag _ = request.getText newpage = Page(request, new_pagename) - if newpage.exists(includeDeleted=1) and request.user.may.write(new_pagename) and request.user.may.delete(pagename): + if newpage.exists(includeDeleted=1) and request.user.may.attach(new_pagename) and request.user.may.delete(pagename): new_attachment_path = os.path.join(getAttachDir(request, new_pagename, create=1), new_attachment).encode(config.charset) attachment_path = os.path.join(getAttachDir(request, pagename), diff -r a63c473a100d -r 0c2f60b3c01f MoinMoin/config/multiconfig.py --- a/MoinMoin/config/multiconfig.py Sat May 05 15:26:15 2007 +0200 +++ b/MoinMoin/config/multiconfig.py Sat May 12 02:27:03 2007 +0200 @@ -208,10 +208,10 @@ class DefaultConfig: DesktopEdition = False # All acl_rights_* lines must use unicode! - acl_rights_default = u"Trusted:read,write,delete,revert Known:read,write,delete,revert All:read,write" + acl_rights_default = u"Trusted:read,write,attach,delete,revert Known:read,write,delete,revert All:read,write" acl_rights_before = u"" acl_rights_after = u"" - acl_rights_valid = ['read', 'write', 'delete', 'revert', 'admin'] + acl_rights_valid = ['read', 'write', 'attach', 'delete', 'revert', 'admin'] actions_excluded = [] # ['DeletePage', 'AttachFile', 'RenamePage', 'test', ] allow_xslt = False diff -r a63c473a100d -r 0c2f60b3c01f MoinMoin/security/__init__.py --- a/MoinMoin/security/__init__.py Sat May 05 15:26:15 2007 +0200 +++ b/MoinMoin/security/__init__.py Sat May 12 02:27:03 2007 +0200 @@ -156,7 +156,7 @@ class AccessControlList: cfg.acl_rights_default It is is ONLY used when no other ACLs are given. - Default: "Known:read,write,delete All:read,write", + Default: "Trusted:read,write,attach,delete,revert Known:read,write,delete,revert All:read,write", cfg.acl_rights_before When the page has ACL entries, this will be inserted BEFORE @@ -171,7 +171,7 @@ class AccessControlList: cfg.acl_rights_valid These are the acceptable (known) rights (and the place to extend, if necessary). - Default: ["read", "write", "delete", "admin"] + Default: ["read", "write", "attach", "delete", "admin"] ''' special_users = ["All", "Known", "Trusted"] # order is important diff -r a63c473a100d -r 0c2f60b3c01f wiki/config/wikiconfig.py --- a/wiki/config/wikiconfig.py Sat May 05 15:26:15 2007 +0200 +++ b/wiki/config/wikiconfig.py Sat May 12 02:27:03 2007 +0200 @@ -93,7 +93,7 @@ class Config(DefaultConfig): # IMPORTANT: grant yourself admin rights! replace YourName with # your user name. See HelpOnAccessControlLists for more help. # All acl_rights_xxx options must use unicode [Unicode] - #acl_rights_before = u"YourName:read,write,delete,revert,admin" + #acl_rights_before = u"YourName:read,write,attach,delete,revert,admin" # Link spam protection for public wikis (Uncomment to enable) # Needs a reliable internet connection. diff -r a63c473a100d -r 0c2f60b3c01f wiki/config/wikifarm/farmconfig.py --- a/wiki/config/wikifarm/farmconfig.py Sat May 05 15:26:15 2007 +0200 +++ b/wiki/config/wikifarm/farmconfig.py Sat May 12 02:27:03 2007 +0200 @@ -110,7 +110,7 @@ class FarmConfig(DefaultConfig): # IMPORTANT: grant yourself admin rights! replace YourName with # your user name. See HelpOnAccessControlLists for more help. # All acl_rights_xxx options must use unicode [Unicode] - #acl_rights_before = u"YourName:read,write,delete,revert,admin" + #acl_rights_before = u"YourName:read,write,attach,delete,revert,admin" # Link spam protection for public wikis (uncomment to enable). # Needs a reliable internet connection. diff -r a63c473a100d -r 0c2f60b3c01f wikiconfig.py --- a/wikiconfig.py Sat May 05 15:26:15 2007 +0200 +++ b/wikiconfig.py Sat May 12 02:27:03 2007 +0200 @@ -15,7 +15,7 @@ class Config(DefaultConfig): data_underlay_dir = os.path.join(moinmoin_dir, 'wiki', 'underlay') DesktopEdition = True # give all local users full powers - acl_rights_default = u"All:read,write,delete,revert,admin" + acl_rights_default = u"All:read,write,attach,delete,revert,admin" surge_action_limits = None # no surge protection sitename = u'MoinMoin DesktopEdition' logo_string = u'<img src="/moin_static170/common/moinmoin.png" alt="MoinMoin Logo">'
Diff to moin-1.5.8 (tested)
diff -ur a/MoinMoin/action/AttachFile.py b/MoinMoin/action/AttachFile.py --- a/MoinMoin/action/AttachFile.py 2007-05-05 22:48:23.000000000 +0000 +++ b/MoinMoin/action/AttachFile.py 2007-05-17 08:52:07.000000000 +0000 @@ -276,7 +276,7 @@ viewlink += ' | <a href="%(baseurl)s/%(urlpagename)s?action=%(action)s&do=install&target=%(urlfile)s">%(label_install)s</a>' % parmdict elif (zipfile.is_zipfile(os.path.join(attach_dir,file).encode(config.charset)) and request.user.may.read(pagename) and request.user.may.delete(pagename) - and request.user.may.write(pagename)): + and request.user.may.attach(pagename)): viewlink += ' | <a href="%(baseurl)s/%(urlpagename)s?action=%(action)s&do=unzip&target=%(urlfile)s">%(label_unzip)s</a>' % parmdict @@ -406,7 +406,7 @@ request.write('<h2>' + _("Attached Files") + '</h2>') request.write(_get_filelist(request, pagename)) - if not request.user.may.write(pagename): + if not request.user.may.attach(pagename): request.write('<p>%s</p>' % _('You are not allowed to attach a file to this page.')) return @@ -467,7 +467,7 @@ if action_name in request.cfg.actions_excluded: msg = _('File attachments are not allowed in this wiki!') elif request.form.has_key('filepath'): - if request.user.may.write(pagename): + if request.user.may.attach(pagename): save_drawing(pagename, request) request.http_headers() request.write("OK") @@ -476,7 +476,7 @@ elif do is None: upload_form(pagename, request) elif do == 'upload': - if request.user.may.write(pagename): + if request.user.may.attach(pagename): if request.form.has_key('file'): do_upload(pagename, request) else: @@ -514,7 +514,7 @@ else: msg = _('You are not allowed to get attachments from this page.') elif do == 'unzip': - if request.user.may.delete(pagename) and request.user.may.read(pagename) and request.user.may.write(pagename): + if request.user.may.delete(pagename) and request.user.may.read(pagename) and request.user.may.attach(pagename): unzip_file(pagename, request) else: msg = _('You are not allowed to unzip attachments of this page.') @@ -680,7 +680,7 @@ _ = request.getText newpage = Page(request, new_pagename) - if newpage.exists(includeDeleted=1) and request.user.may.write(new_pagename) and request.user.may.delete(pagename): + if newpage.exists(includeDeleted=1) and request.user.may.attach(new_pagename) and request.user.may.delete(pagename): new_attachment_path = os.path.join(getAttachDir(request, new_pagename, create=1), new_attachment).encode(config.charset) attachment_path = os.path.join(getAttachDir(request, pagename), diff -ur a/MoinMoin/multiconfig.py b/MoinMoin/multiconfig.py --- a/MoinMoin/multiconfig.py 2007-05-12 19:19:57.000000000 +0000 +++ b/MoinMoin/multiconfig.py 2007-05-17 08:52:39.000000000 +0000 @@ -173,10 +173,10 @@ """ default config values """ # All acl_right lines must use unicode! - acl_rights_default = u"Trusted:read,write,delete,revert Known:read,write,delete,revert All:read,write" + acl_rights_default = u"Trusted:read,write,attach,delete,revert Known:read,write,delete,revert All:read,write" acl_rights_before = u"" acl_rights_after = u"" - acl_rights_valid = ['read', 'write', 'delete', 'revert', 'admin'] + acl_rights_valid = ['read', 'write', 'attach', 'delete', 'revert', 'admin'] actions_excluded = [] # ['DeletePage', 'AttachFile', 'RenamePage'] allow_xslt = 0