001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.web.tomcat.security;
023:
024: import java.io.IOException;
025: import java.lang.reflect.Method;
026: import java.security.Principal;
027: import java.security.Permission;
028: import java.security.ProtectionDomain;
029: import java.security.Policy;
030: import java.security.CodeSource;
031: import java.util.Set;
032: import java.util.List;
033:
034: import javax.security.jacc.WebUserDataPermission;
035: import javax.security.jacc.PolicyContext;
036: import javax.security.jacc.WebResourcePermission;
037: import javax.security.jacc.WebRoleRefPermission;
038: import javax.security.jacc.PolicyContextException;
039: import javax.security.auth.Subject;
040: import javax.servlet.http.HttpServletRequest;
041: import javax.servlet.http.HttpServletResponse;
042:
043: import org.apache.catalina.Context;
044: import org.apache.catalina.Wrapper;
045: import org.apache.catalina.connector.Request;
046: import org.apache.catalina.connector.Response;
047: import org.apache.catalina.deploy.SecurityConstraint;
048: import org.jboss.logging.Logger;
049: import org.jboss.metadata.WebMetaData;
050: import org.jboss.metadata.SecurityRoleRefMetaData;
051:
052: /** A subclass of JBossSecurityMgrRealm that peforms authorization based on
053: * the JACC permissions and active Policy implementation.
054: *
055: * @author Scott.Stark@jboss.org
056: * @author Anil.Saldhana@jboss.org
057: * @version $Revision: 60856 $
058: */
059: public class JaccAuthorizationRealm extends JBossSecurityMgrRealm {
060: static Logger log = Logger.getLogger(JaccAuthorizationRealm.class);
061:
062: /** The JACC PolicyContext key for the current Subject */
063: private static final String SUBJECT_CONTEXT_KEY = "javax.security.auth.Subject.container";
064: /** The current servlet request */
065: private static ThreadLocal activeRequest = new ThreadLocal();
066: private boolean trace;
067: protected Policy policy;
068:
069: /**
070: * JBAS-2519:Delegate to JACC provider for unsecured resources in web.xml
071: */
072: private boolean unprotectedResourceDelegation = false;
073: private String securityConstraintProviderClass = "";
074:
075: public JaccAuthorizationRealm() {
076: policy = Policy.getPolicy();
077: trace = log.isTraceEnabled();
078: }
079:
080: public boolean hasResourcePermission(Request request,
081: Response response,
082: SecurityConstraint[] securityConstraints, Context context)
083: throws IOException {
084: Wrapper servlet = request.getWrapper();
085: if (servlet != null) {
086: activeRequest.set(getServletName(servlet));
087: }
088: Principal requestPrincipal = request.getPrincipal();
089: HttpServletRequest httpRequest = request.getRequest();
090: String uri = requestURI(request);
091: WebResourcePermission perm = new WebResourcePermission(uri,
092: httpRequest.getMethod());
093: boolean allowed = checkSecurityAssociation(perm,
094: requestPrincipal);
095: if (trace)
096: log.trace("hasResourcePermission, perm=" + perm
097: + ", allowed=" + allowed);
098: if (allowed == false) {
099: response.sendError(HttpServletResponse.SC_FORBIDDEN, sm
100: .getString("realmBase.forbidden"));
101: }
102: return allowed;
103: }
104:
105: public boolean hasRole(Principal principal, String name) {
106: //
107: String servletName = (String) activeRequest.get();
108: WebMetaData metaData = (WebMetaData) SecurityAssociationValve.activeWebMetaData
109: .get();
110: List roleRefs = metaData.getSecurityRoleRefs(servletName);
111: String roleName = name;
112: int len = roleRefs != null ? roleRefs.size() : 0;
113: for (int n = 0; n < len; n++) {
114: SecurityRoleRefMetaData ref = (SecurityRoleRefMetaData) roleRefs
115: .get(n);
116: if (ref.getLink().equals(name)) {
117: roleName = ref.getName();
118: break;
119: }
120: }
121:
122: WebRoleRefPermission perm = new WebRoleRefPermission(
123: servletName, roleName);
124: Principal[] principals = { principal };
125: Set roles = getPrincipalRoles(principal);
126: if (roles != null) {
127: principals = new Principal[roles.size()];
128: roles.toArray(principals);
129: }
130: boolean allowed = checkSecurityAssociation(perm, principals);
131: if (trace)
132: log.trace("hasRole, perm=" + perm + ", allowed=" + allowed);
133: return allowed;
134: }
135:
136: public boolean hasUserDataPermission(Request request,
137: Response response, SecurityConstraint[] constraints)
138: throws IOException {
139: HttpServletRequest httpRequest = request.getRequest();
140: Principal requestPrincpal = request.getPrincipal();
141: establishSubjectContext(requestPrincpal);
142: String uri = requestURI(request);
143: WebUserDataPermission perm = new WebUserDataPermission(uri,
144: httpRequest.getMethod());
145: if (trace)
146: log.trace("hasUserDataPermission, p=" + perm);
147: boolean ok = false;
148: try {
149: Principal[] principals = null;
150: ok = checkSecurityAssociation(perm, principals);
151: } catch (Exception e) {
152: if (trace)
153: log.trace("Failed to checkSecurityAssociation", e);
154: }
155:
156: /* If the constraint is not valid delegate to super to redirect to the
157: ssl port if allowed
158: */
159: if (ok == false)
160: ok = super .hasUserDataPermission(request, response,
161: constraints);
162: return ok;
163: }
164:
165: /**
166: * Get the Security Constraints Provider Class
167: * @return
168: */
169: public String getSecurityConstraintProviderClass() {
170: return securityConstraintProviderClass;
171: }
172:
173: /**
174: * Set the Security Constraints Provider Class
175: * @param securityConstraintProviderClass
176: */
177: public void setSecurityConstraintProviderClass(
178: String securityConstraintProviderClass) {
179: this .securityConstraintProviderClass = securityConstraintProviderClass;
180: }
181:
182: /**
183: * Whether the delegation to JACC provider
184: * for unprotected resources is enabled
185: *
186: * @return
187: */
188: public boolean isUnprotectedResourceDelegation() {
189: return unprotectedResourceDelegation;
190: }
191:
192: /**
193: * Set whether the delegation to JACC provider
194: * for unprotected resources must be enabled
195: *
196: * @param unprotectedResourceDelegation
197: */
198: public void setUnprotectedResourceDelegation(
199: boolean unprotectedResourceDelegation) {
200: this .unprotectedResourceDelegation = unprotectedResourceDelegation;
201: }
202:
203: /**
204: * JBAS-2519:Delegate to JACC provider for unsecured resources in web.xml
205: */
206: public SecurityConstraint[] findSecurityConstraints(
207: Request request, Context context) {
208: SecurityConstraint[] scarr = super .findSecurityConstraints(
209: request, context);
210: if ((scarr == null || scarr.length == 0)
211: && this .unprotectedResourceDelegation) {
212: scarr = getSecurityConstraintsFromProvider(request, context);
213: }
214: return scarr;
215: }
216:
217: /** See if the given JACC permission is implied using the caller as
218: * obtained from either the
219: * PolicyContext.getContext(javax.security.auth.Subject.container) or
220: * the info associated with the requestPrincipal.
221: *
222: * @param perm - the JACC permission to check
223: * @param requestPrincpal - the http request getPrincipal
224: * @return true if the permission is allowed, false otherwise
225: */
226: protected boolean checkSecurityAssociation(Permission perm,
227: Principal requestPrincpal) {
228: // Get the caller
229: Subject caller = establishSubjectContext(requestPrincpal);
230:
231: // Get the caller principals, its null if there is no caller
232: Principal[] principals = null;
233: if (caller != null) {
234: if (trace)
235: log.trace("No active subject found, using ");
236: Set principalsSet = caller.getPrincipals();
237: principals = new Principal[principalsSet.size()];
238: principalsSet.toArray(principals);
239: }
240: return checkSecurityAssociation(perm, principals);
241: }
242:
243: /** See if the given permission is implied by the Policy. This calls
244: * Policy.implies(pd, perm) with the ProtectionDomain built from the
245: * active CodeSource set by the JaccContextValve, and the given
246: * principals.
247: *
248: * @param perm - the JACC permission to evaluate
249: * @param principals - the possibly null set of principals for the caller
250: * @return true if the permission is allowed, false otherwise
251: */
252: protected boolean checkSecurityAssociation(Permission perm,
253: Principal[] principals) {
254: CodeSource webCS = (CodeSource) JaccContextValve.activeCS.get();
255: ProtectionDomain pd = new ProtectionDomain(webCS, null, null,
256: principals);
257: boolean allowed = policy.implies(pd, perm);
258: if (trace) {
259: String msg = (allowed ? "Allowed: " : "Denied: ") + perm;
260: log.trace(msg);
261: }
262: return allowed;
263: }
264:
265: /**
266: * Ensure that the JACC PolicyContext Subject handler has access to the
267: * authenticated Subject. The caching of the authentication state by tomcat
268: * means that we need to retrieve the Subject from the JBossGenericPrincipal
269: * if the realm was not invoked to authenticate the caller.
270: *
271: * @param principal - the http request getPrincipal
272: * @return the authenticated Subject is there is one, null otherwise
273: */
274: protected Subject establishSubjectContext(Principal principal) {
275: Subject caller = null;
276: try {
277: caller = (Subject) PolicyContext
278: .getContext(SUBJECT_CONTEXT_KEY);
279: } catch (PolicyContextException e) {
280: if (trace)
281: log
282: .trace(
283: "Failed to get subject from PolicyContext",
284: e);
285: }
286:
287: if (caller == null) {
288: // Test the request principal that may come from the session cache
289: if (principal instanceof JBossGenericPrincipal) {
290: JBossGenericPrincipal jgp = (JBossGenericPrincipal) principal;
291: caller = jgp.getSubject();
292: //
293: if (trace)
294: log.trace("Restoring principal info from cache");
295: SecurityAssociationActions.setPrincipalInfo(jgp
296: .getAuthPrincipal(), jgp.getCredentials(), jgp
297: .getSubject());
298: }
299: }
300: return caller;
301: }
302:
303: /**
304: * Jacc Specification : Appendix
305: * B.19 Calling isUserInRole from JSP not mapped to a Servlet
306: * Checking a WebRoleRefPermission requires the name of a Servlet to
307: * identify the scope of the reference to role translation. The name of a
308: * scoping servlet has not been established for an unmapped JSP.
309: *
310: * Resolution- For every security role in the web application add a
311: * WebRoleRefPermission to the corresponding role. The name of all such
312: * permissions shall be the empty string, and the actions of each
313: * permission shall be the corresponding role name.
314: * When checking a WebRoleRefPermission from a JSP not mapped to a servlet,
315: * use a permission with the empty string as its name and with the argument to is
316: * UserInRole as its actions.
317: *
318: * @param servlet Wrapper
319: * @return empty string if it is for an unmapped jsp or name of the servlet for others
320: */
321: private String getServletName(Wrapper servlet) {
322: //For jsp, the mapping will be (*.jsp, *.jspx)
323: String[] mappings = servlet.findMappings();
324: if (trace)
325: log.trace("[getServletName:servletmappings=" + mappings
326: + ":servlet.getName()=" + servlet.getName() + "]");
327: if ("jsp".equals(servlet.getName())
328: && (mappings != null && mappings[0].indexOf("*.jsp") > -1))
329: return "";
330: else
331: return servlet.getName();
332: }
333:
334: /**
335: * Get a set of SecurityConstraints from either the PolicyProvider
336: * or the securityConstraintProviderClass class, via reflection
337: *
338: * @param request
339: * @param context
340: * @return an array of SecurityConstraints
341: */
342: private SecurityConstraint[] getSecurityConstraintsFromProvider(
343: Request request, Context context) {
344: SecurityConstraint[] scarr = null;
345: Class[] sig = { Request.class, Context.class };
346: Object[] args = { request, context };
347:
348: Method findsc = null;
349:
350: //Try the Policy Provider
351: try {
352: findsc = policy.getClass().getMethod(
353: "findSecurityConstraints", sig);
354: scarr = (SecurityConstraint[]) findsc.invoke(policy, args);
355: } catch (Throwable t) {
356: if (trace)
357: log
358: .error(
359: "Error obtaining security constraints from policy",
360: t);
361: }
362: //If the policy provider did not provide the security constraints
363: //check if a seperate SC provider is plugged in
364: if (scarr == null || scarr.length == 0) {
365: if (securityConstraintProviderClass == ""
366: || securityConstraintProviderClass.length() == 0) {
367: if (trace)
368: log
369: .trace("unprotectedResourceDelegation is true "
370: + "but securityConstraintProviderClass is empty");
371: } else
372: //Try to call the method on the provider class
373: try {
374: Class clazz = Thread.currentThread()
375: .getContextClassLoader().loadClass(
376: securityConstraintProviderClass);
377: Object obj = clazz.newInstance();
378: findsc = clazz.getMethod("findSecurityConstraints",
379: sig);
380: if (trace)
381: log
382: .trace("findSecurityConstraints method found in securityConstraintProviderClass");
383: scarr = (SecurityConstraint[]) findsc.invoke(obj,
384: args);
385: } catch (Throwable t) {
386: log.error("Error instantiating "
387: + securityConstraintProviderClass, t);
388: }
389: }
390: return scarr;
391: }
392:
393: /**
394: * Get the canonical request uri from the request mapping data requestPath
395: * @param request
396: * @return the request URI path
397: */
398: static String requestURI(Request request) {
399: String uri = request.getMappingData().requestPath.getString();
400: if (uri == null || uri.equals("/")) {
401: uri = "";
402: }
403: return uri;
404: }
405:
406: }
|