/**********************************************************************************
* $URL: https://source.sakaiproject.org/svn/providers/trunk/jldap/src/test/edu/amc/sakai/user/JLDAPDirectoryProviderTest.java $
* $Id: JLDAPDirectoryProviderTest.java 109333 2012-06-17 02:42:00Z azeckoski@unicon.net $
***********************************************************************************
*
* Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008 The Sakai Foundation
*
* Licensed under the Educational Community 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.opensource.org/licenses/ECL-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 edu.amc.sakai.user;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Stack;
import org.jmock.Mock;
import org.jmock.cglib.MockObjectTestCase;
import org.jmock.core.Invocation;
import org.jmock.core.Stub;
import org.sakaiproject.user.api.UserEdit;
import com.novell.ldap.LDAPConnection;
import com.novell.ldap.LDAPEntry;
import com.novell.ldap.LDAPException;
import com.novell.ldap.LDAPSearchResults;
/**
*
* @author dmccallum@unicon.net
*
*/
public class JLDAPDirectoryProviderTest extends MockObjectTestCase {
private JLDAPDirectoryProvider provider;
private EidValidator eidValidator;
private Mock mockEidValidator;
private LdapAttributeMapper attributeMapper;
private Mock mockAttributeMapper;
private LdapConnectionManager connManager;
private Mock mockConnManager;
private LDAPConnection conn;
private Mock mockConn;
private LDAPSearchResults searchResults;
private Mock mockSearchResults;
private LDAPEntry entry;
private Mock mockEntry;
private LdapUserData userData;
protected void setUp() {
// we need control over the LdapUserData returned from searches so
// we can do things like assign EID values. Otherwise caching
// completely blows up.
userData = new LdapUserData();
provider = new JLDAPDirectoryProvider() {
protected LdapUserData newLdapUserData() {
return userData;
}
};
mockEidValidator = mock(EidValidator.class);
eidValidator = (EidValidator)mockEidValidator.proxy();
provider.setEidValidator(eidValidator);
mockAttributeMapper = mock(LdapAttributeMapper.class);
attributeMapper = (LdapAttributeMapper)mockAttributeMapper.proxy();
provider.setLdapAttributeMapper(attributeMapper);
mockConnManager = mock(LdapConnectionManager.class);
connManager = (LdapConnectionManager)mockConnManager.proxy();
provider.setLdapConnectionManager(connManager);
mockConn = mock(LDAPConnection.class);
conn = (LDAPConnection) mockConn.proxy();
mockSearchResults = mock(LDAPSearchResults.class);
searchResults = (LDAPSearchResults) mockSearchResults.proxy();
mockEntry = mock(LDAPEntry.class);
entry = (LDAPEntry)mockEntry.proxy();
provider.setMemoryService(new TestMemoryService());
mockConnManager.expects(once()).method("setConfig").with(same(provider));
mockConnManager.expects(once()).method("init");
provider.init();
}
public void testRefusesToSearchOnInvalidEids() throws LDAPException {
final String eid = "some-eid";
mockEidValidator.expects(once()).method("isSearchableEid").with(eq(eid)).will(returnValue(false));
assertNull(provider.getUserByEid(eid, null));
}
public void testAllowsSearchesOnAnyEidIfNoValidatorConfigured() throws LDAPException {
final String eid = "some-eid";
provider.setEidValidator(null);
expectValidUserEidSearch(eid);
assertNotNull(provider.getUserByEid(eid, null));
}
public void testAllowsSearchesOnValidEids() throws LDAPException {
final String eid = "some-eid";
mockEidValidator.expects(once()).method("isSearchableEid").with(eq(eid)).will(returnValue(true));
expectValidUserEidSearch(eid);
assertNotNull(provider.getUserByEid(eid, null));
}
protected void expectValidUserEidSearch(String eid) {
final String filter = "(cn=" + eid + ")";
mockAttributeMapper.expects(once()).method("getFindUserByEidFilter").
with(eq(eid)).will(returnValue(filter));
mockConnManager.expects(once()).method("getConnection").will(returnValue(conn));
mockAttributeMapper.expects(once()).method("getSearchResultAttributes").will(returnValue(null));
mockConn.expects(once()).method("search").
// we're not interested in actually testing argument marshaling, so no with()
will(returnValue(searchResults));
mockSearchResults.expects(exactly(2)).method("hasMore").
will(onConsecutiveCalls(returnValue(true), returnValue(false)));
mockSearchResults.expects(once()).method("next").will(returnValue(entry));
userData.setEid(eid); // otherwise caching operation will blow up
mockAttributeMapper.expects(once()).method("mapLdapEntryOntoUserData").
with(same(entry), same(userData));
mockConnManager.expects(once()).method("returnConnection").with(same(conn));
}
public void testSupportsCaseSensitiveCacheKeys() {
// some "local constants"
final String EID = "some-eid";
// some additional fixture setup
provider.setCaseSensitiveCacheKeys(true);
LdapUserData cachedObject = new LdapUserData();
cachedObject.setEid(EID); // dont care about any other attribs
provider.cacheUserData(cachedObject);
// the code exercise
LdapUserData retrievedObject =
provider.getCachedUserEntry(EID);
assertSame("Should have failed to find cached objecct", cachedObject,
retrievedObject);
}
public void testSupportsCaseSensitiveCacheKeys_Negative() {
// some "local constants"
final String CACHED_EID = "some-eid";
final String MIXED_CASE_EID = "sOmE-eId";
// some additional fixture setup
provider.setCaseSensitiveCacheKeys(true);
LdapUserData cachedObject = new LdapUserData();
cachedObject.setEid(CACHED_EID); // dont care about any other attribs
provider.cacheUserData(cachedObject);
// the code exercise
LdapUserData retrievedObject =
provider.getCachedUserEntry(MIXED_CASE_EID);
assertNull("Should have failed to find cached objecct", retrievedObject);
}
public void testSupportsCaseInsensitiveCacheKeys() {
// some "local constants"
final String CACHED_EID = "some-eid";
final String MIXED_CASE_EID = "sOmE-eId";
// some additional fixture setup
provider.setCaseSensitiveCacheKeys(false);
LdapUserData cachedObject = new LdapUserData();
cachedObject.setEid(CACHED_EID); // dont care about any other attribs
provider.cacheUserData(cachedObject);
// the code exercise
LdapUserData retrievedObject =
provider.getCachedUserEntry(MIXED_CASE_EID);
assertSame("Should have ignored cache key case",
cachedObject, retrievedObject);
}
public void testSupportsCaseInsensitiveCacheKeys_Negative() {
// some "local constants"
final String CACHED_EID = "some-eid";
// some additional fixture setup
provider.setCaseSensitiveCacheKeys(false);
LdapUserData cachedObject = new LdapUserData();
cachedObject.setEid(CACHED_EID); // dont care about any other attribs
provider.cacheUserData(cachedObject);
// the code exercise
LdapUserData retrievedObject =
provider.getCachedUserEntry(CACHED_EID + "X");
assertNull("Should have ignored cache key case", retrievedObject);
}
/**
* Verifies behaviors which, when not implemented correctly, resulted in the
* bug described by <a href="http://bugs.sakaiproject.org/jira/browse/SAK-12705">SAK-12705</a>.
* Essentially, the problem is that the UDS "trusts" the EIDs returned from
* the UDP and places them in persistent and transient caches as-is, regardless
* of either component's case-sensitivity configuration. This can lead to
* orphaned user IDs in some cases. See the Jira ticket for more detail.
*/
public void testForcesEIDsToUniformCaseIfConfiguredForCaseInsensitiveCacheKeys() {
// Exercises mapUserDataOntoUserEdit() since that method should be invoked
// for all user lookup operations. Other test methods in this class
// verify that that is indeed the case.
// some "local constants"
final String MIXED_CASE_EID = "SoMe-EiD";
// toCaseInsensitiveCacheKey() behavior is directly tested elsewhere
final String UNIFORM_CASE_EID = provider.toCaseInsensitiveCacheKey(MIXED_CASE_EID);
// fixture/expectation setup
provider.setCaseSensitiveCacheKeys(false);
LdapUserData cachedUserData = new LdapUserData();
cachedUserData.setEid(MIXED_CASE_EID);
UserEdit expectedUserEdit = new UserEditStub();
expectedUserEdit.setEid(UNIFORM_CASE_EID);
UserEdit actualUserEdit = new UserEditStub();
mockAttributeMapper.expects(once()).method("mapUserDataOntoUserEdit").
with(same(cachedUserData), same(actualUserEdit)).
will(setEidOnReceivedUserEdit());
// the code exercise
provider.mapUserDataOntoUserEdit(cachedUserData, actualUserEdit);
assertEquals("Should have forced the UserEdit's EID to a consistent case",
expectedUserEdit, actualUserEdit);
}
/**
* Tests the inverse of {@link #testForcesEIDsToUniformCaseIfConfiguredForCaseInsensitiveCacheKeys()}.
*/
public void testReturnsMixedCaseEIDsIfConfiguredForCaseSensitiveCacheKeys() {
// some "local constants"
final String MIXED_CASE_EID = "SoMe-EiD";
// fixture/expectation setup
provider.setCaseSensitiveCacheKeys(true);
LdapUserData cachedUserData = new LdapUserData();
cachedUserData.setEid(MIXED_CASE_EID);
UserEdit expectedUserEdit = new UserEditStub();
expectedUserEdit.setEid(MIXED_CASE_EID);
UserEdit actualUserEdit = new UserEditStub();
mockAttributeMapper.expects(once()).method("mapUserDataOntoUserEdit").
with(same(cachedUserData), same(actualUserEdit)).
will(setEidOnReceivedUserEdit());
// the code exercise
provider.mapUserDataOntoUserEdit(cachedUserData, actualUserEdit);
assertEquals("Should not have modified the user's EID (i.e. the UserEdit should have a mixed-case EID)",
expectedUserEdit, actualUserEdit);
}
/**
* Verifies that forcing a String to a case-insensitive cache key results
* in a lower-cased String
*/
public void testToCaseInsensitiveCacheKey() {
// some "local constants"
final String MIXED_CASE_EID = "SoMe-EiD";
final String UNIFORM_CASE_EID = MIXED_CASE_EID.toLowerCase();
assertEquals("Should have forced string to lower case",
UNIFORM_CASE_EID, provider.toCaseInsensitiveCacheKey(MIXED_CASE_EID));
}
public void testGetUserDispatch() {
// special treatment of the actual test impl so it
// can be reused for other provider config tests, e.g
// testDisallowingAuthenticationStillAllowsUserLookup()
doTestGetUserDispatch(null);
}
protected void doTestGetUserDispatch(Runnable providerConfigCallback) {
final Mock mockDoGetUserByEid = mock(VarargsMethod.class);
final VarargsMethod doGetUserByEid = (VarargsMethod)mockDoGetUserByEid.proxy();
provider = new JLDAPDirectoryProvider() {
protected boolean getUserByEid(UserEdit userToUpdate, String eid, LDAPConnection conn)
throws LDAPException {
return (Boolean)doGetUserByEid.call(userToUpdate, eid, conn);
}
};
if ( providerConfigCallback != null ) {
providerConfigCallback.run();
}
UserEditStub userEdit = new UserEditStub();
userEdit.setEid("some-eid");
mockDoGetUserByEid.expects(once()).method("call").
with(eq(new Object[] {userEdit, userEdit.getEid(), null})).
will(returnValue(Boolean.TRUE));
assertTrue(provider.getUser(userEdit));
mockDoGetUserByEid.verify();
}
public void testGetUserByEidDispatch() throws LDAPException {
final Mock mockDoGetUserByEid = mock(VarargsMethod.class);
final VarargsMethod doGetUserByEid = (VarargsMethod)mockDoGetUserByEid.proxy();
final Mock mockDoMapUserDataOntoUserEdit = mock(VarargsMethod.class);
final VarargsMethod doMapUserDataOntoUserEdit = (VarargsMethod)mockDoMapUserDataOntoUserEdit.proxy();
provider = new JLDAPDirectoryProvider() {
protected LdapUserData getUserByEid(String eid, LDAPConnection conn)
throws LDAPException {
return (LdapUserData)doGetUserByEid.call(eid, conn);
}
protected void mapUserDataOntoUserEdit(LdapUserData userData, UserEdit userEdit) {
doMapUserDataOntoUserEdit.call(userData,userEdit);
}
};
UserEditStub userEdit = new UserEditStub();
userEdit.setEid("some-eid");
LdapUserData userData = new LdapUserData();
userData.setEid(userEdit.getEid());
mockDoGetUserByEid.expects(once()).method("call")
.with(eq(new Object[] { userEdit.getEid(), conn }))
.will(returnValue(userData));
// this mapUserDataOntoUserEdit() expectation is important for
// guaranteeing the validity of tests like
// testForcesEIDsToUniformCaseIfConfiguredForCaseInsensitiveCacheKeys(),
// i.e. that test is only valid b/c we ensure here that
// mapUserDataOntoUserEdit() is invoked for all user lookups via
// getUserByEid(). Other test methods ensure it is invoked in other
// code paths.
mockDoMapUserDataOntoUserEdit.expects(once()).method("call")
.with(eq(new Object[] {userData, userEdit}))
.after(mockDoGetUserByEid, "call");
assertTrue(provider.getUserByEid(userEdit, userEdit.getEid(), conn));
mockDoGetUserByEid.verify();
mockDoMapUserDataOntoUserEdit.verify();
}
/**
* Verifies the <code>getUserByEid()</code> overload which is
* just maps LDAP attributes to a cacheable bean but which does
* not map those bean attributes onto a <code>UserEdit</code>
* @throws LDAPException
*/
public void testNonUserEditMappingGetUserByEidDispatch() throws LDAPException {
final Mock mockDoGetCachedUserEntry = mock(VarargsMethod.class);
final VarargsMethod doGetCachedUserEntry = (VarargsMethod) mockDoGetCachedUserEntry.proxy();
final Mock mockDoIsSearchableEid = mock(VarargsMethod.class);
final VarargsMethod doIsSearchableEid = (VarargsMethod)mockDoIsSearchableEid.proxy();
final Mock mockDoSearchDirectoryForSingleEntry = mock(VarargsMethod.class);
final VarargsMethod doSearchDirectoryForSingleEntry = (VarargsMethod)mockDoSearchDirectoryForSingleEntry.proxy();
provider = new JLDAPDirectoryProvider() {
protected LdapUserData getCachedUserEntry(String eid) {
return (LdapUserData)doGetCachedUserEntry.call(eid);
}
protected boolean isSearchableEid(String eid) {
return (Boolean)doIsSearchableEid.call(eid);
}
protected Object searchDirectoryForSingleEntry(String filter,
LDAPConnection conn,
LdapEntryMapper mapper,
String[] searchResultPhysicalAttributeNames,
String searchBaseDn)
throws LDAPException {
return doSearchDirectoryForSingleEntry.call(filter,conn,mapper,searchResultPhysicalAttributeNames,searchBaseDn);
}
};
provider.setLdapAttributeMapper(attributeMapper);
String eid = "some-eid";
String eidFilter = "(uid=" + eid + ")";
LdapUserData userData = new LdapUserData();
userData.setEid(eid);
mockDoGetCachedUserEntry.expects(once()).method("call").
with(eq(new Object[] {eid})).will(returnValue(null)); // ? verify early return if cached value found
mockDoIsSearchableEid.expects(once()).method("call").
with(eq(new Object[] {eid})).
after(mockDoGetCachedUserEntry, "call").
will(returnValue(Boolean.TRUE));
mockAttributeMapper.expects(once()).method("getFindUserByEidFilter").
with(eq(eid)).after(mockDoIsSearchableEid, "call").
will(returnValue(eidFilter));
mockDoSearchDirectoryForSingleEntry.expects(once()).method("call").
with(eq(new Object[] {eidFilter, conn, null, null, null})).
after(mockAttributeMapper, "getFindUserByEidFilter").
will(returnValue(userData));
assertSame(userData, provider.getUserByEid(eid, conn));
mockDoGetCachedUserEntry.verify();
mockDoIsSearchableEid.verify();
mockDoSearchDirectoryForSingleEntry.verify();
}
/** Removed this test
*
* It was previously testing how many time getUsers would call over to the single ldap fetch methods
* However, at this point, with the changes, it is actually testing our ability to create a mock object
* which represents an ldap connection and an ldap search and ldap entry, which I do not think is actually
* testing anything useful. Sure, we can make a bunch of mocks, but this only proves we can make a test
* which tests some mocks. Basically, pointless waste of effort. I left the code in here in case someone does
* eventually want to complete these mocks. -AZ
*
public void testGetUsersDispatch() {
final Mock mockDoGetUserByEid = mock(VarargsMethod.class);
final VarargsMethod doGetUserByEid = (VarargsMethod)mockDoGetUserByEid.proxy();
provider = new JLDAPDirectoryProvider() {
protected boolean getUserByEid(UserEdit userToUpdate, String eid, LDAPConnection conn)
throws LDAPException {
return (Boolean) doGetUserByEid.call(userToUpdate, eid, conn);
}
};
provider.setLdapConnectionManager(connManager);
provider.setMemoryService( new TestMemoryService() );
mockConnManager.expects(atLeastOnce()).method("returnConnection");
mockConnManager.expects(atLeastOnce()).method("getConnection").will(returnValue(conn));
mockConnManager.expects(atLeastOnce()).method("setConfig").with(same(provider));
mockConnManager.expects(atLeastOnce()).method("init");
provider.init();
String eid1 = "some-eid-1";
String eid2 = "some-eid-2";
UserEdit userEdit1 = new UserEditStub();
userEdit1.setEid(eid1);
UserEdit userEdit2 = new UserEditStub();
userEdit2.setEid(eid2);
Collection<UserEdit> actualUserEdits = new ArrayList<UserEdit>(2);
actualUserEdits.add(userEdit1);
actualUserEdits.add(userEdit2);
Collection<UserEdit> expectedUserEdits = new ArrayList<UserEdit>(actualUserEdits);
LDAPEntry lde = new LDAPEntry();
// need to mock the ldap entry to make this work
final Stack<LDAPEntry> stack = new Stack<LDAPEntry>();
stack.add(lde);
LDAPSearchResults lsr = new LDAPSearchResults() {
@Override
public boolean hasMore() {
return stack.empty();
}
@Override
public LDAPEntry next() throws LDAPException {
return stack.pop();
}
};
mockConn.expects(once()).method("search").will(returnValue(lsr));
provider.getUsers(actualUserEdits);
assertEquals(expectedUserEdits, actualUserEdits);
}
*******/
/* OLD tests for above method
mockConnManager.expects(once()).method("getConnection").will(returnValue(conn));
mockDoGetUserByEid.expects(once()).method("call").
with(eq(new Object[] {userEdit1, userEdit1.getEid(), conn})).
after(mockConnManager, "getConnection").
will(returnValue(true)).id("call_1");
mockDoGetUserByEid.expects(once()).method("call").
with(eq(new Object[] {userEdit2, userEdit2.getEid(), conn})).
after(mockDoGetUserByEid, "call_1").
will(returnValue(true)).id("call_2");
mockConnManager.expects(once()).method("returnConnection").
with(same(conn)).after(mockDoGetUserByEid, "call_2");
provider.getUsers(actualUserEdits);
assertEquals(expectedUserEdits, actualUserEdits);
mockDoGetUserByEid.verify();
*/
public void testFindUserByEmailDispatch() {
final Mock mockDoSearchDirectoryForSingleEntry = mock(VarargsMethod.class);
final VarargsMethod doSearchDirectoryForSingleEntry = (VarargsMethod)mockDoSearchDirectoryForSingleEntry.proxy();
final Mock mockDoMapUserDataOntoUserEdit = mock(VarargsMethod.class);
final VarargsMethod doMapUserDataOntoUserEdit = (VarargsMethod)mockDoMapUserDataOntoUserEdit.proxy();
provider = new JLDAPDirectoryProvider() {
protected Object searchDirectoryForSingleEntry(String filter,
LDAPConnection conn,
LdapEntryMapper mapper,
String[] searchResultPhysicalAttributeNames,
String searchBaseDn)
throws LDAPException {
return doSearchDirectoryForSingleEntry.call(filter,conn,mapper,searchResultPhysicalAttributeNames,searchBaseDn);
}
protected void mapUserDataOntoUserEdit(LdapUserData userData, UserEdit userEdit) {
doMapUserDataOntoUserEdit.call(userData,userEdit);
}
};
assertFalse(attributeMapper instanceof EidDerivedEmailAddressHandler); // sanity check
provider.setLdapAttributeMapper(attributeMapper);
String eid = "some-eid";
String email = "email@university.edu";
String emailFilter = "(mail=" + email + ")";
LdapUserData userData = new LdapUserData();
userData.setEid(eid);
userData.setEmail(email);
UserEditStub userEdit = new UserEditStub();
userEdit.setEid(eid);
userEdit.setEmail(email);
mockAttributeMapper.expects(once()).method("getFindUserByEmailFilter").
with(eq(email)).will(returnValue(emailFilter));
mockDoSearchDirectoryForSingleEntry.expects(once()).method("call").
with(eq(new Object[] {emailFilter, null, null, null, null})).
after(mockAttributeMapper, "getFindUserByEmailFilter").
will(returnValue(userData));
// see comments re mapUserDataOntoUserEdit() in testGetUserByEidDispatch()
mockDoMapUserDataOntoUserEdit.expects(once()).method("call")
.with(eq(new Object[] {userData, userEdit}));
assertTrue(provider.findUserByEmail(userEdit, email));
mockDoSearchDirectoryForSingleEntry.verify();
mockDoMapUserDataOntoUserEdit.verify();
}
/**
* Exists to allow mocking of the {@link LdapAttributeMapper} and
* {@link EidDerivedEmailAddressHandler} simultaneously
*/
private interface SyntheticEmailLdapAttributeMapper extends LdapAttributeMapper, EidDerivedEmailAddressHandler {}
public void testFindUserByEidDerivedEmailDispatch() {
final Mock mockDoGetUserByEid = mock(VarargsMethod.class);
final VarargsMethod doGetUserByEid = (VarargsMethod)mockDoGetUserByEid.proxy();
final Mock mockDoMapUserDataOntoUserEdit = mock(VarargsMethod.class);
final VarargsMethod doMapUserDataOntoUserEdit = (VarargsMethod)mockDoMapUserDataOntoUserEdit.proxy();
// "override" the default attrib mapper so we can implement two interfaces
this.mockAttributeMapper = mock(SyntheticEmailLdapAttributeMapper.class);
this.attributeMapper = (LdapAttributeMapper) this.mockAttributeMapper.proxy();
provider = new JLDAPDirectoryProvider() {
protected LdapUserData getUserByEid(String eid, LDAPConnection conn)
throws LDAPException {
return (LdapUserData)doGetUserByEid.call(eid, conn);
}
protected void mapUserDataOntoUserEdit(LdapUserData userData, UserEdit userEdit) {
doMapUserDataOntoUserEdit.call(userData,userEdit);
}
};
provider.setLdapAttributeMapper(attributeMapper);
String eid = "some-eid";
String email = eid + "@university.edu"; // other tests explicitly cause the eid and account ID to differ
LdapUserData userData = new LdapUserData();
userData.setEid(eid);
userData.setEmail(email);
UserEditStub userEdit = new UserEditStub();
userEdit.setEid(eid);
userEdit.setEmail(email);
mockAttributeMapper.expects(once()).method("unpackEidFromAddress").
with(eq(email)).will(returnValue(eid));
mockDoGetUserByEid.expects(once()).method("call")
.with(eq(new Object[] { userEdit.getEid(), null
}))
.will(returnValue(userData));
// see comments re mapUserDataOntoUserEdit() in testGetUserByEidDispatch()
mockDoMapUserDataOntoUserEdit.expects(once()).method("call")
.with(eq(new Object[] {userData, userEdit}));
assertTrue(provider.findUserByEmail(userEdit, email));
mockDoGetUserByEid.verify();
mockDoMapUserDataOntoUserEdit.verify();
}
/**
* Verifies that {@link InvalidEmailAddressException}s thrown by a
* {@link EidDerivedEmailAddressHandler} are trapped and cause the
* operation to fall back to "standard" find-by-email processing. This
* allows for situations where email addresses for users in some domain
* are known to the LDAP but host, but others are not.
*/
public void testFindUserByEidDerivedEmailDispatchFallsBackToStandardSearchOnInvalidEmailAddressException() {
// "override" the default attrib mapper so we can implement two interfaces
this.mockAttributeMapper = mock(SyntheticEmailLdapAttributeMapper.class);
this.attributeMapper = (LdapAttributeMapper) this.mockAttributeMapper.proxy();
final Mock mockDoSearchDirectoryForSingleEntry = mock(VarargsMethod.class);
final VarargsMethod doSearchDirectoryForSingleEntry = (VarargsMethod)mockDoSearchDirectoryForSingleEntry.proxy();
final Mock mockDoMapUserDataOntoUserEdit = mock(VarargsMethod.class);
final VarargsMethod doMapUserDataOntoUserEdit = (VarargsMethod)mockDoMapUserDataOntoUserEdit.proxy();
provider = new JLDAPDirectoryProvider() {
protected Object searchDirectoryForSingleEntry(String filter,
LDAPConnection conn,
LdapEntryMapper mapper,
String[] searchResultPhysicalAttributeNames,
String searchBaseDn)
throws LDAPException {
return doSearchDirectoryForSingleEntry.call(filter,conn,mapper,searchResultPhysicalAttributeNames,searchBaseDn);
}
protected void mapUserDataOntoUserEdit(LdapUserData userData, UserEdit userEdit) {
doMapUserDataOntoUserEdit.call(userData,userEdit);
}
};
provider.setLdapAttributeMapper(attributeMapper);
String eid = "some-eid";
String email = eid + "@university.edu"; // other tests explicitly cause the eid and account ID to differ
String emailFilter = "(mail=" + email + ")";
LdapUserData userData = new LdapUserData();
userData.setEid(eid);
userData.setEmail(email);
UserEditStub userEdit = new UserEditStub();
userEdit.setEid(eid);
userEdit.setEmail(email);
mockAttributeMapper.expects(once()).method("unpackEidFromAddress").
with(eq(email)).will(throwException(new InvalidEmailAddressException("Unable to unpack [" + email + "]")));
mockAttributeMapper.expects(once()).method("getFindUserByEmailFilter").
with(eq(email)).after(mockAttributeMapper, "unpackEidFromAddress").
will(returnValue(emailFilter));
mockDoSearchDirectoryForSingleEntry.expects(once()).method("call").
with(eq(new Object[] {emailFilter, null, null, null, null})).
after(mockAttributeMapper, "getFindUserByEmailFilter").
will(returnValue(userData));
// see comments re mapUserDataOntoUserEdit() in testGetUserByEidDispatch()
mockDoMapUserDataOntoUserEdit.expects(once()).method("call")
.with(eq(new Object[] {userData, userEdit})).
after(mockDoSearchDirectoryForSingleEntry, "call");
assertTrue(provider.findUserByEmail(userEdit, email));
mockDoSearchDirectoryForSingleEntry.verify();
mockDoMapUserDataOntoUserEdit.verify();
}
/**
* Tests a scenario that really shouldn't ever occur if a
* {@link EidDerivedEmailAddressHandler} is well implemented. That is, that
* collaborator should never return null from
* {@link EidDerivedEmailAddressHandler#unpackEidFromAddress(String)}, but throw
* a {@link InvalidEmailAddressException}. Here we verify that even if the
* handler is misbehaved in this way, we act as if a {@link InvalidEmailAddressException}
* has been thrown
*/
public void testFindUserByEidDerivedEmailDispatchExitsGracefullyOnNullEid() {
// "override" the default attrib mapper so we can implement two interfaces
this.mockAttributeMapper = mock(SyntheticEmailLdapAttributeMapper.class);
this.attributeMapper = (LdapAttributeMapper) this.mockAttributeMapper.proxy();
final Mock mockDoSearchDirectoryForSingleEntry = mock(VarargsMethod.class);
final VarargsMethod doSearchDirectoryForSingleEntry = (VarargsMethod)mockDoSearchDirectoryForSingleEntry.proxy();
final Mock mockDoMapUserDataOntoUserEdit = mock(VarargsMethod.class);
final VarargsMethod doMapUserDataOntoUserEdit = (VarargsMethod)mockDoMapUserDataOntoUserEdit.proxy();
provider = new JLDAPDirectoryProvider() {
protected Object searchDirectoryForSingleEntry(String filter,
LDAPConnection conn,
LdapEntryMapper mapper,
String[] searchResultPhysicalAttributeNames,
String searchBaseDn)
throws LDAPException {
return doSearchDirectoryForSingleEntry.call(filter,conn,mapper,searchResultPhysicalAttributeNames,searchBaseDn);
}
protected void mapUserDataOntoUserEdit(LdapUserData userData, UserEdit userEdit) {
doMapUserDataOntoUserEdit.call(userData,userEdit);
}
};
provider.setLdapAttributeMapper(attributeMapper);
String eid = "some-eid";
String email = eid + "@university.edu"; // other tests explicitly cause the eid and account ID to differ
String emailFilter = "(mail=" + email + ")";
LdapUserData userData = new LdapUserData();
userData.setEid(eid);
userData.setEmail(email);
UserEditStub userEdit = new UserEditStub();
userEdit.setEid(eid);
userEdit.setEmail(email);
mockAttributeMapper.expects(once()).method("unpackEidFromAddress").
with(eq(email)).will(returnValue(null));
mockAttributeMapper.expects(once()).method("getFindUserByEmailFilter").
with(eq(email)).after(mockAttributeMapper, "unpackEidFromAddress").
will(returnValue(emailFilter));
mockDoSearchDirectoryForSingleEntry.expects(once()).method("call").
with(eq(new Object[] {emailFilter, null, null, null, null})).
after(mockAttributeMapper, "getFindUserByEmailFilter").
will(returnValue(userData));
// see comments re mapUserDataOntoUserEdit() in testGetUserByEidDispatch()
mockDoMapUserDataOntoUserEdit.expects(once()).method("call")
.with(eq(new Object[] {userData, userEdit})).
after(mockDoSearchDirectoryForSingleEntry, "call");
assertTrue(provider.findUserByEmail(userEdit, email));
mockDoSearchDirectoryForSingleEntry.verify();
mockDoMapUserDataOntoUserEdit.verify();
}
/**
* Very similar to {@link #testFindUserByEidDerivedEmailDispatchExitsGracefullyOnInvalidEmailAddressException()},
* but checks for null return values rather than exceptional exits.
*/
public void testFindUserByEidDerivedEmailDispatchExitsGracefullyOnNullSearchResults() {
// "override" the default attrib mapper so we can implement two interfaces
this.mockAttributeMapper = mock(SyntheticEmailLdapAttributeMapper.class);
this.attributeMapper = (LdapAttributeMapper) this.mockAttributeMapper.proxy();
provider = new JLDAPDirectoryProvider() {
protected LdapUserData getUserByEid(String eid, LDAPConnection conn)
throws LDAPException {
return null;
}
};
provider.setLdapAttributeMapper(attributeMapper);
String eid = "some-eid";
String email = eid + "@university.edu"; // other tests explicitly cause the eid and account ID to differ
UserEditStub userEdit = new UserEditStub();
userEdit.setEid(eid);
userEdit.setEmail(email);
mockAttributeMapper.expects(once()).method("unpackEidFromAddress").
with(eq(email)).will(returnValue(eid));
assertFalse(provider.findUserByEmail(userEdit, email));
}
public void testAuthenticateUserDispatch() {
final Mock mockLookupUserBindDn = mock(VarargsMethod.class);
final VarargsMethod doLookupUserBindDn = (VarargsMethod)mockLookupUserBindDn.proxy();
provider = new JLDAPDirectoryProvider() {
protected String lookupUserBindDn(String eid, LDAPConnection conn)
throws LDAPException {
return (String)doLookupUserBindDn.call(eid, conn);
}
};
provider.setLdapConnectionManager(connManager);
final String eid = "some-eid";
final String dn = "cn=some-cn, ou=some-ou";
final String password = "some-password";
final UserEdit userEdit = new UserEditStub();
userEdit.setEid(eid);
mockConnManager.expects(once()).method("getConnection").will(returnValue(conn));
mockLookupUserBindDn.expects(once()).method("call").
with(eq(new Object[] {eid, conn})).
after(mockConnManager, "getConnection").
will(returnValue(dn));
mockConnManager.expects(once()).method("returnConnection").with(same(conn)).
after(mockLookupUserBindDn, "call");
mockConnManager.expects(once()).method("getBoundConnection").
with(eq(dn), eq(password)).after(mockConnManager, "returnConnection").
will(returnValue(conn));
mockConnManager.expects(once()).method("returnConnection").with(same(conn)).
after(mockConnManager, "getBoundConnection");
// implicitly tests that allowAuthentication defaults to true
assertTrue(provider.authenticateUser(eid, userEdit, password));
mockLookupUserBindDn.verify();
}
public void testDisallowingAuthenticationShortCircuitsAuthenticateUser() {
provider.setAllowAuthentication(false);
// the UserEdit arg could be null, but we go ahead and pass one
// to ensure we're not accidentally testing the wrong behavior
final String eid = "some-eid";
final String password = "some-password";
UserEdit userEdit = new UserEditStub();
userEdit.setEid(eid);
assertFalse("Should have refused to authenticate user",
provider.authenticateUser(eid, userEdit, password));
// we rely on mocks reacting angrily if the authentication attempt
// actually attempts any sort of LDAP interaction
}
public void testDisallowingAuthenticationStillAllowsUserLookup() {
doTestGetUserDispatch(new Runnable() {
public void run() {
provider.setAllowAuthentication(false);
}
});
}
public void testSetAuthenticateAllowedAliasesSetAllowAuthentication() {
boolean prevState = provider.isAllowAuthentication();
provider.setAuthenticateAllowed(!(prevState));
boolean newState = provider.isAllowAuthentication();
assertFalse(prevState == newState);
}
public void testSetAuthenticateWithProviderFirst() {
final String eid1 = "some-eid-1";
final String eid2 = "some-eid-2";
provider.setAuthenticateWithProviderFirst(true);
assertTrue(provider.authenticateWithProviderFirst(eid1));
assertTrue(provider.authenticateWithProviderFirst(eid2));
provider.setAuthenticateWithProviderFirst(false);
assertFalse(provider.authenticateWithProviderFirst(eid1));
assertFalse(provider.authenticateWithProviderFirst(eid2));
}
protected EidAssignmentStub setEidOnReceivedUserEdit() {
return new EidAssignmentStub();
}
private static class EidAssignmentStub implements Stub {
public Object invoke(Invocation invocation) throws Throwable {
LdapUserData eidSource = (LdapUserData)invocation.parameterValues.get(0);
UserEdit userToSetEidOn = (UserEdit)invocation.parameterValues.get(1);
userToSetEidOn.setEid(eidSource.getEid());
return null;
}
public StringBuffer describeTo(StringBuffer buffer) {
return buffer.append("assigns a LdapUserData's EID to a UserEdit");
}
}
}