0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017:
0018: package org.apache.catalina.realm;
0019:
0020: import java.beans.PropertyChangeListener;
0021: import java.beans.PropertyChangeSupport;
0022: import java.io.IOException;
0023: import java.io.UnsupportedEncodingException;
0024: import java.security.MessageDigest;
0025: import java.security.NoSuchAlgorithmException;
0026: import java.security.Principal;
0027: import java.security.cert.X509Certificate;
0028: import java.util.ArrayList;
0029:
0030: import javax.management.Attribute;
0031: import javax.management.MBeanRegistration;
0032: import javax.management.MBeanServer;
0033: import javax.management.ObjectName;
0034: import javax.servlet.http.HttpServletResponse;
0035:
0036: import org.apache.catalina.Container;
0037: import org.apache.catalina.Context;
0038: import org.apache.catalina.Globals;
0039: import org.apache.catalina.Lifecycle;
0040: import org.apache.catalina.LifecycleException;
0041: import org.apache.catalina.LifecycleListener;
0042: import org.apache.catalina.Realm;
0043: import org.apache.catalina.connector.Request;
0044: import org.apache.catalina.connector.Response;
0045: import org.apache.catalina.core.ContainerBase;
0046: import org.apache.catalina.deploy.LoginConfig;
0047: import org.apache.catalina.deploy.SecurityConstraint;
0048: import org.apache.catalina.deploy.SecurityCollection;
0049: import org.apache.catalina.util.HexUtils;
0050: import org.apache.catalina.util.LifecycleSupport;
0051: import org.apache.catalina.util.MD5Encoder;
0052: import org.apache.catalina.util.StringManager;
0053: import org.apache.juli.logging.Log;
0054: import org.apache.juli.logging.LogFactory;
0055: import org.apache.tomcat.util.modeler.Registry;
0056:
0057: /**
0058: * Simple implementation of <b>Realm</b> that reads an XML file to configure
0059: * the valid users, passwords, and roles. The file format (and default file
0060: * location) are identical to those currently supported by Tomcat 3.X.
0061: *
0062: * @author Craig R. McClanahan
0063: * @version $Revision: 555304 $ $Date: 2007-07-11 17:28:52 +0200 (mer., 11 juil. 2007) $
0064: */
0065:
0066: public abstract class RealmBase implements Lifecycle, Realm,
0067: MBeanRegistration {
0068:
0069: private static Log log = LogFactory.getLog(RealmBase.class);
0070:
0071: // ----------------------------------------------------- Instance Variables
0072:
0073: /**
0074: * The Container with which this Realm is associated.
0075: */
0076: protected Container container = null;
0077:
0078: /**
0079: * Container log
0080: */
0081: protected Log containerLog = null;
0082:
0083: /**
0084: * Digest algorithm used in storing passwords in a non-plaintext format.
0085: * Valid values are those accepted for the algorithm name by the
0086: * MessageDigest class, or <code>null</code> if no digesting should
0087: * be performed.
0088: */
0089: protected String digest = null;
0090:
0091: /**
0092: * The encoding charset for the digest.
0093: */
0094: protected String digestEncoding = null;
0095:
0096: /**
0097: * Descriptive information about this Realm implementation.
0098: */
0099: protected static final String info = "org.apache.catalina.realm.RealmBase/1.0";
0100:
0101: /**
0102: * The lifecycle event support for this component.
0103: */
0104: protected LifecycleSupport lifecycle = new LifecycleSupport(this );
0105:
0106: /**
0107: * The MessageDigest object for digesting user credentials (passwords).
0108: */
0109: protected MessageDigest md = null;
0110:
0111: /**
0112: * The MD5 helper object for this class.
0113: */
0114: protected static final MD5Encoder md5Encoder = new MD5Encoder();
0115:
0116: /**
0117: * MD5 message digest provider.
0118: */
0119: protected static MessageDigest md5Helper;
0120:
0121: /**
0122: * The string manager for this package.
0123: */
0124: protected static StringManager sm = StringManager
0125: .getManager(Constants.Package);
0126:
0127: /**
0128: * Has this component been started?
0129: */
0130: protected boolean started = false;
0131:
0132: /**
0133: * The property change support for this component.
0134: */
0135: protected PropertyChangeSupport support = new PropertyChangeSupport(
0136: this );
0137:
0138: /**
0139: * Should we validate client certificate chains when they are presented?
0140: */
0141: protected boolean validate = true;
0142:
0143: /**
0144: * The all role mode.
0145: */
0146: protected AllRolesMode allRolesMode = AllRolesMode.STRICT_MODE;
0147:
0148: // ------------------------------------------------------------- Properties
0149:
0150: /**
0151: * Return the Container with which this Realm has been associated.
0152: */
0153: public Container getContainer() {
0154:
0155: return (container);
0156:
0157: }
0158:
0159: /**
0160: * Set the Container with which this Realm has been associated.
0161: *
0162: * @param container The associated Container
0163: */
0164: public void setContainer(Container container) {
0165:
0166: Container oldContainer = this .container;
0167: this .container = container;
0168: support.firePropertyChange("container", oldContainer,
0169: this .container);
0170:
0171: }
0172:
0173: /**
0174: * Return the all roles mode.
0175: */
0176: public String getAllRolesMode() {
0177:
0178: return allRolesMode.toString();
0179:
0180: }
0181:
0182: /**
0183: * Set the all roles mode.
0184: */
0185: public void setAllRolesMode(String allRolesMode) {
0186:
0187: this .allRolesMode = AllRolesMode.toMode(allRolesMode);
0188:
0189: }
0190:
0191: /**
0192: * Return the digest algorithm used for storing credentials.
0193: */
0194: public String getDigest() {
0195:
0196: return digest;
0197:
0198: }
0199:
0200: /**
0201: * Set the digest algorithm used for storing credentials.
0202: *
0203: * @param digest The new digest algorithm
0204: */
0205: public void setDigest(String digest) {
0206:
0207: this .digest = digest;
0208:
0209: }
0210:
0211: /**
0212: * Returns the digest encoding charset.
0213: *
0214: * @return The charset (may be null) for platform default
0215: */
0216: public String getDigestEncoding() {
0217: return digestEncoding;
0218: }
0219:
0220: /**
0221: * Sets the digest encoding charset.
0222: *
0223: * @param charset The charset (null for platform default)
0224: */
0225: public void setDigestEncoding(String charset) {
0226: digestEncoding = charset;
0227: }
0228:
0229: /**
0230: * Return descriptive information about this Realm implementation and
0231: * the corresponding version number, in the format
0232: * <code><description>/<version></code>.
0233: */
0234: public String getInfo() {
0235:
0236: return info;
0237:
0238: }
0239:
0240: /**
0241: * Return the "validate certificate chains" flag.
0242: */
0243: public boolean getValidate() {
0244:
0245: return (this .validate);
0246:
0247: }
0248:
0249: /**
0250: * Set the "validate certificate chains" flag.
0251: *
0252: * @param validate The new validate certificate chains flag
0253: */
0254: public void setValidate(boolean validate) {
0255:
0256: this .validate = validate;
0257:
0258: }
0259:
0260: // --------------------------------------------------------- Public Methods
0261:
0262: /**
0263: * Add a property change listener to this component.
0264: *
0265: * @param listener The listener to add
0266: */
0267: public void addPropertyChangeListener(
0268: PropertyChangeListener listener) {
0269:
0270: support.addPropertyChangeListener(listener);
0271:
0272: }
0273:
0274: /**
0275: * Return the Principal associated with the specified username and
0276: * credentials, if there is one; otherwise return <code>null</code>.
0277: *
0278: * @param username Username of the Principal to look up
0279: * @param credentials Password or other credentials to use in
0280: * authenticating this username
0281: */
0282: public Principal authenticate(String username, String credentials) {
0283:
0284: String serverCredentials = getPassword(username);
0285:
0286: boolean validated;
0287: if (serverCredentials == null) {
0288: validated = false;
0289: } else if (hasMessageDigest()) {
0290: validated = serverCredentials
0291: .equalsIgnoreCase(digest(credentials));
0292: } else {
0293: validated = serverCredentials.equals(credentials);
0294: }
0295: if (!validated) {
0296: if (containerLog.isTraceEnabled()) {
0297: containerLog.trace(sm.getString(
0298: "realmBase.authenticateFailure", username));
0299: }
0300: return null;
0301: }
0302: if (containerLog.isTraceEnabled()) {
0303: containerLog.trace(sm.getString(
0304: "realmBase.authenticateSuccess", username));
0305: }
0306:
0307: return getPrincipal(username);
0308: }
0309:
0310: /**
0311: * Return the Principal associated with the specified username and
0312: * credentials, if there is one; otherwise return <code>null</code>.
0313: *
0314: * @param username Username of the Principal to look up
0315: * @param credentials Password or other credentials to use in
0316: * authenticating this username
0317: */
0318: public Principal authenticate(String username, byte[] credentials) {
0319:
0320: return (authenticate(username, credentials.toString()));
0321:
0322: }
0323:
0324: /**
0325: * Return the Principal associated with the specified username, which
0326: * matches the digest calculated using the given parameters using the
0327: * method described in RFC 2069; otherwise return <code>null</code>.
0328: *
0329: * @param username Username of the Principal to look up
0330: * @param clientDigest Digest which has been submitted by the client
0331: * @param nOnce Unique (or supposedly unique) token which has been used
0332: * for this request
0333: * @param realm Realm name
0334: * @param md5a2 Second MD5 digest used to calculate the digest :
0335: * MD5(Method + ":" + uri)
0336: */
0337: public Principal authenticate(String username, String clientDigest,
0338: String nOnce, String nc, String cnonce, String qop,
0339: String realm, String md5a2) {
0340:
0341: String md5a1 = getDigest(username, realm);
0342: if (md5a1 == null)
0343: return null;
0344: String serverDigestValue = md5a1 + ":" + nOnce + ":" + nc + ":"
0345: + cnonce + ":" + qop + ":" + md5a2;
0346:
0347: byte[] valueBytes = null;
0348: if (getDigestEncoding() == null) {
0349: valueBytes = serverDigestValue.getBytes();
0350: } else {
0351: try {
0352: valueBytes = serverDigestValue
0353: .getBytes(getDigestEncoding());
0354: } catch (UnsupportedEncodingException uee) {
0355: log.error("Illegal digestEncoding: "
0356: + getDigestEncoding(), uee);
0357: throw new IllegalArgumentException(uee.getMessage());
0358: }
0359: }
0360:
0361: String serverDigest = null;
0362: // Bugzilla 32137
0363: synchronized (md5Helper) {
0364: serverDigest = md5Encoder.encode(md5Helper
0365: .digest(valueBytes));
0366: }
0367:
0368: if (log.isDebugEnabled()) {
0369: log.debug("Digest : " + clientDigest + " Username:"
0370: + username + " ClientSigest:" + clientDigest
0371: + " nOnce:" + nOnce + " nc:" + nc + " cnonce:"
0372: + cnonce + " qop:" + qop + " realm:" + realm
0373: + "md5a2:" + md5a2 + " Server digest:"
0374: + serverDigest);
0375: }
0376:
0377: if (serverDigest.equals(clientDigest))
0378: return getPrincipal(username);
0379: else
0380: return null;
0381: }
0382:
0383: /**
0384: * Return the Principal associated with the specified chain of X509
0385: * client certificates. If there is none, return <code>null</code>.
0386: *
0387: * @param certs Array of client certificates, with the first one in
0388: * the array being the certificate of the client itself.
0389: */
0390: public Principal authenticate(X509Certificate certs[]) {
0391:
0392: if ((certs == null) || (certs.length < 1))
0393: return (null);
0394:
0395: // Check the validity of each certificate in the chain
0396: if (log.isDebugEnabled())
0397: log.debug("Authenticating client certificate chain");
0398: if (validate) {
0399: for (int i = 0; i < certs.length; i++) {
0400: if (log.isDebugEnabled())
0401: log.debug(" Checking validity for '"
0402: + certs[i].getSubjectDN().getName() + "'");
0403: try {
0404: certs[i].checkValidity();
0405: } catch (Exception e) {
0406: if (log.isDebugEnabled())
0407: log.debug(" Validity exception", e);
0408: return (null);
0409: }
0410: }
0411: }
0412:
0413: // Check the existence of the client Principal in our database
0414: return (getPrincipal(certs[0]));
0415:
0416: }
0417:
0418: /**
0419: * Execute a periodic task, such as reloading, etc. This method will be
0420: * invoked inside the classloading context of this container. Unexpected
0421: * throwables will be caught and logged.
0422: */
0423: public void backgroundProcess() {
0424: }
0425:
0426: /**
0427: * Return the SecurityConstraints configured to guard the request URI for
0428: * this request, or <code>null</code> if there is no such constraint.
0429: *
0430: * @param request Request we are processing
0431: * @param context Context the Request is mapped to
0432: */
0433: public SecurityConstraint[] findSecurityConstraints(
0434: Request request, Context context) {
0435:
0436: ArrayList<SecurityConstraint> results = null;
0437: // Are there any defined security constraints?
0438: SecurityConstraint constraints[] = context.findConstraints();
0439: if ((constraints == null) || (constraints.length == 0)) {
0440: if (log.isDebugEnabled())
0441: log.debug(" No applicable constraints defined");
0442: return (null);
0443: }
0444:
0445: // Check each defined security constraint
0446: String uri = request.getRequestPathMB().toString();
0447:
0448: String method = request.getMethod();
0449: int i;
0450: boolean found = false;
0451: for (i = 0; i < constraints.length; i++) {
0452: SecurityCollection[] collection = constraints[i]
0453: .findCollections();
0454:
0455: // If collection is null, continue to avoid an NPE
0456: // See Bugzilla 30624
0457: if (collection == null) {
0458: continue;
0459: }
0460:
0461: if (log.isDebugEnabled()) {
0462: log.debug(" Checking constraint '" + constraints[i]
0463: + "' against " + method + " " + uri + " --> "
0464: + constraints[i].included(uri, method));
0465: }
0466:
0467: for (int j = 0; j < collection.length; j++) {
0468: String[] patterns = collection[j].findPatterns();
0469:
0470: // If patterns is null, continue to avoid an NPE
0471: // See Bugzilla 30624
0472: if (patterns == null) {
0473: continue;
0474: }
0475:
0476: for (int k = 0; k < patterns.length; k++) {
0477: if (uri.equals(patterns[k])) {
0478: found = true;
0479: if (collection[j].findMethod(method)) {
0480: if (results == null) {
0481: results = new ArrayList<SecurityConstraint>();
0482: }
0483: results.add(constraints[i]);
0484: }
0485: }
0486: }
0487: }
0488: }
0489:
0490: if (found) {
0491: return resultsToArray(results);
0492: }
0493:
0494: int longest = -1;
0495:
0496: for (i = 0; i < constraints.length; i++) {
0497: SecurityCollection[] collection = constraints[i]
0498: .findCollections();
0499:
0500: // If collection is null, continue to avoid an NPE
0501: // See Bugzilla 30624
0502: if (collection == null) {
0503: continue;
0504: }
0505:
0506: if (log.isDebugEnabled()) {
0507: log.debug(" Checking constraint '" + constraints[i]
0508: + "' against " + method + " " + uri + " --> "
0509: + constraints[i].included(uri, method));
0510: }
0511:
0512: for (int j = 0; j < collection.length; j++) {
0513: String[] patterns = collection[j].findPatterns();
0514:
0515: // If patterns is null, continue to avoid an NPE
0516: // See Bugzilla 30624
0517: if (patterns == null) {
0518: continue;
0519: }
0520:
0521: boolean matched = false;
0522: int length = -1;
0523: for (int k = 0; k < patterns.length; k++) {
0524: String pattern = patterns[k];
0525: if (pattern.startsWith("/")
0526: && pattern.endsWith("/*")
0527: && pattern.length() >= longest) {
0528:
0529: if (pattern.length() == 2) {
0530: matched = true;
0531: length = pattern.length();
0532: } else if (pattern.regionMatches(0, uri, 0,
0533: pattern.length() - 1)
0534: || (pattern.length() - 2 == uri
0535: .length() && pattern
0536: .regionMatches(0, uri, 0,
0537: pattern.length() - 2))) {
0538: matched = true;
0539: length = pattern.length();
0540: }
0541: }
0542: }
0543: if (matched) {
0544: found = true;
0545: if (length > longest) {
0546: if (results != null) {
0547: results.clear();
0548: }
0549: longest = length;
0550: }
0551: if (collection[j].findMethod(method)) {
0552: if (results == null) {
0553: results = new ArrayList<SecurityConstraint>();
0554: }
0555: results.add(constraints[i]);
0556: }
0557: }
0558: }
0559: }
0560:
0561: if (found) {
0562: return resultsToArray(results);
0563: }
0564:
0565: for (i = 0; i < constraints.length; i++) {
0566: SecurityCollection[] collection = constraints[i]
0567: .findCollections();
0568:
0569: // If collection is null, continue to avoid an NPE
0570: // See Bugzilla 30624
0571: if (collection == null) {
0572: continue;
0573: }
0574:
0575: if (log.isDebugEnabled()) {
0576: log.debug(" Checking constraint '" + constraints[i]
0577: + "' against " + method + " " + uri + " --> "
0578: + constraints[i].included(uri, method));
0579: }
0580:
0581: boolean matched = false;
0582: int pos = -1;
0583: for (int j = 0; j < collection.length; j++) {
0584: String[] patterns = collection[j].findPatterns();
0585:
0586: // If patterns is null, continue to avoid an NPE
0587: // See Bugzilla 30624
0588: if (patterns == null) {
0589: continue;
0590: }
0591:
0592: for (int k = 0; k < patterns.length && !matched; k++) {
0593: String pattern = patterns[k];
0594: if (pattern.startsWith("*.")) {
0595: int slash = uri.lastIndexOf("/");
0596: int dot = uri.lastIndexOf(".");
0597: if (slash >= 0
0598: && dot > slash
0599: && dot != uri.length() - 1
0600: && uri.length() - dot == pattern
0601: .length() - 1) {
0602: if (pattern.regionMatches(1, uri, dot, uri
0603: .length()
0604: - dot)) {
0605: matched = true;
0606: pos = j;
0607: }
0608: }
0609: }
0610: }
0611: }
0612: if (matched) {
0613: found = true;
0614: if (collection[pos].findMethod(method)) {
0615: if (results == null) {
0616: results = new ArrayList<SecurityConstraint>();
0617: }
0618: results.add(constraints[i]);
0619: }
0620: }
0621: }
0622:
0623: if (found) {
0624: return resultsToArray(results);
0625: }
0626:
0627: for (i = 0; i < constraints.length; i++) {
0628: SecurityCollection[] collection = constraints[i]
0629: .findCollections();
0630:
0631: // If collection is null, continue to avoid an NPE
0632: // See Bugzilla 30624
0633: if (collection == null) {
0634: continue;
0635: }
0636:
0637: if (log.isDebugEnabled()) {
0638: log.debug(" Checking constraint '" + constraints[i]
0639: + "' against " + method + " " + uri + " --> "
0640: + constraints[i].included(uri, method));
0641: }
0642:
0643: for (int j = 0; j < collection.length; j++) {
0644: String[] patterns = collection[j].findPatterns();
0645:
0646: // If patterns is null, continue to avoid an NPE
0647: // See Bugzilla 30624
0648: if (patterns == null) {
0649: continue;
0650: }
0651:
0652: boolean matched = false;
0653: for (int k = 0; k < patterns.length && !matched; k++) {
0654: String pattern = patterns[k];
0655: if (pattern.equals("/")) {
0656: matched = true;
0657: }
0658: }
0659: if (matched) {
0660: if (results == null) {
0661: results = new ArrayList<SecurityConstraint>();
0662: }
0663: results.add(constraints[i]);
0664: }
0665: }
0666: }
0667:
0668: if (results == null) {
0669: // No applicable security constraint was found
0670: if (log.isDebugEnabled())
0671: log.debug(" No applicable constraint located");
0672: }
0673: return resultsToArray(results);
0674: }
0675:
0676: /**
0677: * Convert an ArrayList to a SecurityContraint [].
0678: */
0679: private SecurityConstraint[] resultsToArray(
0680: ArrayList<SecurityConstraint> results) {
0681: if (results == null) {
0682: return null;
0683: }
0684: SecurityConstraint[] array = new SecurityConstraint[results
0685: .size()];
0686: results.toArray(array);
0687: return array;
0688: }
0689:
0690: /**
0691: * Perform access control based on the specified authorization constraint.
0692: * Return <code>true</code> if this constraint is satisfied and processing
0693: * should continue, or <code>false</code> otherwise.
0694: *
0695: * @param request Request we are processing
0696: * @param response Response we are creating
0697: * @param constraints Security constraint we are enforcing
0698: * @param context The Context to which client of this class is attached.
0699: *
0700: * @exception IOException if an input/output error occurs
0701: */
0702: public boolean hasResourcePermission(Request request,
0703: Response response, SecurityConstraint[] constraints,
0704: Context context) throws IOException {
0705:
0706: if (constraints == null || constraints.length == 0)
0707: return (true);
0708:
0709: // Specifically allow access to the form login and form error pages
0710: // and the "j_security_check" action
0711: LoginConfig config = context.getLoginConfig();
0712: if ((config != null)
0713: && (Constants.FORM_METHOD
0714: .equals(config.getAuthMethod()))) {
0715: String requestURI = request.getRequestPathMB().toString();
0716: String loginPage = config.getLoginPage();
0717: if (loginPage.equals(requestURI)) {
0718: if (log.isDebugEnabled())
0719: log.debug(" Allow access to login page "
0720: + loginPage);
0721: return (true);
0722: }
0723: String errorPage = config.getErrorPage();
0724: if (errorPage.equals(requestURI)) {
0725: if (log.isDebugEnabled())
0726: log.debug(" Allow access to error page "
0727: + errorPage);
0728: return (true);
0729: }
0730: if (requestURI.endsWith(Constants.FORM_ACTION)) {
0731: if (log.isDebugEnabled())
0732: log
0733: .debug(" Allow access to username/password submission");
0734: return (true);
0735: }
0736: }
0737:
0738: // Which user principal have we already authenticated?
0739: Principal principal = request.getPrincipal();
0740: boolean status = false;
0741: boolean denyfromall = false;
0742: for (int i = 0; i < constraints.length; i++) {
0743: SecurityConstraint constraint = constraints[i];
0744:
0745: String roles[];
0746: if (constraint.getAllRoles()) {
0747: // * means all roles defined in web.xml
0748: roles = request.getContext().findSecurityRoles();
0749: } else {
0750: roles = constraint.findAuthRoles();
0751: }
0752:
0753: if (roles == null)
0754: roles = new String[0];
0755:
0756: if (log.isDebugEnabled())
0757: log.debug(" Checking roles " + principal);
0758:
0759: if (roles.length == 0 && !constraint.getAllRoles()) {
0760: if (constraint.getAuthConstraint()) {
0761: if (log.isDebugEnabled())
0762: log.debug("No roles ");
0763: status = false; // No listed roles means no access at all
0764: denyfromall = true;
0765: } else {
0766: if (log.isDebugEnabled())
0767: log.debug("Passing all access");
0768: return (true);
0769: }
0770: } else if (principal == null) {
0771: if (log.isDebugEnabled())
0772: log
0773: .debug(" No user authenticated, cannot grant access");
0774: status = false;
0775: } else if (!denyfromall) {
0776:
0777: for (int j = 0; j < roles.length; j++) {
0778: if (hasRole(principal, roles[j]))
0779: status = true;
0780: if (log.isDebugEnabled())
0781: log.debug("No role found: " + roles[j]);
0782: }
0783: }
0784: }
0785:
0786: if (allRolesMode != AllRolesMode.STRICT_MODE && !status
0787: && principal != null) {
0788: if (log.isDebugEnabled()) {
0789: log.debug("Checking for all roles mode: "
0790: + allRolesMode);
0791: }
0792: // Check for an all roles(role-name="*")
0793: for (int i = 0; i < constraints.length; i++) {
0794: SecurityConstraint constraint = constraints[i];
0795: String roles[];
0796: // If the all roles mode exists, sets
0797: if (constraint.getAllRoles()) {
0798: if (allRolesMode == AllRolesMode.AUTH_ONLY_MODE) {
0799: if (log.isDebugEnabled()) {
0800: log
0801: .debug("Granting access for role-name=*, auth-only");
0802: }
0803: status = true;
0804: break;
0805: }
0806:
0807: // For AllRolesMode.STRICT_AUTH_ONLY_MODE there must be zero roles
0808: roles = request.getContext().findSecurityRoles();
0809: if (roles.length == 0
0810: && allRolesMode == AllRolesMode.STRICT_AUTH_ONLY_MODE) {
0811: if (log.isDebugEnabled()) {
0812: log
0813: .debug("Granting access for role-name=*, strict auth-only");
0814: }
0815: status = true;
0816: break;
0817: }
0818: }
0819: }
0820: }
0821:
0822: // Return a "Forbidden" message denying access to this resource
0823: if (!status) {
0824: response.sendError(HttpServletResponse.SC_FORBIDDEN, sm
0825: .getString("realmBase.forbidden"));
0826: }
0827: return status;
0828:
0829: }
0830:
0831: /**
0832: * Return <code>true</code> if the specified Principal has the specified
0833: * security role, within the context of this Realm; otherwise return
0834: * <code>false</code>. This method can be overridden by Realm
0835: * implementations, but the default is adequate when an instance of
0836: * <code>GenericPrincipal</code> is used to represent authenticated
0837: * Principals from this Realm.
0838: *
0839: * @param principal Principal for whom the role is to be checked
0840: * @param role Security role to be checked
0841: */
0842: public boolean hasRole(Principal principal, String role) {
0843:
0844: // Should be overriten in JAASRealm - to avoid pretty inefficient conversions
0845: if ((principal == null) || (role == null)
0846: || !(principal instanceof GenericPrincipal))
0847: return (false);
0848:
0849: GenericPrincipal gp = (GenericPrincipal) principal;
0850: if (!(gp.getRealm() == this )) {
0851: if (log.isDebugEnabled())
0852: log.debug("Different realm " + this + " "
0853: + gp.getRealm());// return (false);
0854: }
0855: boolean result = gp.hasRole(role);
0856: if (log.isDebugEnabled()) {
0857: String name = principal.getName();
0858: if (result)
0859: log.debug(sm.getString("realmBase.hasRoleSuccess",
0860: name, role));
0861: else
0862: log.debug(sm.getString("realmBase.hasRoleFailure",
0863: name, role));
0864: }
0865: return (result);
0866:
0867: }
0868:
0869: /**
0870: * Enforce any user data constraint required by the security constraint
0871: * guarding this request URI. Return <code>true</code> if this constraint
0872: * was not violated and processing should continue, or <code>false</code>
0873: * if we have created a response already.
0874: *
0875: * @param request Request we are processing
0876: * @param response Response we are creating
0877: * @param constraints Security constraint being checked
0878: *
0879: * @exception IOException if an input/output error occurs
0880: */
0881: public boolean hasUserDataPermission(Request request,
0882: Response response, SecurityConstraint[] constraints)
0883: throws IOException {
0884:
0885: // Is there a relevant user data constraint?
0886: if (constraints == null || constraints.length == 0) {
0887: if (log.isDebugEnabled())
0888: log
0889: .debug(" No applicable security constraint defined");
0890: return (true);
0891: }
0892: for (int i = 0; i < constraints.length; i++) {
0893: SecurityConstraint constraint = constraints[i];
0894: String userConstraint = constraint.getUserConstraint();
0895: if (userConstraint == null) {
0896: if (log.isDebugEnabled())
0897: log
0898: .debug(" No applicable user data constraint defined");
0899: return (true);
0900: }
0901: if (userConstraint.equals(Constants.NONE_TRANSPORT)) {
0902: if (log.isDebugEnabled())
0903: log
0904: .debug(" User data constraint has no restrictions");
0905: return (true);
0906: }
0907:
0908: }
0909: // Validate the request against the user data constraint
0910: if (request.getRequest().isSecure()) {
0911: if (log.isDebugEnabled())
0912: log.debug(" User data constraint already satisfied");
0913: return (true);
0914: }
0915: // Initialize variables we need to determine the appropriate action
0916: int redirectPort = request.getConnector().getRedirectPort();
0917:
0918: // Is redirecting disabled?
0919: if (redirectPort <= 0) {
0920: if (log.isDebugEnabled())
0921: log.debug(" SSL redirect is disabled");
0922: response.sendError(HttpServletResponse.SC_FORBIDDEN,
0923: request.getRequestURI());
0924: return (false);
0925: }
0926:
0927: // Redirect to the corresponding SSL port
0928: StringBuffer file = new StringBuffer();
0929: String protocol = "https";
0930: String host = request.getServerName();
0931: // Protocol
0932: file.append(protocol).append("://").append(host);
0933: // Host with port
0934: if (redirectPort != 443) {
0935: file.append(":").append(redirectPort);
0936: }
0937: // URI
0938: file.append(request.getRequestURI());
0939: String requestedSessionId = request.getRequestedSessionId();
0940: if ((requestedSessionId != null)
0941: && request.isRequestedSessionIdFromURL()) {
0942: file.append(";");
0943: file.append(Globals.SESSION_PARAMETER_NAME);
0944: file.append("=");
0945: file.append(requestedSessionId);
0946: }
0947: String queryString = request.getQueryString();
0948: if (queryString != null) {
0949: file.append('?');
0950: file.append(queryString);
0951: }
0952: if (log.isDebugEnabled())
0953: log.debug(" Redirecting to " + file.toString());
0954: response.sendRedirect(file.toString());
0955: return (false);
0956:
0957: }
0958:
0959: /**
0960: * Remove a property change listener from this component.
0961: *
0962: * @param listener The listener to remove
0963: */
0964: public void removePropertyChangeListener(
0965: PropertyChangeListener listener) {
0966:
0967: support.removePropertyChangeListener(listener);
0968:
0969: }
0970:
0971: // ------------------------------------------------------ Lifecycle Methods
0972:
0973: /**
0974: * Add a lifecycle event listener to this component.
0975: *
0976: * @param listener The listener to add
0977: */
0978: public void addLifecycleListener(LifecycleListener listener) {
0979:
0980: lifecycle.addLifecycleListener(listener);
0981:
0982: }
0983:
0984: /**
0985: * Get the lifecycle listeners associated with this lifecycle. If this
0986: * Lifecycle has no listeners registered, a zero-length array is returned.
0987: */
0988: public LifecycleListener[] findLifecycleListeners() {
0989:
0990: return lifecycle.findLifecycleListeners();
0991:
0992: }
0993:
0994: /**
0995: * Remove a lifecycle event listener from this component.
0996: *
0997: * @param listener The listener to remove
0998: */
0999: public void removeLifecycleListener(LifecycleListener listener) {
1000:
1001: lifecycle.removeLifecycleListener(listener);
1002:
1003: }
1004:
1005: /**
1006: * Prepare for the beginning of active use of the public methods of this
1007: * component. This method should be called before any of the public
1008: * methods of this component are utilized. It should also send a
1009: * LifecycleEvent of type START_EVENT to any registered listeners.
1010: *
1011: * @exception LifecycleException if this component detects a fatal error
1012: * that prevents this component from being used
1013: */
1014: public void start() throws LifecycleException {
1015:
1016: // Validate and update our current component state
1017: if (started) {
1018: if (log.isInfoEnabled())
1019: log.info(sm.getString("realmBase.alreadyStarted"));
1020: return;
1021: }
1022: if (!initialized) {
1023: init();
1024: }
1025: lifecycle.fireLifecycleEvent(START_EVENT, null);
1026: started = true;
1027:
1028: // Create a MessageDigest instance for credentials, if desired
1029: if (digest != null) {
1030: try {
1031: md = MessageDigest.getInstance(digest);
1032: } catch (NoSuchAlgorithmException e) {
1033: throw new LifecycleException(sm.getString(
1034: "realmBase.algorithm", digest), e);
1035: }
1036: }
1037:
1038: }
1039:
1040: /**
1041: * Gracefully terminate the active use of the public methods of this
1042: * component. This method should be the last one called on a given
1043: * instance of this component. It should also send a LifecycleEvent
1044: * of type STOP_EVENT to any registered listeners.
1045: *
1046: * @exception LifecycleException if this component detects a fatal error
1047: * that needs to be reported
1048: */
1049: public void stop() throws LifecycleException {
1050:
1051: // Validate and update our current component state
1052: if (!started) {
1053: if (log.isInfoEnabled())
1054: log.info(sm.getString("realmBase.notStarted"));
1055: return;
1056: }
1057: lifecycle.fireLifecycleEvent(STOP_EVENT, null);
1058: started = false;
1059:
1060: // Clean up allocated resources
1061: md = null;
1062:
1063: destroy();
1064:
1065: }
1066:
1067: public void destroy() {
1068:
1069: // unregister this realm
1070: if (oname != null) {
1071: try {
1072: Registry.getRegistry(null, null).unregisterComponent(
1073: oname);
1074: if (log.isDebugEnabled())
1075: log.debug("unregistering realm " + oname);
1076: } catch (Exception ex) {
1077: log.error("Can't unregister realm " + oname, ex);
1078: }
1079: }
1080:
1081: }
1082:
1083: // ------------------------------------------------------ Protected Methods
1084:
1085: /**
1086: * Digest the password using the specified algorithm and
1087: * convert the result to a corresponding hexadecimal string.
1088: * If exception, the plain credentials string is returned.
1089: *
1090: * @param credentials Password or other credentials to use in
1091: * authenticating this username
1092: */
1093: protected String digest(String credentials) {
1094:
1095: // If no MessageDigest instance is specified, return unchanged
1096: if (hasMessageDigest() == false)
1097: return (credentials);
1098:
1099: // Digest the user credentials and return as hexadecimal
1100: synchronized (this ) {
1101: try {
1102: md.reset();
1103:
1104: byte[] bytes = null;
1105: if (getDigestEncoding() == null) {
1106: bytes = credentials.getBytes();
1107: } else {
1108: try {
1109: bytes = credentials
1110: .getBytes(getDigestEncoding());
1111: } catch (UnsupportedEncodingException uee) {
1112: log.error("Illegal digestEncoding: "
1113: + getDigestEncoding(), uee);
1114: throw new IllegalArgumentException(uee
1115: .getMessage());
1116: }
1117: }
1118: md.update(bytes);
1119:
1120: return (HexUtils.convert(md.digest()));
1121: } catch (Exception e) {
1122: log.error(sm.getString("realmBase.digest"), e);
1123: return (credentials);
1124: }
1125: }
1126:
1127: }
1128:
1129: protected boolean hasMessageDigest() {
1130: return !(md == null);
1131: }
1132:
1133: /**
1134: * Return the digest associated with given principal's user name.
1135: */
1136: protected String getDigest(String username, String realmName) {
1137: if (md5Helper == null) {
1138: try {
1139: md5Helper = MessageDigest.getInstance("MD5");
1140: } catch (NoSuchAlgorithmException e) {
1141: log.error("Couldn't get MD5 digest: ", e);
1142: throw new IllegalStateException(e.getMessage());
1143: }
1144: }
1145:
1146: if (hasMessageDigest()) {
1147: // Use pre-generated digest
1148: return getPassword(username);
1149: }
1150:
1151: String digestValue = username + ":" + realmName + ":"
1152: + getPassword(username);
1153:
1154: byte[] valueBytes = null;
1155: if (getDigestEncoding() == null) {
1156: valueBytes = digestValue.getBytes();
1157: } else {
1158: try {
1159: valueBytes = digestValue.getBytes(getDigestEncoding());
1160: } catch (UnsupportedEncodingException uee) {
1161: log.error("Illegal digestEncoding: "
1162: + getDigestEncoding(), uee);
1163: throw new IllegalArgumentException(uee.getMessage());
1164: }
1165: }
1166:
1167: byte[] digest = null;
1168: // Bugzilla 32137
1169: synchronized (md5Helper) {
1170: digest = md5Helper.digest(valueBytes);
1171: }
1172:
1173: return md5Encoder.encode(digest);
1174: }
1175:
1176: /**
1177: * Return a short name for this Realm implementation, for use in
1178: * log messages.
1179: */
1180: protected abstract String getName();
1181:
1182: /**
1183: * Return the password associated with the given principal's user name.
1184: */
1185: protected abstract String getPassword(String username);
1186:
1187: /**
1188: * Return the Principal associated with the given certificate.
1189: */
1190: protected Principal getPrincipal(X509Certificate usercert) {
1191: return (getPrincipal(usercert.getSubjectDN().getName()));
1192: }
1193:
1194: /**
1195: * Return the Principal associated with the given user name.
1196: */
1197: protected abstract Principal getPrincipal(String username);
1198:
1199: // --------------------------------------------------------- Static Methods
1200:
1201: /**
1202: * Digest password using the algorithm especificied and
1203: * convert the result to a corresponding hex string.
1204: * If exception, the plain credentials string is returned
1205: *
1206: * @param credentials Password or other credentials to use in
1207: * authenticating this username
1208: * @param algorithm Algorithm used to do the digest
1209: * @param encoding Character encoding of the string to digest
1210: */
1211: public final static String Digest(String credentials,
1212: String algorithm, String encoding) {
1213:
1214: try {
1215: // Obtain a new message digest with "digest" encryption
1216: MessageDigest md = (MessageDigest) MessageDigest
1217: .getInstance(algorithm).clone();
1218:
1219: // encode the credentials
1220: // Should use the digestEncoding, but that's not a static field
1221: if (encoding == null) {
1222: md.update(credentials.getBytes());
1223: } else {
1224: md.update(credentials.getBytes(encoding));
1225: }
1226:
1227: // Digest the credentials and return as hexadecimal
1228: return (HexUtils.convert(md.digest()));
1229: } catch (Exception ex) {
1230: log.error(ex);
1231: return credentials;
1232: }
1233:
1234: }
1235:
1236: /**
1237: * Digest password using the algorithm especificied and
1238: * convert the result to a corresponding hex string.
1239: * If exception, the plain credentials string is returned
1240: */
1241: public static void main(String args[]) {
1242:
1243: String encoding = null;
1244: int firstCredentialArg = 2;
1245:
1246: if (args.length > 4 && args[2].equalsIgnoreCase("-e")) {
1247: encoding = args[3];
1248: firstCredentialArg = 4;
1249: }
1250:
1251: if (args.length > firstCredentialArg
1252: && args[0].equalsIgnoreCase("-a")) {
1253: for (int i = firstCredentialArg; i < args.length; i++) {
1254: System.out.print(args[i] + ":");
1255: System.out.println(Digest(args[i], args[1], encoding));
1256: }
1257: } else {
1258: System.out
1259: .println("Usage: RealmBase -a <algorithm> [-e <encoding>] <credentials>");
1260: }
1261:
1262: }
1263:
1264: // -------------------- JMX and Registration --------------------
1265: protected String type;
1266: protected String domain;
1267: protected String host;
1268: protected String path;
1269: protected ObjectName oname;
1270: protected ObjectName controller;
1271: protected MBeanServer mserver;
1272:
1273: public ObjectName getController() {
1274: return controller;
1275: }
1276:
1277: public void setController(ObjectName controller) {
1278: this .controller = controller;
1279: }
1280:
1281: public ObjectName getObjectName() {
1282: return oname;
1283: }
1284:
1285: public String getDomain() {
1286: return domain;
1287: }
1288:
1289: public String getType() {
1290: return type;
1291: }
1292:
1293: public ObjectName preRegister(MBeanServer server, ObjectName name)
1294: throws Exception {
1295: oname = name;
1296: mserver = server;
1297: domain = name.getDomain();
1298:
1299: type = name.getKeyProperty("type");
1300: host = name.getKeyProperty("host");
1301: path = name.getKeyProperty("path");
1302:
1303: return name;
1304: }
1305:
1306: public void postRegister(Boolean registrationDone) {
1307: }
1308:
1309: public void preDeregister() throws Exception {
1310: }
1311:
1312: public void postDeregister() {
1313: }
1314:
1315: protected boolean initialized = false;
1316:
1317: public void init() {
1318: if (initialized && container != null)
1319: return;
1320:
1321: // We want logger as soon as possible
1322: if (container != null) {
1323: this .containerLog = container.getLogger();
1324: }
1325:
1326: initialized = true;
1327: if (container == null) {
1328: ObjectName parent = null;
1329: // Register with the parent
1330: try {
1331: if (host == null) {
1332: // global
1333: parent = new ObjectName(domain + ":type=Engine");
1334: } else if (path == null) {
1335: parent = new ObjectName(domain + ":type=Host,host="
1336: + host);
1337: } else {
1338: parent = new ObjectName(domain
1339: + ":j2eeType=WebModule,name=//" + host
1340: + path);
1341: }
1342: if (mserver.isRegistered(parent)) {
1343: if (log.isDebugEnabled())
1344: log.debug("Register with " + parent);
1345: mserver.setAttribute(parent, new Attribute("realm",
1346: this ));
1347: }
1348: } catch (Exception e) {
1349: log.error("Parent not available yet: " + parent);
1350: }
1351: }
1352:
1353: if (oname == null) {
1354: // register
1355: try {
1356: ContainerBase cb = (ContainerBase) container;
1357: oname = new ObjectName(cb.getDomain() + ":type=Realm"
1358: + cb.getContainerSuffix());
1359: Registry.getRegistry(null, null).registerComponent(
1360: this , oname, null);
1361: if (log.isDebugEnabled())
1362: log.debug("Register Realm " + oname);
1363: } catch (Throwable e) {
1364: log.error("Can't register " + oname, e);
1365: }
1366: }
1367:
1368: }
1369:
1370: protected static class AllRolesMode {
1371:
1372: private String name;
1373: /** Use the strict servlet spec interpretation which requires that the user
1374: * have one of the web-app/security-role/role-name
1375: */
1376: public static final AllRolesMode STRICT_MODE = new AllRolesMode(
1377: "strict");
1378: /** Allow any authenticated user
1379: */
1380: public static final AllRolesMode AUTH_ONLY_MODE = new AllRolesMode(
1381: "authOnly");
1382: /** Allow any authenticated user only if there are no web-app/security-roles
1383: */
1384: public static final AllRolesMode STRICT_AUTH_ONLY_MODE = new AllRolesMode(
1385: "strictAuthOnly");
1386:
1387: static AllRolesMode toMode(String name) {
1388: AllRolesMode mode;
1389: if (name.equalsIgnoreCase(STRICT_MODE.name))
1390: mode = STRICT_MODE;
1391: else if (name.equalsIgnoreCase(AUTH_ONLY_MODE.name))
1392: mode = AUTH_ONLY_MODE;
1393: else if (name.equalsIgnoreCase(STRICT_AUTH_ONLY_MODE.name))
1394: mode = STRICT_AUTH_ONLY_MODE;
1395: else
1396: throw new IllegalStateException(
1397: "Unknown mode, must be one of: strict, authOnly, strictAuthOnly");
1398: return mode;
1399: }
1400:
1401: private AllRolesMode(String name) {
1402: this .name = name;
1403: }
1404:
1405: public boolean equals(Object o) {
1406: boolean equals = false;
1407: if (o instanceof AllRolesMode) {
1408: AllRolesMode mode = (AllRolesMode) o;
1409: equals = name.equals(mode.name);
1410: }
1411: return equals;
1412: }
1413:
1414: public int hashCode() {
1415: return name.hashCode();
1416: }
1417:
1418: public String toString() {
1419: return name;
1420: }
1421: }
1422:
1423: }
|