| /* |
| * 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.ArrayList; |
| import java.util.Iterator; |
| 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. |
| */ |
| 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 ArrayList<>(); |
| |
| /** |
| * 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()) { |
| log.debug(sm.getString("combinedRealm.addRealm", theRealm.getClass().getName(), |
| Integer.toString(realms.size()))); |
| } |
| } |
| |
| |
| /** |
| * @return the array 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 array of Realms contained by this Realm. |
| */ |
| public Realm[] getNestedRealms() { |
| return realms.toArray(new Realm[0]); |
| } |
| |
| |
| @Override |
| public Principal authenticate(String username, String clientDigest, String nonce, String nc, String cnonce, |
| String qop, String realmName, String digestA2, String algorithm) { |
| Principal authenticatedUser = null; |
| |
| for (Realm realm : realms) { |
| if (log.isTraceEnabled()) { |
| log.trace(sm.getString("combinedRealm.authStart", username, realm.getClass().getName())); |
| } |
| |
| authenticatedUser = |
| realm.authenticate(username, clientDigest, nonce, nc, cnonce, qop, realmName, digestA2, algorithm); |
| |
| if (authenticatedUser == null) { |
| if (log.isTraceEnabled()) { |
| log.trace(sm.getString("combinedRealm.authFail", username, realm.getClass().getName())); |
| } |
| } else { |
| if (log.isTraceEnabled()) { |
| log.trace(sm.getString("combinedRealm.authSuccess", username, realm.getClass().getName())); |
| } |
| break; |
| } |
| } |
| return authenticatedUser; |
| } |
| |
| |
| @Override |
| public Principal authenticate(String username) { |
| Principal authenticatedUser = null; |
| |
| for (Realm realm : realms) { |
| if (log.isTraceEnabled()) { |
| log.trace(sm.getString("combinedRealm.authStart", username, realm.getClass().getName())); |
| } |
| |
| authenticatedUser = realm.authenticate(username); |
| |
| if (authenticatedUser == null) { |
| if (log.isTraceEnabled()) { |
| log.trace(sm.getString("combinedRealm.authFail", username, realm.getClass().getName())); |
| } |
| } else { |
| if (log.isTraceEnabled()) { |
| log.trace(sm.getString("combinedRealm.authSuccess", username, realm.getClass().getName())); |
| } |
| break; |
| } |
| } |
| return authenticatedUser; |
| } |
| |
| |
| @Override |
| public Principal authenticate(String username, String credentials) { |
| Principal authenticatedUser = null; |
| |
| for (Realm realm : realms) { |
| if (log.isTraceEnabled()) { |
| log.trace(sm.getString("combinedRealm.authStart", username, realm.getClass().getName())); |
| } |
| |
| authenticatedUser = realm.authenticate(username, credentials); |
| |
| if (authenticatedUser == null) { |
| if (log.isTraceEnabled()) { |
| log.trace(sm.getString("combinedRealm.authFail", username, realm.getClass().getName())); |
| } |
| } else { |
| if (log.isTraceEnabled()) { |
| log.trace(sm.getString("combinedRealm.authSuccess", username, realm.getClass().getName())); |
| } |
| break; |
| } |
| } |
| return authenticatedUser; |
| } |
| |
| |
| @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); |
| } |
| |
| |
| @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); |
| } |
| } |
| } |
| |
| if (getCredentialHandler() == null) { |
| // Set a credential handler that will ask the nested realms so that it can |
| // be set by the context in the attributes, it won't be used directly |
| super.setCredentialHandler(new CombinedRealmCredentialHandler()); |
| } |
| super.startInternal(); |
| } |
| |
| |
| @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(); |
| } |
| } |
| |
| |
| @Override |
| public Principal authenticate(X509Certificate[] certs) { |
| Principal authenticatedUser = null; |
| String username = null; |
| if (certs != null && certs.length > 0) { |
| username = certs[0].getSubjectX500Principal().toString(); |
| } |
| |
| for (Realm realm : realms) { |
| if (log.isTraceEnabled()) { |
| log.trace(sm.getString("combinedRealm.authStart", username, realm.getClass().getName())); |
| } |
| |
| authenticatedUser = realm.authenticate(certs); |
| |
| if (authenticatedUser == null) { |
| if (log.isTraceEnabled()) { |
| log.trace(sm.getString("combinedRealm.authFail", username, realm.getClass().getName())); |
| } |
| } else { |
| if (log.isTraceEnabled()) { |
| log.trace(sm.getString("combinedRealm.authSuccess", username, realm.getClass().getName())); |
| } |
| break; |
| } |
| } |
| return authenticatedUser; |
| } |
| |
| |
| @Override |
| public Principal authenticate(GSSContext gssContext, boolean storeCred) { |
| if (gssContext.isEstablished()) { |
| Principal authenticatedUser = null; |
| GSSName gssName; |
| try { |
| gssName = gssContext.getSrcName(); |
| } catch (GSSException e) { |
| log.warn(sm.getString("realmBase.gssNameFail"), e); |
| return null; |
| } |
| |
| for (Realm realm : realms) { |
| if (log.isTraceEnabled()) { |
| log.trace(sm.getString("combinedRealm.authStart", gssName, realm.getClass().getName())); |
| } |
| |
| authenticatedUser = realm.authenticate(gssContext, storeCred); |
| |
| if (authenticatedUser == null) { |
| if (log.isTraceEnabled()) { |
| log.trace(sm.getString("combinedRealm.authFail", gssName, realm.getClass().getName())); |
| } |
| } else { |
| if (log.isTraceEnabled()) { |
| log.trace(sm.getString("combinedRealm.authSuccess", gssName, realm.getClass().getName())); |
| } |
| break; |
| } |
| } |
| return authenticatedUser; |
| } |
| |
| // Fail in all other cases |
| return null; |
| } |
| |
| |
| @Override |
| public Principal authenticate(GSSName gssName, GSSCredential gssCredential) { |
| Principal authenticatedUser = null; |
| |
| for (Realm realm : realms) { |
| if (log.isTraceEnabled()) { |
| log.trace(sm.getString("combinedRealm.authStart", gssName, realm.getClass().getName())); |
| } |
| |
| authenticatedUser = realm.authenticate(gssName, gssCredential); |
| |
| if (authenticatedUser == null) { |
| if (log.isTraceEnabled()) { |
| log.trace(sm.getString("combinedRealm.authFail", gssName, realm.getClass().getName())); |
| } |
| } else { |
| if (log.isTraceEnabled()) { |
| log.trace(sm.getString("combinedRealm.authSuccess", gssName, realm.getClass().getName())); |
| } |
| break; |
| } |
| } |
| return authenticatedUser; |
| } |
| |
| |
| @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(uoe.getMessage(), 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(uoe.getMessage(), 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); |
| } |
| |
| private class CombinedRealmCredentialHandler implements CredentialHandler { |
| |
| @Override |
| public boolean matches(String inputCredentials, String storedCredentials) { |
| for (Realm realm : realms) { |
| if (realm.getCredentialHandler().matches(inputCredentials, storedCredentials)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public String mutate(String inputCredentials) { |
| if (realms.isEmpty()) { |
| return null; |
| } |
| for (Realm realm : realms) { |
| String mutatedCredentials = realm.getCredentialHandler().mutate(inputCredentials); |
| if (mutatedCredentials != null) { |
| return mutatedCredentials; |
| } |
| } |
| return null; |
| } |
| |
| } |
| } |