/* * 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.Lifecycle; import org.apache.catalina.LifecycleException; import org.apache.catalina.Realm; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.ietf.jgss.GSSContext; 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. */ public class CombinedRealm extends RealmBase { private static final Log log = LogFactory.getLog(CombinedRealm.class); /** * 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. * @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())); } } /** * @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; } /** * @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 <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 <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 <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. * * @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 * {@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 * {@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. */ @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. */ @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 <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} */ @Override public Principal authenticate(GSSContext gssContext, boolean storeCreds) { if (gssContext.isEstablished()) { Principal authenticatedUser = null; String username = null; GSSName name = null; try { name = gssContext.getSrcName(); } catch (GSSException e) { log.warn(sm.getString("realmBase.gssNameFail"), e); return null; } username = name.toString(); for (Realm realm : realms) { if (log.isDebugEnabled()) { log.debug(sm.getString("combinedRealm.authStart", username, realm.getClass().getName())); } authenticatedUser = realm.authenticate(gssContext, storeCreds); 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; } // Fail in all other cases return null; } @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; } }