001: /*
002: * $Id: AccessRule.java,v 1.49 2007/09/18 08:45:06 agoubard Exp $
003: *
004: * Copyright 2003-2007 Orange Nederland Breedband B.V.
005: * See the COPYRIGHT file for redistribution and use restrictions.
006: */
007: package org.xins.server;
008:
009: import java.util.StringTokenizer;
010:
011: import org.apache.oro.text.regex.Perl5Matcher;
012: import org.apache.oro.text.regex.Perl5Pattern;
013:
014: import org.xins.common.MandatoryArgumentChecker;
015: import org.xins.common.Utils;
016: import org.xins.common.text.ParseException;
017: import org.xins.common.text.SimplePatternParser;
018:
019: /**
020: * Access rule. This class can take a character string to produce an
021: * {@link AccessRule} object from it.
022: *
023: * <h3>Descriptor format</h3>
024: *
025: * <p>A descriptor must comply to the following format:
026: * <ul>
027: * <li>start with either <code>"allow"</code> or <code>"deny"</code>;
028: * <li>followed by any number of white space characters;
029: * <li>followed by a valid IP address;
030: * <li>followed by a slash character (<code>'/'</code>);
031: * <li>followed by a mask between 0 and 32 in decimal format, without
032: * leading zeroes;
033: * <li>followed by any number of white space characters;
034: * <li>followed by a simple pattern, see class {@link SimplePatternParser}.
035: * </ul>
036: *
037: * <h3>Descriptor examples</h3>
038: *
039: * <p>Example of access rule descriptors:
040: *
041: * <dl>
042: * <dt><code>"allow 194.134.168.213/32 *"</code></dt>
043: * <dd>Allows 194.134.168.213 to access any function.</dd>
044: *
045: * <dt><code>"deny 194.134.168.213/24 _*"</code></dt>
046: * <dd>Denies all 194.134.168.x IP addresses to access any function
047: * starting with an underscore (<code>'_'</code>).</dd>
048: * </dl>
049: *
050: * @version $Revision: 1.49 $ $Date: 2007/09/18 08:45:06 $
051: * @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
052: * @author <a href="mailto:chris.gilbride@orange-ftgroup.com">Chris Gilbride</a>
053: * @author <a href="mailto:anthony.goubard@japplis.com">Anthony Goubard</a>
054: *
055: * @since XINS 1.0.0
056: */
057: public final class AccessRule implements AccessRuleContainer {
058:
059: /**
060: * If the access method is 'allow' or not.
061: */
062: private final boolean _allow;
063:
064: /**
065: * The IP address filter used to create the access rule. Cannot be
066: * <code>null</code>.
067: */
068: private final IPFilter _ipFilter;
069:
070: /**
071: * The function name pattern. Cannot be <code>null</code>.
072: */
073: private final Perl5Pattern _functionNameRegex;
074:
075: /**
076: * The calling convention name pattern. Cannot be <code>null</code>.
077: */
078: private final Perl5Pattern _conventionNameRegex;
079:
080: /**
081: * String representation of this object. Cannot be <code>null</code>.
082: */
083: private final String _asString;
084:
085: /**
086: * Flag that indicates whether this object is disposed.
087: */
088: private boolean _disposed;
089:
090: /**
091: * Constructs a new <code>AccessRule</code>.
092: *
093: * @param allow
094: * flag that indicates if this rule grants access (<code>true</code>) or
095: * denies access (<code>false</code>).
096: *
097: * @param ipFilter
098: * filter used for matching (or not) IP addresses, cannot be
099: * <code>null</code>.
100: *
101: * @param asString
102: * textual presentation of this access rule, cannot be
103: * <code>null</code>.
104: *
105: * @param functionNameRegex
106: * regular expression used for matching (or not) a function name; cannot
107: * be <code>null</code>.
108: *
109: * @param conventionNameRegex
110: * regular expression used for matching (or not) a calling convention name; cannot be <code>null</code>.
111: *
112: * @throws IllegalArgumentException
113: * if <code>ipFilter == null
114: * || functionNameRegex == null
115: * || conventionNameRegex == null
116: * || asString == null</code>.
117: */
118: private AccessRule(boolean allow, IPFilter ipFilter,
119: Perl5Pattern functionNameRegex,
120: Perl5Pattern conventionNameRegex, String asString)
121: throws IllegalArgumentException {
122:
123: // Check preconditions
124: MandatoryArgumentChecker.check("ipFilter", ipFilter,
125: "functionNameRegex", functionNameRegex,
126: "conventionNameRegex", conventionNameRegex, "asString",
127: asString);
128:
129: // Store the data
130: _allow = allow;
131: _ipFilter = ipFilter;
132: _functionNameRegex = functionNameRegex;
133: _conventionNameRegex = conventionNameRegex;
134: _asString = asString;
135: }
136:
137: /**
138: * Parses the specified character string to construct a new
139: * <code>AccessRule</code> object.
140: *
141: * @param descriptor
142: * the access rule descriptor, the character string to parse, cannot be
143: * <code>null</code>.
144: *
145: * @return
146: * an {@link AccessRule} instance, never <code>null</code>.
147: *
148: * @throws IllegalArgumentException
149: * if <code>descriptor == null</code>.
150: *
151: * @throws ParseException
152: * If there was a parsing error.
153: */
154: public static AccessRule parseAccessRule(String descriptor)
155: throws IllegalArgumentException, ParseException {
156:
157: // Check preconditions
158: MandatoryArgumentChecker.check("descriptor", descriptor);
159:
160: StringTokenizer tokenizer = new StringTokenizer(descriptor,
161: " \t\n\r");
162:
163: // Determine if it is an 'allow' or a 'deny' rule
164: boolean allow;
165: String sAllow = nextToken(descriptor, tokenizer);
166: if ("allow".equals(sAllow)) {
167: allow = true;
168: } else if ("deny".equals(sAllow)) {
169: allow = false;
170: } else {
171: String message = "First token of descriptor is \"" + sAllow
172: + "\", instead of either 'allow' or 'deny'.";
173: throw new ParseException(message);
174: }
175:
176: // Determine the IP address filter
177: String sFilter = nextToken(descriptor, tokenizer);
178: IPFilter filter = IPFilter.parseIPFilter(sFilter);
179:
180: SimplePatternParser parser = new SimplePatternParser();
181: // Determine the function the access is to be checked for
182: String functionPatternString = nextToken(descriptor, tokenizer);
183: Perl5Pattern functionPattern = parser
184: .parseSimplePattern(functionPatternString);
185:
186: // Determine the function the access is to be checked for
187: String conventionPatternString = "*";
188: if (tokenizer.hasMoreTokens()) {
189: conventionPatternString = tokenizer.nextToken();
190: }
191: Perl5Pattern conventionPattern = parser
192: .parseSimplePattern(conventionPatternString);
193:
194: // Construct a description
195: String asString = sAllow + ' ' + filter.toString() + ' '
196: + functionPatternString + ' ' + conventionPatternString;
197:
198: return new AccessRule(allow, filter, functionPattern,
199: conventionPattern, asString);
200: }
201:
202: /**
203: * Returns the next token in the descriptor.
204: *
205: * @param descriptor
206: * the original descriptor, useful when constructing the message for a
207: * {@link ParseException}, when appropriate, should not be
208: * <code>null</code>.
209: *
210: * @param tokenizer
211: * the {@link StringTokenizer} to retrieve the next token from, cannot be
212: * <code>null</code>.
213: *
214: * @return
215: * the next token, never <code>null</code>.
216: *
217: * @throws ParseException
218: * if <code>tokenizer.{@link StringTokenizer#hasMoreTokens()
219: * hasMoreTokens}() == false</code>.
220: */
221: private static String nextToken(String descriptor,
222: StringTokenizer tokenizer) throws ParseException {
223:
224: if (!tokenizer.hasMoreTokens()) {
225: String message = "The string \"" + descriptor
226: + "\" is invalid as an access rule descriptor. "
227: + "More tokens expected.";
228: throw new ParseException(message);
229: } else {
230: return tokenizer.nextToken();
231: }
232: }
233:
234: /**
235: * Returns if this rule is an <em>allow</em> or a <em>deny</em> rule.
236: *
237: * @return
238: * <code>true</code> if this is an <em>allow</em> rule, or
239: * <code>false</code> if this is a <em>deny</em> rule.
240: */
241: public boolean isAllowRule() {
242: return _allow;
243: }
244:
245: /**
246: * Returns the IP filter.
247: *
248: * @return
249: * the {@link IPFilter} associated with this access rule, never
250: * <code>null</code>.
251: */
252: public IPFilter getIPFilter() {
253: return _ipFilter;
254: }
255:
256: /**
257: * Determines if the specified IP address and function match this rule.
258: *
259: * <p>Calling this function is equivalent to calling:
260: *
261: * <blockquote><code>{@link #isAllowed(String,String) isAllowed}(ip,
262: * functionName) != null</code></blockquote>
263: *
264: * @param ip
265: * the IP address to match, cannot be <code>null</code>.
266: *
267: * @param functionName
268: * the name of the function to match, cannot be <code>null</code>.
269: *
270: * @param conventionName
271: * the name of the calling convention to match, can be <code>null</code>.
272: *
273: * @return
274: * <code>true</code> if this rule matches, <code>false</code> otherwise.
275: *
276: * @throws IllegalStateException
277: * if this access rule is disposed (<em>since XINS 1.3.0</em>).
278: *
279: * @throws IllegalArgumentException
280: * if <code>ip == null || functionName == null</code>.
281: *
282: * @throws ParseException
283: * if the specified IP address cannot be parsed.
284: *
285: * @since XINS 2.1.
286: */
287: public boolean match(String ip, String functionName,
288: String conventionName) throws IllegalStateException,
289: IllegalArgumentException, ParseException {
290:
291: // Check state
292: if (_disposed) {
293: String detail = "This AccessRule is disposed.";
294: Utils.logProgrammingError(detail);
295: throw new IllegalStateException(detail);
296: }
297:
298: // Delegate to the isAllowed method
299: return isAllowed(ip, functionName, conventionName) != null;
300: }
301:
302: /**
303: * Determines if the specified IP address is allowed to access the
304: * specified function, returning a <code>Boolean</code> object or
305: * <code>null</code>.
306: *
307: * <p>This method finds the first matching rule and then returns the
308: * <em>allow</em> property of that rule (see
309: * {@link AccessRule#isAllowRule()}). If there is no matching rule, then
310: * <code>null</code> is returned.
311: *
312: * @param ip
313: * the IP address, cannot be <code>null</code>.
314: *
315: * @param functionName
316: * the name of the function, cannot be <code>null</code>.
317: *
318: * @param conventionName
319: * the name of the calling convention, can be <code>null</code>.
320: *
321: * @return
322: * {@link Boolean#TRUE} if the specified IP address is allowed to access
323: * the specified function, {@link Boolean#FALSE} if it is disallowed
324: * access or <code>null</code> if there is no match.
325: *
326: * @throws IllegalStateException
327: * if this object is disposed (<em>since XINS 1.3.0</em>).
328: *
329: * @throws IllegalArgumentException
330: * if <code>ip == null || functionName == null</code>.
331: *
332: * @throws ParseException
333: * if the specified IP address is malformed.
334: *
335: * @since XINS 2.1.
336: */
337: public Boolean isAllowed(String ip, String functionName,
338: String conventionName) throws IllegalStateException,
339: IllegalArgumentException, ParseException {
340:
341: // Check state
342: if (_disposed) {
343: String detail = "This AccessRule is disposed.";
344: Utils.logProgrammingError(detail);
345: throw new IllegalStateException(detail);
346: }
347:
348: // Check arguments
349: MandatoryArgumentChecker.check("ip", ip, "functionName",
350: functionName);
351:
352: // First check if the IP filter matches
353: Perl5Matcher patternMatcher = new Perl5Matcher();
354: if (_ipFilter.match(ip)) {
355:
356: // Then check if the function name matches
357: if (patternMatcher
358: .matches(functionName, _functionNameRegex)
359: && (conventionName == null || patternMatcher
360: .matches(conventionName,
361: _conventionNameRegex))) {
362: return _allow ? Boolean.TRUE : Boolean.FALSE;
363: }
364: }
365:
366: return null;
367: }
368:
369: /**
370: * Disposes this access rule. All claimed resources are freed as much as
371: * possible.
372: *
373: * <p>Once disposed, neither {@link #match} nor {@link #isAllowed} should
374: * be called.
375: *
376: * @throws IllegalStateException
377: * if this access rule is already disposed (<em>since XINS 1.3.0</em>).
378: */
379: public void dispose() {
380:
381: // Check state
382: if (_disposed) {
383: String detail = "This AccessRule is already disposed.";
384: Utils.logProgrammingError(detail);
385: throw new IllegalStateException(detail);
386: }
387:
388: // Mark this object as disposed
389: _disposed = true;
390: }
391:
392: /**
393: * Returns a character string representation of this object. The returned
394: * string is in the form:
395: *
396: * <blockquote><em>type a.b.c.d/m pattern</em></blockquote>
397: *
398: * where <em>type</em> is either <code>"allow"</code> or
399: * <code>"deny"</code>, <em>a.b.c.d</em> is the base IP address, <em>m</em>
400: * is the mask, and <em>pattern</em> is the function name simple pattern.
401: *
402: * @return
403: * a character string representation of this access rule, never
404: * <code>null</code>.
405: */
406: public String toString() {
407: return _asString;
408: }
409: }
|