/*******************************************************************************
* Cloud Foundry
* Copyright (c) [2009-2016] 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 com.fasterxml.jackson.core.type.TypeReference;
import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal;
import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest;
import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils;
import org.cloudfoundry.identity.uaa.scim.ScimUser;
import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning;
import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimUserProvisioning;
import org.cloudfoundry.identity.uaa.user.UaaAuthority;
import org.cloudfoundry.identity.uaa.util.JsonUtils;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
import org.junit.Test;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.restdocs.snippet.Snippet;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.crypto.codec.Base64;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import java.util.Arrays;
import java.util.Map;
import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf;
import static org.cloudfoundry.identity.uaa.test.SnippetUtils.fieldWithPath;
import static org.cloudfoundry.identity.uaa.test.SnippetUtils.headerWithName;
import static org.cloudfoundry.identity.uaa.test.SnippetUtils.parameterWithName;
import static org.springframework.http.HttpHeaders.ACCEPT;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;
import static org.springframework.restdocs.payload.JsonFieldType.ARRAY;
import static org.springframework.restdocs.payload.JsonFieldType.BOOLEAN;
import static org.springframework.restdocs.payload.JsonFieldType.OBJECT;
import static org.springframework.restdocs.payload.JsonFieldType.STRING;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.request.RequestDocumentation.requestParameters;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
public class LoginInfoEndpointDocs extends InjectedMockContextTest {
@Test
public void info_endpoint_for_json() throws Exception {
Snippet requestParameters = requestParameters();
Snippet responseFields = responseFields(
fieldWithPath("app.version").type(STRING).description("The UAA version"),
fieldWithPath("commit_id").type(STRING).description("The GIT sha for the UAA version"),
fieldWithPath("timestamp").type(STRING).description("JSON timestamp for the commit of the UAA version"),
fieldWithPath("idpDefinitions").optional().type(OBJECT).description("A list of alias/url pairs of SAML IDP providers configured. Each url is the starting point to initiate the authentication process for the SAML identity provider."),
fieldWithPath("idpDefinitions.*").optional().type(ARRAY).description("A list of alias/url pairs of SAML IDP providers configured. Each url is the starting point to initiate the authentication process for the SAML identity provider."),
fieldWithPath("links").type(OBJECT).description("A list of alias/url pairs of configured action URLs for the UAA"),
fieldWithPath("links.login").type(STRING).description("The link to the login host alias of the UAA"),
fieldWithPath("links.uaa").type(STRING).description("The link to the uaa alias host of the UAA"),
fieldWithPath("links.passwd").type(STRING).description("The link to the 'Forgot Password' functionality. Can be external or internal to the UAA"),
fieldWithPath("links.register").type(STRING).description("The link to the 'Create Account' functionality. Can be external or internal to the UAA"),
fieldWithPath("entityID").type(STRING).description("The UAA is always a SAML service provider. This field contains the configured entityID"),
fieldWithPath("prompts").type(OBJECT).description("A list of name/value pairs of configured prompts that the UAA will login a user. Format for each prompt is [type, display name] where type can be 'text' or 'password'"),
fieldWithPath("prompts.username").type(ARRAY).description("Information about the username prompt."),
fieldWithPath("prompts.password").type(ARRAY).description("Information about the password prompt."),
fieldWithPath("prompts.passcode").optional().type(ARRAY).description("If a SAML identity provider is configured, this prompt contains a URL to where the user can initiate the SAML authentication flow."),
fieldWithPath("zone_name").type(STRING).description("The name of the zone invoked"),
fieldWithPath("showLoginLinks").optional(false).type(BOOLEAN).description("Set to true if there are SAML or OAUTH/OIDC providers with a visible link on the login page.")
);
Snippet requestHeaders = requestHeaders(
headerWithName(ACCEPT).description("When set to accept " + APPLICATION_JSON_VALUE + " the server will return prompts and server info in JSON format.")
);
getMockMvc().perform(get("/info")
.header(ACCEPT, APPLICATION_JSON_VALUE))
.andExpect(status().isOk())
.andDo(
document("{ClassName}/{methodName}",
preprocessResponse(prettyPrint()),
requestHeaders,
requestParameters,
responseFields)
);
}
@Test
public void user_ui_login() throws Exception {
Snippet requestParameters = requestParameters(
parameterWithName("username").required().type(STRING).description("The username of the user, sometimes the email address."),
parameterWithName("password").required().type(STRING).description("The user's password"),
parameterWithName("X-Uaa-Csrf").required().type(STRING).description("Automatically configured by the server upon /login. Must match the value of the X-Uaa-Csrf cookie.")
);
Snippet requestHeaders = requestHeaders(
headerWithName("Cookie").required().type(STRING).description("Must contain the a value for the cookie X-Uaa-Csrf and that must match the request parameter of the same name")
);
getMockMvc().perform(
post("/login.do")
.with(cookieCsrf())
.header("Cookie","X-Uaa-Csrf=12345a")
.param("username", "marissa")
.param("password", "koala")
.param("X-Uaa-Csrf", "12345a"))
.andDo(
document("{ClassName}/{methodName}",
preprocessResponse(prettyPrint()),
requestHeaders,
requestParameters))
.andExpect(status().isFound())
.andExpect(redirectedUrl("/"));
}
@Test
public void invalid_request() throws Exception {
getMockMvc().perform(get("/invalid_request"))
.andDo(
document("{ClassName}/{methodName}", preprocessResponse(prettyPrint()))
)
.andExpect(status().isOk());
}
@Test
public void passcode_request() throws Exception {
ScimUserProvisioning userProvisioning = getWebApplicationContext().getBean(JdbcScimUserProvisioning.class);
ScimUser marissa = userProvisioning.query("username eq \"marissa\" and origin eq \"uaa\"").get(0);
UaaPrincipal uaaPrincipal = new UaaPrincipal(marissa.getId(), marissa.getUserName(), marissa.getPrimaryEmail(), marissa.getOrigin(), marissa.getExternalId(), IdentityZoneHolder.get().getId());
UsernamePasswordAuthenticationToken principal = new UsernamePasswordAuthenticationToken(uaaPrincipal, null, Arrays.asList(UaaAuthority.fromAuthorities("uaa.user")));
MockHttpSession session = new MockHttpSession();
session.setAttribute(
HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
new MockMvcUtils.MockSecurityContext(principal)
);
MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/passcode")
.accept(APPLICATION_JSON_VALUE)
.session(session)
.header("Cookie","JSESSIONID="+session.getId());
getMockMvc().perform(get)
.andDo(
document("{ClassName}/{methodName}",
preprocessResponse(prettyPrint()),
requestHeaders(headerWithName("Cookie").required().description("JSESSIONID cookie to match the server side session of the authenticated user."))
)
)
.andExpect(status().isOk());
}
@Test
public void generate_auto_login_code() throws Exception {
generate_auto_login_code(true);
}
public Map<String,Object> generate_auto_login_code(boolean x) throws Exception {
Snippet requestFields = requestFields(
fieldWithPath("username").required().type(STRING).description("The username for the autologin request"),
fieldWithPath("password").required().type(STRING).description("The password for the autologin request")
);
Snippet requestHeaders = requestHeaders(
headerWithName("Authorization").required().description("Basic authorization header for the client making the autologin request"),
headerWithName("Content-Type").required().description("Set to "+APPLICATION_JSON_VALUE),
headerWithName("Accept").required().description("Set to "+APPLICATION_JSON_VALUE)
);
Snippet responseFields = responseFields(
fieldWithPath("code").required().type(STRING).description("The code used to authenticate the user."),
fieldWithPath("path").optional(null).type(STRING).description("Not used. Hardcoded to /oauth/authorize")
);
AutologinRequest request = new AutologinRequest();
request.setUsername("marissa");
request.setPassword("koala");
String body = getMockMvc().perform(
post("/autologin")
.header("Authorization", "Basic " + new String(new Base64().encode("admin:adminsecret".getBytes())))
.contentType(APPLICATION_JSON)
.accept(APPLICATION_JSON)
.content(JsonUtils.writeValueAsString(request)))
.andDo(
document("{ClassName}/{methodName}",
preprocessResponse(prettyPrint()),
requestHeaders,
requestFields,
responseFields
)
)
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString();
return JsonUtils.readValue(body, new TypeReference<Map<String, Object>>() {});
}
@Test
public void perform_auto_login() throws Exception {
Map<String,Object> code = generate_auto_login_code(true);
Snippet requestParameters = requestParameters(
parameterWithName("code").required().type(STRING).description("The code generated from the POST /autologin"),
parameterWithName("client_id").required().type(STRING).description("The client_id that generated the autologin code")
);
getMockMvc().perform(MockMvcRequestBuilders.get("/autologin")
.param("code", (String)code.get("code"))
.param("client_id", "admin"))
.andDo(print())
.andDo(
document("{ClassName}/{methodName}",
preprocessResponse(prettyPrint()),
requestParameters
)
)
.andExpect(redirectedUrl("home"));
}
}