1 # -*- coding: iso-8859-1 -*- 2 """ 3 MoinMoin - ApproveChanges library 4 5 This library relies on the existence of a user (by default 6 "ApprovalQueueUser") who has sufficient privileges to write pages with ACLs 7 to an approval queue (ACL permissions "write,admin"). 8 9 If users other than the superuser are to be able to edit pages freely, they 10 must be present in a group (by default "ApprovedGroup"), and if they are to 11 be allowed to review changes, they must be present in a different group (by 12 default "PageReviewersGroup"). 13 14 @copyright: 2011 by Paul Boddie <paul@boddie.org.uk> 15 @license: GNU GPL (v2 or later), see COPYING.txt for details. 16 """ 17 18 from MoinMoin import user 19 import re 20 import base64 21 import md5 22 import hmac 23 24 try: 25 from hashlib import sha1 26 except ImportError: 27 from sha import new as sha1 28 29 acl_pattern = re.compile(ur"^#acl .*$", re.UNICODE | re.MULTILINE) 30 31 __version__ = "0.1" 32 33 def get_queued_changes_page(request): 34 return getattr(request.cfg, "queued_changes_page", "ApprovalQueue") 35 36 def get_approved_editors_group(request): 37 return getattr(request.cfg, "approved_editors_group", "ApprovedGroup") 38 39 def get_page_reviewers_group(request): 40 return getattr(request.cfg, "reviewers_group", "PageReviewersGroup") 41 42 def get_queued_changes_user(request): 43 return getattr(request.cfg, "queued_changes_user", "ApprovalQueueUser") 44 45 def get_secret_key(request): 46 return request.cfg.secrets["wikiutil/tickets"] 47 48 def is_reviewer(request): 49 return request.user.valid and ( 50 request.dicts.has_member(get_approved_editors_group(request), request.user.name) or \ 51 request.user.isSuperUser()) 52 53 def is_approved(request): 54 return request.user.valid and ( 55 request.dicts.has_member(get_approved_editors_group(request), request.user.name) or \ 56 request.user.isSuperUser()) 57 58 def is_queued_changes_page(request, pagename): 59 60 "Return whether 'pagename' is a queued changes page by testing its name." 61 62 parts = pagename.split("/") 63 return len(parts) > 1 and parts[-1] == get_queued_changes_page(request) 64 65 def get_target_page_name(pagename): 66 67 "Return the target page name for the given queued changes 'pagename'." 68 69 return "/".join(pagename.split("/")[:-1]) 70 71 def get_user_for_saving(request): 72 73 "Return a user that can save pages with ACLs." 74 75 username = get_queued_changes_user(request) 76 uid = user.getUserId(request, username) 77 78 # If the user does not exist, just return the existing user. 79 80 if not uid: 81 return request.user 82 83 # Otherwise, return the privileged user. 84 85 return user.User(request, uid) 86 87 def add_access_control(request, body): 88 89 """ 90 Using the 'request', add an ACL to the page 'body' in order to prevent 91 anyone other than reviewers from seeing it in the queue. 92 """ 93 94 # Find existing ACLs. 95 96 match = acl_pattern.search(body) 97 if match: 98 start, end = match.span() 99 100 # Comment out existing ACLs. 101 102 parts = [] 103 parts.append(body[:start]) 104 parts.append("#") 105 parts.append(body[start:]) 106 else: 107 parts = [body] 108 109 # Add the ACL. 110 111 parts.insert(0, "#acl %s:read,write,delete,revert,admin %s:write All:\n" % ( 112 get_page_reviewers_group(request), get_queued_changes_user(request))) 113 return "".join(parts) 114 115 def remove_access_control(request, body): 116 117 "Using the 'request', remove any added ACL to the page 'body'." 118 119 lines = body.split("\n") 120 121 try: 122 directive = lines[0].split()[0] 123 if directive == "#acl": 124 return "\n".join(lines[1:]) 125 except ValueError: 126 pass 127 128 return body 129 130 def get_page_signature(request, body): 131 132 """ 133 Using the 'request', return a signature/digest for the given page 'body' 134 using a secret known only by the server. 135 """ 136 137 secret_key = get_secret_key(request) 138 hash = hmac.new(secret_key, body.encode("utf-8"), sha1) 139 return base64.standard_b64encode(hash.digest()) 140 141 def sign_page(request, body): 142 143 """ 144 Using the 'request', sign the page 'body' using a secret known only by the 145 server. 146 """ 147 148 return "#signature %s\n%s" % (get_page_signature(request, body), body) 149 150 def check_page(request, body): 151 152 """ 153 Using the 'request', find and check the signature in the page 'body', 154 returning the original page or None (if no valid signature is found). 155 """ 156 157 lines = body.split("\n") 158 body = "\n".join(lines[1:]) 159 160 try: 161 directive, signature = lines[0].split() 162 if directive == "#signature" and signature == get_page_signature(request, body): 163 return body 164 except ValueError: 165 pass 166 167 return None 168 169 # Utility classes and associated functions. 170 # NOTE: These are a subset of EventAggregatorSupport. 171 172 class Form: 173 174 """ 175 A wrapper preserving MoinMoin 1.8.x (and earlier) behaviour in a 1.9.x 176 environment. 177 """ 178 179 def __init__(self, form): 180 self.form = form 181 182 def get(self, name, default=None): 183 values = self.form.getlist(name) 184 if not values: 185 return default 186 else: 187 return values 188 189 def __getitem__(self, name): 190 return self.form.getlist(name) 191 192 class ActionSupport: 193 194 """ 195 Work around disruptive MoinMoin changes in 1.9, and also provide useful 196 convenience methods. 197 """ 198 199 def get_form(self): 200 return get_form(self.request) 201 202 def get_form(request): 203 204 "Work around disruptive MoinMoin changes in 1.9." 205 206 if hasattr(request, "values"): 207 return Form(request.values) 208 else: 209 return request.form 210 211 # vim: tabstop=4 expandtab shiftwidth=4