1.1 --- a/ApproveChangesSupport.py Mon Oct 10 22:28:58 2011 +0200
1.2 +++ b/ApproveChangesSupport.py Tue Oct 11 01:13:12 2011 +0200
1.3 @@ -2,11 +2,31 @@
1.4 """
1.5 MoinMoin - ApproveChanges library
1.6
1.7 + This library relies on the existence of a user (by default
1.8 + "ApprovalQueueUser") who has sufficient privileges to write pages with ACLs
1.9 + to an approval queue (ACL permissions "write,admin").
1.10 +
1.11 + If users other than the superuser are to be able to edit pages freely, they
1.12 + must be present in a group (by default "ApprovedGroup"), and if they are to
1.13 + be allowed to review changes, they must be present in a different group (by
1.14 + default "PageReviewersGroup").
1.15 +
1.16 @copyright: 2011 by Paul Boddie <paul@boddie.org.uk>
1.17 @license: GNU GPL (v2 or later), see COPYING.txt for details.
1.18 """
1.19
1.20 +from MoinMoin import user
1.21 import re
1.22 +import base64
1.23 +import md5
1.24 +import hmac
1.25 +
1.26 +try:
1.27 + from hashlib import sha1
1.28 +except ImportError:
1.29 + from sha import new as sha1
1.30 +
1.31 +acl_pattern = re.compile(ur"^#acl .*$", re.UNICODE | re.MULTILINE)
1.32
1.33 __version__ = "0.1"
1.34
1.35 @@ -16,6 +36,25 @@
1.36 def get_approved_editors_group(request):
1.37 return getattr(request.cfg, "approved_editors_group", "ApprovedGroup")
1.38
1.39 +def get_page_reviewers_group(request):
1.40 + return getattr(request.cfg, "reviewers_group", "PageReviewersGroup")
1.41 +
1.42 +def get_queued_changes_user(request):
1.43 + return getattr(request.cfg, "queued_changes_user", "ApprovalQueueUser")
1.44 +
1.45 +def get_secret_key(request):
1.46 + return request.cfg.secrets["wikiutil/tickets"]
1.47 +
1.48 +def is_reviewer(request):
1.49 + return request.user.valid and (
1.50 + request.dicts.has_member(get_approved_editors_group(request), request.user.name) or \
1.51 + request.user.isSuperUser())
1.52 +
1.53 +def is_approved(request):
1.54 + return request.user.valid and (
1.55 + request.dicts.has_member(get_approved_editors_group(request), request.user.name) or \
1.56 + request.user.isSuperUser())
1.57 +
1.58 def is_queued_changes_page(request, pagename):
1.59
1.60 "Return whether 'pagename' is a queued changes page by testing its name."
1.61 @@ -29,6 +68,104 @@
1.62
1.63 return "/".join(pagename.split("/")[:-1])
1.64
1.65 +def get_user_for_saving(request):
1.66 +
1.67 + "Return a user that can save pages with ACLs."
1.68 +
1.69 + username = get_queued_changes_user(request)
1.70 + uid = user.getUserId(request, username)
1.71 +
1.72 + # If the user does not exist, just return the existing user.
1.73 +
1.74 + if not uid:
1.75 + return request.user
1.76 +
1.77 + # Otherwise, return the privileged user.
1.78 +
1.79 + return user.User(request, uid)
1.80 +
1.81 +def add_access_control(request, body):
1.82 +
1.83 + """
1.84 + Using the 'request', add an ACL to the page 'body' in order to prevent
1.85 + anyone other than reviewers from seeing it in the queue.
1.86 + """
1.87 +
1.88 + # Find existing ACLs.
1.89 +
1.90 + match = acl_pattern.search(body)
1.91 + if match:
1.92 + start, end = match.span()
1.93 +
1.94 + # Comment out existing ACLs.
1.95 +
1.96 + parts = []
1.97 + parts.append(body[:start])
1.98 + parts.append("#")
1.99 + parts.append(body[start:])
1.100 + else:
1.101 + parts = [body]
1.102 +
1.103 + # Add the ACL.
1.104 +
1.105 + parts.insert(0, "#acl %s:read,write,delete,revert,admin %s:write All:\n" % (
1.106 + get_approved_editors_group(request), get_queued_changes_user(request)))
1.107 + return "".join(parts)
1.108 +
1.109 +def remove_access_control(request, body):
1.110 +
1.111 + "Using the 'request', remove any added ACL to the page 'body'."
1.112 +
1.113 + lines = body.split("\n")
1.114 +
1.115 + try:
1.116 + directive = lines[0].split()[0]
1.117 + if directive == "#acl":
1.118 + return "\n".join(lines[1:])
1.119 + except ValueError:
1.120 + pass
1.121 +
1.122 + return body
1.123 +
1.124 +def get_page_signature(request, body):
1.125 +
1.126 + """
1.127 + Using the 'request', return a signature/digest for the given page 'body'
1.128 + using a secret known only by the server.
1.129 + """
1.130 +
1.131 + secret_key = get_secret_key(request)
1.132 + hash = hmac.new(secret_key, body.encode("utf-8"), sha1)
1.133 + return base64.standard_b64encode(hash.digest())
1.134 +
1.135 +def sign_page(request, body):
1.136 +
1.137 + """
1.138 + Using the 'request', sign the page 'body' using a secret known only by the
1.139 + server.
1.140 + """
1.141 +
1.142 + return "#signature %s\n%s" % (get_page_signature(request, body), body)
1.143 +
1.144 +def check_page(request, body):
1.145 +
1.146 + """
1.147 + Using the 'request', find and check the signature in the page 'body',
1.148 + returning the original page or None (if no valid signature is found).
1.149 + """
1.150 +
1.151 + lines = body.split("\n")
1.152 + body = "\n".join(lines[1:])
1.153 +
1.154 + try:
1.155 + directive, signature = lines[0].split()
1.156 + if directive == "#signature" and signature == get_page_signature(request, body):
1.157 + return body
1.158 + except ValueError:
1.159 + pass
1.160 +
1.161 + return None
1.162 +
1.163 # Utility classes and associated functions.
1.164 # NOTE: These are a subset of EventAggregatorSupport.
1.165