    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}