/* * RHQ Management Platform * Copyright (C) 2005-2013 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ package org.rhq.enterprise.server.core.jaas; import java.io.IOException; import java.security.Principal; import java.security.acl.Group; import java.util.List; import java.util.Map; 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.LoginContext; import javax.security.auth.login.LoginException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jboss.security.SimpleGroup; import org.jboss.security.SimplePrincipal; import org.jboss.security.auth.spi.UsernamePasswordLoginModule; import org.rhq.core.util.StringUtil; /** * A login module that just delegates all work to a different security domain.<p/> * When you use container managed security (CMS), EAP 6.1 requires the security domain being * already present in standalone.xml * * With our setup we (re)-create the security domain of RHQUserSecurityDomain dynamically, * which makes CMS fail on startup and also on re-create. * * The approach of just exchanging login modules does not work correctly either (principals * keep being cached, server goes into need-reload state). * * So we now have a security domain for the CMS for the REST api that just delegates to the * RHQUserSecuritDomain. * * <pre> * <security-domain name="RHQRESTSecurityDomain" cache-type="default"> * <authentication> * <login-module code="org.rhq.enterprise.server.core.jaas.DelegatingLoginModule" flag="required"> * <module-option name="delegateTo" value="RHQUserSecurityDomain"/> * <module-option name="additionalRoles" value="rest-user"/> * </login-module> * </authentication> * </security-domain> *</pre> * * @author Heiko W. Rupp */ @SuppressWarnings("unused") public class DelegatingLoginModule extends UsernamePasswordLoginModule { private static Log LOG = LogFactory.getLog("DelegatingLoginModule"); LoginContext loginContext; private String[] usernamePassword; private Principal identity; private List<String> rolesList; private boolean debugEnabled; @Override public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) { debugEnabled = LOG.isDebugEnabled(); super.initialize(subject, callbackHandler, sharedState, options); /* This is the login context (=security domain) we want to delegate to */ String delegateTo = (String) options.get("delegateTo"); /* Comma separated list of roles that should be set for the principal */ String additionalRoles = (String) options.get("roles"); rolesList = StringUtil.explode(additionalRoles, ","); if (delegateTo ==null || delegateTo.isEmpty()) { delegateTo = "other"; LOG.warn("module-option 'delegateTo' was not set. Defaults to 'other'."); } if (debugEnabled) { LOG.debug("Delegating to " + delegateTo + " with roles " + additionalRoles); } // Now create the context for later use try { loginContext = new LoginContext(delegateTo, new DelegateCallbackHandler()); } catch (LoginException e) { LOG.warn("Initialize failed : " + e.getMessage()); } } /** * Do the actual login work - we obtain the user/password passed in and then try to * log into the delegated context. If this succeeds, we tell the super-module, * so this can do further processing (especially running the #commit() method). * * @return True on success * @throws LoginException If anything goes wrong */ @Override public boolean login() throws LoginException { try { // Get the username / password the user entered and save if for later use usernamePassword = super.getUsernameAndPassword(); // Try to log in via the delegate loginContext.login(); // Nix out the password usernamePassword[1] = null; // login was success, so we can continue identity = createIdentity(usernamePassword[0]); useFirstPass=true; // This next flag is important. Without it the principal will not be // propagated loginOk = true; if (debugEnabled) { LOG.debug("Login ok for " + usernamePassword[0]); } return true; } catch (Exception e) { if (debugEnabled) { LOG.debug("Login failed for : " + usernamePassword[0] + ": " + e.getMessage()); } loginOk = false; return false; } } @Override protected String getUsersPassword() throws LoginException { // This is not used but abstract in super. return null; } @Override protected Principal getIdentity() { return identity; } @Override protected Group[] getRoleSets() throws LoginException { SimpleGroup roles = new SimpleGroup("Roles"); for (String role : rolesList ) { roles.addMember( new SimplePrincipal(role)); } Group[] roleSets = { roles }; return roleSets; } /** * Handle the callbacks from the other security domain that we delegate to */ private class DelegateCallbackHandler implements CallbackHandler { @Override public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { if (debugEnabled) { LOG.debug("private handle callbacks"); } for (Callback cb : callbacks) { if (cb instanceof NameCallback) { NameCallback nc = (NameCallback) cb; nc.setName(usernamePassword[0]); } else if (cb instanceof PasswordCallback) { PasswordCallback pc = (PasswordCallback) cb; pc.setPassword(usernamePassword[1].toCharArray()); } else { throw new UnsupportedCallbackException(cb,"Callback " + cb + " not supported"); } } } } }