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}