001: /*
002: * Copyright 2005 Joe Walker
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.directwebremoting.impl;
017:
018: import java.lang.reflect.Method;
019: import java.lang.reflect.Modifier;
020: import java.util.ArrayList;
021: import java.util.HashMap;
022: import java.util.HashSet;
023: import java.util.Iterator;
024: import java.util.List;
025: import java.util.Map;
026: import java.util.Set;
027:
028: import javax.servlet.http.HttpServletRequest;
029:
030: import org.directwebremoting.WebContextFactory;
031: import org.directwebremoting.extend.AccessControl;
032: import org.directwebremoting.extend.AccessDeniedException;
033: import org.directwebremoting.extend.Creator;
034: import org.directwebremoting.extend.LoginRequiredException;
035: import org.directwebremoting.util.Messages;
036:
037: /**
038: * Control who should be accessing which methods on which classes.
039: * @author Joe Walker [joe at getahead dot ltd dot uk]
040: */
041: public class DefaultAccessControl implements AccessControl {
042: /* (non-Javadoc)
043: * @see org.directwebremoting.extend.AccessControl#assertExecutionIsPossible(org.directwebremoting.extend.Creator, java.lang.String, java.lang.reflect.Method)
044: */
045: public void assertExecutionIsPossible(Creator creator,
046: String className, Method method) throws SecurityException {
047: assertIsRestrictedByRole(className, method);
048: assertIsDisplayable(creator, className, method);
049: }
050:
051: /* (non-Javadoc)
052: * @see org.directwebremoting.AccessControl#getReasonToNotDisplay(org.directwebremoting.Creator, java.lang.String, java.lang.reflect.Method)
053: */
054: public void assertIsDisplayable(Creator creator, String className,
055: Method method) throws SecurityException {
056: assertIsMethodPublic(method);
057: assertIsExecutable(className, method.getName());
058: assertIsNotOnBaseObject(method);
059:
060: if (!exposeInternals) {
061: assertIsClassDwrInternal(creator);
062: assertAreParametersDwrInternal(method);
063: }
064: }
065:
066: /* (non-Javadoc)
067: * @see org.directwebremoting.AccessControl#addRoleRestriction(java.lang.String, java.lang.String, java.lang.String)
068: */
069: public void addRoleRestriction(String scriptName,
070: String methodName, String role) {
071: String key = scriptName + '.' + methodName;
072: Set<String> roles = roleRestrictMap.get(key);
073: if (roles == null) {
074: roles = new HashSet<String>();
075: roleRestrictMap.put(key, roles);
076: }
077:
078: roles.add(role);
079: }
080:
081: /* (non-Javadoc)
082: * @see org.directwebremoting.AccessControl#addIncludeRule(java.lang.String, java.lang.String)
083: */
084: public void addIncludeRule(String scriptName, String methodName) {
085: Policy policy = getPolicy(scriptName);
086:
087: // If the policy for the given type is defaultAllow then we need to go
088: // to default disallow mode, and check that the are not rules applied
089: if (policy.defaultAllow) {
090: if (!policy.rules.isEmpty()) {
091: throw new IllegalArgumentException(
092: Messages
093: .getString(
094: "DefaultAccessControl.MixedIncludesAndExcludes",
095: scriptName));
096: }
097:
098: policy.defaultAllow = false;
099: }
100:
101: // Add the rule to this policy
102: policy.rules.add(methodName);
103: }
104:
105: /* (non-Javadoc)
106: * @see org.directwebremoting.AccessControl#addExcludeRule(java.lang.String, java.lang.String)
107: */
108: public void addExcludeRule(String scriptName, String methodName) {
109: Policy policy = getPolicy(scriptName);
110:
111: // If the policy for the given type is defaultAllow then we need to go
112: // to default disallow mode, and check that the are not rules applied
113: if (!policy.defaultAllow) {
114: if (!policy.rules.isEmpty()) {
115: throw new IllegalArgumentException(
116: Messages
117: .getString(
118: "DefaultAccessControl.MixedIncludesAndExcludes",
119: scriptName));
120: }
121:
122: policy.defaultAllow = true;
123: }
124:
125: // Add the rule to this policy
126: policy.rules.add(methodName);
127: }
128:
129: /**
130: * @param scriptName The name of the creator to Javascript
131: * @param method The method to execute
132: */
133: protected void assertIsRestrictedByRole(String scriptName,
134: Method method) {
135: String methodName = method.getName();
136:
137: // What if there is some J2EE role based restriction?
138: Set<String> roles = getRoleRestrictions(scriptName, methodName);
139: if (roles != null && !roles.isEmpty()) {
140: HttpServletRequest req = WebContextFactory.get()
141: .getHttpServletRequest();
142:
143: assertAuthenticationIsValid(req);
144: assertAllowedByRoles(req, roles);
145: }
146: }
147:
148: /**
149: * @param scriptName The name of the creator to Javascript
150: * @param methodName The name of the method (without brackets)
151: * @return A Set of all the roles for the given script and method
152: */
153: protected Set<String> getRoleRestrictions(String scriptName,
154: String methodName) {
155: String key = scriptName + '.' + methodName;
156: return roleRestrictMap.get(key);
157: }
158:
159: /**
160: * Check the users session for validity
161: * @param req The users request
162: * @throws SecurityException if the users session is invalid
163: */
164: protected static void assertAuthenticationIsValid(
165: HttpServletRequest req) throws SecurityException {
166: // ensure that at least the next call has a valid session
167: req.getSession();
168:
169: // if there was an expired session, the request has to fail
170: if (!req.isRequestedSessionIdValid()) {
171: throw new LoginRequiredException(
172: Messages
173: .getString("DefaultAccessControl.DeniedByInvalidSession"));
174: }
175:
176: if (req.getRemoteUser() == null) {
177: throw new LoginRequiredException(
178: Messages
179: .getString("DefaultAccessControl.DeniedByAuthenticationRequired"));
180: }
181: }
182:
183: /**
184: * Is this current user in the given list of roles
185: * @param req The users request
186: * @param roles The list of roles to check
187: * @throws SecurityException if this user is not allowed by the list of roles
188: */
189: protected static void assertAllowedByRoles(HttpServletRequest req,
190: Set<String> roles) throws SecurityException {
191: for (String role : roles) {
192: if ("*".equals(role) || req.isUserInRole(role)) {
193: return;
194: }
195: }
196:
197: throw new AccessDeniedException(Messages
198: .getString("DefaultAccessControl.DeniedByJ2EERoles"));
199: }
200:
201: /**
202: * Is the method public?
203: * @param method The method that we wish to execute
204: */
205: protected static void assertIsMethodPublic(Method method) {
206: if (!Modifier.isPublic(method.getModifiers())) {
207: throw new SecurityException(Messages
208: .getString("DefaultAccessControl.DeniedNonPublic"));
209: }
210: }
211:
212: /**
213: * We ban some methods from {@link java.lang.Object}
214: * @param method The method that should not be owned by {@link java.lang.Object}
215: */
216: protected static void assertIsNotOnBaseObject(Method method) {
217: if (method.getDeclaringClass() == Object.class) {
218: throw new SecurityException(
219: Messages
220: .getString("DefaultAccessControl.DeniedObjectMethod"));
221: }
222: }
223:
224: /**
225: * Test to see if a method is excluded or included.
226: * @param scriptName The name of the creator to Javascript
227: * @param methodName The name of the method (without brackets)
228: * @throws SecurityException if the method is allowed by the rules in addIncludeRule()
229: * @see AccessControl#addIncludeRule(String, String)
230: */
231: protected void assertIsExecutable(String scriptName,
232: String methodName) throws SecurityException {
233: Policy policy = policyMap.get(scriptName);
234: if (policy == null) {
235: return;
236: }
237:
238: // Find a match for this method in the policy rules
239: String match = null;
240: for (Iterator<String> it = policy.rules.iterator(); it
241: .hasNext()
242: && match == null;) {
243: String test = it.next();
244:
245: // If at some point we wish to do regex matching on rules, here is
246: // the place to do it.
247: if (methodName.equals(test)) {
248: match = test;
249: }
250: }
251:
252: if (policy.defaultAllow && match != null) {
253: // We are in default allow mode so the rules are exclusions and we
254: // have a match, so this method is excluded.
255: //log.debug("method excluded for creator " + type + " due to defaultAllow=" + policy.defaultAllow + " and rule: " + match);
256: throw new SecurityException(
257: Messages
258: .getString("DefaultAccessControl.DeniedByAccessRules"));
259: }
260:
261: // There may be a more optimized if statement here, but I value code
262: // clarity over performance.
263: //noinspection RedundantIfStatement
264:
265: if (!policy.defaultAllow && match == null) {
266: // We are in default deny mode so the rules are inclusions and we
267: // do not have a match, so this method is excluded.
268: //log.debug("method excluded for creator " + type + " due to defaultAllow=" + policy.defaultAllow + " and rule: " + match);
269: throw new SecurityException(
270: Messages
271: .getString("DefaultAccessControl.DeniedByAccessRules"));
272: }
273: }
274:
275: /**
276: * Check the parameters are not DWR internal either
277: * @param method The method that we want to execute
278: */
279: protected static void assertAreParametersDwrInternal(Method method) {
280: for (int j = 0; j < method.getParameterTypes().length; j++) {
281: Class<?> paramType = method.getParameterTypes()[j];
282:
283: // Access to org.directwebremoting is denied except for .io
284: if (paramType.getName().startsWith(PACKAGE_DWR_DENY)
285: && !paramType.getName().startsWith(
286: PACKAGE_ALLOW_CONVERT)) {
287: throw new SecurityException(
288: Messages
289: .getString("DefaultAccessControl.DeniedParamDWR"));
290: }
291: }
292: }
293:
294: /**
295: * Is the class that we are executing a method on part of DWR?
296: * @param creator The {@link Creator} that exposes the class
297: */
298: protected static void assertIsClassDwrInternal(Creator creator) {
299: String name = creator.getType().getName();
300:
301: // Access to org.directwebremoting is denied except for .export
302: if (name.startsWith(PACKAGE_DWR_DENY)
303: && !name.startsWith(PACKAGE_ALLOW_CREATE)) {
304: throw new SecurityException(Messages
305: .getString("DefaultAccessControl.DeniedCoreDWR"));
306: }
307: }
308:
309: /**
310: * Find the policy for the given type and create one if none exists.
311: * @param type The name of the creator
312: * @return The policy for the given Creator
313: */
314: protected Policy getPolicy(String type) {
315: Policy policy = policyMap.get(type);
316: if (policy == null) {
317: policy = new Policy();
318: policyMap.put(type, policy);
319: }
320:
321: return policy;
322: }
323:
324: /**
325: * @param exposeInternals the exposeInternals to set
326: */
327: public void setExposeInternals(boolean exposeInternals) {
328: this .exposeInternals = exposeInternals;
329: }
330:
331: /**
332: * Do we allow DWR classes to be remoted?
333: * @see #PACKAGE_DWR_DENY
334: */
335: protected boolean exposeInternals = false;
336:
337: /**
338: * A map of Creators to policies
339: */
340: protected Map<String, Policy> policyMap = new HashMap<String, Policy>();
341:
342: /**
343: * What role based restrictions are there?
344: */
345: protected Map<String, Set<String>> roleRestrictMap = new HashMap<String, Set<String>>();
346:
347: /**
348: * A struct that contains a method access policy for a Creator
349: */
350: static class Policy {
351: boolean defaultAllow = true;
352: List<String> rules = new ArrayList<String>();
353: }
354:
355: /**
356: * My package name, so we can ban DWR classes from being created or marshalled
357: */
358: protected static final String PACKAGE_DWR_DENY = "org.directwebremoting.";
359:
360: /**
361: * Special dwr package name from which classes may be created
362: */
363: protected static final String PACKAGE_ALLOW_CREATE = "org.directwebremoting.export.";
364:
365: /**
366: * Special dwr package name from which classes may be converted
367: */
368: protected static final String PACKAGE_ALLOW_CONVERT = "org.directwebremoting.io.";
369: }
|