/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.catalina.realm;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import jakarta.servlet.annotation.ServletSecurity.TransportGuarantee;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.catalina.Container;
import org.apache.catalina.Context;
import org.apache.catalina.CredentialHandler;
import org.apache.catalina.Engine;
import org.apache.catalina.Host;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.Realm;
import org.apache.catalina.Server;
import org.apache.catalina.Service;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.util.LifecycleMBeanBase;
import org.apache.catalina.util.SessionConfig;
import org.apache.catalina.util.ToStringUtil;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.IntrospectionUtils;
import org.apache.tomcat.util.buf.B2CConverter;
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
import org.apache.tomcat.util.res.StringManager;
import org.apache.tomcat.util.security.ConcurrentMessageDigest;
import org.apache.tomcat.util.security.MD5Encoder;
import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSName;
Simple implementation of Realm that reads an XML file to configure
the valid users, passwords, and roles. The file format (and default file
location) are identical to those currently supported by Tomcat 3.X.
Author: Craig R. McClanahan
/**
* Simple implementation of <b>Realm</b> that reads an XML file to configure
* the valid users, passwords, and roles. The file format (and default file
* location) are identical to those currently supported by Tomcat 3.X.
*
* @author Craig R. McClanahan
*/
public abstract class RealmBase extends LifecycleMBeanBase implements Realm {
private static final Log log = LogFactory.getLog(RealmBase.class);
private static final List<Class<? extends DigestCredentialHandlerBase>> credentialHandlerClasses =
new ArrayList<>();
static {
// Order is important since it determines the search order for a
// matching handler if only an algorithm is specified when calling
// main()
credentialHandlerClasses.add(MessageDigestCredentialHandler.class);
credentialHandlerClasses.add(SecretKeyCredentialHandler.class);
}
// ----------------------------------------------------- Instance Variables
The Container with which this Realm is associated.
/**
* The Container with which this Realm is associated.
*/
protected Container container = null;
Container log
/**
* Container log
*/
protected Log containerLog = null;
private CredentialHandler credentialHandler;
The string manager for this package.
/**
* The string manager for this package.
*/
protected static final StringManager sm = StringManager.getManager(RealmBase.class);
The property change support for this component.
/**
* The property change support for this component.
*/
protected final PropertyChangeSupport support = new PropertyChangeSupport(this);
Should we validate client certificate chains when they are presented?
/**
* Should we validate client certificate chains when they are presented?
*/
protected boolean validate = true;
The name of the class to use for retrieving user names from X509
certificates.
/**
* The name of the class to use for retrieving user names from X509
* certificates.
*/
protected String x509UsernameRetrieverClassName;
The object that will extract user names from X509 client certificates.
/**
* The object that will extract user names from X509 client certificates.
*/
protected X509UsernameRetriever x509UsernameRetriever;
The all role mode.
/**
* The all role mode.
*/
protected AllRolesMode allRolesMode = AllRolesMode.STRICT_MODE;
When processing users authenticated via the GSS-API, should any
"@..." be stripped from the end of the user name?
/**
* When processing users authenticated via the GSS-API, should any
* "@..." be stripped from the end of the user name?
*/
protected boolean stripRealmForGss = true;
private int transportGuaranteeRedirectStatus = HttpServletResponse.SC_FOUND;
// ------------------------------------------------------------- Properties
Returns: The HTTP status code used when the container needs to issue an
HTTP redirect to meet the requirements of a configured transport
guarantee.
/**
* @return The HTTP status code used when the container needs to issue an
* HTTP redirect to meet the requirements of a configured transport
* guarantee.
*/
public int getTransportGuaranteeRedirectStatus() {
return transportGuaranteeRedirectStatus;
}
Set the HTTP status code used when the container needs to issue an HTTP
redirect to meet the requirements of a configured transport guarantee.
Params: - transportGuaranteeRedirectStatus – The status to use. This value is
not validated
/**
* Set the HTTP status code used when the container needs to issue an HTTP
* redirect to meet the requirements of a configured transport guarantee.
*
* @param transportGuaranteeRedirectStatus The status to use. This value is
* not validated
*/
public void setTransportGuaranteeRedirectStatus(int transportGuaranteeRedirectStatus) {
this.transportGuaranteeRedirectStatus = transportGuaranteeRedirectStatus;
}
@Override
public CredentialHandler getCredentialHandler() {
return credentialHandler;
}
@Override
public void setCredentialHandler(CredentialHandler credentialHandler) {
this.credentialHandler = credentialHandler;
}
Return the Container with which this Realm has been associated.
/**
* Return the Container with which this Realm has been associated.
*/
@Override
public Container getContainer() {
return container;
}
Set the Container with which this Realm has been associated.
Params: - container – The associated Container
/**
* Set the Container with which this Realm has been associated.
*
* @param container The associated Container
*/
@Override
public void setContainer(Container container) {
Container oldContainer = this.container;
this.container = container;
support.firePropertyChange("container", oldContainer, this.container);
}
Return the all roles mode.
Returns: A string representation of the current all roles mode
/**
* Return the all roles mode.
* @return A string representation of the current all roles mode
*/
public String getAllRolesMode() {
return allRolesMode.toString();
}
Set the all roles mode.
Params: - allRolesMode – A string representation of the new all roles mode
/**
* Set the all roles mode.
* @param allRolesMode A string representation of the new all roles mode
*/
public void setAllRolesMode(String allRolesMode) {
this.allRolesMode = AllRolesMode.toMode(allRolesMode);
}
Return the "validate certificate chains" flag.
Returns: The value of the validate certificate chains flag
/**
* Return the "validate certificate chains" flag.
* @return The value of the validate certificate chains flag
*/
public boolean getValidate() {
return validate;
}
Set the "validate certificate chains" flag.
Params: - validate – The new validate certificate chains flag
/**
* Set the "validate certificate chains" flag.
*
* @param validate The new validate certificate chains flag
*/
public void setValidate(boolean validate) {
this.validate = validate;
}
Gets the name of the class that will be used to extract user names
from X509 client certificates.
Returns: The name of the class that will be used to extract user names
from X509 client certificates.
/**
* Gets the name of the class that will be used to extract user names
* from X509 client certificates.
* @return The name of the class that will be used to extract user names
* from X509 client certificates.
*/
public String getX509UsernameRetrieverClassName() {
return x509UsernameRetrieverClassName;
}
Sets the name of the class that will be used to extract user names
from X509 client certificates. The class must implement
X509UsernameRetriever.
Params: - className – The name of the class that will be used to extract user names
from X509 client certificates.
See Also:
/**
* Sets the name of the class that will be used to extract user names
* from X509 client certificates. The class must implement
* X509UsernameRetriever.
*
* @param className The name of the class that will be used to extract user names
* from X509 client certificates.
* @see X509UsernameRetriever
*/
public void setX509UsernameRetrieverClassName(String className) {
this.x509UsernameRetrieverClassName = className;
}
public boolean isStripRealmForGss() {
return stripRealmForGss;
}
public void setStripRealmForGss(boolean stripRealmForGss) {
this.stripRealmForGss = stripRealmForGss;
}
// --------------------------------------------------------- Public Methods
Add a property change listener to this component.
Params: - listener – The listener to add
/**
* Add a property change listener to this component.
*
* @param listener The listener to add
*/
@Override
public void addPropertyChangeListener(PropertyChangeListener listener) {
support.addPropertyChangeListener(listener);
}
Return the Principal associated with the specified username, if there
is one; otherwise return null
.
Params: - username – Username of the Principal to look up
/**
* Return the Principal associated with the specified username, if there
* is one; otherwise return <code>null</code>.
*
* @param username Username of the Principal to look up
*/
@Override
public Principal authenticate(String username) {
if (username == null) {
return null;
}
if (containerLog.isTraceEnabled()) {
containerLog.trace(sm.getString("realmBase.authenticateSuccess", username));
}
return getPrincipal(username);
}
Return the Principal associated with the specified username and
credentials, if there is one; otherwise return null
.
Params: - username – Username of the Principal to look up
- credentials – Password or other credentials to use in
authenticating this username
Returns: the associated principal, or null
if there is none.
/**
* Return the Principal associated with the specified username and
* credentials, if there is one; otherwise return <code>null</code>.
*
* @param username Username of the Principal to look up
* @param credentials Password or other credentials to use in
* authenticating this username
* @return the associated principal, or <code>null</code> if there is none.
*/
@Override
public Principal authenticate(String username, String credentials) {
// No user or no credentials
// Can't possibly authenticate, don't bother doing anything.
if(username == null || credentials == null) {
if (containerLog.isTraceEnabled()) {
containerLog.trace(sm.getString("realmBase.authenticateFailure",
username));
}
return null;
}
// Look up the user's credentials
String serverCredentials = getPassword(username);
if (serverCredentials == null) {
// User was not found
// Waste a bit of time as not to reveal that the user does not exist.
getCredentialHandler().mutate(credentials);
if (containerLog.isTraceEnabled()) {
containerLog.trace(sm.getString("realmBase.authenticateFailure",
username));
}
return null;
}
boolean validated = getCredentialHandler().matches(credentials, serverCredentials);
if (validated) {
if (containerLog.isTraceEnabled()) {
containerLog.trace(sm.getString("realmBase.authenticateSuccess",
username));
}
return getPrincipal(username);
} else {
if (containerLog.isTraceEnabled()) {
containerLog.trace(sm.getString("realmBase.authenticateFailure",
username));
}
return null;
}
}
Try to authenticate with the specified username, which
matches the digest calculated using the given parameters using the
method described in RFC 2617 (which is a superset of RFC 2069).
Params: - username – Username of the Principal to look up
- clientDigest – Digest which has been submitted by the client
- nonce – Unique (or supposedly unique) token which has been used
for this request
- nc – the nonce counter
- cnonce – the client chosen nonce
- qop – the "quality of protection" (
nc
and cnonce
will only be used, if qop
is not null
). - realm – Realm name
- md5a2 – Second MD5 digest used to calculate the digest :
MD5(Method + ":" + uri)
Returns: the associated principal, or null
if there is none.
/**
* Try to authenticate with the specified username, which
* matches the digest calculated using the given parameters using the
* method described in RFC 2617 (which is a superset of RFC 2069).
*
* @param username Username of the Principal to look up
* @param clientDigest Digest which has been submitted by the client
* @param nonce Unique (or supposedly unique) token which has been used
* for this request
* @param nc the nonce counter
* @param cnonce the client chosen nonce
* @param qop the "quality of protection" (<code>nc</code> and <code>cnonce</code>
* will only be used, if <code>qop</code> is not <code>null</code>).
* @param realm Realm name
* @param md5a2 Second MD5 digest used to calculate the digest :
* MD5(Method + ":" + uri)
* @return the associated principal, or <code>null</code> if there is none.
*/
@Override
public Principal authenticate(String username, String clientDigest,
String nonce, String nc, String cnonce,
String qop, String realm,
String md5a2) {
// In digest auth, digests are always lower case
String md5a1 = getDigest(username, realm);
if (md5a1 == null)
return null;
md5a1 = md5a1.toLowerCase(Locale.ENGLISH);
String serverDigestValue;
if (qop == null) {
serverDigestValue = md5a1 + ":" + nonce + ":" + md5a2;
} else {
serverDigestValue = md5a1 + ":" + nonce + ":" + nc + ":" +
cnonce + ":" + qop + ":" + md5a2;
}
byte[] valueBytes = null;
try {
valueBytes = serverDigestValue.getBytes(getDigestCharset());
} catch (UnsupportedEncodingException uee) {
throw new IllegalArgumentException(sm.getString("realmBase.invalidDigestEncoding", getDigestEncoding()), uee);
}
String serverDigest = MD5Encoder.encode(ConcurrentMessageDigest.digestMD5(valueBytes));
if (log.isDebugEnabled()) {
log.debug("Digest : " + clientDigest + " Username:" + username
+ " ClientDigest:" + clientDigest + " nonce:" + nonce
+ " nc:" + nc + " cnonce:" + cnonce + " qop:" + qop
+ " realm:" + realm + "md5a2:" + md5a2
+ " Server digest:" + serverDigest);
}
if (serverDigest.equals(clientDigest)) {
return getPrincipal(username);
}
return null;
}
Return the Principal associated with the specified chain of X509
client certificates. If there is none, return null
.
Params: - certs – Array of client certificates, with the first one in
the array being the certificate of the client itself.
/**
* Return the Principal associated with the specified chain of X509
* client certificates. If there is none, return <code>null</code>.
*
* @param certs Array of client certificates, with the first one in
* the array being the certificate of the client itself.
*/
@Override
public Principal authenticate(X509Certificate certs[]) {
if ((certs == null) || (certs.length < 1))
return null;
// Check the validity of each certificate in the chain
if (log.isDebugEnabled())
log.debug("Authenticating client certificate chain");
if (validate) {
for (X509Certificate cert : certs) {
if (log.isDebugEnabled())
log.debug(" Checking validity for '" +
cert.getSubjectDN().getName() + "'");
try {
cert.checkValidity();
} catch (Exception e) {
if (log.isDebugEnabled())
log.debug(" Validity exception", e);
return null;
}
}
}
// Check the existence of the client Principal in our database
return getPrincipal(certs[0]);
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
@Override
public Principal authenticate(GSSContext gssContext, boolean storeCred) {
if (gssContext.isEstablished()) {
GSSName gssName = null;
try {
gssName = gssContext.getSrcName();
} catch (GSSException e) {
log.warn(sm.getString("realmBase.gssNameFail"), e);
}
if (gssName!= null) {
GSSCredential gssCredential = null;
if (storeCred) {
if (gssContext.getCredDelegState()) {
try {
gssCredential = gssContext.getDelegCred();
} catch (GSSException e) {
log.warn(sm.getString(
"realmBase.delegatedCredentialFail", gssName), e);
}
} else {
if (log.isDebugEnabled()) {
log.debug(sm.getString(
"realmBase.credentialNotDelegated", gssName));
}
}
}
return getPrincipal(gssName, gssCredential);
}
} else {
log.error(sm.getString("realmBase.gssContextNotEstablished"));
}
// Fail in all other cases
return null;
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
@Override
public Principal authenticate(GSSName gssName, GSSCredential gssCredential) {
if (gssName == null) {
return null;
}
return getPrincipal(gssName, gssCredential);
}
Execute a periodic task, such as reloading, etc. This method will be
invoked inside the classloading context of this container. Unexpected
throwables will be caught and logged.
/**
* Execute a periodic task, such as reloading, etc. This method will be
* invoked inside the classloading context of this container. Unexpected
* throwables will be caught and logged.
*/
@Override
public void backgroundProcess() {
// NOOP in base class
}
Return the SecurityConstraints configured to guard the request URI for
this request, or null
if there is no such constraint.
Params: - request – Request we are processing
- context – Context the Request is mapped to
/**
* Return the SecurityConstraints configured to guard the request URI for
* this request, or <code>null</code> if there is no such constraint.
*
* @param request Request we are processing
* @param context Context the Request is mapped to
*/
@Override
public SecurityConstraint [] findSecurityConstraints(Request request,
Context context) {
ArrayList<SecurityConstraint> results = null;
// Are there any defined security constraints?
SecurityConstraint constraints[] = context.findConstraints();
if ((constraints == null) || (constraints.length == 0)) {
if (log.isDebugEnabled())
log.debug(" No applicable constraints defined");
return null;
}
// Check each defined security constraint
String uri = request.getRequestPathMB().toString();
// Bug47080 - in rare cases this may be null or ""
// Mapper treats as '/' do the same to prevent NPE
if (uri == null || uri.length() == 0) {
uri = "/";
}
String method = request.getMethod();
int i;
boolean found = false;
for (i = 0; i < constraints.length; i++) {
SecurityCollection[] collections = constraints[i].findCollections();
// If collection is null, continue to avoid an NPE
// See Bugzilla 30624
if (collections == null) {
continue;
}
if (log.isDebugEnabled()) {
log.debug(" Checking constraint '" + constraints[i] +
"' against " + method + " " + uri + " --> " +
constraints[i].included(uri, method));
}
for (SecurityCollection securityCollection : collections) {
String[] patterns = securityCollection.findPatterns();
// If patterns is null, continue to avoid an NPE
// See Bugzilla 30624
if (patterns == null) {
continue;
}
for (String pattern : patterns) {
// Exact match including special case for the context root.
if (uri.equals(pattern) || pattern.length() == 0 && uri.equals("/")) {
found = true;
if (securityCollection.findMethod(method)) {
if (results == null) {
results = new ArrayList<>();
}
results.add(constraints[i]);
}
}
}
}
}
if(found) {
return resultsToArray(results);
}
int longest = -1;
for (i = 0; i < constraints.length; i++) {
SecurityCollection [] collection = constraints[i].findCollections();
// If collection is null, continue to avoid an NPE
// See Bugzilla 30624
if ( collection == null) {
continue;
}
if (log.isDebugEnabled()) {
log.debug(" Checking constraint '" + constraints[i] +
"' against " + method + " " + uri + " --> " +
constraints[i].included(uri, method));
}
for (SecurityCollection securityCollection : collection) {
String[] patterns = securityCollection.findPatterns();
// If patterns is null, continue to avoid an NPE
// See Bugzilla 30624
if (patterns == null) {
continue;
}
boolean matched = false;
int length = -1;
for (String pattern : patterns) {
if (pattern.startsWith("/") && pattern.endsWith("/*") &&
pattern.length() >= longest) {
if (pattern.length() == 2) {
matched = true;
length = pattern.length();
} else if (pattern.regionMatches(0, uri, 0, pattern.length() - 1) ||
(pattern.length() - 2 == uri.length() &&
pattern.regionMatches(0, uri, 0, pattern.length() - 2))) {
matched = true;
length = pattern.length();
}
}
}
if (matched) {
if (length > longest) {
found = false;
if (results != null) {
results.clear();
}
longest = length;
}
if (securityCollection.findMethod(method)) {
found = true;
if (results == null) {
results = new ArrayList<>();
}
results.add(constraints[i]);
}
}
}
}
if(found) {
return resultsToArray(results);
}
for (i = 0; i < constraints.length; i++) {
SecurityCollection [] collection = constraints[i].findCollections();
// If collection is null, continue to avoid an NPE
// See Bugzilla 30624
if ( collection == null) {
continue;
}
if (log.isDebugEnabled()) {
log.debug(" Checking constraint '" + constraints[i] +
"' against " + method + " " + uri + " --> " +
constraints[i].included(uri, method));
}
boolean matched = false;
int pos = -1;
for(int j=0; j < collection.length; j++){
String [] patterns = collection[j].findPatterns();
// If patterns is null, continue to avoid an NPE
// See Bugzilla 30624
if ( patterns == null) {
continue;
}
for(int k=0; k < patterns.length && !matched; k++) {
String pattern = patterns[k];
if(pattern.startsWith("*.")){
int slash = uri.lastIndexOf('/');
int dot = uri.lastIndexOf('.');
if(slash >= 0 && dot > slash &&
dot != uri.length()-1 &&
uri.length()-dot == pattern.length()-1) {
if(pattern.regionMatches(1,uri,dot,uri.length()-dot)) {
matched = true;
pos = j;
}
}
}
}
}
if(matched) {
found = true;
if(collection[pos].findMethod(method)) {
if(results == null) {
results = new ArrayList<>();
}
results.add(constraints[i]);
}
}
}
if(found) {
return resultsToArray(results);
}
for (i = 0; i < constraints.length; i++) {
SecurityCollection [] collection = constraints[i].findCollections();
// If collection is null, continue to avoid an NPE
// See Bugzilla 30624
if ( collection == null) {
continue;
}
if (log.isDebugEnabled()) {
log.debug(" Checking constraint '" + constraints[i] +
"' against " + method + " " + uri + " --> " +
constraints[i].included(uri, method));
}
for (SecurityCollection securityCollection : collection) {
String[] patterns = securityCollection.findPatterns();
// If patterns is null, continue to avoid an NPE
// See Bugzilla 30624
if (patterns == null) {
continue;
}
boolean matched = false;
for (String pattern : patterns) {
if (pattern.equals("/")) {
matched = true;
break;
}
}
if (matched) {
if (results == null) {
results = new ArrayList<>();
}
results.add(constraints[i]);
}
}
}
if(results == null) {
// No applicable security constraint was found
if (log.isDebugEnabled())
log.debug(" No applicable constraint located");
}
return resultsToArray(results);
}
Convert an ArrayList to a SecurityConstraint [].
/**
* Convert an ArrayList to a SecurityConstraint [].
*/
private SecurityConstraint [] resultsToArray(
ArrayList<SecurityConstraint> results) {
if(results == null || results.size() == 0) {
return null;
}
SecurityConstraint [] array = new SecurityConstraint[results.size()];
results.toArray(array);
return array;
}
Perform access control based on the specified authorization constraint.
Return true
if this constraint is satisfied and processing
should continue, or false
otherwise.
Params: - request – Request we are processing
- response – Response we are creating
- constraints – Security constraint we are enforcing
- context – The Context to which client of this class is attached.
Throws: - IOException – if an input/output error occurs
/**
* Perform access control based on the specified authorization constraint.
* Return <code>true</code> if this constraint is satisfied and processing
* should continue, or <code>false</code> otherwise.
*
* @param request Request we are processing
* @param response Response we are creating
* @param constraints Security constraint we are enforcing
* @param context The Context to which client of this class is attached.
*
* @exception IOException if an input/output error occurs
*/
@Override
public boolean hasResourcePermission(Request request,
Response response,
SecurityConstraint []constraints,
Context context)
throws IOException {
if (constraints == null || constraints.length == 0)
return true;
// Which user principal have we already authenticated?
Principal principal = request.getPrincipal();
boolean status = false;
boolean denyfromall = false;
for (SecurityConstraint constraint : constraints) {
String roles[];
if (constraint.getAllRoles()) {
// * means all roles defined in web.xml
roles = request.getContext().findSecurityRoles();
} else {
roles = constraint.findAuthRoles();
}
if (roles == null)
roles = new String[0];
if (log.isDebugEnabled())
log.debug(" Checking roles " + principal);
if (constraint.getAuthenticatedUsers() && principal != null) {
if (log.isDebugEnabled()) {
log.debug("Passing all authenticated users");
}
status = true;
}
else if (roles.length == 0 && !constraint.getAllRoles() &&
!constraint.getAuthenticatedUsers()) {
if (constraint.getAuthConstraint()) {
if (log.isDebugEnabled()) {
log.debug("No roles");
}
status = false; // No listed roles means no access at all
denyfromall = true;
break;
}
if (log.isDebugEnabled()) {
log.debug("Passing all access");
}
status = true;
} else if (principal == null) {
if (log.isDebugEnabled()) {
log.debug(" No user authenticated, cannot grant access");
}
} else {
for (String role : roles) {
if (hasRole(request.getWrapper(), principal, role)) {
status = true;
if (log.isDebugEnabled()) {
log.debug("Role found: " + role);
}
} else if (log.isDebugEnabled()) {
log.debug("No role found: " + role);
}
}
}
}
if (!denyfromall && allRolesMode != AllRolesMode.STRICT_MODE &&
!status && principal != null) {
if (log.isDebugEnabled()) {
log.debug("Checking for all roles mode: " + allRolesMode);
}
// Check for an all roles(role-name="*")
for (SecurityConstraint constraint : constraints) {
String roles[];
// If the all roles mode exists, sets
if (constraint.getAllRoles()) {
if (allRolesMode == AllRolesMode.AUTH_ONLY_MODE) {
if (log.isDebugEnabled()) {
log.debug("Granting access for role-name=*, auth-only");
}
status = true;
break;
}
// For AllRolesMode.STRICT_AUTH_ONLY_MODE there must be zero roles
roles = request.getContext().findSecurityRoles();
if (roles.length == 0 && allRolesMode == AllRolesMode.STRICT_AUTH_ONLY_MODE) {
if (log.isDebugEnabled()) {
log.debug("Granting access for role-name=*, strict auth-only");
}
status = true;
break;
}
}
}
}
// Return a "Forbidden" message denying access to this resource
if(!status) {
response.sendError
(HttpServletResponse.SC_FORBIDDEN,
sm.getString("realmBase.forbidden"));
}
return status;
}
{@inheritDoc} This method or hasRoleInternal(Principal, String)
can be overridden by Realm implementations, but the default is adequate when an instance of GenericPrincipal
is used to
represent authenticated Principals from this Realm.
/**
* {@inheritDoc}
*
* This method or {@link #hasRoleInternal(Principal,
* String)} can be overridden by Realm implementations, but the default is
* adequate when an instance of <code>GenericPrincipal</code> is used to
* represent authenticated Principals from this Realm.
*/
@Override
public boolean hasRole(Wrapper wrapper, Principal principal, String role) {
// Check for a role alias
if (wrapper != null) {
String realRole = wrapper.findSecurityReference(role);
if (realRole != null) {
role = realRole;
}
}
// Should be overridden in JAASRealm - to avoid pretty inefficient conversions
if (principal == null || role == null) {
return false;
}
boolean result = hasRoleInternal(principal, role);
if (log.isDebugEnabled()) {
String name = principal.getName();
if (result)
log.debug(sm.getString("realmBase.hasRoleSuccess", name, role));
else
log.debug(sm.getString("realmBase.hasRoleFailure", name, role));
}
return result;
}
Check if the specified Principal has the specified security role, within the context of this Realm. This method or hasRoleInternal(Principal, String)
can be overridden by Realm implementations, but the default is adequate when an instance of GenericPrincipal
is used to
represent authenticated Principals from this Realm.
Params: - principal – Principal for whom the role is to be checked
- role – Security role to be checked
Returns: true
if the specified Principal has the specified
security role, within the context of this Realm; otherwise return
false
.
/**
* Check if the specified Principal has the specified
* security role, within the context of this Realm.
*
* This method or {@link #hasRoleInternal(Principal,
* String)} can be overridden by Realm implementations, but the default is
* adequate when an instance of <code>GenericPrincipal</code> is used to
* represent authenticated Principals from this Realm.
*
* @param principal Principal for whom the role is to be checked
* @param role Security role to be checked
*
* @return <code>true</code> if the specified Principal has the specified
* security role, within the context of this Realm; otherwise return
* <code>false</code>.
*/
protected boolean hasRoleInternal(Principal principal, String role) {
// Should be overridden in JAASRealm - to avoid pretty inefficient conversions
if (!(principal instanceof GenericPrincipal)) {
return false;
}
GenericPrincipal gp = (GenericPrincipal) principal;
return gp.hasRole(role);
}
Enforce any user data constraint required by the security constraint
guarding this request URI. Return true
if this constraint
was not violated and processing should continue, or false
if we have created a response already.
Params: - request – Request we are processing
- response – Response we are creating
- constraints – Security constraint being checked
Throws: - IOException – if an input/output error occurs
/**
* Enforce any user data constraint required by the security constraint
* guarding this request URI. Return <code>true</code> if this constraint
* was not violated and processing should continue, or <code>false</code>
* if we have created a response already.
*
* @param request Request we are processing
* @param response Response we are creating
* @param constraints Security constraint being checked
*
* @exception IOException if an input/output error occurs
*/
@Override
public boolean hasUserDataPermission(Request request,
Response response,
SecurityConstraint []constraints)
throws IOException {
// Is there a relevant user data constraint?
if (constraints == null || constraints.length == 0) {
if (log.isDebugEnabled())
log.debug(" No applicable security constraint defined");
return true;
}
for (SecurityConstraint constraint : constraints) {
String userConstraint = constraint.getUserConstraint();
if (userConstraint == null) {
if (log.isDebugEnabled())
log.debug(" No applicable user data constraint defined");
return true;
}
if (userConstraint.equals(TransportGuarantee.NONE.name())) {
if (log.isDebugEnabled())
log.debug(" User data constraint has no restrictions");
return true;
}
}
// Validate the request against the user data constraint
if (request.getRequest().isSecure()) {
if (log.isDebugEnabled())
log.debug(" User data constraint already satisfied");
return true;
}
// Initialize variables we need to determine the appropriate action
int redirectPort = request.getConnector().getRedirectPortWithOffset();
// Is redirecting disabled?
if (redirectPort <= 0) {
if (log.isDebugEnabled())
log.debug(" SSL redirect is disabled");
response.sendError
(HttpServletResponse.SC_FORBIDDEN,
request.getRequestURI());
return false;
}
// Redirect to the corresponding SSL port
StringBuilder file = new StringBuilder();
String protocol = "https";
String host = request.getServerName();
// Protocol
file.append(protocol).append("://").append(host);
// Host with port
if(redirectPort != 443) {
file.append(':').append(redirectPort);
}
// URI
file.append(request.getRequestURI());
String requestedSessionId = request.getRequestedSessionId();
if ((requestedSessionId != null) &&
request.isRequestedSessionIdFromURL()) {
file.append(';');
file.append(SessionConfig.getSessionUriParamName(
request.getContext()));
file.append('=');
file.append(requestedSessionId);
}
String queryString = request.getQueryString();
if (queryString != null) {
file.append('?');
file.append(queryString);
}
if (log.isDebugEnabled())
log.debug(" Redirecting to " + file.toString());
response.sendRedirect(file.toString(), transportGuaranteeRedirectStatus);
return false;
}
Remove a property change listener from this component.
Params: - listener – The listener to remove
/**
* Remove a property change listener from this component.
*
* @param listener The listener to remove
*/
@Override
public void removePropertyChangeListener(PropertyChangeListener listener) {
support.removePropertyChangeListener(listener);
}
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
// We want logger as soon as possible
if (container != null) {
this.containerLog = container.getLogger();
}
x509UsernameRetriever = createUsernameRetriever(x509UsernameRetrieverClassName);
}
Prepare for the beginning of active use of the public methods of this component and implement the requirements of LifecycleBase.startInternal()
. Throws: - LifecycleException – if this component detects a fatal error
that prevents this component from being used
/**
* Prepare for the beginning of active use of the public methods of this
* component and implement the requirements of
* {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected void startInternal() throws LifecycleException {
if (credentialHandler == null) {
credentialHandler = new MessageDigestCredentialHandler();
}
setState(LifecycleState.STARTING);
}
Gracefully terminate the active use of the public methods of this component and implement the requirements of LifecycleBase.stopInternal()
. Throws: - LifecycleException – if this component detects a fatal error
that needs to be reported
/**
* Gracefully terminate the active use of the public methods of this
* component and implement the requirements of
* {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that needs to be reported
*/
@Override
protected void stopInternal() throws LifecycleException {
setState(LifecycleState.STOPPING);
}
Return a String representation of this component.
/**
* Return a String representation of this component.
*/
@Override
public String toString() {
return ToStringUtil.toString(this);
}
// ------------------------------------------------------ Protected Methods
protected boolean hasMessageDigest() {
CredentialHandler ch = credentialHandler;
if (ch instanceof MessageDigestCredentialHandler) {
return ((MessageDigestCredentialHandler) ch).getAlgorithm() != null;
}
return false;
}
Return the digest associated with given principal's user name.
Params: - username – the user name
- realmName – the realm name
Returns: the digest for the specified user
/**
* Return the digest associated with given principal's user name.
* @param username the user name
* @param realmName the realm name
* @return the digest for the specified user
*/
protected String getDigest(String username, String realmName) {
if (hasMessageDigest()) {
// Use pre-generated digest
return getPassword(username);
}
String digestValue = username + ":" + realmName + ":"
+ getPassword(username);
byte[] valueBytes = null;
try {
valueBytes = digestValue.getBytes(getDigestCharset());
} catch (UnsupportedEncodingException uee) {
throw new IllegalArgumentException(sm.getString("realmBase.invalidDigestEncoding", getDigestEncoding()), uee);
}
return MD5Encoder.encode(ConcurrentMessageDigest.digestMD5(valueBytes));
}
private String getDigestEncoding() {
CredentialHandler ch = credentialHandler;
if (ch instanceof MessageDigestCredentialHandler) {
return ((MessageDigestCredentialHandler) ch).getEncoding();
}
return null;
}
private Charset getDigestCharset() throws UnsupportedEncodingException {
String charset = getDigestEncoding();
if (charset == null) {
return StandardCharsets.ISO_8859_1;
} else {
return B2CConverter.getCharset(charset);
}
}
Get the password for the specified user.
Params: - username – The user name
Returns: the password associated with the given principal's user name.
/**
* Get the password for the specified user.
* @param username The user name
* @return the password associated with the given principal's user name.
*/
protected abstract String getPassword(String username);
Get the principal associated with the specified certificate.
Params: - usercert – The user certificate
Returns: the Principal associated with the given certificate.
/**
* Get the principal associated with the specified certificate.
* @param usercert The user certificate
* @return the Principal associated with the given certificate.
*/
protected Principal getPrincipal(X509Certificate usercert) {
String username = x509UsernameRetriever.getUsername(usercert);
if(log.isDebugEnabled())
log.debug(sm.getString("realmBase.gotX509Username", username));
return getPrincipal(username);
}
Get the principal associated with the specified user.
Params: - username – The user name
Returns: the Principal associated with the given user name.
/**
* Get the principal associated with the specified user.
* @param username The user name
* @return the Principal associated with the given user name.
*/
protected abstract Principal getPrincipal(String username);
Get the principal associated with the specified GSSName
. Params: - gssName – The GSS name
- gssCredential – the GSS credential of the principal
Returns: the principal associated with the given user name.
/**
* Get the principal associated with the specified {@link GSSName}.
*
* @param gssName The GSS name
* @param gssCredential the GSS credential of the principal
* @return the principal associated with the given user name.
*/
protected Principal getPrincipal(GSSName gssName,
GSSCredential gssCredential) {
String name = gssName.toString();
if (isStripRealmForGss()) {
int i = name.indexOf('@');
if (i > 0) {
// Zero so we don't leave a zero length name
name = name.substring(0, i);
}
}
Principal p = getPrincipal(name);
if (p instanceof GenericPrincipal) {
((GenericPrincipal) p).setGssCredential(gssCredential);
}
return p;
}
Return the Server object that is the ultimate parent for the container
with which this Realm is associated. If the server cannot be found (eg
because the container hierarchy is not complete), null
is
returned.
Returns: the Server associated with the realm
/**
* Return the Server object that is the ultimate parent for the container
* with which this Realm is associated. If the server cannot be found (eg
* because the container hierarchy is not complete), <code>null</code> is
* returned.
* @return the Server associated with the realm
*/
protected Server getServer() {
Container c = container;
if (c instanceof Context) {
c = c.getParent();
}
if (c instanceof Host) {
c = c.getParent();
}
if (c instanceof Engine) {
Service s = ((Engine)c).getService();
if (s != null) {
return s.getServer();
}
}
return null;
}
// --------------------------------------------------------- Static Methods
Generate a stored credential string for the given password and associated
parameters.
The following parameters are supported:
- -a - The algorithm to use to generate the stored
credential. If not specified a default of SHA-512 will be
used.
- -e - The encoding to use for any byte to/from character conversion that may be necessary. If not specified, the system encoding (
Charset.defaultCharset()
) will be used.
- -i - The number of iterations to use when generating the
stored credential. If not specified, the default for the
CredentialHandler will be used.
- -s - The length (in bytes) of salt to generate and store as
part of the credential. If not specified, the default for
the CredentialHandler will be used.
- -k - The length (in bits) of the key(s), if any, created while
generating the credential. If not specified, the default
for the CredentialHandler will be used.
- -h - The fully qualified class name of the CredentialHandler
to use. If not specified, the built-in handlers will be
tested in turn and the first one to accept the specified
algorithm will be used.
This generation process currently supports the following
CredentialHandlers, the correct one being selected based on the algorithm
specified:
Params: - args – The parameters passed on the command line
/**
* Generate a stored credential string for the given password and associated
* parameters.
* <p>The following parameters are supported:</p>
* <ul>
* <li><b>-a</b> - The algorithm to use to generate the stored
* credential. If not specified a default of SHA-512 will be
* used.</li>
* <li><b>-e</b> - The encoding to use for any byte to/from character
* conversion that may be necessary. If not specified, the
* system encoding ({@link Charset#defaultCharset()}) will
* be used.</li>
* <li><b>-i</b> - The number of iterations to use when generating the
* stored credential. If not specified, the default for the
* CredentialHandler will be used.</li>
* <li><b>-s</b> - The length (in bytes) of salt to generate and store as
* part of the credential. If not specified, the default for
* the CredentialHandler will be used.</li>
* <li><b>-k</b> - The length (in bits) of the key(s), if any, created while
* generating the credential. If not specified, the default
* for the CredentialHandler will be used.</li>
* <li><b>-h</b> - The fully qualified class name of the CredentialHandler
* to use. If not specified, the built-in handlers will be
* tested in turn and the first one to accept the specified
* algorithm will be used.</li>
* </ul>
* <p>This generation process currently supports the following
* CredentialHandlers, the correct one being selected based on the algorithm
* specified:</p>
* <ul>
* <li>{@link MessageDigestCredentialHandler}</li>
* <li>{@link SecretKeyCredentialHandler}</li>
* </ul>
* @param args The parameters passed on the command line
*/
public static void main(String args[]) {
// Use negative values since null is not an option to indicate 'not set'
int saltLength = -1;
int iterations = -1;
int keyLength = -1;
// Default
String encoding = Charset.defaultCharset().name();
// Default values for these depend on whether either of them are set on
// the command line
String algorithm = null;
String handlerClassName = null;
if (args.length == 0) {
usage();
return;
}
int argIndex = 0;
while (args.length > argIndex + 2 && args[argIndex].length() == 2 &&
args[argIndex].charAt(0) == '-' ) {
switch (args[argIndex].charAt(1)) {
case 'a': {
algorithm = args[argIndex + 1];
break;
}
case 'e': {
encoding = args[argIndex + 1];
break;
}
case 'i': {
iterations = Integer.parseInt(args[argIndex + 1]);
break;
}
case 's': {
saltLength = Integer.parseInt(args[argIndex + 1]);
break;
}
case 'k': {
keyLength = Integer.parseInt(args[argIndex + 1]);
break;
}
case 'h': {
handlerClassName = args[argIndex + 1];
break;
}
default: {
usage();
return;
}
}
argIndex += 2;
}
// Determine defaults for -a and -h. The rules are more complex to
// express than the implementation:
// - if neither -a nor -h is set, use SHA-512 and
// MessageDigestCredentialHandler
// - if only -a is set the built-in handlers will be searched in order
// (MessageDigestCredentialHandler, SecretKeyCredentialHandler) and
// the first handler that supports the algorithm will be used
// - if only -h is set no default will be used for -a. The handler may
// or may nor support -a and may or may not supply a sensible default
if (algorithm == null && handlerClassName == null) {
algorithm = "SHA-512";
}
CredentialHandler handler = null;
if (handlerClassName == null) {
for (Class<? extends DigestCredentialHandlerBase> clazz : credentialHandlerClasses) {
try {
handler = clazz.getConstructor().newInstance();
if (IntrospectionUtils.setProperty(handler, "algorithm", algorithm)) {
break;
}
} catch (ReflectiveOperationException e) {
// This isn't good.
throw new RuntimeException(e);
}
}
} else {
try {
Class<?> clazz = Class.forName(handlerClassName);
handler = (DigestCredentialHandlerBase) clazz.getConstructor().newInstance();
IntrospectionUtils.setProperty(handler, "algorithm", algorithm);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
if (handler == null) {
throw new RuntimeException(new NoSuchAlgorithmException(algorithm));
}
IntrospectionUtils.setProperty(handler, "encoding", encoding);
if (iterations > 0) {
IntrospectionUtils.setProperty(handler, "iterations", Integer.toString(iterations));
}
if (saltLength > -1) {
IntrospectionUtils.setProperty(handler, "saltLength", Integer.toString(saltLength));
}
if (keyLength > 0) {
IntrospectionUtils.setProperty(handler, "keyLength", Integer.toString(keyLength));
}
for (; argIndex < args.length; argIndex++) {
String credential = args[argIndex];
System.out.print(credential + ":");
System.out.println(handler.mutate(credential));
}
}
private static void usage() {
System.out.println("Usage: RealmBase [-a <algorithm>] [-e <encoding>] " +
"[-i <iterations>] [-s <salt-length>] [-k <key-length>] " +
"[-h <handler-class-name>] <credentials>");
}
// -------------------- JMX and Registration --------------------
@Override
public String getObjectNameKeyProperties() {
StringBuilder keyProperties = new StringBuilder("type=Realm");
keyProperties.append(getRealmSuffix());
keyProperties.append(container.getMBeanKeyProperties());
return keyProperties.toString();
}
@Override
public String getDomainInternal() {
return container.getDomain();
}
protected String realmPath = "/realm0";
public String getRealmPath() {
return realmPath;
}
public void setRealmPath(String theRealmPath) {
realmPath = theRealmPath;
}
protected String getRealmSuffix() {
return ",realmPath=" + getRealmPath();
}
protected static class AllRolesMode {
private final String name;
Use the strict servlet spec interpretation which requires that the user
have one of the web-app/security-role/role-name
/** Use the strict servlet spec interpretation which requires that the user
* have one of the web-app/security-role/role-name
*/
public static final AllRolesMode STRICT_MODE = new AllRolesMode("strict");
Allow any authenticated user
/** Allow any authenticated user
*/
public static final AllRolesMode AUTH_ONLY_MODE = new AllRolesMode("authOnly");
Allow any authenticated user only if there are no web-app/security-roles
/** Allow any authenticated user only if there are no web-app/security-roles
*/
public static final AllRolesMode STRICT_AUTH_ONLY_MODE = new AllRolesMode("strictAuthOnly");
static AllRolesMode toMode(String name) {
AllRolesMode mode;
if (name.equalsIgnoreCase(STRICT_MODE.name)) {
mode = STRICT_MODE;
} else if (name.equalsIgnoreCase(AUTH_ONLY_MODE.name)) {
mode = AUTH_ONLY_MODE;
} else if (name.equalsIgnoreCase(STRICT_AUTH_ONLY_MODE.name)) {
mode = STRICT_AUTH_ONLY_MODE;
} else {
throw new IllegalStateException(
sm.getString("realmBase.unknownAllRolesMode", name));
}
return mode;
}
private AllRolesMode(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
boolean equals = false;
if (o instanceof AllRolesMode) {
AllRolesMode mode = (AllRolesMode) o;
equals = name.equals(mode.name);
}
return equals;
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public String toString() {
return name;
}
}
private static X509UsernameRetriever createUsernameRetriever(String className)
throws LifecycleException {
if(null == className || className.trim().isEmpty())
return new X509SubjectDnRetriever();
try {
@SuppressWarnings("unchecked")
Class<? extends X509UsernameRetriever> clazz = (Class<? extends X509UsernameRetriever>)Class.forName(className);
return clazz.getConstructor().newInstance();
} catch (ReflectiveOperationException e) {
throw new LifecycleException(sm.getString("realmBase.createUsernameRetriever.newInstance", className), e);
} catch (ClassCastException e) {
throw new LifecycleException(sm.getString("realmBase.createUsernameRetriever.ClassCastException", className), e);
}
}
}