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