package hudson.security; import static hudson.security.HudsonPrivateSecurityRealm.CLASSIC; import static hudson.security.HudsonPrivateSecurityRealm.PASSWORD_ENCODER; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertThat; import static org.hamcrest.Matchers.is; import static org.hamcrest.xml.HasXPath.hasXPath; import java.io.UnsupportedEncodingException; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.JenkinsRule.WebClient; import org.jvnet.hudson.test.WithoutJenkins; import org.jvnet.hudson.test.recipes.LocalData; import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException; import com.gargoylesoftware.htmlunit.html.HtmlPage; import com.gargoylesoftware.htmlunit.xml.XmlPage; import hudson.model.User; import hudson.remoting.Base64; import jenkins.security.ApiTokenProperty; /** * @author Kohsuke Kawaguchi */ public class HudsonPrivateSecurityRealmTest { @Rule public JenkinsRule j = new JenkinsRule(); /** * Tests the data compatibility with Hudson before 1.283. * Starting 1.283, passwords are now stored hashed. */ @Test @Issue("JENKINS-2381") @LocalData public void dataCompatibilityWith1_282() throws Exception { // make sure we can login with the same password as before WebClient wc = j.createWebClient().login("alice", "alice"); try { // verify the sanity that the password is really used // this should fail j.createWebClient().login("bob", "bob"); } catch (FailingHttpStatusCodeException e) { assertEquals(401,e.getStatusCode()); } // resubmit the config and this should force the data store to be rewritten HtmlPage p = wc.goTo("user/alice/configure"); j.submit(p.getFormByName("config")); // verify that we can still login j.createWebClient().login("alice", "alice"); } @Test @WithoutJenkins public void hashCompatibility() { String old = CLASSIC.encodePassword("hello world", null); assertTrue(PASSWORD_ENCODER.isPasswordValid(old,"hello world",null)); String secure = PASSWORD_ENCODER.encodePassword("hello world", null); assertTrue(PASSWORD_ENCODER.isPasswordValid(old,"hello world",null)); assertFalse(secure.equals(old)); } @Issue("SECURITY-243") @Test public void fullNameCollisionPassword() throws Exception { HudsonPrivateSecurityRealm securityRealm = new HudsonPrivateSecurityRealm(false, false, null); j.jenkins.setSecurityRealm(securityRealm); User u1 = securityRealm.createAccount("user1", "password1"); u1.setFullName("User One"); u1.save(); User u2 = securityRealm.createAccount("user2", "password2"); u2.setFullName("User Two"); u2.save(); WebClient wc1 = j.createWebClient(); wc1.login("user1", "password1"); WebClient wc2 = j.createWebClient(); wc2.login("user2", "password2"); // Check both users can use their token XmlPage w1 = (XmlPage) wc1.goTo("whoAmI/api/xml", "application/xml"); assertThat(w1, hasXPath("//name", is("user1"))); XmlPage w2 = (XmlPage) wc2.goTo("whoAmI/api/xml", "application/xml"); assertThat(w2, hasXPath("//name", is("user2"))); u1.setFullName("user2"); u1.save(); // check the tokens still work wc1 = j.createWebClient(); wc1.login("user1", "password1"); wc2 = j.createWebClient(); // throws FailingHttpStatusCodeException on login failure wc2.login("user2", "password2"); // belt and braces incase the failed login no longer throws exceptions. w1 = (XmlPage) wc1.goTo("whoAmI/api/xml", "application/xml"); assertThat(w1, hasXPath("//name", is("user1"))); w2 = (XmlPage) wc2.goTo("whoAmI/api/xml", "application/xml"); assertThat(w2, hasXPath("//name", is("user2"))); } @Issue("SECURITY-243") @Test public void fullNameCollisionToken() throws Exception { HudsonPrivateSecurityRealm securityRealm = new HudsonPrivateSecurityRealm(false, false, null); j.jenkins.setSecurityRealm(securityRealm); User u1 = securityRealm.createAccount("user1", "password1"); u1.setFullName("User One"); u1.save(); String u1Token = u1.getProperty(ApiTokenProperty.class).getApiToken(); User u2 = securityRealm.createAccount("user2", "password2"); u2.setFullName("User Two"); u2.save(); String u2Token = u2.getProperty(ApiTokenProperty.class).getApiToken(); WebClient wc1 = j.createWebClient(); wc1.addRequestHeader("Authorization", basicHeader("user1", u1Token)); //wc1.setCredentialsProvider(new FixedCredentialsProvider("user1", u1Token)); WebClient wc2 = j.createWebClient(); wc2.addRequestHeader("Authorization", basicHeader("user2", u2Token)); //wc2.setCredentialsProvider(new FixedCredentialsProvider("user2", u1Token)); // Check both users can use their token XmlPage w1 = (XmlPage) wc1.goTo("whoAmI/api/xml", "application/xml"); assertThat(w1, hasXPath("//name", is("user1"))); XmlPage w2 = (XmlPage) wc2.goTo("whoAmI/api/xml", "application/xml"); assertThat(w2, hasXPath("//name", is("user2"))); u1.setFullName("user2"); u1.save(); // check the tokens still work w1 = (XmlPage) wc1.goTo("whoAmI/api/xml", "application/xml"); assertThat(w1, hasXPath("//name", is("user1"))); w2 = (XmlPage) wc2.goTo("whoAmI/api/xml", "application/xml"); assertThat(w2, hasXPath("//name", is("user2"))); } private static final String basicHeader(String user, String pass) throws UnsupportedEncodingException { String str = user +':' + pass; String auth = Base64.encode(str.getBytes("US-ASCII")); String authHeader = "Basic " + auth; return authHeader; } }