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