/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2012-2013 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. */ // Portions Copyright [2016] [Payara Foundation] package org.glassfish.security.services.impl; import java.io.IOException; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.logging.Logger; import javax.inject.Inject; import javax.inject.Singleton; import javax.security.auth.Subject; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.login.AppConfigurationEntry; import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag; import javax.security.auth.login.Configuration; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; import org.glassfish.hk2.api.PostConstruct; import org.glassfish.hk2.api.ServiceLocator; import org.glassfish.internal.api.Globals; import org.glassfish.internal.api.ServerContext; import org.glassfish.security.services.api.authentication.AuthenticationService; import org.glassfish.security.services.api.authentication.ImpersonationService; import org.glassfish.security.services.config.LoginModuleConfig; import org.glassfish.security.services.config.SecurityConfiguration; import org.glassfish.security.services.config.SecurityProvider; import org.glassfish.security.services.config.SecurityProviderConfig; import org.jvnet.hk2.annotations.Service; import org.glassfish.security.services.common.Secure; import com.sun.enterprise.config.serverbeans.Domain; import com.sun.enterprise.security.auth.login.common.PasswordCredential; import com.sun.enterprise.security.auth.realm.RealmsManager; import com.sun.enterprise.security.common.AppservAccessController; /** * The Authentication Service Implementation. * * Use JAAS LoginContext with the LoginModule(s) specified by the service configuration. */ @Service @Singleton @Secure(accessPermissionName = "security/service/authentication") public class AuthenticationServiceImpl implements AuthenticationService, PostConstruct { @Inject private Domain domain; @Inject ServerContext serverContext; @Inject private ServiceLocator locator; @Inject private ImpersonationService impersonationService; private static final Logger LOG = Logger.getLogger(AuthenticationServiceImpl.class.getName()); // Authentication Service Configuration Information private String name = null; private String realmName = null; private Configuration configuration = null; private boolean usePasswordCredential = false; private org.glassfish.security.services.config.AuthenticationService config = null; /** * Initialize the Authentication Service configuration. * * Create the JAAS Configuration using the specified LoginModule configurations */ @Override public void initialize(SecurityConfiguration securityServiceConfiguration) { // org.glassfish.security.services.config.AuthenticationService as = (org.glassfish.security.services.config.AuthenticationService) securityServiceConfiguration; // LOG.info("*** AuthenticationServiceImpl auth svc file realm provider module class: "); // for (SecurityProvider sp : as.getSecurityProviders()) { // LOG.info(" *** Provider name/type" + sp.getName() + "/" + sp.getType()); // if (sp.getSecurityProviderConfig() == null) { // LOG.info(" *** getSecurityProviderConfig returned null"); // } else { // for (SecurityProviderConfig spc : sp.getSecurityProviderConfig()) { // LOG.info(" *** " + spc.getName()); // if (sp.getType().equals("LoginModule")) { // LoginModuleConfig lmc = (LoginModuleConfig) spc; // LOG.info(" *** LoginModule config: class is " + lmc.getModuleClass()); // } // } // } // } config = (org.glassfish.security.services.config.AuthenticationService) securityServiceConfiguration; if (config == null) return; // JAAS LoginContext Name name = config.getName(); // Determine if handling Realm password credential usePasswordCredential = Boolean.valueOf(config.getUsePasswordCredential()); // Build JAAS Configuration based on the individual LoginModuleConfig settings List<SecurityProvider> providers = config.getSecurityProviders(); if (providers != null) { ArrayList<AppConfigurationEntry> lmEntries = new ArrayList<AppConfigurationEntry>(); for (SecurityProvider provider : providers) { // If the provider is a LoginModule look for the LoginModuleConfig if ("LoginModule".equalsIgnoreCase(provider.getType())) { List<SecurityProviderConfig> providerConfig = provider.getSecurityProviderConfig(); if ((providerConfig != null) && (!providerConfig.isEmpty())) { // Create the JAAS AppConfigurationEntry from the LoginModule settings LoginModuleConfig lmConfig = (LoginModuleConfig) providerConfig.get(0); Map<String, ?> lmOptions = lmConfig.getModuleOptions(); lmEntries.add(new AppConfigurationEntry(lmConfig.getModuleClass(), getLoginModuleControlFlag(lmConfig.getControlFlag()),lmOptions)); // Obtain the Realm name for password credential from the LoginModule options // Use the first LoginModule with auth-realm (i.e. unable to stack Realms) if (usePasswordCredential && (realmName == null)) { String authRealm = (String) lmOptions.get("auth-realm"); if ((authRealm != null) && (!authRealm.isEmpty())) realmName = authRealm; } } } } if (!lmEntries.isEmpty()) configuration = new AuthenticationJaasConfiguration(name,lmEntries); } // If required, initialize the currently configured Realm instances // TODO - Reconcile initialization with SecurityLifeCycle if (usePasswordCredential && (realmName != null)) { RealmsManager realmsManager = locator.getService(RealmsManager.class); realmsManager.createRealms(); } } @Override public Subject login(String username, char[] password, Subject subject) throws LoginException { CallbackHandler cbh = new AuthenticationCallbackHandler(username, password); return loginEx(cbh, subject); } @Override public Subject login(CallbackHandler cbh, Subject subject) throws LoginException { if (cbh == null) throw new LoginException("AuthenticationService: JAAS CallbackHandler not supplied"); // TODO - Wrap CallbackHandler to obtain name for auditing return loginEx(cbh, subject); } private Subject loginEx(CallbackHandler handler, Subject subject) throws LoginException { // Use the supplied Subject or create a new Subject Subject _subject = subject; if (_subject == null) _subject = new Subject(); ClassLoader tcl = null; boolean restoreTcl = false; try { // Unable to login without a JAAS Configuration instance // TODO - Dynamic configuration handling? if (configuration == null) { throw new UnsupportedOperationException( "JAAS Configuration setup incomplete, unable to perform login"); } // Setup the PasswordCredential for the Realm LoginModules when configured if (usePasswordCredential) { setupPasswordCredential(_subject, handler); } // When needed, setup the Context ClassLoader so JAAS can load the LoginModule(s) tcl = (ClassLoader) AppservAccessController.doPrivileged(new PrivilegedAction<ClassLoader>() { public ClassLoader run() { return Thread.currentThread().getContextClassLoader(); } }); final ClassLoader ccl = serverContext.getCommonClassLoader(); if (!ccl.equals(tcl)) { AppservAccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { Thread.currentThread().setContextClassLoader(ccl); return null; } }); restoreTcl = true; } // Perform the login via the JAAS LoginContext LoginContext context = new LoginContext(name, _subject, handler, configuration); context.login(); } catch (Exception exc) { // TODO - Address Audit/Log/Debug handling options if (exc instanceof LoginException) throw (LoginException) exc; throw (LoginException) new LoginException( "AuthenticationService: "+ exc.getMessage()).initCause(exc); } finally { if (restoreTcl) { final ClassLoader cl = tcl; AppservAccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { Thread.currentThread().setContextClassLoader(cl); return null; } }); } } // Return the Subject that was logged in return _subject; } @Override public Subject impersonate(String user, String[] groups, Subject subject, boolean virtual) throws LoginException { return impersonationService.impersonate(user, groups, subject, virtual); } /** * Handle lookup of authentication service configuration and initialization. * If no service is configured the service run-time will throw exceptions. * * Addresses alternate configuration handling until adopt @Proxiable support. */ @Override public void postConstruct() { /* * Realm-related classes uses Globals (they are not services) * so make sure it is set up. */ if (Globals.getDefaultBaseServiceLocator() == null) { Globals.setDefaultHabitat(locator); } // TODO - Dynamic configuration changes? initialize(AuthenticationServiceFactory.getAuthenticationServiceConfiguration(domain)); } /** * A PasswordCredential object is needed when using the existing Realm LoginModules. * * Unless the CallbackHandler is from the AuthenticationService obtain the name * and password from the supplied JAAS CallbackHandler directly. Establishing the * PasswordCredential in the Subject is determined by service configuration. * * @throws LoginException when unable to obtain data from the CallbackHandler */ private void setupPasswordCredential(Subject subject, CallbackHandler callbackHandler) throws LoginException { String username = null; char[] password = null; // Obtain the username and password for the PasswordCredential if (callbackHandler instanceof AuthenticationCallbackHandler) { username = ((AuthenticationCallbackHandler) callbackHandler).getUsername(); password = ((AuthenticationCallbackHandler) callbackHandler).getPassword(); } else { // Use the supplied callback handler to obtain the PasswordCredential information // TODO - How does this impact Audit ability to get name? Callback[] callbacks = new Callback[2]; callbacks[0] = new NameCallback("username: "); callbacks[1] = new PasswordCallback("password: ", false); try { callbackHandler.handle(callbacks); username = ((NameCallback) callbacks[0]).getName(); password = ((PasswordCallback) callbacks[1]).getPassword(); } catch (IOException ioe) { throw (LoginException) new LoginException("AuthenticationService unable to create PasswordCredential: "+ ioe.getMessage()).initCause(ioe); } catch (UnsupportedCallbackException uce) { throw (LoginException) new LoginException("AuthenticationService unable to create PasswordCredential: "+ uce.getMessage()).initCause(uce); } } // Add the PasswordCredential to the Subject final Subject s = subject; final PasswordCredential pc = new PasswordCredential(username, password, realmName); AppservAccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { s.getPrivateCredentials().add(pc); return null; } }); } /** * Convert the String setting to the JAAS LoginModuleControlFlag. * An unknown or null flag value is treated as LoginModuleControlFlag.REQUIRED. */ private LoginModuleControlFlag getLoginModuleControlFlag(String controlFlag) { LoginModuleControlFlag flag = LoginModuleControlFlag.REQUIRED; // TODO - Handle invalid control flag? if (controlFlag != null) { if ("required".equalsIgnoreCase(controlFlag)) return flag; // Check additional flag types if ("sufficient".equalsIgnoreCase(controlFlag)) flag = LoginModuleControlFlag.SUFFICIENT; else if ("optional".equalsIgnoreCase(controlFlag)) flag = LoginModuleControlFlag.OPTIONAL; else if ("requisite".equalsIgnoreCase(controlFlag)) flag = LoginModuleControlFlag.REQUISITE; } return flag; } /** * The Authentication Service CallbackHandler implementation. * * Facilitates use of the standard JAAS NameCallback and PasswordCallback. */ private static class AuthenticationCallbackHandler implements CallbackHandler { private String user; private char[] pass; public AuthenticationCallbackHandler(String username, char[] password) { user = username; pass = password; } protected String getUsername() { return user; } protected char[] getPassword() { return pass; } @Override public void handle(Callback[] callbacks) throws UnsupportedCallbackException { for (int i = 0; i < callbacks.length; i++) { if (callbacks[i] instanceof NameCallback) ((NameCallback) callbacks[i]).setName(user); else if (callbacks[i] instanceof PasswordCallback) ((PasswordCallback) callbacks[i]).setPassword(pass); else // TODO - Have configuration setting for throwing exception throw new UnsupportedCallbackException(callbacks[i], "AuthenticationCallbackHandler: Unrecognized Callback " + callbacks[i].getClass().getName()); } } } /** * The Authentication Service JAAS Configuration implementation. * * Facilitates the use of JAAS LoginContext with Authentication Service LoginModule configuration. */ private static class AuthenticationJaasConfiguration extends Configuration { private String configurationName; private AppConfigurationEntry[] lmEntries; /** * Create the JAAS Configuration instance used by the Authentication Service. */ private AuthenticationJaasConfiguration(String name, ArrayList<AppConfigurationEntry> entries) { configurationName = name; lmEntries = entries.toArray(new AppConfigurationEntry[entries.size()]); } /** * Get the LoginModule(s) specified by the Authentication Service configuration. */ @Override public AppConfigurationEntry[] getAppConfigurationEntry(String name) { if (configurationName.equals(name)) return lmEntries; else return null; } } }