Author: markt Date: Thu Nov 12 09:24:10 2015 New Revision: 1713987 URL: http://svn.apache.org/viewvc?rev=1713987&view=rev Log: Add support for DIGEST authentication to the JNDIRealm Based on a patch by Alexis Hassler This closes #24
Added: tomcat/trunk/test/org/apache/catalina/realm/TestJNDIRealm.java (with props) Modified: tomcat/trunk/java/org/apache/catalina/realm/JNDIRealm.java Modified: tomcat/trunk/java/org/apache/catalina/realm/JNDIRealm.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/realm/JNDIRealm.java?rev=1713987&r1=1713986&r2=1713987&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/realm/JNDIRealm.java (original) +++ tomcat/trunk/java/org/apache/catalina/realm/JNDIRealm.java Thu Nov 12 09:24:10 2015 @@ -2178,8 +2178,23 @@ public class JNDIRealm extends RealmBase */ @Override protected String getPassword(String username) { + String userPassword = getUserPassword(); + if (userPassword == null || userPassword.isEmpty()) { + return null; + } - return (null); + try { + User user = getUser(open(), username, null); + if (user == null) { + // User should be found... + return null; + } else { + // ... and have a password + return user.getPassword(); + } + } catch (NamingException e) { + return null; + } } Added: tomcat/trunk/test/org/apache/catalina/realm/TestJNDIRealm.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/realm/TestJNDIRealm.java?rev=1713987&view=auto ============================================================================== --- tomcat/trunk/test/org/apache/catalina/realm/TestJNDIRealm.java (added) +++ tomcat/trunk/test/org/apache/catalina/realm/TestJNDIRealm.java Thu Nov 12 09:24:10 2015 @@ -0,0 +1,153 @@ +package org.apache.catalina.realm; + +import org.apache.catalina.Context; +import org.apache.catalina.LifecycleException; +import org.apache.catalina.core.TesterContext; +import org.apache.naming.NameParserImpl; +import org.apache.tomcat.util.security.MD5Encoder; +import org.easymock.EasyMock; +import org.junit.BeforeClass; +import org.junit.Test; + +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.*; +import java.lang.reflect.Field; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.Principal; + +import static org.easymock.EasyMock.*; +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.assertThat; + +public class TestJNDIRealm { + + private static final String ALGORITHM = "MD5"; + + private static final String USER = "test-user"; + private static final String PASSWORD = "test-password"; + private static final String REALM = "test-realm"; + + private static final String NONCE = "test-nonce"; + private static final String HA2 = "test-md5a2"; + public static final String USER_PASSWORD_ATTR = "test-pwd"; + + private static MessageDigest md5Helper; + + @BeforeClass + public static void setupClass() throws Exception { + md5Helper = MessageDigest.getInstance(ALGORITHM); + } + + @Test + public void testAuthenticateWithoutUserPassword() throws Exception { + // GIVEN + JNDIRealm realm = buildRealm(PASSWORD); + + // WHEN + String expectedResponse = + MD5Encoder.encode(md5Helper.digest((ha1() + ":" + NONCE + ":" + HA2).getBytes())); + Principal principal = + realm.authenticate(USER, expectedResponse, NONCE, null, null, null, REALM, HA2); + + // THEN + assertThat(principal, is(nullValue())); + } + + @Test + public void testAuthenticateWithUserPassword() throws Exception { + // GIVEN + JNDIRealm realm = buildRealm(PASSWORD); + realm.setUserPassword(USER_PASSWORD_ATTR); + + // WHEN + String expectedResponse = + MD5Encoder.encode(md5Helper.digest((ha1() + ":" + NONCE + ":" + HA2).getBytes())); + Principal principal = + realm.authenticate(USER, expectedResponse, NONCE, null, null, null, REALM, HA2); + + // THEN + assertThat(principal, is(instanceOf(GenericPrincipal.class))); + assertThat( ((GenericPrincipal)principal).getPassword(), equalTo(PASSWORD)); + } + + @Test + public void testAuthenticateWithUserPasswordAndCredentialHandler() throws Exception { + // GIVEN + JNDIRealm realm = buildRealm(ha1()); + realm.setCredentialHandler(buildCredentialHandler()); + realm.setUserPassword(USER_PASSWORD_ATTR); + + // WHEN + String expectedResponse = + MD5Encoder.encode(md5Helper.digest((ha1() + ":" + NONCE + ":" + HA2).getBytes())); + Principal principal = + realm.authenticate(USER, expectedResponse, NONCE, null, null, null, REALM, HA2); + + // THEN + assertThat(principal, is(instanceOf(GenericPrincipal.class))); + assertThat( ((GenericPrincipal)principal).getPassword(), equalTo(ha1())); + } + + + private JNDIRealm buildRealm(String password) throws javax.naming.NamingException, + NoSuchFieldException, IllegalAccessException, LifecycleException { + Context context = new TesterContext(); + JNDIRealm realm = new JNDIRealm(); + realm.setContainer(context); + realm.setUserSearch(""); + + Field field = JNDIRealm.class.getDeclaredField("context"); + field.setAccessible(true); + field.set(realm, mockDirContext(mockSearchResults(password))); + + realm.start(); + + return realm; + } + + private MessageDigestCredentialHandler buildCredentialHandler() + throws NoSuchAlgorithmException { + MessageDigestCredentialHandler credentialHandler = new MessageDigestCredentialHandler(); + credentialHandler.setAlgorithm(ALGORITHM); + return credentialHandler; + } + + private NamingEnumeration<SearchResult> mockSearchResults(String password) + throws NamingException { + @SuppressWarnings("unchecked") + NamingEnumeration<SearchResult> searchResults = createNiceMock(NamingEnumeration.class); + expect(Boolean.valueOf(searchResults.hasMore())) + .andReturn(Boolean.TRUE) + .andReturn(Boolean.FALSE) + .andReturn(Boolean.TRUE) + .andReturn(Boolean.FALSE); + expect(searchResults.next()) + .andReturn(new SearchResult("ANY RESULT", "", + new BasicAttributes(USER_PASSWORD_ATTR, password))) + .times(2); + EasyMock.replay(searchResults); + return searchResults; + } + + private DirContext mockDirContext(NamingEnumeration<SearchResult> namingEnumeration) + throws NamingException { + DirContext dirContext = createNiceMock(InitialDirContext.class); + expect(dirContext.search(anyString(), anyString(), anyObject(SearchControls.class))) + .andReturn(namingEnumeration) + .times(2); + expect(dirContext.getNameParser("")) + .andReturn(new NameParserImpl()).times(2); + expect(dirContext.getNameInNamespace()) + .andReturn("ANY NAME") + .times(2); + EasyMock.replay(dirContext); + return dirContext; + } + + private String ha1() { + String a1 = USER + ":" + REALM + ":" + PASSWORD; + return MD5Encoder.encode(md5Helper.digest(a1.getBytes())); + } +} Propchange: tomcat/trunk/test/org/apache/catalina/realm/TestJNDIRealm.java ------------------------------------------------------------------------------ svn:eol-style = native --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org