| /* |
| * 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<>(); |
| |
| /** |
| * Descriptive information about this Realm implementation. |
| */ |
| protected static final String name = "CombinedRealm"; |
| |
| /** |
| * 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 getName() { |
| return name; |
| } |
| |
| @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; |
| } |
| |
| } |