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.WikiContext; 037import org.apache.wiki.WikiEngine; 038import org.apache.wiki.WikiPage; 039import org.apache.wiki.api.exceptions.ProviderException; 040import org.apache.wiki.attachment.Attachment; 041import org.apache.wiki.auth.AuthorizationManager; 042import org.apache.wiki.auth.PrincipalComparator; 043import org.apache.wiki.auth.WikiSecurityException; 044import org.apache.wiki.auth.permissions.PagePermission; 045import org.apache.wiki.auth.permissions.PermissionFactory; 046import org.apache.wiki.pages.PageLock; 047import org.apache.wiki.pages.PageManager; 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 if (page.getAcl() == null) { 187 page.setAcl( new AclImpl() ); 188 } 189 acl = page.getAcl(); 190 } 191 } 192 193 return acl; 194 } 195 196 /** 197 * Sets the access control list for the page and persists it by prepending 198 * it to the wiki page markup and saving the page. When this method is 199 * called, all other ACL markup in the page is removed. This method will forcibly 200 * expire locks on the wiki page if they exist. Any ProviderExceptions will be 201 * re-thrown as WikiSecurityExceptions. 202 * 203 * @param page the wiki page 204 * @param acl the access control list 205 * @throws WikiSecurityException of the Acl cannot be set 206 * @since 2.5 207 */ 208 public void setPermissions(WikiPage page, Acl acl) throws WikiSecurityException { 209 PageManager pageManager = m_engine.getPageManager(); 210 211 // Forcibly expire any page locks 212 PageLock lock = pageManager.getCurrentLock(page); 213 if (lock != null) { 214 pageManager.unlockPage(lock); 215 } 216 217 // Remove all of the existing ACLs. 218 String pageText = m_engine.getPureText(page); 219 Matcher matcher = DefaultAclManager.ACL_PATTERN.matcher(pageText); 220 String cleansedText = matcher.replaceAll(""); 221 String newText = DefaultAclManager.printAcl(page.getAcl()) + cleansedText; 222 try { 223 pageManager.putPageText(page, newText); 224 } catch (ProviderException e) { 225 throw new WikiSecurityException("Could not set Acl. Reason: ProviderExcpetion " + e.getMessage(), e); 226 } 227 } 228 229 /** 230 * Generates an ACL string for inclusion in a wiki page, based on a supplied Acl object. 231 * All of the permissions in this Acl are assumed to apply to the same page scope. 232 * The names of the pages are ignored; only the actions and principals matter. 233 * 234 * @param acl the ACL 235 * @return the ACL string 236 */ 237 protected static String printAcl(Acl acl) { 238 // Extract the ACL entries into a Map with keys == permissions, values == principals 239 Map<String, List<Principal>> permissionPrincipals = new TreeMap<String, List<Principal>>(); 240 Enumeration<AclEntry> entries = acl.entries(); 241 while (entries.hasMoreElements()) { 242 AclEntry entry = entries.nextElement(); 243 Principal principal = entry.getPrincipal(); 244 Enumeration<Permission> permissions = entry.permissions(); 245 while (permissions.hasMoreElements()) { 246 Permission permission = permissions.nextElement(); 247 List<Principal> principals = permissionPrincipals.get(permission.getActions()); 248 if (principals == null) { 249 principals = new ArrayList<Principal>(); 250 String action = permission.getActions(); 251 if (action.indexOf(',') != -1) { 252 throw new IllegalStateException("AclEntry permission cannot have multiple targets."); 253 } 254 permissionPrincipals.put(action, principals); 255 } 256 principals.add(principal); 257 } 258 } 259 260 // Now, iterate through each permission in the map and generate an ACL string 261 262 StringBuilder s = new StringBuilder(); 263 for (Map.Entry<String, List<Principal>> entry : permissionPrincipals.entrySet()) { 264 String action = entry.getKey(); 265 List<Principal> principals = entry.getValue(); 266 Collections.sort(principals, new PrincipalComparator()); 267 s.append("[{ALLOW "); 268 s.append(action); 269 s.append(" "); 270 for (int i = 0; i < principals.size(); i++) { 271 Principal principal = principals.get(i); 272 s.append(principal.getName()); 273 if (i < (principals.size() - 1)) { 274 s.append(","); 275 } 276 } 277 s.append("}]\n"); 278 } 279 return s.toString(); 280 } 281 282}