/*
 * 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.security.Principal;
import java.security.cert.X509Certificate;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import javax.management.ObjectName;

import org.apache.catalina.Container;
import org.apache.catalina.CredentialHandler;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.Realm;
import org.apache.catalina.Wrapper;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSName;

Realm implementation that contains one or more realms. Authentication is attempted for each realm in the order they were configured. If any realm authenticates the user then the authentication succeeds. When combining realms usernames should be unique across all combined realms.
/** * Realm implementation that contains one or more realms. Authentication is * attempted for each realm in the order they were configured. If any realm * authenticates the user then the authentication succeeds. When combining * realms usernames should be unique across all combined realms. */
public class CombinedRealm extends RealmBase { private static final Log log = LogFactory.getLog(CombinedRealm.class);
The list of Realms contained by this Realm.
/** * The list of Realms contained by this Realm. */
protected final List<Realm> realms = new LinkedList<>();
Add a realm to the list of realms that will be used to authenticate users.
Params:
  • theRealm – realm which should be wrapped by the combined realm
/** * Add a realm to the list of realms that will be used to authenticate * users. * @param theRealm realm which should be wrapped by the combined realm */
public void addRealm(Realm theRealm) { realms.add(theRealm); if (log.isDebugEnabled()) { sm.getString("combinedRealm.addRealm", theRealm.getClass().getName(), Integer.toString(realms.size())); } }
Returns:the set of Realms that this Realm is wrapping
/** * @return the set of Realms that this Realm is wrapping */
public ObjectName[] getRealms() { ObjectName[] result = new ObjectName[realms.size()]; for (Realm realm : realms) { if (realm instanceof RealmBase) { result[realms.indexOf(realm)] = ((RealmBase) realm).getObjectName(); } } return result; }
Returns:the list of Realms contained by this Realm.
/** * @return the list of Realms contained by this Realm. */
public Realm[] getNestedRealms() { return realms.toArray(new Realm[0]); }
Return the Principal associated with the specified username, which matches the digest calculated using the given parameters using the method described in RFC 2069; otherwise return null.
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
  • realmName – Realm name
  • md5a2 – Second MD5 digest used to calculate the digest : MD5(Method + ":" + uri)
/** * Return the Principal associated with the specified username, which * matches the digest calculated using the given parameters using the * method described in RFC 2069; otherwise return <code>null</code>. * * @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 realmName Realm name * @param md5a2 Second MD5 digest used to calculate the digest : * MD5(Method + ":" + uri) */
@Override public Principal authenticate(String username, String clientDigest, String nonce, String nc, String cnonce, String qop, String realmName, String md5a2) { Principal authenticatedUser = null; for (Realm realm : realms) { if (log.isDebugEnabled()) { log.debug(sm.getString("combinedRealm.authStart", username, realm.getClass().getName())); } authenticatedUser = realm.authenticate(username, clientDigest, nonce, nc, cnonce, qop, realmName, md5a2); if (authenticatedUser == null) { if (log.isDebugEnabled()) { log.debug(sm.getString("combinedRealm.authFail", username, realm.getClass().getName())); } } else { if (log.isDebugEnabled()) { log.debug(sm.getString("combinedRealm.authSuccess", username, realm.getClass().getName())); } break; } } return authenticatedUser; }
Return the Principal associated with the specified user name otherwise return null.
Params:
  • username – User name of the Principal to look up
/** * Return the Principal associated with the specified user name otherwise * return <code>null</code>. * * @param username User name of the Principal to look up */
@Override public Principal authenticate(String username) { Principal authenticatedUser = null; for (Realm realm : realms) { if (log.isDebugEnabled()) { log.debug(sm.getString("combinedRealm.authStart", username, realm.getClass().getName())); } authenticatedUser = realm.authenticate(username); if (authenticatedUser == null) { if (log.isDebugEnabled()) { log.debug(sm.getString("combinedRealm.authFail", username, realm.getClass().getName())); } } else { if (log.isDebugEnabled()) { log.debug(sm.getString("combinedRealm.authSuccess", username, realm.getClass().getName())); } break; } } return authenticatedUser; }
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
/** * 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 */
@Override public Principal authenticate(String username, String credentials) { Principal authenticatedUser = null; for (Realm realm : realms) { if (log.isDebugEnabled()) { log.debug(sm.getString("combinedRealm.authStart", username, realm.getClass().getName())); } authenticatedUser = realm.authenticate(username, credentials); if (authenticatedUser == null) { if (log.isDebugEnabled()) { log.debug(sm.getString("combinedRealm.authFail", username, realm.getClass().getName())); } } else { if (log.isDebugEnabled()) { log.debug(sm.getString("combinedRealm.authSuccess", username, realm.getClass().getName())); } break; } } return authenticatedUser; }
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) { for(Realm realm : realms) { // Set the realmPath for JMX naming if (realm instanceof RealmBase) { ((RealmBase) realm).setRealmPath( getRealmPath() + "/realm" + realms.indexOf(realm)); } // Set the container for sub-realms. Mainly so logging works. realm.setContainer(container); } super.setContainer(container); }
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 { // Start 'sub-realms' then this one Iterator<Realm> iter = realms.iterator(); while (iter.hasNext()) { Realm realm = iter.next(); if (realm instanceof Lifecycle) { try { ((Lifecycle) realm).start(); } catch (LifecycleException e) { // If realm doesn't start can't authenticate against it iter.remove(); log.error(sm.getString("combinedRealm.realmStartFail", realm.getClass().getName()), e); } } } super.startInternal(); }
Gracefully terminate the active use of the public methods of this component and implement the requirements of LifecycleBase.stopInternal().
Throws:
/** * 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 { // Stop this realm, then the sub-realms (reverse order to start) super.stopInternal(); for (Realm realm : realms) { if (realm instanceof Lifecycle) { ((Lifecycle) realm).stop(); } } }
Ensure child Realms are destroyed when this Realm is destroyed.
/** * Ensure child Realms are destroyed when this Realm is destroyed. */
@Override protected void destroyInternal() throws LifecycleException { for (Realm realm : realms) { if (realm instanceof Lifecycle) { ((Lifecycle) realm).destroy(); } } super.destroyInternal(); }
Delegate the backgroundProcess call to all sub-realms.
/** * Delegate the backgroundProcess call to all sub-realms. */
@Override public void backgroundProcess() { super.backgroundProcess(); for (Realm r : realms) { r.backgroundProcess(); } }
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) { Principal authenticatedUser = null; String username = null; if (certs != null && certs.length >0) { username = certs[0].getSubjectDN().getName(); } for (Realm realm : realms) { if (log.isDebugEnabled()) { log.debug(sm.getString("combinedRealm.authStart", username, realm.getClass().getName())); } authenticatedUser = realm.authenticate(certs); if (authenticatedUser == null) { if (log.isDebugEnabled()) { log.debug(sm.getString("combinedRealm.authFail", username, realm.getClass().getName())); } } else { if (log.isDebugEnabled()) { log.debug(sm.getString("combinedRealm.authSuccess", username, realm.getClass().getName())); } break; } } return authenticatedUser; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public Principal authenticate(GSSContext gssContext, boolean storeCred) { if (gssContext.isEstablished()) { Principal authenticatedUser = null; GSSName gssName = null; try { gssName = gssContext.getSrcName(); } catch (GSSException e) { log.warn(sm.getString("realmBase.gssNameFail"), e); return null; } for (Realm realm : realms) { if (log.isDebugEnabled()) { log.debug(sm.getString("combinedRealm.authStart", gssName, realm.getClass().getName())); } authenticatedUser = realm.authenticate(gssContext, storeCred); if (authenticatedUser == null) { if (log.isDebugEnabled()) { log.debug(sm.getString("combinedRealm.authFail", gssName, realm.getClass().getName())); } } else { if (log.isDebugEnabled()) { log.debug(sm.getString("combinedRealm.authSuccess", gssName, realm.getClass().getName())); } break; } } return authenticatedUser; } // Fail in all other cases return null; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public Principal authenticate(GSSName gssName, GSSCredential gssCredential) { Principal authenticatedUser = null; for (Realm realm : realms) { if (log.isDebugEnabled()) { log.debug(sm.getString("combinedRealm.authStart", gssName, realm.getClass().getName())); } authenticatedUser = realm.authenticate(gssName, gssCredential); if (authenticatedUser == null) { if (log.isDebugEnabled()) { log.debug(sm.getString("combinedRealm.authFail", gssName, realm.getClass().getName())); } } else { if (log.isDebugEnabled()) { log.debug(sm.getString("combinedRealm.authSuccess", gssName, realm.getClass().getName())); } break; } } return authenticatedUser; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public boolean hasRole(Wrapper wrapper, Principal principal, String role) { for (Realm realm : realms) { if (realm.hasRole(wrapper, principal, role)) { return true; } } return false; } @Override protected String getPassword(String username) { // This method should never be called // Stack trace will show where this was called from UnsupportedOperationException uoe = new UnsupportedOperationException( sm.getString("combinedRealm.getPassword")); log.error(sm.getString("combinedRealm.unexpectedMethod"), uoe); throw uoe; } @Override protected Principal getPrincipal(String username) { // This method should never be called // Stack trace will show where this was called from UnsupportedOperationException uoe = new UnsupportedOperationException( sm.getString("combinedRealm.getPrincipal")); log.error(sm.getString("combinedRealm.unexpectedMethod"), uoe); throw uoe; } @Override public boolean isAvailable() { for (Realm realm : realms) { if (!realm.isAvailable()) { return false; } } return true; } @Override public void setCredentialHandler(CredentialHandler credentialHandler) { // This is unusual for a CombinedRealm as it does not use // CredentialHandlers. It might be a mis-configuration so warn the user. log.warn(sm.getString("combinedRealm.setCredentialHandler")); super.setCredentialHandler(credentialHandler); } }