CombinedRealm.java
/*
* 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 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]);
}
@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 = null;
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(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);
}
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;
}
}
}