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 2003-2007 MoinMoin:ThomasWaldmann, 16 2003 by Gustavo Niemeyer 17 @license: GNU GPL (v2 or later), see COPYING.txt for details. 18 """ 19 20 from MoinMoin import user 21 from MoinMoin.Page import Page 22 from MoinMoin.PageEditor import PageEditor 23 from MoinMoin.wikiutil import escape 24 import re 25 26 __version__ = "0.1" 27 28 space_pattern = re.compile("(\s+)") 29 group_member_pattern = re.compile(ur'^ \* +(?:\[\[)?(?P<member>.+?)(?:\]\])? *$', re.MULTILINE | re.UNICODE) 30 31 def get_queued_changes_page(request): 32 return getattr(request.cfg, "queued_changes_page", "ApprovalQueue") 33 34 def get_approved_editors_group(request): 35 return getattr(request.cfg, "approved_editors_group", "ApprovedGroup") 36 37 def get_page_reviewers_group(request): 38 return getattr(request.cfg, "reviewers_group", "PageReviewersGroup") 39 40 def get_queued_changes_user(request): 41 return getattr(request.cfg, "queued_changes_user", "ApprovalQueueUser") 42 43 def is_reviewer(request): 44 return request.user.valid and ( 45 has_member(request, get_approved_editors_group(request), request.user.name) or \ 46 request.user.isSuperUser()) 47 48 def is_approved(request): 49 return request.user.valid and ( 50 user_is_approved(request, request.user.name) or \ 51 request.user.isSuperUser()) 52 53 def user_is_approved(request, username): 54 return has_member(request, get_approved_editors_group(request), username) 55 56 def is_queued_changes_user(request): 57 return request.user.valid and request.user.name == get_queued_changes_user(request) 58 59 def is_queued_changes_page(request, pagename): 60 61 "Return whether 'pagename' is a queued changes page by testing its name." 62 63 parts = pagename.split("/") 64 return len(parts) > 1 and parts[-1] == get_queued_changes_page(request) 65 66 def get_target_page_name(pagename): 67 68 "Return the target page name for the given queued changes 'pagename'." 69 70 return "/".join(pagename.split("/")[:-1]) 71 72 def get_user_for_saving(request): 73 74 "Return a user that can save pages with ACLs." 75 76 username = get_queued_changes_user(request) 77 78 # If the user does not exist, just return the existing user. 79 80 return get_user(request, username) or request.user 81 82 def get_user(request, username): 83 84 "Return the user having the given 'username'." 85 86 uid = user.getUserId(request, username) 87 88 # If the user does not exist, just return None. 89 90 if not uid: 91 return None 92 93 # Otherwise, return the privileged user. 94 95 return user.User(request, uid) 96 97 def get_parent_revision_directive(request, pagename): 98 99 """ 100 Using the 'request', return a parent page revision directive for the page 101 having the given 'pagename'. 102 """ 103 104 page = Page(request, pagename) 105 return "#parent-revision %s" % page.current_rev() 106 107 def get_access_control_directive(request): 108 109 """ 110 Using the 'request', return an ACL directive for use in a page body in order 111 to prevent anyone other than reviewers from seeing it in the queue. 112 """ 113 114 return "#acl %s:read,write,delete,revert,admin All:" % ( 115 get_page_reviewers_group(request)) 116 117 def get_user_directive(request): 118 119 """ 120 Using the 'request', return a user directive for use in a page body in order 121 to record who saved the changes originally. 122 """ 123 124 if request.user.valid: 125 return "#unapproved-user %s" % request.user.name 126 else: 127 return "" 128 129 def add_directives(body, directives): 130 131 "Add to the page 'body' the given 'directives'." 132 133 return "\n".join([directive for directive in directives if directive] + [body]) 134 135 def remove_directives(body, names): 136 137 """ 138 Return a new page body, copying the page 'body' provided but removing the 139 first of each directive having one of the given 'names', along with a 140 dictionary mapping directive names to values. 141 """ 142 143 new_body = [] 144 header = 1 145 found = {} 146 147 for line in body.split("\n"): 148 if header: 149 150 # Detect the end of the header. 151 152 if not line.startswith("#"): 153 header = 0 154 155 # Process the comment or directive. 156 157 else: 158 parts = space_pattern.split(line[1:]) 159 160 # Identify any directive. 161 162 directive = parts[0] 163 164 # Obtain the value of the first instance of any directive, 165 # stripping any initial space. 166 167 if directive in names and not found.has_key(directive): 168 found[directive] = "".join(parts[2:]) 169 continue 170 171 new_body.append(line) 172 173 return "\n".join(new_body), found 174 175 def add_to_group_page(request, username, groupname): 176 177 """ 178 Using the 'request', add 'username' to 'groupname', changing the group page. 179 This is not the same as adding a member to the group, but it will have the 180 same effect when the group is rescanned. 181 """ 182 183 _ = request.getText 184 185 page = PageEditor(request, groupname) 186 body = page.get_raw_body() 187 match = None 188 189 # Find the last matching span. 190 191 for match in group_member_pattern.finditer(body): 192 start, end = match.span() 193 194 # Add a group member to the body. 195 196 entry = ("\n * %s" % username) 197 198 if match: 199 body = body[:end] + entry + body[end:] 200 else: 201 body += entry 202 203 page.saveText(body, 0, comment=_("Added %s to the approved editors group.") % username) 204 205 # Utility classes and associated functions. 206 # NOTE: These are a subset of EventAggregatorSupport. 207 208 class Form: 209 210 """ 211 A wrapper preserving MoinMoin 1.8.x (and earlier) behaviour in a 1.9.x 212 environment. 213 """ 214 215 def __init__(self, form): 216 self.form = form 217 218 def get(self, name, default=None): 219 values = self.form.getlist(name) 220 if not values: 221 return default 222 else: 223 return values 224 225 def __getitem__(self, name): 226 return self.form.getlist(name) 227 228 class ActionSupport: 229 230 """ 231 Work around disruptive MoinMoin changes in 1.9, and also provide useful 232 convenience methods. 233 """ 234 235 def get_form(self): 236 return get_form(self.request) 237 238 def get_form(request): 239 240 "Work around disruptive MoinMoin changes in 1.9." 241 242 if hasattr(request, "values"): 243 return Form(request.values) 244 else: 245 return request.form 246 247 def escattr(s): 248 return escape(s, 1) 249 250 # More Moin 1.9 compatibility functions. 251 252 def has_member(request, groupname, username): 253 if hasattr(request.dicts, "has_member"): 254 return request.dicts.has_member(groupname, username) 255 else: 256 return username in request.dicts.get(groupname, []) 257 258 # vim: tabstop=4 expandtab shiftwidth=4