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