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     */
019    package org.apache.wiki.auth.acl;
020    
021    import java.security.Permission;
022    import java.security.Principal;
023    import java.util.ArrayList;
024    import java.util.Collections;
025    import java.util.Enumeration;
026    import java.util.List;
027    import java.util.Map;
028    import java.util.NoSuchElementException;
029    import java.util.Properties;
030    import java.util.StringTokenizer;
031    import java.util.TreeMap;
032    import java.util.regex.Matcher;
033    import java.util.regex.Pattern;
034    
035    import org.apache.log4j.Logger;
036    import org.apache.wiki.PageLock;
037    import org.apache.wiki.PageManager;
038    import org.apache.wiki.WikiContext;
039    import org.apache.wiki.WikiEngine;
040    import org.apache.wiki.WikiPage;
041    import org.apache.wiki.api.exceptions.ProviderException;
042    import org.apache.wiki.attachment.Attachment;
043    import org.apache.wiki.auth.AuthorizationManager;
044    import org.apache.wiki.auth.PrincipalComparator;
045    import org.apache.wiki.auth.WikiSecurityException;
046    import org.apache.wiki.auth.permissions.PagePermission;
047    import org.apache.wiki.auth.permissions.PermissionFactory;
048    import org.apache.wiki.render.RenderingManager;
049    
050    /**
051     * Default implementation that parses Acls from wiki page markup.
052     *
053     * @since 2.3
054     */
055    public class DefaultAclManager implements AclManager {
056    
057        private static final Logger log = Logger.getLogger(DefaultAclManager.class);
058    
059        private AuthorizationManager m_auth = null;
060        private WikiEngine m_engine = null;
061        private static final String PERM_REGEX = "(" +
062                PagePermission.COMMENT_ACTION + "|" +
063                PagePermission.DELETE_ACTION + "|" +
064                PagePermission.EDIT_ACTION + "|" +
065                PagePermission.MODIFY_ACTION + "|" +
066                PagePermission.RENAME_ACTION + "|" +
067                PagePermission.UPLOAD_ACTION + "|" +
068                PagePermission.VIEW_ACTION + ")";
069        private static final String ACL_REGEX = "\\[\\{\\s*ALLOW\\s+" + PERM_REGEX + "\\s*(.*?)\\s*\\}\\]";
070    
071        /**
072         * Identifies ACL strings in wiki text; the first group is the action (view, edit) and
073         * the second is the list of Principals separated by commas. The overall match is
074         * the ACL string from [{ to }].
075         */
076        public static final Pattern ACL_PATTERN = Pattern.compile(ACL_REGEX);
077    
078        /**
079         * Initializes the AclManager with a supplied wiki engine and properties.
080         *
081         * @param engine the wiki engine
082         * @param props  the initialization properties
083         * @see org.apache.wiki.auth.acl.AclManager#initialize(org.apache.wiki.WikiEngine,
084         *      java.util.Properties)
085         */
086        public void initialize(WikiEngine engine, Properties props) {
087            m_auth = engine.getAuthorizationManager();
088            m_engine = engine;
089        }
090    
091        /**
092         * A helper method for parsing textual AccessControlLists. The line is in
093         * form "ALLOW <permission> <principal>, <principal>, <principal>". This
094         * method was moved from Authorizer.
095         *
096         * @param page     The current wiki page. If the page already has an ACL, it
097         *                 will be used as a basis for this ACL in order to avoid the
098         *                 creation of a new one.
099         * @param ruleLine The rule line, as described above.
100         * @return A valid Access Control List. May be empty.
101         * @throws WikiSecurityException if the ruleLine was faulty somehow.
102         * @since 2.1.121
103         */
104        public Acl parseAcl(WikiPage page, String ruleLine) throws WikiSecurityException {
105            Acl acl = page.getAcl();
106            if (acl == null) {
107                acl = new AclImpl();
108            }
109    
110            try {
111                StringTokenizer fieldToks = new StringTokenizer(ruleLine);
112                fieldToks.nextToken();
113                String actions = fieldToks.nextToken();
114                page.getName();
115    
116                while (fieldToks.hasMoreTokens()) {
117                    String principalName = fieldToks.nextToken(",").trim();
118                    Principal principal = m_auth.resolvePrincipal(principalName);
119                    AclEntry oldEntry = acl.getEntry(principal);
120    
121                    if (oldEntry != null) {
122                        log.debug("Adding to old acl list: " + principal + ", " + actions);
123                        oldEntry.addPermission(PermissionFactory.getPagePermission(page, actions));
124                    } else {
125                        log.debug("Adding new acl entry for " + actions);
126                        AclEntry entry = new AclEntryImpl();
127    
128                        entry.setPrincipal(principal);
129                        entry.addPermission(PermissionFactory.getPagePermission(page, actions));
130    
131                        acl.addEntry(entry);
132                    }
133                }
134    
135                page.setAcl(acl);
136    
137                log.debug(acl.toString());
138            } catch (NoSuchElementException nsee) {
139                log.warn("Invalid access rule: " + ruleLine + " - defaults will be used.");
140                throw new WikiSecurityException("Invalid access rule: " + ruleLine, nsee);
141            } catch (IllegalArgumentException iae) {
142                throw new WikiSecurityException("Invalid permission type: " + ruleLine, iae);
143            }
144    
145            return acl;
146        }
147    
148    
149        /**
150         * Returns the access control list for the page.
151         * If the ACL has not been parsed yet, it is done
152         * on-the-fly. If the page has a parent page, then that is tried also.
153         * This method was moved from Authorizer;
154         * it was consolidated with some code from AuthorizationManager.
155         * This method is guaranteed to return a non-<code>null</code> Acl.
156         *
157         * @param page the page
158         * @return the Acl representing permissions for the page
159         * @since 2.2.121
160         */
161        public Acl getPermissions(WikiPage page) {
162            //
163            //  Does the page already have cached ACLs?
164            //
165            Acl acl = page.getAcl();
166            log.debug("page=" + page.getName() + "\n" + acl);
167    
168            if (acl == null) {
169                //
170                //  If null, try the parent.
171                //
172                if (page instanceof Attachment) {
173                    WikiPage parent = m_engine.getPage(((Attachment) page).getParentName());
174    
175                    acl = getPermissions(parent);
176                } else {
177                    //
178                    //  Or, try parsing the page
179                    //
180                    WikiContext ctx = new WikiContext(m_engine, page);
181    
182                    ctx.setVariable(RenderingManager.VAR_EXECUTE_PLUGINS, Boolean.FALSE);
183    
184                    m_engine.getHTML(ctx, page);
185    
186                    page = m_engine.getPage(page.getName(), page.getVersion());
187                    acl = page.getAcl();
188    
189                    if (acl == null) {
190                        acl = new AclImpl();
191                        page.setAcl(acl);
192                    }
193                }
194            }
195    
196            return acl;
197        }
198    
199        /**
200         * Sets the access control list for the page and persists it by prepending
201         * it to the wiki page markup and saving the page. When this method is
202         * called, all other ACL markup in the page is removed. This method will forcibly
203         * expire locks on the wiki page if they exist. Any ProviderExceptions will be
204         * re-thrown as WikiSecurityExceptions.
205         *
206         * @param page the wiki page
207         * @param acl  the access control list
208         * @throws WikiSecurityException of the Acl cannot be set
209         * @since 2.5
210         */
211        public void setPermissions(WikiPage page, Acl acl) throws WikiSecurityException {
212            PageManager pageManager = m_engine.getPageManager();
213    
214            // Forcibly expire any page locks
215            PageLock lock = pageManager.getCurrentLock(page);
216            if (lock != null) {
217                pageManager.unlockPage(lock);
218            }
219    
220            // Remove all of the existing ACLs.
221            String pageText = m_engine.getPureText(page);
222            Matcher matcher = DefaultAclManager.ACL_PATTERN.matcher(pageText);
223            String cleansedText = matcher.replaceAll("");
224            String newText = DefaultAclManager.printAcl(page.getAcl()) + cleansedText;
225            try {
226                pageManager.putPageText(page, newText);
227            } catch (ProviderException e) {
228                throw new WikiSecurityException("Could not set Acl. Reason: ProviderExcpetion " + e.getMessage(), e);
229            }
230        }
231    
232        /**
233         * Generates an ACL string for inclusion in a wiki page, based on a supplied Acl object.
234         * All of the permissions in this Acl are assumed to apply to the same page scope.
235         * The names of the pages are ignored; only the actions and principals matter.
236         *
237         * @param acl the ACL
238         * @return the ACL string
239         */
240        protected static String printAcl(Acl acl) {
241            // Extract the ACL entries into a Map with keys == permissions, values == principals
242            Map<String, List<Principal>> permissionPrincipals = new TreeMap<String, List<Principal>>();
243            Enumeration<AclEntry> entries = acl.entries();
244            while (entries.hasMoreElements()) {
245                AclEntry entry = entries.nextElement();
246                Principal principal = entry.getPrincipal();
247                Enumeration<Permission> permissions = entry.permissions();
248                while (permissions.hasMoreElements()) {
249                    Permission permission = permissions.nextElement();
250                    List<Principal> principals = permissionPrincipals.get(permission.getActions());
251                    if (principals == null) {
252                        principals = new ArrayList<Principal>();
253                        String action = permission.getActions();
254                        if (action.indexOf(',') != -1) {
255                            throw new IllegalStateException("AclEntry permission cannot have multiple targets.");
256                        }
257                        permissionPrincipals.put(action, principals);
258                    }
259                    principals.add(principal);
260                }
261            }
262    
263            // Now, iterate through each permission in the map and generate an ACL string
264    
265            StringBuffer s = new StringBuffer();
266            for (Map.Entry<String, List<Principal>> entry : permissionPrincipals.entrySet()) {
267                String action = entry.getKey();
268                List<Principal> principals = entry.getValue();
269                Collections.sort(principals, new PrincipalComparator());
270                s.append("[{ALLOW ");
271                s.append(action);
272                s.append(" ");
273                for (int i = 0; i < principals.size(); i++) {
274                    Principal principal = principals.get(i);
275                    s.append(principal.getName());
276                    if (i < (principals.size() - 1)) {
277                        s.append(",");
278                    }
279                }
280                s.append("}]\n");
281            }
282            return s.toString();
283        }
284    
285    }