@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = DefaultIntegrationTestConfig.class) @OAuth2ContextConfiguration(OAuth2ContextConfiguration.ClientCredentials.class) public class AppApprovalIT { @Rule public ServerRunning serverRunning = ServerRunning.isRunning(); UaaTestAccounts testAccounts = UaaTestAccounts.standard(serverRunning); @Rule public OAuth2ContextSetup context = OAuth2ContextSetup.withTestAccounts(serverRunning, testAccounts); @Rule public TestAccountSetup testAccountSetup = TestAccountSetup.standard(serverRunning, testAccounts); public RestOperations restTemplate; @Autowired @Rule public IntegrationTestRule integrationTestRule; @Autowired WebDriver webDriver; @Value("${integration.test.base_url}") String baseUrl; @Value("${integration.test.app_url}") String appUrl; @Before @After public void logout_and_clear_cookies() { restTemplate = serverRunning.getRestTemplate(); try { webDriver.get(baseUrl + "/logout.do"); } catch (org.openqa.selenium.TimeoutException x) { // try again - this should not be happening - 20 second timeouts webDriver.get(baseUrl + "/logout.do"); } webDriver.get(appUrl + "/j_spring_security_logout"); webDriver.manage().deleteAllCookies(); } @Test public void testApprovingAnApp() throws Exception { ResponseEntity<SearchResults<ScimGroup>> getGroups = restTemplate.exchange( baseUrl + "/Groups?filter=displayName eq '{displayName}'", HttpMethod.GET, null, new ParameterizedTypeReference<SearchResults<ScimGroup>>() {}, "cloud_controller.read"); ScimGroup group = getGroups.getBody().getResources().stream().findFirst().get(); group.setDescription("Read about your clouds."); HttpHeaders headers = new HttpHeaders(); headers.add("If-Match", Integer.toString(group.getVersion())); HttpEntity request = new HttpEntity(group, headers); restTemplate.exchange( baseUrl + "/Groups/{group-id}", HttpMethod.PUT, request, Object.class, group.getId()); ScimUser user = createUnapprovedUser(); // Visit app webDriver.get(appUrl); // Sign in to login server webDriver.findElement(By.name("username")).sendKeys(user.getUserName()); webDriver.findElement(By.name("password")).sendKeys(user.getPassword()); webDriver.findElement(By.xpath("//input[@value='Sign in']")).click(); // Authorize the app for some scopes Assert.assertEquals( "Application Authorization", webDriver.findElement(By.cssSelector("h1")).getText()); webDriver .findElement(By.xpath("//label[text()='Change your password']/preceding-sibling::input")) .click(); webDriver .findElement( By.xpath( "//label[text()='Read user IDs and retrieve users by ID']/preceding-sibling::input")) .click(); webDriver.findElement( By.xpath("//label[text()='Read about your clouds.']/preceding-sibling::input")); webDriver.findElement(By.xpath("//button[text()='Authorize']")).click(); Assert.assertEquals("Sample Home Page", webDriver.findElement(By.cssSelector("h1")).getText()); // View profile on the login server webDriver.get(baseUrl + "/profile"); Assert.assertFalse( webDriver.findElement(By.xpath("//input[@value='app-password.write']")).isSelected()); Assert.assertFalse( webDriver.findElement(By.xpath("//input[@value='app-scim.userids']")).isSelected()); Assert.assertTrue( webDriver .findElement(By.xpath("//input[@value='app-cloud_controller.read']")) .isSelected()); Assert.assertTrue( webDriver .findElement(By.xpath("//input[@value='app-cloud_controller.write']")) .isSelected()); // Add approvals webDriver.findElement(By.xpath("//input[@value='app-password.write']")).click(); webDriver.findElement(By.xpath("//input[@value='app-scim.userids']")).click(); webDriver.findElement(By.xpath("//button[text()='Update']")).click(); Assert.assertTrue( webDriver.findElement(By.xpath("//input[@value='app-password.write']")).isSelected()); Assert.assertTrue( webDriver.findElement(By.xpath("//input[@value='app-scim.userids']")).isSelected()); Assert.assertTrue( webDriver .findElement(By.xpath("//input[@value='app-cloud_controller.read']")) .isSelected()); Assert.assertTrue( webDriver .findElement(By.xpath("//input[@value='app-cloud_controller.write']")) .isSelected()); // Revoke app webDriver.findElement(By.linkText("Revoke Access")).click(); Assert.assertEquals( "Are you sure you want to revoke access to The Ultimate Oauth App?", webDriver.findElement(By.cssSelector(".revocation-modal p")).getText()); // click cancel webDriver.findElement(By.cssSelector("#app-form .revocation-cancel")).click(); webDriver.findElement(By.linkText("Revoke Access")).click(); // click confirm webDriver.findElement(By.cssSelector("#app-form .revocation-confirm")).click(); Assert.assertThat( webDriver.findElements(By.xpath("//input[@value='app-password.write']")), Matchers.empty()); } @Test public void testInvalidAppRedirectDisplaysError() throws Exception { ScimUser user = createUnapprovedUser(); // given we vist the app (specifying an invalid redirect - incorrect protocol https) webDriver.get(appUrl + "?redirect_uri=https://localhost:8080/app/"); // Sign in to login server webDriver.findElement(By.name("username")).sendKeys(user.getUserName()); webDriver.findElement(By.name("password")).sendKeys(user.getPassword()); webDriver.findElement(By.xpath("//input[@value='Sign in']")).click(); // Authorize the app for some scopes assertThat( webDriver.findElement(By.className("alert-error")).getText(), RegexMatcher.matchesRegex( "^Invalid redirect (.*) did not match one of the registered values")); } private ScimUser createUnapprovedUser() throws Exception { String userName = "******" + new RandomValueStringGenerator().generate(); String userEmail = userName + "@example.com"; RestOperations restTemplate = serverRunning.getRestTemplate(); ScimUser user = new ScimUser(); user.setUserName(userName); user.setPassword("s3Cretsecret"); user.addEmail(userEmail); user.setActive(true); user.setVerified(true); ResponseEntity<ScimUser> result = restTemplate.postForEntity(serverRunning.getUrl("/Users"), user, ScimUser.class); assertEquals(HttpStatus.CREATED, result.getStatusCode()); return user; } public static class RegexMatcher extends TypeSafeMatcher<String> { private final String regex; public RegexMatcher(final String regex) { this.regex = regex; } @Override public void describeTo(final Description description) { description.appendText("matches regex=`" + regex + "`"); } @Override public boolean matchesSafely(final String string) { return string.matches(regex); } public static RegexMatcher matchesRegex(final String regex) { return new RegexMatcher(regex); } } }
/** @author Dave Syer */ public class PasswordChangeEndpointIntegrationTests { private final String JOE = "joe_" + new RandomValueStringGenerator().generate().toLowerCase(); private final String userEndpoint = "/Users"; @Rule public ServerRunning serverRunning = ServerRunning.isRunning(); private UaaTestAccounts testAccounts = UaaTestAccounts.standard(serverRunning); @Rule public TestAccountSetup testAccountSetup = TestAccountSetup.standard(serverRunning, testAccounts); @Rule public OAuth2ContextSetup context = OAuth2ContextSetup.withTestAccounts(serverRunning, testAccounts); private RestOperations client; private ScimUser joe; private ResponseEntity<ScimUser> createUser( String username, String firstName, String lastName, String email) { ScimUser user = new ScimUser(); user.setUserName(username); user.setName(new ScimUser.Name(firstName, lastName)); user.addEmail(email); user.setPassword("pas5Word"); user.setVerified(true); return client.postForEntity(serverRunning.getUrl(userEndpoint), user, ScimUser.class); } @Before public void createRestTemplate() throws Exception { Assume.assumeTrue(!testAccounts.isProfileActive("vcap")); client = serverRunning.getRestTemplate(); ((RestTemplate) serverRunning.getRestTemplate()) .setErrorHandler( new OAuth2ErrorHandler(context.getResource()) { // Pass errors through in response entity for status code analysis @Override public boolean hasError(ClientHttpResponse response) throws IOException { return false; } @Override public void handleError(ClientHttpResponse response) throws IOException {} }); } @BeforeOAuth2Context @OAuth2ContextConfiguration(OAuth2ContextConfiguration.ClientCredentials.class) public void createAccount() throws Exception { client = serverRunning.getRestTemplate(); ResponseEntity<ScimUser> response = createUser(JOE, "Joe", "User", "*****@*****.**"); joe = response.getBody(); assertEquals(JOE, joe.getUserName()); } // curl -v -H "Content-Type: application/json" -X PUT -H // "Accept: application/json" --data // "{\"password\":\"newpassword\",\"schemas\":[\"urn:scim:schemas:core:1.0\"]}" // http://localhost:8080/uaa/User/{id}/password @Test @OAuth2ContextConfiguration(OAuth2ContextConfiguration.ClientCredentials.class) public void testChangePasswordSucceeds() throws Exception { PasswordChangeRequest change = new PasswordChangeRequest(); change.setPassword("Newpasswo3d"); HttpHeaders headers = new HttpHeaders(); ResponseEntity<Void> result = client.exchange( serverRunning.getUrl(userEndpoint) + "/{id}/password", HttpMethod.PUT, new HttpEntity<>(change, headers), Void.class, joe.getId()); assertEquals(HttpStatus.OK, result.getStatusCode()); } @Test @OAuth2ContextConfiguration(OAuth2ContextConfiguration.ClientCredentials.class) public void testChangePasswordSameAsOldFails() { PasswordChangeRequest change = new PasswordChangeRequest(); change.setPassword("pas5Word"); HttpHeaders headers = new HttpHeaders(); ResponseEntity<Void> result = client.exchange( serverRunning.getUrl(userEndpoint) + "/{id}/password", HttpMethod.PUT, new HttpEntity<>(change, headers), Void.class, joe.getId()); assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, result.getStatusCode()); } @Test @OAuth2ContextConfiguration( resource = OAuth2ContextConfiguration.Implicit.class, initialize = false) public void testUserChangesOwnPassword() throws Exception { MultiValueMap<String, String> parameters = new LinkedMultiValueMap<String, String>(); parameters.set("source", "credentials"); parameters.set("username", joe.getUserName()); parameters.set("password", "pas5Word"); context.getAccessTokenRequest().putAll(parameters); PasswordChangeRequest change = new PasswordChangeRequest(); change.setOldPassword("pas5Word"); change.setPassword("Newpasswo3d"); HttpHeaders headers = new HttpHeaders(); ResponseEntity<Void> result = client.exchange( serverRunning.getUrl(userEndpoint) + "/{id}/password", HttpMethod.PUT, new HttpEntity<>(change, headers), Void.class, joe.getId()); assertEquals(HttpStatus.OK, result.getStatusCode()); } @Test @OAuth2ContextConfiguration( resource = OAuth2ContextConfiguration.Implicit.class, initialize = false) public void testUserMustSupplyOldPassword() throws Exception { MultiValueMap<String, String> parameters = new LinkedMultiValueMap<String, String>(); parameters.set("source", "credentials"); parameters.set("username", joe.getUserName()); parameters.set("password", "pas5Word"); context.getAccessTokenRequest().putAll(parameters); PasswordChangeRequest change = new PasswordChangeRequest(); change.setPassword("Newpasswo3d"); HttpHeaders headers = new HttpHeaders(); ResponseEntity<Void> result = client.exchange( serverRunning.getUrl(userEndpoint) + "/{id}/password", HttpMethod.PUT, new HttpEntity<>(change, headers), Void.class, joe.getId()); assertEquals(HttpStatus.BAD_REQUEST, result.getStatusCode()); } @Test @OAuth2ContextConfiguration( resource = OAuth2ContextConfiguration.ClientCredentials.class, initialize = false) public void testUserAccountGetsUnlockedAfterPasswordChange() throws Exception { HttpHeaders headers = new HttpHeaders(); headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); headers.set("Authorization", testAccounts.getAuthorizationHeader("app", "appclientsecret")); MultiValueMap<String, String> data = new LinkedMultiValueMap<String, String>(); data.put("grant_type", Collections.singletonList("password")); data.put("username", Collections.singletonList(joe.getUserName())); data.put("password", Collections.singletonList("pas5Word")); ResponseEntity<Map> result = serverRunning.postForMap( serverRunning.buildUri("/oauth/token").build().toString(), data, headers); assertEquals(HttpStatus.OK, result.getStatusCode()); // Lock out the account data.put("password", Collections.singletonList("randomPassword1")); for (int i = 0; i < 5; i++) { result = serverRunning.postForMap( serverRunning.buildUri("/oauth/token").build().toString(), data, headers); assertEquals(HttpStatus.UNAUTHORIZED, result.getStatusCode()); } // Check that it is locked result = serverRunning.postForMap( serverRunning.buildUri("/oauth/token").build().toString(), data, headers); assertEquals("Login policy rejected authentication", result.getBody().get("error_description")); assertEquals(HttpStatus.UNAUTHORIZED, result.getStatusCode()); PasswordChangeRequest change = new PasswordChangeRequest(); change.setPassword("Newpasswo3d"); MultiValueMap<String, String> parameters = new LinkedMultiValueMap<String, String>(); parameters.set("grant_type", "client_credentials"); parameters.set("username", "admin"); parameters.set("password", "adminsecret"); context.getAccessTokenRequest().putAll(parameters); // Change the password HttpHeaders passwordChangeHeaders = new HttpHeaders(); ResponseEntity<Void> passwordChangeResult = client.exchange( serverRunning.getUrl(userEndpoint) + "/{id}/password", HttpMethod.PUT, new HttpEntity<>(change, passwordChangeHeaders), Void.class, joe.getId()); assertEquals(HttpStatus.OK, passwordChangeResult.getStatusCode()); MultiValueMap<String, String> newData = new LinkedMultiValueMap<String, String>(); newData.put("grant_type", Collections.singletonList("password")); newData.put("username", Collections.singletonList(joe.getUserName())); newData.put("password", Collections.singletonList("Newpasswo3d")); ResponseEntity<Map> updatedResult = serverRunning.postForMap( serverRunning.buildUri("/oauth/token").build().toString(), newData, headers); assertEquals(HttpStatus.OK, updatedResult.getStatusCode()); } }