/*******************************************************************************
* Cloud Foundry
* Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved.
*
* This product is licensed to you under the Apache License, Version 2.0 (the "License").
* You may not use this product except in compliance with the License.
*
* This product includes a number of subcomponents with
* separate copyright notices and license terms. Your use of these
* subcomponents is subject to the terms and conditions of the
* subcomponent's license, as noted in the LICENSE file.
*******************************************************************************/
package org.cloudfoundry.identity.uaa.login;
import static org.hamcrest.Matchers.hasSize;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.xpath;
import org.cloudfoundry.identity.uaa.authentication.login.Prompt;
import org.cloudfoundry.identity.uaa.login.test.ThymeleafConfig;
import org.cloudfoundry.identity.uaa.test.UaaTestAccounts;
import org.cloudfoundry.identity.uaa.user.UaaAuthority;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import java.util.Arrays;
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = RemoteUaaControllerViewTests.ContextConfiguration.class)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class RemoteUaaControllerViewTests {
@Autowired
WebApplicationContext webApplicationContext;
@Autowired
RestTemplate restTemplate;
@Autowired
MockEnvironment environment;
private MockMvc mockMvc;
private MockRestServiceServer mockRestServiceServer;
private UaaTestAccounts testAccounts = UaaTestAccounts.standard(null);
@Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.build();
mockRestServiceServer = MockRestServiceServer.createServer(restTemplate);
}
@Test
public void testDefaultBranding() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/login"))
.andExpect(xpath("//head/link[@rel='shortcut icon']/@href").string("/resources/oss/images/favicon.ico"))
.andExpect(xpath("//head/link[@href='/resources/oss/stylesheets/application.css']").exists())
.andExpect(xpath("//div[@class='header' and contains(@style,'/resources/oss/images/logo.png')]").exists());
}
@Test
public void testExternalizedBranding() throws Exception {
environment.setProperty("assetBaseUrl", "//cdn.example.com/pivotal");
mockMvc.perform(MockMvcRequestBuilders.get("/login"))
.andExpect(xpath("//head/link[@rel='shortcut icon']/@href").string("//cdn.example.com/pivotal/images/favicon.ico"))
.andExpect(xpath("//head/link[@href='//cdn.example.com/pivotal/stylesheets/application.css']").exists())
.andExpect(xpath("//div[@class='header' and contains(@style,'//cdn.example.com/pivotal/images/logo.png')]").exists());
}
@Test
public void testAccessConfirmationPage() throws Exception {
mockRestServiceServer.expect(requestTo("https://uaa.cloudfoundry.com/oauth/authorize"))
.andExpect(method(HttpMethod.POST))
.andRespond(withSuccess(UAA_JSON, MediaType.APPLICATION_JSON));
UsernamePasswordAuthenticationToken principal = new UsernamePasswordAuthenticationToken(testAccounts.getUserName(), null, Arrays.asList(UaaAuthority.fromAuthorities("uaa.user")));
MockHttpServletRequestBuilder get = get("/oauth/authorize")
.param("response_type", "code")
.param("client_id", "app")
.param("redirect_uri", "http://example.com")
.principal(principal);
mockMvc.perform(get)
.andExpect(status().isOk())
.andExpect(model().attributeExists("client_id"))
.andExpect(model().attribute("undecided_scopes", hasSize(2)))
.andExpect(model().attribute("approved_scopes", hasSize(1)))
.andExpect(model().attribute("denied_scopes", hasSize(1)))
.andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML))
.andExpect(xpath("//h1[text()='Application Authorization']").exists())
.andExpect(xpath("//input[@type='checkbox' and @name='scope.0' and @value='scope.cloud_controller.write' and @checked='checked']").exists())
.andExpect(xpath("//input[@type='checkbox' and @name='scope.1' and @value='scope.scim.userids' and @checked='checked']").exists())
.andExpect(xpath("//input[@type='checkbox' and @name='scope.2' and @value='scope.password.write' and @checked='checked']").exists())
.andExpect(xpath("//input[@type='checkbox' and @name='scope.3' and @value='scope.cloud_controller.read']").exists())
.andExpect(xpath("//input[@type='checkbox' and @name='scope.3' and @value='scope.cloud_controller.read' and @checked='checked']").doesNotExist());
}
@Test
public void testSignupsAndResetPasswordEnabled() throws Exception {
environment.setProperty("login.selfServiceLinksEnabled", "true");
mockMvc.perform(MockMvcRequestBuilders.get("/login"))
.andExpect(xpath("//a[text()='Create account']").exists())
.andExpect(xpath("//a[text()='Reset password']").exists());
}
@Test
public void testSignupsAndResetPasswordDisabledWithNoLinksConfigured() throws Exception {
environment.setProperty("login.selfServiceLinksEnabled", "false");
mockMvc.perform(MockMvcRequestBuilders.get("/login"))
.andExpect(xpath("//a[text()='Create account']").doesNotExist())
.andExpect(xpath("//a[text()='Reset password']").doesNotExist());
}
@Test
public void testSignupsAndResetPasswordDisabledWithSomeLinksConfigured() throws Exception {
environment.setProperty("login.selfServiceLinksEnabled", "false");
environment.setProperty("links.signup", "http://example.com/signup");
environment.setProperty("links.passwd", "http://example.com/reset_passwd");
mockMvc.perform(MockMvcRequestBuilders.get("/login"))
.andExpect(xpath("//a[text()='Create account']").doesNotExist())
.andExpect(xpath("//a[text()='Reset password']").doesNotExist());
}
@Test
public void testSignupsAndResetPasswordEnabledWithCustomLinks() throws Exception {
environment.setProperty("login.selfServiceLinksEnabled", "true");
environment.setProperty("links.signup", "http://example.com/signup");
environment.setProperty("links.passwd", "http://example.com/reset_passwd");
mockMvc.perform(MockMvcRequestBuilders.get("/login"))
.andExpect(xpath("//a[text()='Create account']/@href").string("http://example.com/signup"))
.andExpect(xpath("//a[text()='Reset password']/@href").string("http://example.com/reset_passwd"));
}
@Configuration
@EnableWebMvc
@Import(ThymeleafConfig.class)
static class ContextConfiguration extends WebMvcConfigurerAdapter {
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
@Bean
BuildInfo buildInfo() {
return new BuildInfo();
}
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
MockEnvironment environment() {
return new MockEnvironment();
}
@Bean
RemoteUaaController remoteUaaController(MockEnvironment environment, RestTemplate restTemplate) {
RemoteUaaController remoteUaaController = new RemoteUaaController(environment, new RestTemplate());
Prompt first = new Prompt("how", "text", "How did I get here?");
Prompt second = new Prompt("where", "password", "Where does that highway go to?");
remoteUaaController.setPrompts(Arrays.asList(first, second));
remoteUaaController.setAuthorizationTemplate(restTemplate);
return remoteUaaController;
}
}
private static String UAA_JSON = "{" +
" \"approved_scopes\": [{" +
" \"code\": \"scope.password.write\"," +
" \"text\": \"Access your 'password' resources with scope 'write'\"" +
" }]," +
" \"auth_request\": {" +
" \"approvalParameters\": {}," +
" \"approved\": false," +
" \"authorities\": [" +
" {" +
" \"authority\": \"uaa.resource\"" +
" }" +
" ]," +
" \"authorizationParameters\": {" +
" \"add_new\": \"false\"," +
" \"client_id\": \"app\"," +
" \"external_scopes\": \"\"," +
" \"redirect_uri\": \"http://localhost:8080/app/\"," +
" \"response_type\": \"code\"," +
" \"scope\": \"cloud_controller.read cloud_controller.write openid password.write scim.userids\"," +
" \"source\": \"login\"," +
" \"state\": \"MSS7Nu\"," +
" \"username\": \"marissa\"" +
" }," +
" \"clientId\": \"app\"," +
" \"denied\": true," +
" \"redirectUri\": \"http://localhost:8080/app/\"," +
" \"resourceIds\": [" +
" \"scim\"," +
" \"openid\"," +
" \"cloud_controller\"," +
" \"password\"" +
" ]," +
" \"responseTypes\": [" +
" \"code\"" +
" ]," +
" \"scope\": [" +
" \"cloud_controller.read\"," +
" \"cloud_controller.write\"," +
" \"openid\"," +
" \"password.write\"," +
" \"scim.userids\"" +
" ]," +
" \"state\": \"MSS7Nu\"" +
" }," +
" \"client\": {" +
" \"authorities\": [" +
" \"uaa.resource\"" +
" ]," +
" \"authorized_grant_types\": [" +
" \"authorization_code\"," +
" \"client_credentials\"," +
" \"implicit\"," +
" \"password\"," +
" \"refresh_token\"" +
" ]," +
" \"client_id\": \"app\"," +
" \"resource_ids\": [" +
" \"none\"" +
" ]," +
" \"scope\": [" +
" \"cloud_controller.read\"," +
" \"cloud_controller.write\"," +
" \"openid\"," +
" \"organizations.acme\"," +
" \"password.write\"," +
" \"scim.userids\"" +
" ]" +
" }," +
" \"client_id\": \"app\"," +
" \"denied_scopes\": [{" +
" \"code\": \"scope.cloud_controller.read\"," +
" \"text\": \"Access your 'cloud_controller' resources with scope 'read'\"" +
" }]," +
" \"message\": \"To confirm or deny access POST to the following locations with the parameters requested.\"," +
" \"options\": {" +
" \"confirm\": {" +
" \"key\": \"user_oauth_approval\"," +
" \"location\": \"http://localhost/uaa/oauth/authorize\"," +
" \"path\": \"/uaa/oauth/authorize\"," +
" \"value\": \"true\"" +
" }," +
" \"deny\": {" +
" \"key\": \"user_oauth_approval\"," +
" \"location\": \"http://localhost/uaa/oauth/authorize\"," +
" \"path\": \"/uaa/oauth/authorize\"," +
" \"value\": \"false\"" +
" }" +
" }," +
" \"redirect_uri\": \"http://localhost:8080/app/\"," +
" \"scopes\": [" +
" {" +
" \"code\": \"scope.password.write\"," +
" \"text\": \"Access your 'password' resources with scope 'write'\"" +
" }," +
" {" +
" \"code\": \"scope.cloud_controller.read\"," +
" \"text\": \"Access your 'cloud_controller' resources with scope 'read'\"" +
" }," +
" {" +
" \"code\": \"scope.cloud_controller.write\"," +
" \"text\": \"Access your 'cloud_controller' resources with scope 'write'\"" +
" }," +
" {" +
" \"code\": \"scope.scim.userids\"," +
" \"text\": \"Access your 'scim' resources with scope 'userids'\"" +
" }" +
" ]," +
" \"undecided_scopes\": [" +
" {" +
" \"code\": \"scope.cloud_controller.write\"," +
" \"text\": \"Access your 'cloud_controller' resources with scope 'write'\"" +
" }," +
" {" +
" \"code\": \"scope.scim.userids\"," +
" \"text\": \"Access your 'scim' resources with scope 'userids'\"" +
" }" +
" ]" +
"}";
}