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 */ 019package org.apache.wiki.auth.acl; 020 021import java.security.Permission; 022import java.security.Principal; 023import java.util.ArrayList; 024import java.util.Collections; 025import java.util.Enumeration; 026import java.util.List; 027import java.util.Map; 028import java.util.NoSuchElementException; 029import java.util.Properties; 030import java.util.StringTokenizer; 031import java.util.TreeMap; 032import java.util.regex.Matcher; 033import java.util.regex.Pattern; 034 035import org.apache.log4j.Logger; 036import org.apache.wiki.PageLock; 037import org.apache.wiki.PageManager; 038import org.apache.wiki.WikiContext; 039import org.apache.wiki.WikiEngine; 040import org.apache.wiki.WikiPage; 041import org.apache.wiki.api.exceptions.ProviderException; 042import org.apache.wiki.attachment.Attachment; 043import org.apache.wiki.auth.AuthorizationManager; 044import org.apache.wiki.auth.PrincipalComparator; 045import org.apache.wiki.auth.WikiSecurityException; 046import org.apache.wiki.auth.permissions.PagePermission; 047import org.apache.wiki.auth.permissions.PermissionFactory; 048import org.apache.wiki.render.RenderingManager; 049 050/** 051 * Default implementation that parses Acls from wiki page markup. 052 * 053 * @since 2.3 054 */ 055public 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 StringBuilder s = new StringBuilder(); 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}