Skip to content

Instantly share code, notes, and snippets.

@roanbester
Last active March 27, 2018 06:57
Show Gist options
  • Save roanbester/338d81350f4b427f320c to your computer and use it in GitHub Desktop.
Save roanbester/338d81350f4b427f320c to your computer and use it in GitHub Desktop.
Testing post-login functionality for Websphere Application Server
package thecodingglass.testing.postloginsamples;
import static org.unitils.reflectionassert.ReflectionAssert.assertLenientEquals;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
/**
* Shows how you can grab/verify that your stuff is populated on the Subject. In the real world the unit tests will test some unit of code
* that internally interrogates the Subject.
*/
public class PostLoginSampleTests {
@Test
public void verifyPrivateCredentialsAreBlank() {
// log in using a default user
new LoggedinTestContext().login();
// WSSubject should return correct value
assertNotNull(WSSubject.getCallerSubject()));
assertNotNull(WSSubject.getRunAsSubject()));
assertEquals(WSSubject.getCallerPrincipal(), LoggedinTestContext.DEFAULT_USERNAME);
// you could test some sample code that needs a Subject and specific private credentials here.
}
@Test
public void privateCredentialsMustBePopulatedOnSubject {
LoggedinTestContext loggedinContext = new LoggedinTestContext();
Map<String, String> testCredentials = loggedinContext.createDefaultPrivateCredentials();
loggedinContext.withCustomCredentials(testCredentials, LoggedinTestContext.DEFAULT_USERNAME).login();
// you can also use loggedinContext.withDefaultCredentials().login(); if you're OK with the defaults.
Set<Object> privateCredentialSet = WSSubject.getCallerSubject().getPrivateCredentials();
Map<String, String> myPrivateCredentials = (Map<String, String>) privateCredentialSet.get(0);
assertLenientEquals(testCredentials, myPrivateCredentials);
}
}
package thecodingglass.testing.utils;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.security.Principal;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.CredentialExpiredException;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import momentum.retail.audit.messages.LoggedinAuditMessage.PrivateCredentialKey;
import com.ibm.websphere.security.WSSecurityException;
import com.ibm.websphere.security.auth.CredentialDestroyedException;
import com.ibm.websphere.security.auth.WSSubject;
import com.ibm.websphere.security.cred.WSCredential;
import com.ibm.ws.security.core.ContextManagerFactory;
/**
* Creates a LoggedinTestContext where you can simulate user login.
*
* Some sample usage:
* <ul>
*
* <li><code>new LoggedinTestContext().login(); // logs you in with no username or private credentials</code>
* <li><code>new LoggedinTestContext().withDefaultCredentials().login(); //logs you in with 'derek' username and example private credentials as the SalesLoginModule will provide.</code>
* <li>
* <code>
* LoggedinTestContext loggedinContext = new LoggedinTestContext();
* Map<String, String> defaultCredentials = loggedinContext.createDefaultPrivateCredentials(); // modify or add to defaultCredentials your own values
* loggedinContext.withCustomCredentials(defaultCredentials, "derek").login(); // log in with your modified credentials
*
* </code>
* <li> <code>new LoggedinTestContext().withCustomCredentials(<your own Map here>, <own username>).login();// log in with your own stuff.</code>
* </ul>
*
* You can then expect the following to be valid:
* <ul>
* <li>WSSubject.getCallerPrincipal() returns your username;
* <li>WSSubject.getCallerSubject() gets you a populated Subject;
* <li>WSWSubject.getRunAsSubject() gets you the same subject.
* </ul>
* @author RBester
*
*/
public class LoggedinTestContext {
public static final String DEFAULT_USERNAME = "joedirt";
public static final String DEFAULT_IP_ADDRESS = "10.1.1.1";
public static final String DEFAULT_UNIQUE_ID = "SF_GUID_123";
public static final String DEFAULT_IMPERSONATING_USER = "no-one";
public static final String DEFAULT_IMPERSONATING_USER_GUID = "no-one123";
public static final String DEFAULT_SESSION_ID = "joedirt_session_id";
private static final String LOGIN_MODULE_NAME = "thecodingglass.testing.utils.MockLoginModule";// The Login process creates an instance of this via reflection.
private LoginContext context;
private Set<Object> privateCredentials = new HashSet<Object>();
private Set<Object> publicCredentials = new HashSet<Object>();
private Subject subject = null;
public LoggedinTestContext() {
}
/**
* Will add default private credentials to the Subject (see static Strings in LoggedinTestContext)
* @return Returns this instance for chaining.
*/
public LoggedinTestContext withDefaultCredentials() {
return withCustomCredentials(createDefaultPrivateCredentials(), DEFAULT_USERNAME);
}
/**
* Will add your custom private credentials (as Map) and username to the Subject.
* @param privateCredentialMap ustom private credentials. This can be accessed from the Subject via the subject.getPrivateCredentials (returns Set) grabbing the first object you can find.
* @param username Your custom username.
* @return Returns this instance for chaining.
*/
public LoggedinTestContext withCustomCredentials(Map<String, String> privateCredentialMap, String username) {
WSCredential userCredential = mock(WSCredential.class);
try {
when(userCredential.getSecurityName()).thenReturn(username);
} catch (CredentialExpiredException e) {
e.printStackTrace();
} catch (CredentialDestroyedException e) {
e.printStackTrace();
}
privateCredentials.add(privateCredentialMap);
publicCredentials.add(userCredential);
return this;
}
/**
* Expose creation of the default private credentials if you want to use this as basis for your test expectations or custom input.
* @return Returns a Map with default credentials.
*/
public Map<String, String> createDefaultPrivateCredentials() {
Map<String, String> credentialMap = new HashMap<String, String>();
credentialMap.put(PrivateCredentialKey.IMPERSONATING_USERNAME.name(), DEFAULT_IMPERSONATING_USER);
credentialMap.put(PrivateCredentialKey.IMPERSONATING_USERGUID.name(), DEFAULT_IMPERSONATING_USER_GUID);
credentialMap.put(PrivateCredentialKey.IP_ADDRESS.name(), DEFAULT_IP_ADDRESS);
credentialMap.put(PrivateCredentialKey.UNIQUE_ID.name(), DEFAULT_UNIQUE_ID);
credentialMap.put(PrivateCredentialKey.SESSION_ID.name(), DEFAULT_UNIQUE_ID);
return credentialMap;
}
/**
* Trigger the login process.
*/
public void login() {
try {
init();
context.login();
} catch (LoginException e) {
e.printStackTrace();
}
}
private void init() throws LoginException {
CallbackHandler handler = mock(CallbackHandler.class);
subject = createSubject();
try {
// the business-end: create LoginContext and configure it with modules.
context = new LoginContext("AuditLoggerTest", subject, handler, new Configuration() {
@Override
public AppConfigurationEntry[] getAppConfigurationEntry(String arg0) {
AppConfigurationEntry[] toReturn = new AppConfigurationEntry[] {
// Our login module. You can add more here.
new AppConfigurationEntry(LOGIN_MODULE_NAME, AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, new HashMap<String, Object>())
};
return toReturn;
}
});
} catch (LoginException e) {
e.printStackTrace();
}
}
private Subject createSubject() {
Subject subject = new Subject(true, new HashSet<Principal>(), publicCredentials, privateCredentials);
try {
ContextManagerFactory.getInstance().setCallerSubject(subject);
WSSubject.setRunAsSubject(subject);
} catch (WSSecurityException e) {
e.printStackTrace();
}
return subject;
}
}
package thecodingglass.testing.utils;
import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
/**
* A JAAS Module that you can use to test post-login situations. The LoginModule is allowed to modify credentials etc.
* Refer to the LoggedinTestContext: this class is instantiated by the javax.security.auth.login.LoginContext via reflection.
*/
public class MockLoginModule implements LoginModule {
/**
* Simply prints out the provided private credentials, but a login module can also add credentials. These will then be available
* on the Subject.
* @param subject The JAAS subject in the process of logging in.
* @param callbackHandler JAAS chain callback handler (see the JEE spec for more on this).
* @param sharedState State shared between login modules.
* @param options Options get passed in to the LoginModule via the container on initialization.
*/
@Override
public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {
if (subject.getPrivateCredentials() != null && !subject.getPrivateCredentials().isEmpty()) {
Map<String, ? extends Object> privateCredentials = (Map<String, ? extends Object>) subject.getPrivateCredentials().toArray()[0];
System.out.println("MockLoginModule: Subject private credentials: " + privateCredentials.toString());
} else {
System.out.println("MockLoginModule: No private credentials.");
}
}
/*
* Simply allow login to proceed. You can return false if login shoud fail based on what happened on initialize.
*/
@Override
public boolean login() throws LoginException {
return true;
}
/**
* All modules executed with true; commit.
*/
@Override
public boolean commit() throws LoginException {
return true;
}
/*
* Rollback as a commit or something else failed.
*/
@Override
public boolean abort() throws LoginException {
return true;
}
/*
* On the logout leg. True if logout was OK, false otherwise.
*/
@Override
public boolean logout() throws LoginException {
return true;
}
}
@roanbester
Copy link
Author

What is this?

These code samples show you how you can unit test Java code that uses JAAS and JEE Subject information to do its business. This is typically a very hard thing to test in Websphere Application Server-based projects.

The samples contain

  • A LoggedinTestContext with helpers to facilitate the login in out-of-container unit testing scenarios;
  • A MockLoginModule to show how to test JAAS Modules and/or add stuff to a Subject;
  • Sample unit test to illustrate the LoggedinTestContext's usage and what it can provide.

Dependencies needed

The code samples below requires you to (either in maven dependencies or elsewhere) have the following libraries available.

WAS (7+) dependencies

Replace %WAS_ROOT% with your WAS installation root, e.g. IBM/Websphere/AppServer.

  • /lib/bootstrap.jar
  • /plugins/com.ibm.ws.runtime.jar
  • /runtimes/com.ibm.ws.admin.client_.jar
  • /lib/j2ee.jar
  • /plugins/com.ibm.ws.emf.jar
  • /plugins/org.eclipse.emf.ecore.jar
  • /plugins/org.eclipse.emf.common.jar
  • /java/jre/lib/ibmcfw.jar

JUnit dependencies

Also add these testing dependencies for the samples as needed, available on mvn central:

  • org.mockito:mockito-all:1.10.16 (for some mocking goodness, optional but recommended)
  • junit:junit (basic junit 4)
  • org.powermock:powermock-module-junit4:1.6.2 (for static mocking)
  • org.powermock:powermock-api-mockito:1.6.2 (for static mocking when using mockito)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment