001: /*
002: jGuard is a security framework based on top of jaas (java authentication and authorization security).
003: it is written for web applications, to resolve simply, access control problems.
004: version $Name$
005: http://sourceforge.net/projects/jguard/
006:
007: Copyright (C) 2004 Charles GAY
008:
009: This library is free software; you can redistribute it and/or
010: modify it under the terms of the GNU Lesser General Public
011: License as published by the Free Software Foundation; either
012: version 2.1 of the License, or (at your option) any later version.
013:
014: This library is distributed in the hope that it will be useful,
015: but WITHOUT ANY WARRANTY; without even the implied warranty of
016: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017: Lesser General Public License for more details.
018:
019: You should have received a copy of the GNU Lesser General Public
020: License along with this library; if not, write to the Free Software
021: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
022:
023:
024: jGuard project home page:
025: http://sourceforge.net/projects/jguard/
026:
027: */
028: package net.sf.jguard.core.authorization.permissions;
029:
030: import java.io.Serializable;
031: import java.net.URI;
032: import java.net.URISyntaxException;
033: import java.security.AccessController;
034: import java.security.Guard;
035: import java.security.Permission;
036: import java.util.ArrayList;
037: import java.util.Collection;
038: import java.util.logging.Level;
039: import java.util.logging.Logger;
040: import java.util.regex.Matcher;
041: import java.util.regex.Pattern;
042:
043: /**
044: * this permission, <strong>only</strong> implies URLPermission.
045: *
046: * @see java.lang.IllegalArgumentException which wrap the
047: * @see java.net.URISyntaxException thrown if the URI is not correct.
048: * @author <a href="mailto:diabolo512@users.sourceforge.net">Charles Gay</a>
049: *
050: */
051: public final class URLPermission extends java.security.BasicPermission
052: implements Serializable, Cloneable, Comparable {
053:
054: /** Logger for this class */
055: private static final Logger logger = Logger
056: .getLogger(URLPermission.class.getName());
057:
058: //HTTP methods
059: public static final String DELETE = "DELETE";
060: public static final String GET = "GET";
061: public static final String HEAD = "HEAD";
062: public static final String OPTIONS = "OPTIONS";
063: public static final String POST = "POST";
064: public static final String PUT = "PUT";
065: public static final String TRACE = "TRACE";
066: public static final String ANY = "ANY";
067:
068: //protocoles
069: public static final String HTTP = "http";
070: public static final String HTTPS = "https";
071:
072: /**
073: * serial version id.
074: */
075: private static final long serialVersionUID = 3257283643243574067L;
076:
077: private Pattern pattern;
078:
079: /**
080: * regexp to display (include /* instead of /.*)
081: */
082: private String prettyPattern;
083:
084: private URI uri;
085:
086: /**
087: * unique permission's name.
088: */
089: private String name;
090:
091: /**
092: * explain permission
093: */
094: private String description = "";
095:
096: private URLParameterCollection parameters;
097:
098: private Collection methods = new ArrayList();
099:
100: /**
101: * protocol (http, https...). default value is 'http'
102: */
103: private String scheme = URLPermission.ANY;
104:
105: /**
106: * actions
107: */
108: private StringBuffer actions = new StringBuffer();
109:
110: /**
111: * Creates a new instance of URLPermission.
112: *
113: * @param n
114: * permission's name
115: */
116: public URLPermission(String n) {
117: super (n);
118: this .name = n;
119: try {
120: uri = new URI("");
121: } catch (URISyntaxException e) {
122: throw new IllegalArgumentException(e.getMessage());
123: }
124: parameters = new URLParameterCollection();
125: }
126:
127: /**
128: * Creates a new instance of URLPermission.
129: *
130: * @param name
131: * permission name
132: * @param actions permission's actions splitted by a "," :
133: * regexp,scheme(optional),description(optional),http methods(optional)
134: * http methods and schemes (Http or https) are automatically recognized, after the regexp.
135: * @throws IllegalArgumentException
136: * which wraps a
137: * @see URISyntaxException
138: */
139: public URLPermission(String name, String actions) {
140: super (name);
141: this .name = name;
142: String[] actionsArray = actions.split(",");
143:
144: if (actionsArray.length > 4)
145: throw new IllegalArgumentException(
146: " 'actions' argument can contain a maximum of three elements separated by ',' ");
147:
148: try {
149: setURI(actionsArray[0]);
150: } catch (URISyntaxException e) {
151: throw new IllegalArgumentException(e.getMessage());
152: }
153:
154: for (int i = 1; i < actionsArray.length; i++) {
155:
156: if (URLPermission.HTTPS.equalsIgnoreCase(actionsArray[i])
157: || URLPermission.HTTP
158: .equalsIgnoreCase(actionsArray[i])) {
159: this .scheme = actionsArray[i];
160: continue;
161: } else if (URLPermission.DELETE
162: .equalsIgnoreCase(actionsArray[i])
163: || URLPermission.GET
164: .equalsIgnoreCase(actionsArray[i])
165: || URLPermission.HEAD
166: .equalsIgnoreCase(actionsArray[i])
167: || URLPermission.OPTIONS
168: .equalsIgnoreCase(actionsArray[i])
169: || URLPermission.POST
170: .equalsIgnoreCase(actionsArray[i])
171: || URLPermission.PUT
172: .equalsIgnoreCase(actionsArray[i])
173: || URLPermission.TRACE
174: .equalsIgnoreCase(actionsArray[i])) {
175: methods.add(actionsArray[i]);
176: continue;
177:
178: } else {
179: this .description = actionsArray[i];
180: continue;
181: }
182:
183: }
184:
185: //no scheme specified implies any scheme
186: if (scheme == null) {
187: scheme = URLPermission.ANY;
188: }
189:
190: //no methods specified implies all methods
191: if (methods.size() == 0) {
192: methods.add(URLPermission.ANY);
193: }
194:
195: // Mount the string for actions
196:
197: // regexp output form
198: this .actions.append(this .prettyPattern);
199:
200: // scheme
201: this .actions.append(',');
202: this .actions.append(this .scheme);
203:
204: // description
205: if (this .description.length() > 0) {
206: this .actions.append(',');
207: this .actions.append(this .description);
208: }
209:
210: }
211:
212: /**
213: * 'prettyPattern' is the regexp included in the URI : uri with a 'star'(*) operator, which can be evaluated.
214: *
215: * @param pPattern
216: * @throws URISyntaxException
217: */
218: private void setURI(String pPattern) throws URISyntaxException {
219:
220: // build the regexp pattern
221: String regexp = pPattern;
222: regexp = buildRegexpFromString(getPathFromURIString(regexp));
223:
224: pattern = Pattern.compile(regexp);
225: if (logger.isLoggable(Level.FINEST)) {
226: logger.log(Level.FINEST, "regexp=" + regexp);
227: }
228: String uriWithoutRegexp = removeRegexpFromURI(pPattern);
229: this .uri = new URI(uriWithoutRegexp);
230:
231: if (logger.isLoggable(Level.FINEST)) {
232: logger.finest("uri=" + uri);
233: }
234:
235: prettyPattern = pPattern;
236:
237: parameters = URLParameterCollection
238: .getURLParameters(getQueryFromURIString(pPattern));
239: }
240:
241: /**
242: * replace '*'character (not followed by '*' character, or if it's the last '*') by '' and we replace '**' by '*'.
243: *
244: * @param uriPath
245: * @return URI escaped
246: */
247: static public String removeRegexpFromURI(String uriPath) {
248: // we replace '*'character (not followed by '*' character, or if it's
249: // the last '*') by ''
250: // => '*' in the regexp is replaced
251: uriPath = uriPath.replaceAll("\\*(?!\\*)", "");
252: if (logger.isLoggable(Level.FINEST)) {
253: // logger.log(Level.FINEST,"uriPath="+uriPath);
254: }
255: // we replace '**' by '*' => we convert the escaped(double) '*' in the
256: // simple '*'
257: uriPath = uriPath.replaceAll("\\*{2}", "\\*");
258:
259: // --------------- added by VBE
260: // replace '$' by UTF-8 symbol for '$'
261: uriPath = uriPath.replaceAll("\\$\\{", "%24%7B");
262: // replace '}' by UTF-8 symbol for '}'
263: uriPath = uriPath.replaceAll("\\}", "%7D");
264: // --------------- end added by VBE
265:
266: return uriPath;
267: }
268:
269: public static String getPathFromURIString(String uriString) {
270: String uriPath = uriString;
271: int position = uriString.indexOf("?");
272: if (position != -1) {
273: uriPath = uriString.substring(0, position);
274: }
275: return uriPath;
276: }
277:
278: public static String getQueryFromURIString(String uriString) {
279: String uriQuery = "";
280: int position = uriString.indexOf("?");
281: if (position != -1) {
282: uriQuery = uriString.substring(position + 1, uriString
283: .length());
284: }
285: return uriQuery;
286: }
287:
288: /**
289: * convenient method to escape regexp special characters, and only use the '*' characters for building the regexp Pattern.
290: *
291: * @param regexp
292: * @return escaped regexp candidate
293: */
294: public static String buildRegexpFromString(String regexp) {
295:
296: // replace '\' by '\\'
297: regexp = regexp.replaceAll("\\\\", "\\\\\\\\");
298: // replace '**' by '\*\*'
299: regexp = regexp.replaceAll("\\*\\*", "\\\\*\\\\*");
300: // replace '?' by '\\?'
301: regexp = regexp.replaceAll("\\?", "\\\\\\\\?");
302: // replace '+' by '\\+'
303: regexp = regexp.replaceAll("\\+", "\\\\\\\\+");
304: // replace '{' by '\\{'
305: // regexp = regexp.replaceAll("\\{", "\\\\\\\\{");
306: // replace '}' by '\\}'
307: // regexp = regexp.replaceAll("\\}", "\\\\\\\\}");
308: // replace '[' by '\\['
309: regexp = regexp.replaceAll("\\[", "\\\\\\\\[");
310: // replace ']' by '\\]'
311: regexp = regexp.replaceAll("\\[", "\\\\\\\\]");
312: // replace '^' by '\\^'
313: regexp = regexp.replaceAll("\\^", "\\\\\\\\^");
314: // replace '$' by '\\$'
315: // regexp = regexp.replaceAll("\\$", "\\\\\\\\$");
316:
317: // replace '&' by '\\&'
318: regexp = regexp.replaceAll("\\&", "\\\\\\\\&");
319:
320: // replace '*' by '\.*'
321: regexp = regexp.replaceAll("\\*", "\\.\\*");
322: return regexp;
323: }
324:
325: /**
326: * Determines whether or not to allow access to the guarded object object.
327: * this method comes from the {@link Guard} interface.
328: *
329: * @param perm Permission to check
330: */
331: public void checkGuard(Object perm) {
332: Permission p = (Permission) perm;
333: AccessController.checkPermission(p);
334: }
335:
336: /**
337: * override the java.lang.Object 's <i>clone</i> method.
338: *
339: * @return new URLPermission with the <strong>same Domain</strong>.
340: */
341: public Object clone() throws CloneNotSupportedException {
342:
343: URLPermission permission = null;
344: permission = new URLPermission(this .name, this .getActions());
345: return permission;
346:
347: }
348:
349: /**
350: * @param obj
351: * @return true if equals, false otherwise
352: *
353: */
354: public boolean equals(Object obj) {
355: if ((obj instanceof URLPermission)
356: && ((URLPermission) obj).getName().equals(
357: this .getName())) {
358: // we compare two URLPermission with the same name
359: URLPermission tempPerm = (URLPermission) obj;
360:
361: String[] tempActions = tempPerm.getActions().split(",");
362: URI tempUri = null;
363: try {
364: tempUri = new URI(removeRegexpFromURI(tempActions[0]));
365: } catch (URISyntaxException e) {
366: logger.log(Level.SEVERE, " URI syntax error: "
367: + removeRegexpFromURI(tempActions[0]));
368: }
369:
370: if (!tempPerm.getScheme().equals(this .scheme)) {
371: return false;
372: }
373:
374: if (!tempPerm.getMethods().equals(this .methods)) {
375: return false;
376: }
377:
378: // we should compare paths of these URLPermissions and parameters
379: // but parameter order
380: // doesn't mind
381: if (uri.getPath().equals(tempUri.getPath())) {
382: if (uri.getQuery() == null
383: && tempUri.getQuery() == null) {
384: return true;
385: } else if (uri.getQuery() == null
386: || tempUri.getQuery() == null) {
387: return false;
388: } else if (uri.getQuery().equals(tempUri.getQuery())) {
389: return true;
390: }
391: }
392: return false;
393: }
394: return false;
395: }
396:
397: /**
398: * return actions in a String splitted by comma.
399: *
400: * @return permitted actions.
401: */
402: public String getActions() {
403: return actions.toString();
404: }
405:
406: /**
407: * methode used to accelerate the comparation process: useful when hashcode return different int.
408: *
409: * @return hashcode
410: */
411: public int hashCode() {
412: return name.hashCode();
413: }
414:
415: /**
416: * verify if this permission implies another URLPermission.
417: *
418: * @param permission
419: * @return true if implies, false otherwise
420: */
421: public boolean implies(java.security.Permission permission) {
422: URLPermission urlpTemp = null;
423: if (!(permission instanceof URLPermission)) {
424: if (logger.isLoggable(Level.FINEST)) {
425: logger.log(Level.FINEST,
426: " permission is not an URLPermission. type = "
427: + permission.getClass().getName());
428: }
429: return false;
430:
431: }
432:
433: urlpTemp = (URLPermission) permission;
434:
435: if (this .equals(permission)) {
436: return true;
437: }
438:
439: //test ations
440: String urlpTempActions = urlpTemp.getActions();
441: if (urlpTempActions == null || "".equals(urlpTempActions)) {
442: if (actions == null || "".equals(actions.toString())) {
443: return true;
444: }
445: return false;
446: }
447:
448: //test scheme
449: if (!this .scheme.equals(URLPermission.ANY)
450: && !this .scheme.equals(urlpTemp.getScheme())) {
451: return false;
452: }
453:
454: //test methods
455: if (!this .methods.contains(URLPermission.ANY)) {
456: Collection httpMethods = new ArrayList(urlpTemp
457: .getMethods());
458: httpMethods.retainAll(this .methods);
459: if (httpMethods.size() == 0) {
460: return false;
461: }
462: }
463:
464: boolean b = impliesURI(urlpTemp.getURI());
465:
466: // test the parameters
467: // only if the uri is right
468: if (!b) {
469: return false;
470: }
471:
472: b = impliesParameters(getQueryFromURIString(urlpTemp.getURI()));
473:
474: if (logger.isLoggable(Level.FINEST)) {
475: logger.finest("access decision =" + b);
476: }
477: return b;
478:
479: }
480:
481: private boolean impliesURI(String uri) {
482: String regexp = getPathFromURIString(uri);
483: Matcher m = pattern.matcher(regexp);
484: if (logger.isLoggable(Level.FINEST)) {
485: logger.log(Level.FINEST, "pattern used to check access ="
486: + pattern.pattern());
487: logger.log(Level.FINEST, " path to be checked =" + regexp);
488: }
489: boolean b = m.matches();
490: if (logger.isLoggable(Level.FINEST)) {
491: logger.log(Level.FINEST, "access decision =" + b);
492: }
493: m.reset();
494: return b;
495: }
496:
497: /**
498: * look at the provided parameters by the user permission.
499: *
500: * @param queryFromUserPermission
501: * @return
502: */
503: private boolean impliesParameters(String queryFromUserPermission) {
504:
505: if ("".equals(queryFromUserPermission)) {
506: queryFromUserPermission = null;
507: }
508:
509: // if the permission hasn't got any parameters
510: // => the permission implies any parameters
511: if (queryFromUserPermission != null && !parameters.isEmpty()) {
512: String[] params = queryFromUserPermission.split("&");
513:
514: // iterate over required parameters keys/values
515: for (int a = 0; a < params.length; a++) {
516: String[] keyAndValue = params[a].split("=");
517: URLParameter urlparam = new URLParameter();
518: urlparam.setKey(keyAndValue[0]);
519: String[] values = new String[1];
520: if (keyAndValue.length != 1) {
521: values[0] = keyAndValue[1];
522: } else {
523: values[0] = "";
524: }
525: urlparam.setValue(values);
526: // if the evaluated permissionList does not contain the right
527: // URLParameter
528: if (!parameters.implies(urlparam)) {
529: return false;
530: }
531:
532: }
533: } else if (parameters.isEmpty()
534: && queryFromUserPermission != null) {
535: return true;
536: } else if (parameters.isEmpty()
537: && queryFromUserPermission == null) {
538: return true;
539: } else if (!parameters.isEmpty()
540: && queryFromUserPermission == null) {
541: return false;
542: }
543:
544: // we have iterate over the permission collection
545: // and have found each URLParameter in the evaluated permission
546: return true;
547: }
548:
549: /**
550: * return an enmpy JGPermissionCollection.
551: *
552: * @return empty JGPermissionCollection
553: */
554: public java.security.PermissionCollection newPermissionCollection() {
555: return new JGPositivePermissionCollection();
556: }
557:
558: /**
559: * return a String representation of the permission.
560: *
561: * @return String
562: */
563: public String toString() {
564:
565: StringBuffer sb = new StringBuffer();
566: sb.append(" name: " + this .name);
567: sb.append("\n scheme: " + this .scheme);
568: sb.append("\n parameters: " + this .parameters.toString());
569: sb.append("\n pattern: " + this .pattern);
570: sb.append("\n uri: " + this .uri);
571: sb.append("\n description: " + this .description);
572: sb.append("\n");
573:
574: return sb.toString();
575: }
576:
577: /**
578: * method used to compare two objects. this method is used in Collection to <strong>order</strong> items, and MUST be
579: * consistent with the <i>equals</i> method (eache method should return 0/true in the same cases).
580: *
581: * @param o
582: * object to compare
583: * @see java.lang.Comparable#compareTo(java.lang.Object)
584: * @return 0 if both objects are equals, <0 if 0 is lesser than the URLPermission, >0 if 0 is greater than the
585: * URLPermission
586: */
587: public int compareTo(Object o) {
588:
589: URLPermission perm = (URLPermission) o;
590: if (this .equals(perm)) {
591: return 0;
592: }
593: return this .name.compareTo(perm.getName());
594: }
595:
596: public final String getURI() {
597: return prettyPattern;
598: }
599:
600: public Collection getMethods() {
601: return methods;
602: }
603:
604: public String getScheme() {
605: return scheme;
606: }
607:
608: }
|