/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2010, 2012 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.enterprise.connectors.work.context;
import com.sun.enterprise.security.SecurityContext;
import com.sun.enterprise.connectors.work.LogFacade;
import org.glassfish.logging.annotation.LogMessageInfo;
import org.glassfish.security.common.Group;
import org.glassfish.security.common.PrincipalImpl;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.message.callback.CallerPrincipalCallback;
import javax.security.auth.message.callback.GroupPrincipalCallback;
import javax.security.auth.message.callback.PasswordValidationCallback;
import javax.security.auth.Subject;
import java.io.IOException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.security.Principal;
/**
* Connector callback handler to intercept the callbacks provided by the work instance
* in order to map the security credentials between container and EIS domain
*
* @author Jagadish Ramu
* @since GlassFish v3
*/
//TODO V3 need contract based handlers for individual callbacks ?
public class ConnectorCallbackHandler implements CallbackHandler {
private static final Logger logger = LogFacade.getLogger();
public static final List<String> supportedCallbacks = new ArrayList<String>();
static {
supportedCallbacks.add(GroupPrincipalCallback.class.getName());
supportedCallbacks.add(CallerPrincipalCallback.class.getName());
}
private CallbackHandler handler;
private boolean needMapping;
private Map securityMap;
private Subject executionSubject;
public ConnectorCallbackHandler(Subject executionSubject, CallbackHandler handler, Map securityMap) {
this.handler = handler;
if (securityMap != null && securityMap.size() > 0) {
needMapping = true;
if(logger.isLoggable(Level.FINEST)){
logger.finest("translation required for security info ");
}
} else {
if(logger.isLoggable(Level.FINEST)){
logger.finest("no translation required for security info ");
}
}
this.executionSubject = executionSubject;
this.securityMap = securityMap;
}
@LogMessageInfo(
message = "Unsupported callback {0} during credential mapping.",
comment = "Unsupported callback class.",
level = "WARNING",
cause = "Resource adapter has used a callback that is not supported by application server.",
action = "Check whether the callback in question is supported by application server.",
publish = true)
private static final String RAR_UNSUPPORT_CALLBACK = "AS-RAR-05012";
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
Callback[] mappedCallbacks = callbacks;
if (callbacks != null) {
List<Callback> asCallbacks = new ArrayList<Callback>();
boolean hasCallerPrincipalCallback = hasCallerPrincipalCallback(callbacks);
if (needMapping) {
for (Callback callback : callbacks) {
boolean callbackSupported = false;
for (String supportedCallback : supportedCallbacks) {
try {
//TODO V3 what if there is a callback impl that implements multiple callbacks ?
if (Class.forName(supportedCallback).isAssignableFrom(callback.getClass())) {
callbackSupported = true;
asCallbacks.add(handleSupportedCallback(callback));
}
} catch (ClassNotFoundException cnfe) {
if(logger.isLoggable(Level.FINEST)){
logger.log(Level.FINEST, "class not found", cnfe);
}
}
}
if (!callbackSupported) {
UnsupportedCallbackException uce = new UnsupportedCallbackException(callback);
logger.log(Level.WARNING, RAR_UNSUPPORT_CALLBACK, new Object[]{callback.getClass().getName(), uce});
throw uce;
}
}
mappedCallbacks = new Callback[asCallbacks.size()];
for (int i = 0; i < asCallbacks.size(); i++) {
mappedCallbacks[i] = asCallbacks.get(i);
}
}
//TODO V3 what happens to multiple callbacks ?
handler.handle(mappedCallbacks);
processResults(mappedCallbacks, hasCallerPrincipalCallback);
}
}
private boolean hasCallerPrincipalCallback(Callback[] callbacks) {
if (callbacks != null) {
for (Callback c : callbacks) {
if (c instanceof CallerPrincipalCallback) {
return true;
}
}
}
return false;
}
private void processResults(Callback[] mappedCallbacks, boolean hasCallerPrincipalCallback) {
if (mappedCallbacks != null) {
Subject s = new Subject();
// Handle Single Principal as the caller identity
if (!hasCallerPrincipalCallback) {
Set<Principal> principals = executionSubject.getPrincipals();
if (principals != null && principals.size() == 1) {
//process if there is only one principal
for (Principal p : principals) {
Principal mappedPrincipal = null;
if (needMapping) {
mappedPrincipal = getMappedPrincipal(p, null);
} else {
mappedPrincipal = p;
}
if (mappedPrincipal != null) {
s.getPrincipals().add(mappedPrincipal);
}
}
s.getPublicCredentials().addAll(executionSubject.getPublicCredentials());
s.getPrivateCredentials().addAll(executionSubject.getPrivateCredentials());
}
}
//TODO V3 what happens for Public/Private Credentials of Mapped case (Case II)
for (Callback callback : mappedCallbacks) {
if (callback instanceof CallerPrincipalCallback) {
CallerPrincipalCallback cpc = (CallerPrincipalCallback) callback;
s.getPrincipals().addAll(cpc.getSubject().getPrincipals());
s.getPublicCredentials().addAll(cpc.getSubject().getPublicCredentials());
s.getPrivateCredentials().addAll(cpc.getSubject().getPrivateCredentials());
} else if (callback instanceof GroupPrincipalCallback) {
GroupPrincipalCallback gpc = (GroupPrincipalCallback) callback;
s.getPrincipals().addAll(gpc.getSubject().getPrincipals());
s.getPublicCredentials().addAll(gpc.getSubject().getPublicCredentials());
s.getPrivateCredentials().addAll(gpc.getSubject().getPrivateCredentials());
} else if (callback instanceof PasswordValidationCallback) {
PasswordValidationCallback pvc = (PasswordValidationCallback) callback;
s.getPrincipals().addAll(pvc.getSubject().getPrincipals());
s.getPublicCredentials().addAll(pvc.getSubject().getPublicCredentials());
s.getPrivateCredentials().addAll(pvc.getSubject().getPrivateCredentials());
}
}
SecurityContext.setCurrent(new SecurityContext(s));
}
}
private Callback handleSupportedCallback(Callback callback) throws UnsupportedCallbackException {
/* TODO V3 need to merge the principals/maps after calling all the callbacks and then
TODO V3 set the security context ? */
if (callback instanceof CallerPrincipalCallback) {
return handleCallerPrincipalCallbackWithMapping((CallerPrincipalCallback) callback);
} else if (callback instanceof GroupPrincipalCallback) {
return handleGroupPrincipalCallbackWithMapping((GroupPrincipalCallback) callback);
} else {
throw new UnsupportedCallbackException(callback);
}
}
private Callback handleGroupPrincipalCallbackWithMapping(GroupPrincipalCallback gpc) {
String[] groups = gpc.getGroups();
List<String> asGroupNames = new ArrayList<String>();
for (String groupName : groups) {
Group mappedGroup = (Group) securityMap.get(new Group(groupName));
if (mappedGroup != null) {
if(logger.isLoggable(Level.FINEST)){
logger.finest("got mapped group as [" + groupName + "] for eis-group [" + mappedGroup.getName() + "]");
}
asGroupNames.add(mappedGroup.getName());
}
}
String[] asGroupsString = new String[asGroupNames.size()];
for (int i = 0; i < asGroupNames.size(); i++) {
asGroupsString[i] = asGroupNames.get(i);
}
return new GroupPrincipalCallback(gpc.getSubject(), asGroupsString);
//SecurityContext.setCurrent(new SecurityContext(gpc.getSubject()));
}
public Callback handleCallerPrincipalCallbackWithMapping(CallerPrincipalCallback cpc) {
CallerPrincipalCallback asCPC;
Principal eisPrincipal = cpc.getPrincipal();
String eisName = cpc.getName();
Principal asPrincipal = getMappedPrincipal(eisPrincipal, eisName);
asCPC = new CallerPrincipalCallback(cpc.getSubject(), asPrincipal);
return asCPC;
/*
Set<Principal> principals = cpc.getSubject().getPrincipals();
for (Principal p : principals) {
Principal mappedPrincipal = (Principal) securityMap.get(p);
if (mappedPrincipal != null) {
DistinguishedPrincipalCredential dpc = new DistinguishedPrincipalCredential(mappedPrincipal);
cpc.getSubject().getPublicCredentials().add(dpc);
}
}
SecurityContext.setCurrent(new SecurityContext(cpc.getSubject()));
*/
}
private Principal getMappedPrincipal(Principal eisPrincipal, String eisName) {
Principal asPrincipal = null;
if (eisPrincipal != null) {
asPrincipal = (PrincipalImpl) securityMap.get(eisPrincipal);
if(logger.isLoggable(Level.FINEST)){
logger.finest("got mapped principal as [" + asPrincipal + "] for eis-group [" + eisPrincipal.getName() + "]");
}
} else if (eisName != null) {
asPrincipal = ((PrincipalImpl) securityMap.get(new PrincipalImpl(eisName)));
if(logger.isLoggable(Level.FINEST)){
logger.finest("got mapped principal as [" + asPrincipal + "] for eis-group [" + eisName + "]");
}
}
return asPrincipal;
}
}