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}