/*******************************************************************************
* 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.jdbc;
import org.cloudfoundry.identity.uaa.constants.OriginKeys;
import org.cloudfoundry.identity.uaa.resources.jdbc.JdbcPagingListFactory;
import org.cloudfoundry.identity.uaa.resources.jdbc.LimitSqlAdapterFactory;
import org.cloudfoundry.identity.uaa.scim.ScimGroup;
import org.cloudfoundry.identity.uaa.scim.ScimGroupMember;
import org.cloudfoundry.identity.uaa.scim.ScimUser;
import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning;
import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceNotFoundException;
import org.cloudfoundry.identity.uaa.scim.test.TestUtils;
import org.cloudfoundry.identity.uaa.test.JdbcTestBase;
import org.cloudfoundry.identity.uaa.zone.IdentityZone;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture;
import org.cloudfoundry.identity.uaa.zone.ZoneManagementScopes;
import org.cloudfoundry.identity.uaa.zone.event.IdentityZoneModifiedEvent;
import org.junit.Before;
import org.junit.Test;
import org.springframework.security.oauth2.common.util.RandomValueStringGenerator;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.util.StringUtils.hasText;
public class JdbcScimGroupProvisioningTests extends JdbcTestBase {
private JdbcScimGroupProvisioning dao;
private JdbcScimGroupMembershipManager memberships;
private ScimUserProvisioning users;
private static final String SQL_INJECTION_FIELDS = "displayName,version,created,lastModified";
private int existingGroupCount = -1;
private ScimGroup g1;
private ScimGroup g2;
private ScimGroup g3;
@Before
public void initJdbcScimGroupProvisioningTests() {
memberships = new JdbcScimGroupMembershipManager(jdbcTemplate, new JdbcPagingListFactory(jdbcTemplate, LimitSqlAdapterFactory.getLimitSqlAdapter()));
dao = new JdbcScimGroupProvisioning(jdbcTemplate, new JdbcPagingListFactory(jdbcTemplate, limitSqlAdapter));
memberships.setScimGroupProvisioning(dao);
users = mock(ScimUserProvisioning.class);
memberships.setScimUserProvisioning(users);
g1 = addGroup("g1", "uaa.user");
g2 = addGroup("g2", "uaa.admin");
g3 = addGroup("g3", "openid");
validateGroupCount(3);
}
private void validateGroupCount(int expected) {
existingGroupCount = jdbcTemplate.queryForObject("select count(id) from groups where identity_zone_id='" + IdentityZoneHolder.get().getId() + "'", Integer.class);
assertEquals(expected, existingGroupCount);
}
private void validateGroup(ScimGroup group, String name, String zoneId) {
assertNotNull(group);
assertNotNull(group.getId());
assertNotNull(group.getDisplayName());
if (hasText(name)) {
assertEquals(name, group.getDisplayName());
}
if (hasText(zoneId)) {
assertEquals(zoneId, group.getZoneId());
}
}
private void validateGroup(ScimGroup group, String name, String zoneId, String description) {
validateGroup(group, name, zoneId);
if (hasText(description)) {
assertEquals(description, group.getDescription());
}
}
@Test
public void canRetrieveGroups() throws Exception {
List<ScimGroup> groups = dao.retrieveAll();
assertEquals(3, groups.size());
for (ScimGroup g : groups) {
validateGroup(g, null, IdentityZoneHolder.get().getId());
}
}
@Test
public void canRetrieveGroupsWithFilter() throws Exception {
assertEquals(1, dao.query("displayName eq \"uaa.user\"").size());
assertEquals(3, dao.query("displayName pr").size());
assertEquals(1, dao.query("displayName eq \"openid\"").size());
assertEquals(1, dao.query("DISPLAYNAMe eq \"uaa.admin\"").size());
assertEquals(1, dao.query("displayName EQ \"openid\"").size());
assertEquals(1, dao.query("displayName eq \"Openid\"").size());
assertEquals(1, dao.query("displayName co \"user\"").size());
assertEquals(3, dao.query("id sw \"g\"").size());
assertEquals(3, dao.query("displayName gt \"oauth\"").size());
assertEquals(0, dao.query("displayName lt \"oauth\"").size());
assertEquals(1, dao.query("displayName eq \"openid\" and meta.version eq 0").size());
assertEquals(3, dao.query("meta.created gt \"1970-01-01T00:00:00.000Z\"").size());
assertEquals(3, dao.query("displayName pr and id co \"g\"").size());
assertEquals(2, dao.query("displayName eq \"openid\" or displayName co \".user\"").size());
assertEquals(3, dao.query("displayName eq \"foo\" or id sw \"g\"").size());
}
@Test
public void canRetrieveGroupsWithFilterAndSortBy() {
assertEquals(3, dao.query("displayName pr", "id", true).size());
assertEquals(1, dao.query("id co \"2\"", "displayName", false).size());
}
@Test(expected = IllegalArgumentException.class)
public void cannotRetrieveGroupsWithIllegalQuotesFilter() {
assertEquals(1, dao.query("displayName eq \"bar").size());
}
@Test(expected = IllegalArgumentException.class)
public void cannotRetrieveGroupsWithMissingQuotesFilter() {
assertEquals(0, dao.query("displayName eq bar").size());
}
@Test(expected = IllegalArgumentException.class)
public void cannotRetrieveGroupsWithInvalidFieldsFilter() {
assertEquals(1, dao.query("name eq \"openid\"").size());
}
@Test(expected = IllegalArgumentException.class)
public void cannotRetrieveGroupsWithWrongFilter() {
assertEquals(0, dao.query("displayName pr \"r\"").size());
}
@Test
public void canRetrieveGroup() throws Exception {
ScimGroup group = dao.retrieve("g1");
validateGroup(group, "uaa.user", IdentityZoneHolder.get().getId());
}
@Test(expected = ScimResourceNotFoundException.class)
public void cannotRetrieveNonExistentGroup() {
dao.retrieve("invalidgroup");
}
@Test
public void canCreateGroup() throws Exception {
ScimGroup g = new ScimGroup(null, "test.1", IdentityZoneHolder.get().getId());
g.setDescription("description-create");
ScimGroupMember m1 = new ScimGroupMember("m1", ScimGroupMember.Type.USER, ScimGroupMember.GROUP_MEMBER);
ScimGroupMember m2 = new ScimGroupMember("m2", ScimGroupMember.Type.USER, ScimGroupMember.GROUP_ADMIN);
g.setMembers(Arrays.asList(m1, m2));
g = dao.create(g);
validateGroupCount(4);
validateGroup(g, "test.1", IdentityZoneHolder.get().getId(), "description-create");
}
@Test
public void canDeleteGroupsUsingFilter1() throws Exception {
dao.delete("displayName eq \"uaa.user\"");
validateGroupCount(2);
}
@Test
public void canDeleteGroupsUsingFilter2() throws Exception {
dao.delete("displayName sw \"uaa\"");
validateGroupCount(1);
}
@Test
public void canDeleteGroupsUsingFilter3() throws Exception {
dao.delete("id eq \"g1\"");
validateGroupCount(2);
}
@Test
public void canUpdateGroup() throws Exception {
ScimGroup g = dao.retrieve("g1");
assertEquals("uaa.user", g.getDisplayName());
ScimGroupMember m1 = new ScimGroupMember("m1", ScimGroupMember.Type.USER, ScimGroupMember.GROUP_MEMBER);
ScimGroupMember m2 = new ScimGroupMember("g2", ScimGroupMember.Type.USER, ScimGroupMember.GROUP_ADMIN);
g.setMembers(Arrays.asList(m1, m2));
g.setDisplayName("uaa.none");
g.setDescription("description-update");
dao.update("g1", g);
g = dao.retrieve("g1");
validateGroup(g, "uaa.none", IdentityZoneHolder.get().getId(), "description-update");
}
@Test
public void canRemoveGroup() throws Exception {
addUserToGroup(g1.getId(), "joe@example.com");
addUserToGroup(g1.getId(), "mary@example.com");
ScimGroupMember bill = addUserToGroup(g2.getId(), "bill@example.com");
dao.delete("g1", 0);
validateGroupCount(2);
List<ScimGroupMember> remainingMemberships = memberships.query("");
assertEquals(1, remainingMemberships.size());
ScimGroupMember survivor = remainingMemberships.get(0);
assertThat(survivor.getType(), is(ScimGroupMember.Type.USER));
assertEquals(bill.getMemberId(), survivor.getMemberId());
}
@Test
public void deleteGroupWithNestedMembers() {
ScimGroup appUsers = addGroup("appuser", "app.user");
addGroupToGroup(appUsers.getId(), g1.getId());
dao.delete(appUsers.getId(), 0);
List<ScimGroupMember> remainingMemberships = memberships.query("");
assertEquals(0, remainingMemberships.size());
}
@Test
public void test_that_uaa_scopes_are_bootstrapped_when_zone_is_created() {
String id = new RandomValueStringGenerator().generate();
IdentityZone zone = MultitenancyFixture.identityZone(id, "subdomain-" + id);
IdentityZoneModifiedEvent event = IdentityZoneModifiedEvent.identityZoneCreated(zone);
dao.onApplicationEvent(event);
List<String> groups = dao.retrieveAll(id).stream().map(g -> g.getDisplayName()).collect(Collectors.toList());
ZoneManagementScopes.getSystemScopes()
.stream()
.forEach(
scope ->
assertTrue("Scope:" + scope + " should have been bootstrapped into the new zone", groups.contains(scope))
);
}
private ScimGroup addGroup(String id, String name) {
TestUtils.assertNoSuchUser(jdbcTemplate, "id", id);
//"id,displayName,created,lastModified,version,identity_zone_id"
jdbcTemplate.update(dao.ADD_GROUP_SQL,
id,
name,
name + "-description",
new Timestamp(System.currentTimeMillis()),
new Timestamp(System.currentTimeMillis()),
0,
IdentityZoneHolder.get().getId());
return dao.retrieve(id);
}
private ScimGroupMember<ScimUser> addUserToGroup(String groupId, String username) {
String userId = UUID.randomUUID().toString();
ScimUser scimUser = new ScimUser(userId, username, username, username);
scimUser.setZoneId(OriginKeys.UAA);
when(users.retrieve(userId)).thenReturn(scimUser);
ScimGroupMember<ScimUser> member = new ScimGroupMember<>(scimUser);
memberships.addMember(groupId, member);
return member;
}
private ScimGroupMember addGroupToGroup(String parentGroupId, String childGroupId) {
ScimGroupMember<ScimGroup> member = new ScimGroupMember<>(dao.retrieve(childGroupId));
memberships.addMember(parentGroupId, member);
return member;
}
@Test(expected = IllegalArgumentException.class)
public void sqlInjectionAttack1Fails() {
dao.query("displayName='something'; select " + SQL_INJECTION_FIELDS
+ " from groups where displayName='something'");
}
@Test(expected = IllegalArgumentException.class)
public void sqlInjectionAttack2Fails() {
dao.query("displayName gt 'a'; select " + SQL_INJECTION_FIELDS
+ " from groups where displayName='something'");
}
@Test(expected = IllegalArgumentException.class)
public void sqlInjectionAttack3Fails() {
dao.query("displayName eq \"something\"; select " + SQL_INJECTION_FIELDS
+ " from groups where displayName='something'");
}
@Test(expected = IllegalArgumentException.class)
public void sqlInjectionAttack4Fails() {
dao.query("displayName eq \"something\"; select id from groups where id='''; select " + SQL_INJECTION_FIELDS
+ " from groups where displayName='something'");
}
@Test(expected = IllegalArgumentException.class)
public void sqlInjectionAttack5Fails() {
dao.query("displayName eq \"something\"'; select " + SQL_INJECTION_FIELDS
+ " from groups where displayName='something''");
}
}