001 /*
002 Licensed to the Apache Software Foundation (ASF) under one
003 or more contributor license agreements. See the NOTICE file
004 distributed with this work for additional information
005 regarding copyright ownership. The ASF licenses this file
006 to you under the Apache License, Version 2.0 (the
007 "License"); you may not use this file except in compliance
008 with the License. You may obtain a copy of the License at
009
010 http://www.apache.org/licenses/LICENSE-2.0
011
012 Unless required by applicable law or agreed to in writing,
013 software distributed under the License is distributed on an
014 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 KIND, either express or implied. See the License for the
016 specific language governing permissions and limitations
017 under the License.
018 */
019 package org.apache.wiki.auth.acl;
020
021 import java.security.Permission;
022 import java.security.Principal;
023 import java.util.ArrayList;
024 import java.util.Collections;
025 import java.util.Enumeration;
026 import java.util.List;
027 import java.util.Map;
028 import java.util.NoSuchElementException;
029 import java.util.Properties;
030 import java.util.StringTokenizer;
031 import java.util.TreeMap;
032 import java.util.regex.Matcher;
033 import java.util.regex.Pattern;
034
035 import org.apache.log4j.Logger;
036 import org.apache.wiki.PageLock;
037 import org.apache.wiki.PageManager;
038 import org.apache.wiki.WikiContext;
039 import org.apache.wiki.WikiEngine;
040 import org.apache.wiki.WikiPage;
041 import org.apache.wiki.api.exceptions.ProviderException;
042 import org.apache.wiki.attachment.Attachment;
043 import org.apache.wiki.auth.AuthorizationManager;
044 import org.apache.wiki.auth.PrincipalComparator;
045 import org.apache.wiki.auth.WikiSecurityException;
046 import org.apache.wiki.auth.permissions.PagePermission;
047 import org.apache.wiki.auth.permissions.PermissionFactory;
048 import org.apache.wiki.render.RenderingManager;
049
050 /**
051 * Default implementation that parses Acls from wiki page markup.
052 *
053 * @since 2.3
054 */
055 public class DefaultAclManager implements AclManager {
056
057 private static final Logger log = Logger.getLogger(DefaultAclManager.class);
058
059 private AuthorizationManager m_auth = null;
060 private WikiEngine m_engine = null;
061 private static final String PERM_REGEX = "(" +
062 PagePermission.COMMENT_ACTION + "|" +
063 PagePermission.DELETE_ACTION + "|" +
064 PagePermission.EDIT_ACTION + "|" +
065 PagePermission.MODIFY_ACTION + "|" +
066 PagePermission.RENAME_ACTION + "|" +
067 PagePermission.UPLOAD_ACTION + "|" +
068 PagePermission.VIEW_ACTION + ")";
069 private static final String ACL_REGEX = "\\[\\{\\s*ALLOW\\s+" + PERM_REGEX + "\\s*(.*?)\\s*\\}\\]";
070
071 /**
072 * Identifies ACL strings in wiki text; the first group is the action (view, edit) and
073 * the second is the list of Principals separated by commas. The overall match is
074 * the ACL string from [{ to }].
075 */
076 public static final Pattern ACL_PATTERN = Pattern.compile(ACL_REGEX);
077
078 /**
079 * Initializes the AclManager with a supplied wiki engine and properties.
080 *
081 * @param engine the wiki engine
082 * @param props the initialization properties
083 * @see org.apache.wiki.auth.acl.AclManager#initialize(org.apache.wiki.WikiEngine,
084 * java.util.Properties)
085 */
086 public void initialize(WikiEngine engine, Properties props) {
087 m_auth = engine.getAuthorizationManager();
088 m_engine = engine;
089 }
090
091 /**
092 * A helper method for parsing textual AccessControlLists. The line is in
093 * form "ALLOW <permission> <principal>, <principal>, <principal>". This
094 * method was moved from Authorizer.
095 *
096 * @param page The current wiki page. If the page already has an ACL, it
097 * will be used as a basis for this ACL in order to avoid the
098 * creation of a new one.
099 * @param ruleLine The rule line, as described above.
100 * @return A valid Access Control List. May be empty.
101 * @throws WikiSecurityException if the ruleLine was faulty somehow.
102 * @since 2.1.121
103 */
104 public Acl parseAcl(WikiPage page, String ruleLine) throws WikiSecurityException {
105 Acl acl = page.getAcl();
106 if (acl == null) {
107 acl = new AclImpl();
108 }
109
110 try {
111 StringTokenizer fieldToks = new StringTokenizer(ruleLine);
112 fieldToks.nextToken();
113 String actions = fieldToks.nextToken();
114 page.getName();
115
116 while (fieldToks.hasMoreTokens()) {
117 String principalName = fieldToks.nextToken(",").trim();
118 Principal principal = m_auth.resolvePrincipal(principalName);
119 AclEntry oldEntry = acl.getEntry(principal);
120
121 if (oldEntry != null) {
122 log.debug("Adding to old acl list: " + principal + ", " + actions);
123 oldEntry.addPermission(PermissionFactory.getPagePermission(page, actions));
124 } else {
125 log.debug("Adding new acl entry for " + actions);
126 AclEntry entry = new AclEntryImpl();
127
128 entry.setPrincipal(principal);
129 entry.addPermission(PermissionFactory.getPagePermission(page, actions));
130
131 acl.addEntry(entry);
132 }
133 }
134
135 page.setAcl(acl);
136
137 log.debug(acl.toString());
138 } catch (NoSuchElementException nsee) {
139 log.warn("Invalid access rule: " + ruleLine + " - defaults will be used.");
140 throw new WikiSecurityException("Invalid access rule: " + ruleLine, nsee);
141 } catch (IllegalArgumentException iae) {
142 throw new WikiSecurityException("Invalid permission type: " + ruleLine, iae);
143 }
144
145 return acl;
146 }
147
148
149 /**
150 * Returns the access control list for the page.
151 * If the ACL has not been parsed yet, it is done
152 * on-the-fly. If the page has a parent page, then that is tried also.
153 * This method was moved from Authorizer;
154 * it was consolidated with some code from AuthorizationManager.
155 * This method is guaranteed to return a non-<code>null</code> Acl.
156 *
157 * @param page the page
158 * @return the Acl representing permissions for the page
159 * @since 2.2.121
160 */
161 public Acl getPermissions(WikiPage page) {
162 //
163 // Does the page already have cached ACLs?
164 //
165 Acl acl = page.getAcl();
166 log.debug("page=" + page.getName() + "\n" + acl);
167
168 if (acl == null) {
169 //
170 // If null, try the parent.
171 //
172 if (page instanceof Attachment) {
173 WikiPage parent = m_engine.getPage(((Attachment) page).getParentName());
174
175 acl = getPermissions(parent);
176 } else {
177 //
178 // Or, try parsing the page
179 //
180 WikiContext ctx = new WikiContext(m_engine, page);
181
182 ctx.setVariable(RenderingManager.VAR_EXECUTE_PLUGINS, Boolean.FALSE);
183
184 m_engine.getHTML(ctx, page);
185
186 page = m_engine.getPage(page.getName(), page.getVersion());
187 acl = page.getAcl();
188
189 if (acl == null) {
190 acl = new AclImpl();
191 page.setAcl(acl);
192 }
193 }
194 }
195
196 return acl;
197 }
198
199 /**
200 * Sets the access control list for the page and persists it by prepending
201 * it to the wiki page markup and saving the page. When this method is
202 * called, all other ACL markup in the page is removed. This method will forcibly
203 * expire locks on the wiki page if they exist. Any ProviderExceptions will be
204 * re-thrown as WikiSecurityExceptions.
205 *
206 * @param page the wiki page
207 * @param acl the access control list
208 * @throws WikiSecurityException of the Acl cannot be set
209 * @since 2.5
210 */
211 public void setPermissions(WikiPage page, Acl acl) throws WikiSecurityException {
212 PageManager pageManager = m_engine.getPageManager();
213
214 // Forcibly expire any page locks
215 PageLock lock = pageManager.getCurrentLock(page);
216 if (lock != null) {
217 pageManager.unlockPage(lock);
218 }
219
220 // Remove all of the existing ACLs.
221 String pageText = m_engine.getPureText(page);
222 Matcher matcher = DefaultAclManager.ACL_PATTERN.matcher(pageText);
223 String cleansedText = matcher.replaceAll("");
224 String newText = DefaultAclManager.printAcl(page.getAcl()) + cleansedText;
225 try {
226 pageManager.putPageText(page, newText);
227 } catch (ProviderException e) {
228 throw new WikiSecurityException("Could not set Acl. Reason: ProviderExcpetion " + e.getMessage(), e);
229 }
230 }
231
232 /**
233 * Generates an ACL string for inclusion in a wiki page, based on a supplied Acl object.
234 * All of the permissions in this Acl are assumed to apply to the same page scope.
235 * The names of the pages are ignored; only the actions and principals matter.
236 *
237 * @param acl the ACL
238 * @return the ACL string
239 */
240 protected static String printAcl(Acl acl) {
241 // Extract the ACL entries into a Map with keys == permissions, values == principals
242 Map<String, List<Principal>> permissionPrincipals = new TreeMap<String, List<Principal>>();
243 Enumeration<AclEntry> entries = acl.entries();
244 while (entries.hasMoreElements()) {
245 AclEntry entry = entries.nextElement();
246 Principal principal = entry.getPrincipal();
247 Enumeration<Permission> permissions = entry.permissions();
248 while (permissions.hasMoreElements()) {
249 Permission permission = permissions.nextElement();
250 List<Principal> principals = permissionPrincipals.get(permission.getActions());
251 if (principals == null) {
252 principals = new ArrayList<Principal>();
253 String action = permission.getActions();
254 if (action.indexOf(',') != -1) {
255 throw new IllegalStateException("AclEntry permission cannot have multiple targets.");
256 }
257 permissionPrincipals.put(action, principals);
258 }
259 principals.add(principal);
260 }
261 }
262
263 // Now, iterate through each permission in the map and generate an ACL string
264
265 StringBuffer s = new StringBuffer();
266 for (Map.Entry<String, List<Principal>> entry : permissionPrincipals.entrySet()) {
267 String action = entry.getKey();
268 List<Principal> principals = entry.getValue();
269 Collections.sort(principals, new PrincipalComparator());
270 s.append("[{ALLOW ");
271 s.append(action);
272 s.append(" ");
273 for (int i = 0; i < principals.size(); i++) {
274 Principal principal = principals.get(i);
275 s.append(principal.getName());
276 if (i < (principals.size() - 1)) {
277 s.append(",");
278 }
279 }
280 s.append("}]\n");
281 }
282 return s.toString();
283 }
284
285 }