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