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 org.apache.log4j.Logger; 022import org.apache.wiki.api.core.Acl; 023import org.apache.wiki.api.core.AclEntry; 024import org.apache.wiki.api.core.Attachment; 025import org.apache.wiki.api.core.Context; 026import org.apache.wiki.api.core.Engine; 027import org.apache.wiki.api.core.Page; 028import org.apache.wiki.api.exceptions.ProviderException; 029import org.apache.wiki.api.spi.Wiki; 030import org.apache.wiki.auth.AuthorizationManager; 031import org.apache.wiki.auth.WikiSecurityException; 032import org.apache.wiki.auth.permissions.PagePermission; 033import org.apache.wiki.auth.permissions.PermissionFactory; 034import org.apache.wiki.pages.PageLock; 035import org.apache.wiki.pages.PageManager; 036import org.apache.wiki.render.RenderingManager; 037import org.apache.wiki.util.comparators.PrincipalComparator; 038 039import java.security.Permission; 040import java.security.Principal; 041import java.util.ArrayList; 042import java.util.Enumeration; 043import java.util.List; 044import java.util.Map; 045import java.util.NoSuchElementException; 046import java.util.Properties; 047import java.util.StringTokenizer; 048import java.util.TreeMap; 049import java.util.regex.Matcher; 050import java.util.regex.Pattern; 051 052/** 053 * Default implementation that parses Acls from wiki page markup. 054 * 055 * @since 2.3 056 */ 057public class DefaultAclManager implements AclManager { 058 059 private static final Logger log = Logger.getLogger(DefaultAclManager.class); 060 061 private AuthorizationManager m_auth = null; 062 private Engine m_engine = null; 063 private static final String PERM_REGEX = "(" 064 + PagePermission.COMMENT_ACTION + "|" 065 + PagePermission.DELETE_ACTION + "|" 066 + PagePermission.EDIT_ACTION + "|" 067 + PagePermission.MODIFY_ACTION + "|" 068 + PagePermission.RENAME_ACTION + "|" 069 + PagePermission.UPLOAD_ACTION + "|" 070 + PagePermission.VIEW_ACTION + 071 ")"; 072 private static final String ACL_REGEX = "\\[\\{\\s*ALLOW\\s+" + PERM_REGEX + "\\s*(.*?)\\s*\\}\\]"; 073 074 /** 075 * Identifies ACL strings in wiki text; the first group is the action (view, edit) and 076 * the second is the list of Principals separated by commas. The overall match is 077 * the ACL string from [{ to }]. 078 */ 079 public static final Pattern ACL_PATTERN = Pattern.compile( ACL_REGEX ); 080 081 /** {@inheritDoc} */ 082 @Override 083 public void initialize( final Engine engine, final Properties props ) { 084 m_auth = engine.getManager( AuthorizationManager.class ); 085 m_engine = engine; 086 } 087 088 /** {@inheritDoc} */ 089 @Override 090 public Acl parseAcl( final Page page, final String ruleLine ) throws WikiSecurityException { 091 Acl acl = page.getAcl(); 092 if (acl == null) { 093 acl = Wiki.acls().acl(); 094 } 095 096 try { 097 final StringTokenizer fieldToks = new StringTokenizer(ruleLine); 098 fieldToks.nextToken(); 099 final String actions = fieldToks.nextToken(); 100 101 while( fieldToks.hasMoreTokens() ) { 102 final String principalName = fieldToks.nextToken(",").trim(); 103 final Principal principal = m_auth.resolvePrincipal(principalName); 104 final AclEntry oldEntry = acl.getAclEntry(principal); 105 106 if( oldEntry != null ) { 107 log.debug( "Adding to old acl list: " + principal + ", " + actions ); 108 oldEntry.addPermission( PermissionFactory.getPagePermission( page, actions ) ); 109 } else { 110 log.debug( "Adding new acl entry for " + actions ); 111 final AclEntry entry = Wiki.acls().entry(); 112 entry.setPrincipal( principal ); 113 entry.addPermission( PermissionFactory.getPagePermission( page, actions ) ); 114 115 acl.addEntry( entry ); 116 } 117 } 118 119 page.setAcl( acl ); 120 log.debug( acl.toString() ); 121 } catch( final NoSuchElementException nsee ) { 122 log.warn( "Invalid access rule: " + ruleLine + " - defaults will be used." ); 123 throw new WikiSecurityException( "Invalid access rule: " + ruleLine, nsee ); 124 } catch( final IllegalArgumentException iae ) { 125 throw new WikiSecurityException("Invalid permission type: " + ruleLine, iae); 126 } 127 128 return acl; 129 } 130 131 132 /** {@inheritDoc} */ 133 @Override 134 public Acl getPermissions( final Page page ) { 135 // Does the page already have cached ACLs? 136 Acl acl = page.getAcl(); 137 log.debug( "page=" + page.getName() + "\n" + acl ); 138 139 if( acl == null ) { 140 // If null, try the parent. 141 if( page instanceof Attachment ) { 142 final Page parent = m_engine.getManager( PageManager.class ).getPage( ( ( Attachment ) page ).getParentName() ); 143 acl = getPermissions(parent); 144 } else { 145 // Or, try parsing the page 146 final Context ctx = Wiki.context().create( m_engine, page ); 147 ctx.setVariable( Context.VAR_EXECUTE_PLUGINS, Boolean.FALSE ); 148 m_engine.getManager( RenderingManager.class ).getHTML(ctx, page); 149 150 if (page.getAcl() == null) { 151 page.setAcl( Wiki.acls().acl() ); 152 } 153 acl = page.getAcl(); 154 } 155 } 156 157 return acl; 158 } 159 160 /** {@inheritDoc} */ 161 @Override 162 public void setPermissions( final Page page, final Acl acl ) throws WikiSecurityException { 163 final PageManager pageManager = m_engine.getManager( PageManager.class ); 164 165 // Forcibly expire any page locks 166 final PageLock lock = pageManager.getCurrentLock( page ); 167 if( lock != null ) { 168 pageManager.unlockPage( lock ); 169 } 170 171 // Remove all of the existing ACLs. 172 final String pageText = m_engine.getManager( PageManager.class ).getPureText( page ); 173 final Matcher matcher = DefaultAclManager.ACL_PATTERN.matcher( pageText ); 174 final String cleansedText = matcher.replaceAll("" ); 175 final String newText = DefaultAclManager.printAcl( page.getAcl() ) + cleansedText; 176 try { 177 pageManager.putPageText( page, newText ); 178 } catch( final ProviderException e ) { 179 throw new WikiSecurityException( "Could not set Acl. Reason: ProviderExcpetion " + e.getMessage(), e ); 180 } 181 } 182 183 /** 184 * Generates an ACL string for inclusion in a wiki page, based on a supplied Acl object. All of the permissions in this Acl are 185 * assumed to apply to the same page scope. The names of the pages are ignored; only the actions and principals matter. 186 * 187 * @param acl the ACL 188 * @return the ACL string 189 */ 190 protected static String printAcl( final Acl acl ) { 191 // Extract the ACL entries into a Map with keys == permissions, values == principals 192 final Map< String, List< Principal > > permissionPrincipals = new TreeMap<>(); 193 final Enumeration< AclEntry > entries = acl.aclEntries(); 194 while( entries.hasMoreElements() ) { 195 final AclEntry entry = entries.nextElement(); 196 final Principal principal = entry.getPrincipal(); 197 final Enumeration< Permission > permissions = entry.permissions(); 198 while( permissions.hasMoreElements() ) { 199 final Permission permission = permissions.nextElement(); 200 List< Principal > principals = permissionPrincipals.get( permission.getActions() ); 201 if (principals == null) { 202 principals = new ArrayList<>(); 203 final String action = permission.getActions(); 204 if( action.indexOf(',') != -1 ) { 205 throw new IllegalStateException("AclEntry permission cannot have multiple targets."); 206 } 207 permissionPrincipals.put( action, principals ); 208 } 209 principals.add( principal ); 210 } 211 } 212 213 // Now, iterate through each permission in the map and generate an ACL string 214 final StringBuilder s = new StringBuilder(); 215 for( final Map.Entry< String, List< Principal > > entry : permissionPrincipals.entrySet() ) { 216 final String action = entry.getKey(); 217 final List< Principal > principals = entry.getValue(); 218 principals.sort( new PrincipalComparator() ); 219 s.append( "[{ALLOW " ).append( action ).append( " " ); 220 for( int i = 0; i < principals.size(); i++ ) { 221 final Principal principal = principals.get( i ); 222 s.append( principal.getName() ); 223 if( i < ( principals.size() - 1 ) ) { 224 s.append( "," ); 225 } 226 } 227 s.append( "}]\n" ); 228 } 229 return s.toString(); 230 } 231 232}