/*******************************************************************************
* 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.scim.endpoints;
import org.cloudfoundry.identity.uaa.constants.OriginKeys;
import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest;
import org.cloudfoundry.identity.uaa.scim.ScimUser;
import org.cloudfoundry.identity.uaa.test.UaaTestAccounts;
import org.cloudfoundry.identity.uaa.util.JsonUtils;
import org.cloudfoundry.identity.uaa.provider.IdentityProvider;
import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.oauth2.common.util.RandomValueStringGenerator;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.utils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
public class ScimUserLookupMockMvcTests extends InjectedMockContextTest {
private RandomValueStringGenerator generator = new RandomValueStringGenerator();
private String clientId = generator.generate().toLowerCase();
private String clientSecret = generator.generate().toLowerCase();
private String scimLookupIdUserToken;
private String adminToken;
private int testUserCount = 25, pageSize = 5;
private static String[][] testUsers;
private boolean originalEnabled;
private ScimUser user;
@Before
public void setUp() throws Exception {
adminToken = testClient.getClientCredentialsOAuthAccessToken("admin", "adminsecret", "clients.read clients.write clients.secret scim.read scim.write clients.admin");
user = new ScimUser(null, new RandomValueStringGenerator().generate()+"@test.org", "PasswordResetUserFirst", "PasswordResetUserLast");
user.setPrimaryEmail(user.getUserName());
user.setPassword("secr3T");
user = utils().createUser(getMockMvc(), adminToken, user);
originalEnabled = getWebApplicationContext().getBean(UserIdConversionEndpoints.class).isEnabled();
getWebApplicationContext().getBean(UserIdConversionEndpoints.class).setEnabled(true);
List<String> scopes = Arrays.asList("scim.userids","scim.me");
utils().createClient(this.getMockMvc(), adminToken, clientId, clientSecret, Collections.singleton("scim"), scopes, Arrays.asList(new String[]{"client_credentials", "password"}), "uaa.none");
scimLookupIdUserToken = testClient.getUserOAuthAccessToken(clientId, clientSecret, user.getUserName(), "secr3T", "scim.userids");
if (testUsers==null) {
testUsers = createUsers(adminToken, testUserCount);
}
}
@After
public void restoreEnabled() throws Exception {
getWebApplicationContext().getBean(UserIdConversionEndpoints.class).setEnabled(originalEnabled);
}
@Test
public void testLookupIdFromUsername() throws Exception {
String username = UaaTestAccounts.standard(null).getUserName();
MockHttpServletRequestBuilder post = getIdLookupRequest(scimLookupIdUserToken, username, "eq");
String body = getMockMvc().perform(post)
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString();
validateLookupResults(new String[] {username}, body);
}
@Test
public void testLookupUsingOnlyOrigin() throws Exception {
String filter = "origin eq \"uaa\"";
MockHttpServletRequestBuilder post = post("/ids/Users")
.header("Authorization", "Bearer " + scimLookupIdUserToken)
.accept(APPLICATION_JSON)
.param("filter", filter)
.param("startIndex", String.valueOf(1))
.param("count", String.valueOf(50));
getMockMvc().perform(post)
.andExpect(status().isBadRequest());
}
@Test
public void lookupId_DoesntReturnInactiveIdp_ByDefault() throws Exception {
ScimUser scimUser = createInactiveIdp(new RandomValueStringGenerator().generate() + "test-origin");
String filter = "(username eq \"" + user.getUserName() + "\" OR username eq \"" + scimUser.getUserName() + "\")";
MockHttpServletRequestBuilder post = post("/ids/Users")
.header("Authorization", "Bearer " + scimLookupIdUserToken)
.accept(APPLICATION_JSON)
.param("filter", filter);
MockHttpServletResponse response = getMockMvc().perform(post)
.andExpect(status().isOk())
.andReturn().getResponse();
Map<String, Object> map = JsonUtils.readValue(response.getContentAsString(), Map.class);
List<Map<String, Object>> resources = (List<Map<String, Object>>) map.get("resources");
assertEquals(resources.size(), 1);
assertNotEquals(resources.get(0).get("origin"), "test-origin");
}
@Test
public void lookupId_ReturnInactiveIdp_WithIncludeInactiveParam() throws Exception {
ScimUser scimUser = createInactiveIdp(new RandomValueStringGenerator().generate() + "test-origin");
String filter = "(username eq \"" + user.getUserName() + "\" OR username eq \"" + scimUser.getUserName() + "\")";
MockHttpServletRequestBuilder post = post("/ids/Users")
.header("Authorization", "Bearer " + scimLookupIdUserToken)
.accept(APPLICATION_JSON)
.param("filter", filter)
.param("includeInactive", "true");
MockHttpServletResponse response = getMockMvc().perform(post)
.andExpect(status().isOk())
.andReturn().getResponse();
Map<String, Object> map = JsonUtils.readValue(response.getContentAsString(), Map.class);
List<Map<String, Object>> resources = (List<Map<String, Object>>) map.get("resources");
assertEquals(resources.size(), 2);
}
@Test
public void testLookupIdFromUsernameWithIncorrectScope() throws Exception {
String token = testClient.getUserOAuthAccessToken(clientId, clientSecret, user.getUserName(), "secr3T", "scim.me");
String username = UaaTestAccounts.standard(null).getUserName();
MockHttpServletRequestBuilder post = getIdLookupRequest(token, username, "eq");
getMockMvc().perform(post)
.andExpect(status().isForbidden());
}
@Test
public void testLookupIdFromUsernameWithNoToken() throws Exception {
String username = UaaTestAccounts.standard(null).getUserName();
MockHttpServletRequestBuilder post = post("/ids/Users")
.accept(APPLICATION_JSON)
.param("filter", "username eq \"" + username + "\"")
.param("startIndex", String.valueOf(1))
.param("count", String.valueOf(100));
getMockMvc().perform(post)
.andExpect(status().isUnauthorized());
}
@Test
public void testLookupIdFromUsernameWithInvalidFilter() throws Exception {
String username = UaaTestAccounts.standard(null).getUserName();
MockHttpServletRequestBuilder post = getIdLookupRequest(scimLookupIdUserToken, username, "sw");
getMockMvc().perform(post)
.andExpect(status().isBadRequest());
post = getIdLookupRequest(scimLookupIdUserToken, username, "co");
getMockMvc().perform(post)
.andExpect(status().isBadRequest());
}
@Test
public void testLookupUserNameFromId() throws Exception {
String[][] user = createUsers(adminToken, 1);
String id = user[0][0];
String email = user[0][1];
MockHttpServletRequestBuilder post = getUsernameLookupRequest(scimLookupIdUserToken, id, "eq");
String body = getMockMvc().perform(post)
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString();
validateLookupResults(new String[] {email}, body);
}
@Test
public void testLookupUserNameFromIdWithIncorrectScope() throws Exception {
String token = testClient.getUserOAuthAccessToken(clientId, clientSecret, user.getUserName(), "secr3T", "scim.me");
String[][] user = createUsers(adminToken, 1);
String id = user[0][0];
MockHttpServletRequestBuilder post = getUsernameLookupRequest(token, id, "eq");
getMockMvc().perform(post)
.andExpect(status().isForbidden());
}
@Test
public void testLookupIdFromUsernamePagination() throws Exception {
StringBuilder builder = new StringBuilder();
String[] usernames = new String[testUserCount];
String[] ids = new String[testUserCount];
int index = 0;
for (String[] entry : testUsers) {
builder.append("userName eq \"" + entry[1] + "\"");
builder.append(" or ");
usernames[index] = entry[1];
ids[index++] = entry[0];
}
String filter = builder.substring(0, builder.length()-4);
for (int i=0; i< testUserCount; i+= pageSize) {
MockHttpServletRequestBuilder post = getIdLookupRequest(scimLookupIdUserToken, filter, i+1, pageSize);
String[] expectedUsername = new String[pageSize];
System.arraycopy(usernames, i, expectedUsername, 0, pageSize);
String body = getMockMvc().perform(post)
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString();
validateLookupResults(expectedUsername, body);
}
}
private MockHttpServletRequestBuilder getIdLookupRequest(String token, String username, String operator) {
if (operator==null) {
operator = "eq";
}
return getIdLookupRequest(token, "username " + operator + " \"" + username + "\"", 1, 100);
}
private MockHttpServletRequestBuilder getIdLookupRequest(String token, String filter, int startIndex, int count) {
return post("/ids/Users")
.header("Authorization", "Bearer " + token)
.accept(APPLICATION_JSON)
.param("filter", filter)
.param("startIndex", String.valueOf(startIndex))
.param("count", String.valueOf(count));
}
private MockHttpServletRequestBuilder getUsernameLookupRequest(String token, String id,String operator) {
if (operator==null) {
operator = "eq";
}
return post("/ids/Users")
.header("Authorization", "Bearer " + token)
.accept(APPLICATION_JSON)
.param("filter", "id "+operator+" \""+id+"\"");
}
private void validateLookupResults(String[] usernames, String body) throws java.io.IOException {
Map<String, Object> map = JsonUtils.readValue(body, Map.class);
assertTrue("Response should contain 'resources' object", map.get("resources")!=null);
assertTrue("Response should contain 'startIndex' object", map.get("startIndex")!=null);
assertTrue("Response should contain 'itemsPerPage' object", map.get("itemsPerPage")!=null);
assertTrue("Response should contain 'totalResults' object", map.get("totalResults")!=null);
List<Map<String, Object>> resources = (List<Map<String, Object>>) map.get("resources");
assertEquals(usernames.length, resources.size());
for (Map<String, Object> user : resources) {
assertTrue("Response should contain 'origin' object", user.get(OriginKeys.ORIGIN)!=null);
assertTrue("Response should contain 'id' object", user.get("id")!=null);
assertTrue("Response should contain 'userName' object", user.get("userName")!=null);
String userName = (String)user.get("userName");
boolean found = false;
for (String s : usernames) {
if (s.equals(userName)) {
found = true;
break;
}
}
assertTrue("Received non requested user in result set '"+userName+"'", found);
}
for (String s : usernames) {
boolean found = false;
for (Map<String, Object> user : resources) {
String userName = (String)user.get("userName");
if (s.equals(userName)) {
found = true;
break;
}
}
assertTrue("Missing user in result '"+s+"'", found);
}
}
private String[][] createUsers(String token, int count) throws Exception {
String[][] result = new String[count][];
for (int i=0; i<count; i++) {
String id = i>99 ? String.valueOf(i) : i > 9 ? "0" + String.valueOf(i) : "00" + String.valueOf(i);
String email = "joe"+id+"@" + generator.generate().toLowerCase() + ".com";
ScimUser user = new ScimUser();
user.setPassword("password");
user.setUserName(email);
user.setName(new ScimUser.Name("Joe", "User"));
user.addEmail(email);
byte[] requestBody = JsonUtils.writeValueAsBytes(user);
MockHttpServletRequestBuilder post = post("/Users")
.header("Authorization", "Bearer " + token)
.contentType(APPLICATION_JSON)
.content(requestBody);
String body = getMockMvc().perform(post)
.andExpect(status().isCreated())
.andExpect(header().string("ETag", "\"0\""))
.andExpect(jsonPath("$.userName").value(email))
.andExpect(jsonPath("$.emails[0].value").value(email))
.andExpect(jsonPath("$.name.familyName").value("User"))
.andExpect(jsonPath("$.name.givenName").value("Joe"))
.andReturn().getResponse().getContentAsString();
Map<String,Object> map = JsonUtils.readValue(body, Map.class);
result[i] = new String[] {map.get("id").toString(), email};
}
return result;
}
private ScimUser createInactiveIdp(String originKey) throws Exception {
String tokenToCreateIdp = testClient.getClientCredentialsOAuthAccessToken("login", "loginsecret", "idps.write");
IdentityProvider inactiveIdentityProvider = MultitenancyFixture.identityProvider(originKey, "uaa");
inactiveIdentityProvider.setActive(false);
utils().createIdpUsingWebRequest(getMockMvc(), null, tokenToCreateIdp, inactiveIdentityProvider, status().isCreated());
ScimUser scimUser = new ScimUser(null, new RandomValueStringGenerator().generate()+"@test.org", "test", "test");
scimUser.setPrimaryEmail(scimUser.getUserName());
scimUser.setPassword("secr3T");
scimUser.setOrigin(originKey);
scimUser = utils().createUserInZone(getMockMvc(), adminToken, scimUser, "");
return scimUser;
}
}