@Before
 public void setUp() throws Exception {
   String adminToken =
       testClient.getClientCredentialsOAuthAccessToken(
           "admin",
           "adminsecret",
           "clients.read clients.write clients.secret clients.admin uaa.admin");
   String clientId = generator.generate().toLowerCase();
   String clientSecret = generator.generate().toLowerCase();
   String authorities =
       "scim.read,scim.write,password.write,oauth.approvals,scim.create,uaa.admin";
   clientDetails =
       utils()
           .createClient(
               this.getMockMvc(),
               adminToken,
               clientId,
               clientSecret,
               Collections.singleton("oauth"),
               Arrays.asList("foo", "bar"),
               Collections.singletonList("client_credentials"),
               authorities);
   scimReadWriteToken =
       testClient.getClientCredentialsOAuthAccessToken(
           clientId, clientSecret, "scim.read scim.write password.write");
   scimCreateToken =
       testClient.getClientCredentialsOAuthAccessToken(clientId, clientSecret, "scim.create");
   usersRepository = getWebApplicationContext().getBean(ScimUserProvisioning.class);
   codeStore = getWebApplicationContext().getBean(ExpiringCodeStore.class);
   uaaAdminToken =
       testClient.getClientCredentialsOAuthAccessToken(clientId, clientSecret, "uaa.admin");
 }
 @Before
 public void setup() throws Exception {
   String adminToken =
       utils()
           .getClientCredentialsOAuthAccessToken(
               getMockMvc(),
               "admin",
               "adminsecret",
               "clients.read clients.write clients.secret scim.read scim.write clients.admin",
               null);
   domain = generator.generate().toLowerCase() + ".com";
   clientId = generator.generate().toLowerCase();
   clientSecret = generator.generate().toLowerCase();
   authorities = "scim.read,scim.invite";
   utils()
       .createClient(
           getMockMvc(),
           adminToken,
           clientId,
           clientSecret,
           null,
           Arrays.asList("scim.invite"),
           Arrays.asList(new String[] {"client_credentials"}),
           authorities);
   token =
       utils()
           .getClientCredentialsOAuthAccessToken(
               getMockMvc(), clientId, clientSecret, "scim.invite", null, true);
 }
 @Test
 public void canGetMembers_Fails_In_Other_Zone() throws Exception {
   addMember("g1", "m1", "USER", "READER");
   addMember("g1", "g2", "GROUP", "READER");
   addMember("g3", "m2", "USER", "READER,WRITER");
   IdentityZoneHolder.set(
       MultitenancyFixture.identityZone(generator.generate(), generator.generate()));
   assertEquals(0, dao.getMembers("g1", null, false).size());
 }
 @Test
 public void test_cannot_delete_uaa_provider_users_in_other_zone() throws Exception {
   String id = generator.generate();
   IdentityZone zone = MultitenancyFixture.identityZone(id, id);
   IdentityZoneHolder.set(zone);
   ScimUser user = new ScimUser(null, "*****@*****.**", "Jo", "User");
   user.addEmail("*****@*****.**");
   user.setOrigin(UAA);
   ScimUser created = db.createUser(user, "j7hyqpassX");
   assertEquals("*****@*****.**", created.getUserName());
   assertNotNull(created.getId());
   assertEquals(UAA, created.getOrigin());
   assertEquals(zone.getId(), created.getZoneId());
   assertThat(
       jdbcTemplate.queryForObject(
           "select count(*) from users where origin=? and identity_zone_id=?",
           new Object[] {UAA, zone.getId()},
           Integer.class),
       is(1));
   IdentityProvider loginServer =
       new IdentityProvider().setOriginKey(UAA).setIdentityZoneId(zone.getId());
   db.onApplicationEvent(new EntityDeletedEvent<>(loginServer));
   assertThat(
       jdbcTemplate.queryForObject(
           "select count(*) from users where origin=? and identity_zone_id=?",
           new Object[] {UAA, zone.getId()},
           Integer.class),
       is(1));
 }
  @Test
  public void listUsers_in_anotherZone() throws Exception {
    String subdomain = generator.generate();
    MockMvcUtils.IdentityZoneCreationResult result =
        utils()
            .createOtherIdentityZoneAndReturnResult(
                subdomain, getMockMvc(), getWebApplicationContext(), null);
    String zoneAdminToken = result.getZoneAdminToken();
    createUser(
        getScimUser(),
        zoneAdminToken,
        IdentityZone.getUaa().getSubdomain(),
        result.getIdentityZone().getId());

    MockHttpServletRequestBuilder get =
        MockMvcRequestBuilders.get("/Users")
            .header("X-Identity-Zone-Subdomain", subdomain)
            .header("Authorization", "Bearer " + zoneAdminToken)
            .accept(APPLICATION_JSON);

    MvcResult mvcResult = getMockMvc().perform(get).andExpect(status().isOk()).andReturn();
    SearchResults searchResults =
        JsonUtils.readValue(mvcResult.getResponse().getContentAsString(), SearchResults.class);
    MatcherAssert.assertThat(searchResults.getResources().size(), is(1));
  }
  @Override
  public ExpiringCode generateCode(String data, Timestamp expiresAt) {
    cleanExpiredEntries();

    if (data == null || expiresAt == null) {
      throw new NullPointerException();
    }

    if (expiresAt.getTime() < System.currentTimeMillis()) {
      throw new IllegalArgumentException();
    }

    int count = 0;
    while (count < 3) {
      count++;
      String code = generator.generate();
      try {
        int update = jdbcTemplate.update(insert, code, expiresAt.getTime(), data);
        if (update == 1) {
          ExpiringCode expiringCode = new ExpiringCode(code, expiresAt, data);
          return expiringCode;
        } else {
          logger.warn("Unable to store expiring code:" + code);
        }
      } catch (DataIntegrityViolationException x) {
        if (count == 3) {
          throw x;
        }
      }
    }

    return null;
  }
 @Test
 public void test_Create_User_More_Than_One_Email() throws Exception {
   ScimUser scimUser = getScimUser();
   String secondEmail = "joe@" + generator.generate().toLowerCase() + ".com";
   scimUser.addEmail(secondEmail);
   createUserAndReturnResult(scimUser, scimReadWriteToken, null, null)
       .andExpect(status().isBadRequest());
 }
 private ScimUser getScimUser() {
   String email = "joe@" + generator.generate().toLowerCase() + ".com";
   ScimUser user = new ScimUser();
   user.setUserName(email);
   user.setName(new ScimUser.Name("Joe", "User"));
   user.addEmail(email);
   return user;
 }
 @Test
 public void testCanCreateUserWithExclamationMark() throws Exception {
   String email = "joe!!@" + generator.generate().toLowerCase() + ".com";
   ScimUser user = getScimUser();
   user.getEmails().clear();
   user.setUserName(email);
   user.setPrimaryEmail(email);
   createUser(user, scimReadWriteToken, null);
 }
  protected ScimUser updateUser(String token, int status) throws Exception {
    ScimUserProvisioning usersRepository =
        getWebApplicationContext().getBean(ScimUserProvisioning.class);
    String email = "otheruser@" + generator.generate().toLowerCase() + ".com";
    ScimUser user = new ScimUser(null, email, "Other", "User");
    user.addEmail(email);
    user = usersRepository.createUser(user, "pas5Word");
    if (status == HttpStatus.BAD_REQUEST.value()) {
      user.setUserName(null);
    } else {
      String username2 = "ou" + generator.generate().toLowerCase();
      user.setUserName(username2);
    }

    user.setName(new ScimUser.Name("Joe", "Smith"));

    return updateUser(token, status, user);
  }
 @Test(expected = ScimResourceNotFoundException.class)
 public void addMember_In_Different_Zone_Causes_Issues() throws Exception {
   String subdomain = generator.generate();
   IdentityZone otherZone = MultitenancyFixture.identityZone(subdomain, subdomain);
   IdentityZoneHolder.set(otherZone);
   ScimGroupMember m1 = new ScimGroupMember("m1", ScimGroupMember.Type.USER, null);
   m1.setOrigin(OriginKeys.UAA);
   dao.addMember("g2", m1);
 }
 @Test(expected = ScimResourceNotFoundException.class)
 public void canAddMember_Validate_Origin_and_ZoneId() throws Exception {
   String subdomain = generator.generate();
   IdentityZone otherZone = MultitenancyFixture.identityZone(subdomain, subdomain);
   IdentityZoneHolder.set(otherZone);
   validateCount(0);
   ScimGroupMember m1 = new ScimGroupMember("m1", ScimGroupMember.Type.USER, null);
   m1.setOrigin(OriginKeys.UAA);
   dao.addMember("g2", m1);
 }
 @Test
 public void cannot_Delete_With_Filter_Outside_Zone() throws Exception {
   String id = generator.generate();
   addMembers();
   validateCount(4);
   IdentityZone zone = MultitenancyFixture.identityZone(id, id);
   IdentityZoneHolder.set(zone);
   dao.delete("member_id eq \"m3\" and origin eq \"" + OriginKeys.UAA + "\"");
   IdentityZoneHolder.clear();
   validateCount(4);
 }
  @Test
  public void testCreateUserInZoneUsingAdminClient() throws Exception {
    String subdomain = generator.generate();
    mockMvcUtils.createOtherIdentityZone(subdomain, getMockMvc(), getWebApplicationContext());

    String zoneAdminToken =
        testClient.getClientCredentialsOAuthAccessToken(
            "admin", "admin-secret", "scim.write", subdomain);

    createUser(zoneAdminToken, subdomain);
  }
  @Test
  public void test_can_delete_zone_users() throws Exception {
    String id = generator.generate();
    IdentityZone zone = MultitenancyFixture.identityZone(id, id);
    IdentityZoneHolder.set(zone);
    ScimUser user = new ScimUser(null, "*****@*****.**", "Jo", "User");
    user.addEmail("*****@*****.**");
    user.setOrigin(UAA);
    ScimUser created = db.createUser(user, "j7hyqpassX");
    assertEquals("*****@*****.**", created.getUserName());
    assertNotNull(created.getId());
    assertEquals(UAA, created.getOrigin());
    assertEquals(zone.getId(), created.getZoneId());
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from users where origin=? and identity_zone_id=?",
            new Object[] {UAA, zone.getId()},
            Integer.class),
        is(1));
    addApprovalAndMembership(created.getId(), created.getOrigin());
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from authz_approvals where user_id=?",
            new Object[] {created.getId()},
            Integer.class),
        is(1));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from group_membership where member_id=?",
            new Object[] {created.getId()},
            Integer.class),
        is(1));

    db.onApplicationEvent(new EntityDeletedEvent<>(zone));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from users where origin=? and identity_zone_id=?",
            new Object[] {UAA, zone.getId()},
            Integer.class),
        is(0));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from authz_approvals where user_id=?",
            new Object[] {created.getId()},
            Integer.class),
        is(0));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from group_membership where member_id=?",
            new Object[] {created.getId()},
            Integer.class),
        is(0));
  }
 @Test
 public void testCreateUserInZoneUsingZoneAdminUser() throws Exception {
   String subdomain = generator.generate();
   MockMvcUtils.IdentityZoneCreationResult result =
       utils()
           .createOtherIdentityZoneAndReturnResult(
               subdomain, getMockMvc(), getWebApplicationContext(), null);
   String zoneAdminToken = result.getZoneAdminToken();
   createUser(
       getScimUser(),
       zoneAdminToken,
       IdentityZone.getUaa().getSubdomain(),
       result.getIdentityZone().getId());
 }
 private ScimUser setUpScimUser(IdentityZone zone) {
   IdentityZone original = IdentityZoneHolder.get();
   try {
     IdentityZoneHolder.set(zone);
     String email = "joe@" + generator.generate().toLowerCase() + ".com";
     ScimUser joel = new ScimUser(null, email, "Joel", "D'sa");
     joel.setVerified(false);
     joel.addEmail(email);
     joel = usersRepository.createUser(joel, USER_PASSWORD);
     return joel;
   } finally {
     IdentityZoneHolder.set(original);
   }
 }
 @Test
 public void canQuery_Filter_Has_ZoneIn_Effect() throws Exception {
   addMembers();
   validateCount(4);
   String id = generator.generate();
   IdentityZone zone = MultitenancyFixture.identityZone(id, id);
   IdentityZoneHolder.set(zone);
   assertEquals(0, dao.query("origin eq \"" + OriginKeys.UAA + "\"").size());
   IdentityZoneHolder.clear();
   assertEquals(4, dao.query("origin eq \"" + OriginKeys.UAA + "\"").size());
   assertEquals(4, dao.query("origin eq \"" + OriginKeys.UAA + "\"", "member_id", true).size());
   assertEquals(4, dao.query("origin eq \"" + OriginKeys.UAA + "\"", "1,2", true).size());
   assertEquals(4, dao.query("origin eq \"" + OriginKeys.UAA + "\"", "origin", true).size());
 }
  @Test
  public void testCreateUserInOtherZoneIsUnauthorized() throws Exception {
    String subdomain = generator.generate();
    mockMvcUtils.createOtherIdentityZone(subdomain, getMockMvc(), getWebApplicationContext());

    String otherSubdomain = generator.generate();
    mockMvcUtils.createOtherIdentityZone(otherSubdomain, getMockMvc(), getWebApplicationContext());

    String zoneAdminToken =
        testClient.getClientCredentialsOAuthAccessToken(
            "admin", "admin-secret", "scim.write", subdomain);

    ScimUser user = getScimUser();

    byte[] requestBody = JsonUtils.writeValueAsBytes(user);
    MockHttpServletRequestBuilder post =
        post("/Users")
            .with(new SetServerNameRequestPostProcessor(otherSubdomain + ".localhost"))
            .header("Authorization", "Bearer " + zoneAdminToken)
            .contentType(APPLICATION_JSON)
            .content(requestBody);

    getMockMvc().perform(post).andExpect(status().isUnauthorized());
  }
 @Test
 public void test_Create_User_Too_Long_Password() throws Exception {
   String email = "joe@" + generator.generate().toLowerCase() + ".com";
   ScimUser user = getScimUser();
   user.setUserName(email);
   user.setPrimaryEmail(email);
   user.setPassword(new RandomValueStringGenerator(300).generate());
   ResultActions result = createUserAndReturnResult(user, scimReadWriteToken, null, null);
   result
       .andExpect(status().isBadRequest())
       .andExpect(jsonPath("$.error").value("invalid_password"))
       .andExpect(
           jsonPath("$.message").value("Password must be no more than 255 characters in length."))
       .andExpect(
           jsonPath("$.error_description")
               .value("Password must be no more than 255 characters in length."));
 }
  @Test
  public void verification_link_in_non_default_zone_using_switch() throws Exception {
    String subdomain = generator.generate().toLowerCase();
    MockMvcUtils.IdentityZoneCreationResult zoneResult =
        utils()
            .createOtherIdentityZoneAndReturnResult(
                subdomain, getMockMvc(), getWebApplicationContext(), null);
    String zonedClientId = "admin";
    String zonedClientSecret = "adminsecret";
    String zonedScimCreateToken =
        utils()
            .getClientCredentialsOAuthAccessToken(
                getMockMvc(), zonedClientId, zonedClientSecret, "uaa.admin", null);

    ScimUser joel = setUpScimUser(zoneResult.getIdentityZone());

    MockHttpServletRequestBuilder get =
        MockMvcRequestBuilders.get("/Users/" + joel.getId() + "/verify-link")
            .header("Host", "localhost")
            .header("Authorization", "Bearer " + zonedScimCreateToken)
            .header(IdentityZoneSwitchingFilter.SUBDOMAIN_HEADER, subdomain)
            .param("redirect_uri", HTTP_REDIRECT_EXAMPLE_COM)
            .accept(APPLICATION_JSON);

    MvcResult result = getMockMvc().perform(get).andExpect(status().isOk()).andReturn();
    VerificationResponse verificationResponse =
        JsonUtils.readValue(result.getResponse().getContentAsString(), VerificationResponse.class);
    assertThat(
        verificationResponse.getVerifyLink().toString(),
        startsWith("http://" + subdomain + ".localhost/verify_user"));

    String query = verificationResponse.getVerifyLink().getQuery();

    String code = getQueryStringParam(query, "code");
    assertThat(code, is(notNullValue()));

    ExpiringCode expiringCode = codeStore.retrieveCode(code);
    assertThat(expiringCode.getExpiresAt().getTime(), is(greaterThan(System.currentTimeMillis())));
    assertThat(expiringCode.getIntent(), is(REGISTRATION.name()));
    Map<String, String> data =
        JsonUtils.readValue(expiringCode.getData(), new TypeReference<Map<String, String>>() {});
    assertThat(data.get(InvitationConstants.USER_ID), is(notNullValue()));
    assertThat(data.get(CLIENT_ID), is("admin"));
    assertThat(data.get(REDIRECT_URI), is(HTTP_REDIRECT_EXAMPLE_COM));
  }
  @Test
  public void canModifyPassword() throws Exception {
    ScimUser user = new ScimUser(null, generator.generate() + "@foo.com", "Jo", "User");
    user.addEmail(user.getUserName());
    ScimUser created = db.createUser(user, "j7hyqpassX");
    assertNull(user.getPasswordLastModified());
    assertNotNull(created.getPasswordLastModified());
    assertEquals(
        (created.getMeta().getCreated().getTime() / 1000l) * 1000l,
        created.getPasswordLastModified().getTime());
    Thread.sleep(10);
    db.changePassword(created.getId(), "j7hyqpassX", "j7hyqpassXXX");

    user = db.retrieve(created.getId());
    assertNotNull(user.getPasswordLastModified());
    assertEquals(
        (user.getMeta().getLastModified().getTime() / 1000l) * 1000l,
        user.getPasswordLastModified().getTime());
  }
  private void verifyUser(String token) throws Exception {
    ScimUserProvisioning usersRepository =
        getWebApplicationContext().getBean(ScimUserProvisioning.class);
    String email = "joe@" + generator.generate().toLowerCase() + ".com";
    ScimUser joel = new ScimUser(null, email, "Joel", "D'sa");
    joel.addEmail(email);
    joel = usersRepository.createUser(joel, "pas5Word");

    MockHttpServletRequestBuilder get =
        MockMvcRequestBuilders.get("/Users/" + joel.getId() + "/verify")
            .header("Authorization", "Bearer " + token)
            .accept(APPLICATION_JSON);

    getMockMvc()
        .perform(get)
        .andExpect(status().isOk())
        .andExpect(header().string("ETag", "\"0\""))
        .andExpect(jsonPath("$.userName").value(email))
        .andExpect(jsonPath("$.emails[0].value").value(email))
        .andExpect(jsonPath("$.name.familyName").value("D'sa"))
        .andExpect(jsonPath("$.name.givenName").value("Joel"))
        .andExpect(jsonPath("$.verified").value(true));
  }
  @Test
  public void test_zone_deleted() {
    String zoneAdminId = generator.generate();
    addGroup(zoneAdminId, "zones." + zone.getId() + ".admin", IdentityZone.getUaa().getId());
    addMember(zoneAdminId, "m1", "USER", "MEMBER", OriginKeys.UAA);

    IdentityZoneHolder.set(zone);
    addMembers();
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)",
            new Object[] {IdentityZoneHolder.get().getId()},
            Integer.class),
        is(4));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from groups where identity_zone_id=?",
            new Object[] {IdentityZoneHolder.get().getId()},
            Integer.class),
        is(3));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from external_group_mapping where group_id in (select id from groups where identity_zone_id=?)",
            new Object[] {IdentityZoneHolder.get().getId()},
            Integer.class),
        is(3));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=? and displayName like ?)",
            new Object[] {
              IdentityZone.getUaa().getId(), "zones." + IdentityZoneHolder.get().getId() + ".%"
            },
            Integer.class),
        is(1));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from groups where identity_zone_id=? and displayName like ?",
            new Object[] {
              IdentityZone.getUaa().getId(), "zones." + IdentityZoneHolder.get().getId() + ".%"
            },
            Integer.class),
        is(1));
    gdao.onApplicationEvent(new EntityDeletedEvent<>(zone, null));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)",
            new Object[] {IdentityZoneHolder.get().getId()},
            Integer.class),
        is(0));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from groups where identity_zone_id=?",
            new Object[] {IdentityZoneHolder.get().getId()},
            Integer.class),
        is(0));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from external_group_mapping where group_id in (select id from groups where identity_zone_id=?)",
            new Object[] {IdentityZoneHolder.get().getId()},
            Integer.class),
        is(0));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=? and displayName like ?)",
            new Object[] {
              IdentityZone.getUaa().getId(), "zones." + IdentityZoneHolder.get().getId() + ".%"
            },
            Integer.class),
        is(0));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from groups where identity_zone_id=? and displayName like ?",
            new Object[] {
              IdentityZone.getUaa().getId(), "zones." + IdentityZoneHolder.get().getId() + ".%"
            },
            Integer.class),
        is(0));
  }
public class JdbcScimGroupMembershipManagerTests extends JdbcTestBase {

  private JdbcScimGroupProvisioning gdao;

  private JdbcScimUserProvisioning udao;

  private JdbcScimGroupMembershipManager dao;

  private static final String addUserSqlFormat =
      "insert into users (id, username, password, email, givenName, familyName, phoneNumber, authorities ,identity_zone_id) values ('%s','%s','%s','%s','%s','%s','%s','%s','%s')";

  private static final String addGroupSqlFormat =
      "insert into groups (id, displayName, identity_zone_id) values ('%s','%s','%s')";

  private static final String addMemberSqlFormat =
      "insert into group_membership (group_id, member_id, member_type, authorities, origin) values ('%s', '%s', '%s', '%s', '%s')";

  private static final String addExternalMapSql =
      "insert into external_group_mapping (group_id, external_group, added, origin) values (?, ?, ?, ?)";

  private RandomValueStringGenerator generator = new RandomValueStringGenerator();

  private IdentityZone zone =
      MultitenancyFixture.identityZone(generator.generate(), generator.generate());

  @Before
  public void initJdbcScimGroupMembershipManagerTests() {

    JdbcTemplate template = new JdbcTemplate(dataSource);

    JdbcPagingListFactory pagingListFactory = new JdbcPagingListFactory(template, limitSqlAdapter);
    udao = new JdbcScimUserProvisioning(template, pagingListFactory);
    gdao = new JdbcScimGroupProvisioning(template, pagingListFactory);

    dao = new JdbcScimGroupMembershipManager(template, pagingListFactory);
    dao.setScimGroupProvisioning(gdao);
    dao.setScimUserProvisioning(udao);
    dao.setDefaultUserGroups(Collections.singleton("uaa.user"));

    for (String id : Arrays.asList(zone.getId(), IdentityZone.getUaa().getId())) {
      String g1 = id.equals(zone.getId()) ? zone.getId() + "-" + "g1" : "g1";
      String g2 = id.equals(zone.getId()) ? zone.getId() + "-" + "g2" : "g2";
      String g3 = id.equals(zone.getId()) ? zone.getId() + "-" + "g3" : "g3";
      String m1 = id.equals(zone.getId()) ? zone.getId() + "-" + "m1" : "m1";
      String m2 = id.equals(zone.getId()) ? zone.getId() + "-" + "m2" : "m2";
      String m3 = id.equals(zone.getId()) ? zone.getId() + "-" + "m3" : "m3";
      addGroup(g1, "test1", id);
      addGroup(g2, "test2", id);
      addGroup(g3, "test3", id);
      addUser(m1, "test", id);
      addUser(m2, "test", id);
      addUser(m3, "test", id);
      mapExternalGroup(g1, g1 + "-external", UAA);
      mapExternalGroup(g2, g2 + "-external", LOGIN_SERVER);
      mapExternalGroup(g3, g3 + "-external", UAA);
    }
    validateCount(0);
  }

  private void mapExternalGroup(String gId, String external, String origin) {
    Timestamp now = new Timestamp(System.currentTimeMillis());
    jdbcTemplate.update(addExternalMapSql, gId, external, now, origin);
  }

  private void addMember(String gId, String mId, String mType, String authorities) {
    addMember(gId, mId, mType, authorities, OriginKeys.UAA);
  }

  private void addMember(String gId, String mId, String mType, String authorities, String origin) {
    gId = IdentityZoneHolder.isUaa() ? gId : IdentityZoneHolder.get().getId() + "-" + gId;
    mId = IdentityZoneHolder.isUaa() ? mId : IdentityZoneHolder.get().getId() + "-" + mId;
    jdbcTemplate.execute(String.format(addMemberSqlFormat, gId, mId, mType, authorities, origin));
  }

  private void addGroup(String id, String name, String zoneId) {
    TestUtils.assertNoSuchUser(jdbcTemplate, "id", id);
    jdbcTemplate.execute(String.format(addGroupSqlFormat, id, name, zoneId));
  }

  private void addUser(String id, String password, String zoneId) {
    TestUtils.assertNoSuchUser(jdbcTemplate, "id", id);
    jdbcTemplate.execute(
        String.format(addUserSqlFormat, id, id, password, id, id, id, id, "", zoneId));
  }

  private void validateCount(int expected) {
    int existingMemberCount =
        jdbcTemplate.queryForObject(
            "select count(*) from groups g, group_membership gm where g.identity_zone_id=? and gm.group_id=g.id",
            new Object[] {IdentityZoneHolder.get().getId()},
            Integer.class);
    assertEquals(expected, existingMemberCount);
  }

  private void validateUserGroups(String id, String... gNm) {
    Set<ScimGroup> directGroups = dao.getGroupsWithMember(id, false);
    assertNotNull(directGroups);
    Set<ScimGroup> indirectGroups = dao.getGroupsWithMember(id, true);
    indirectGroups.removeAll(directGroups);
    assertNotNull(indirectGroups);

    Set<String> expectedAuthorities = Collections.<String>emptySet();
    if (gNm != null) {
      expectedAuthorities = new HashSet<>(Arrays.asList(gNm));
    }
    expectedAuthorities.add("uaa.user");

    assertEquals(expectedAuthorities.size(), directGroups.size() + indirectGroups.size());
    for (ScimGroup group : directGroups) {
      assertTrue(expectedAuthorities.contains(group.getDisplayName()));
    }
    for (ScimGroup group : indirectGroups) {
      assertTrue(expectedAuthorities.contains(group.getDisplayName() + ".i"));
    }
  }

  @After
  public void cleanupDataSource() throws Exception {
    IdentityZoneHolder.clear();
    TestUtils.deleteFrom(dataSource, "group_membership");
    TestUtils.deleteFrom(dataSource, "groups");
    TestUtils.deleteFrom(dataSource, "users");
    validateCount(0);
  }

  @Test
  public void canQuery_Filter_Has_ZoneIn_Effect() throws Exception {
    addMembers();
    validateCount(4);
    String id = generator.generate();
    IdentityZone zone = MultitenancyFixture.identityZone(id, id);
    IdentityZoneHolder.set(zone);
    assertEquals(0, dao.query("origin eq \"" + OriginKeys.UAA + "\"").size());
    IdentityZoneHolder.clear();
    assertEquals(4, dao.query("origin eq \"" + OriginKeys.UAA + "\"").size());
    assertEquals(4, dao.query("origin eq \"" + OriginKeys.UAA + "\"", "member_id", true).size());
    assertEquals(4, dao.query("origin eq \"" + OriginKeys.UAA + "\"", "1,2", true).size());
    assertEquals(4, dao.query("origin eq \"" + OriginKeys.UAA + "\"", "origin", true).size());
  }

  @Test(expected = IllegalArgumentException.class)
  public void cannotQuery_Filter_Has_Unknown_Sort() throws Exception {
    dao.query("origin eq \"" + OriginKeys.UAA + "\"", "unknown,origin", true);
  }

  @Test
  public void canDeleteWithFilter1() throws Exception {
    addMembers();
    validateCount(4);
    dao.delete("origin eq \"" + OriginKeys.UAA + "\"");
    validateCount(0);
  }

  @Test
  public void canDeleteWithFilter2() throws Exception {
    addMembers();
    validateCount(4);
    dao.delete("origin eq \"" + OriginKeys.ORIGIN + "\"");
    validateCount(4);
  }

  @Test
  public void canDeleteWithFilter3() throws Exception {
    addMembers();
    validateCount(4);
    dao.delete("member_id eq \"m3\" and origin eq \"" + OriginKeys.UAA + "\"");
    validateCount(2);
  }

  @Test
  public void canDeleteWithFilter4() throws Exception {
    addMembers();
    validateCount(4);
    dao.delete("member_id sw \"m\" and origin eq \"" + OriginKeys.UAA + "\"");
    validateCount(1);
  }

  @Test
  public void canDeleteWithFilter5() throws Exception {
    addMembers();
    validateCount(4);
    dao.delete("member_id sw \"m\" and origin eq \"" + OriginKeys.LDAP + "\"");
    validateCount(4);
  }

  @Test
  public void cannot_Delete_With_Filter_Outside_Zone() throws Exception {
    String id = generator.generate();
    addMembers();
    validateCount(4);
    IdentityZone zone = MultitenancyFixture.identityZone(id, id);
    IdentityZoneHolder.set(zone);
    dao.delete("member_id eq \"m3\" and origin eq \"" + OriginKeys.UAA + "\"");
    IdentityZoneHolder.clear();
    validateCount(4);
  }

  @Test
  public void canGetGroupsForMember() {
    addMembers();

    Set<ScimGroup> groups = dao.getGroupsWithMember("g2", false);
    assertNotNull(groups);
    assertEquals(1, groups.size());

    groups = dao.getGroupsWithMember("m3", true);
    assertNotNull(groups);
    assertEquals(3, groups.size());
  }

  private void addMembers(String origin) {
    addMember("g1", "m3", "USER", "READER", origin);
    addMember("g1", "g2", "GROUP", "READER", origin);
    addMember("g3", "m2", "USER", "READER,WRITER", origin);
    addMember("g2", "m3", "USER", "READER", origin);
  }

  private void addMembers() {
    addMembers(OriginKeys.UAA);
  }

  @Test
  public void test_zone_deleted() {
    String zoneAdminId = generator.generate();
    addGroup(zoneAdminId, "zones." + zone.getId() + ".admin", IdentityZone.getUaa().getId());
    addMember(zoneAdminId, "m1", "USER", "MEMBER", OriginKeys.UAA);

    IdentityZoneHolder.set(zone);
    addMembers();
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)",
            new Object[] {IdentityZoneHolder.get().getId()},
            Integer.class),
        is(4));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from groups where identity_zone_id=?",
            new Object[] {IdentityZoneHolder.get().getId()},
            Integer.class),
        is(3));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from external_group_mapping where group_id in (select id from groups where identity_zone_id=?)",
            new Object[] {IdentityZoneHolder.get().getId()},
            Integer.class),
        is(3));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=? and displayName like ?)",
            new Object[] {
              IdentityZone.getUaa().getId(), "zones." + IdentityZoneHolder.get().getId() + ".%"
            },
            Integer.class),
        is(1));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from groups where identity_zone_id=? and displayName like ?",
            new Object[] {
              IdentityZone.getUaa().getId(), "zones." + IdentityZoneHolder.get().getId() + ".%"
            },
            Integer.class),
        is(1));
    gdao.onApplicationEvent(new EntityDeletedEvent<>(zone, null));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)",
            new Object[] {IdentityZoneHolder.get().getId()},
            Integer.class),
        is(0));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from groups where identity_zone_id=?",
            new Object[] {IdentityZoneHolder.get().getId()},
            Integer.class),
        is(0));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from external_group_mapping where group_id in (select id from groups where identity_zone_id=?)",
            new Object[] {IdentityZoneHolder.get().getId()},
            Integer.class),
        is(0));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=? and displayName like ?)",
            new Object[] {
              IdentityZone.getUaa().getId(), "zones." + IdentityZoneHolder.get().getId() + ".%"
            },
            Integer.class),
        is(0));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from groups where identity_zone_id=? and displayName like ?",
            new Object[] {
              IdentityZone.getUaa().getId(), "zones." + IdentityZoneHolder.get().getId() + ".%"
            },
            Integer.class),
        is(0));
  }

  @Test
  public void test_provider_deleted() {
    IdentityZoneHolder.set(zone);
    addMembers(LOGIN_SERVER);
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)",
            new Object[] {IdentityZoneHolder.get().getId()},
            Integer.class),
        is(4));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from groups where identity_zone_id=?",
            new Object[] {IdentityZoneHolder.get().getId()},
            Integer.class),
        is(3));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from external_group_mapping where origin = ? and group_id in (select id from groups where identity_zone_id=?)",
            new Object[] {LOGIN_SERVER, IdentityZoneHolder.get().getId()},
            Integer.class),
        is(1));

    IdentityProvider loginServer =
        new IdentityProvider().setOriginKey(LOGIN_SERVER).setIdentityZoneId(zone.getId());
    gdao.onApplicationEvent(new EntityDeletedEvent<>(loginServer, null));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)",
            new Object[] {IdentityZoneHolder.get().getId()},
            Integer.class),
        is(0));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from groups where identity_zone_id=?",
            new Object[] {IdentityZoneHolder.get().getId()},
            Integer.class),
        is(3));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from external_group_mapping where origin = ? and group_id in (select id from groups where identity_zone_id=?)",
            new Object[] {LOGIN_SERVER, IdentityZoneHolder.get().getId()},
            Integer.class),
        is(0));
  }

  @Test
  public void test_cannot_delete_uaa_zone() {
    addMembers();
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)",
            new Object[] {IdentityZoneHolder.get().getId()},
            Integer.class),
        is(4));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from groups where identity_zone_id=?",
            new Object[] {IdentityZoneHolder.get().getId()},
            Integer.class),
        is(4));
    gdao.onApplicationEvent(new EntityDeletedEvent<>(IdentityZone.getUaa(), null));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)",
            new Object[] {IdentityZoneHolder.get().getId()},
            Integer.class),
        is(4));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from groups where identity_zone_id=?",
            new Object[] {IdentityZoneHolder.get().getId()},
            Integer.class),
        is(4));
  }

  @Test
  public void test_cannot_delete_uaa_provider() {
    IdentityZoneHolder.set(zone);
    addMembers(LOGIN_SERVER);
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)",
            new Object[] {IdentityZoneHolder.get().getId()},
            Integer.class),
        is(4));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from groups where identity_zone_id=?",
            new Object[] {IdentityZoneHolder.get().getId()},
            Integer.class),
        is(3));
    IdentityProvider loginServer =
        new IdentityProvider().setOriginKey(UAA).setIdentityZoneId(zone.getId());
    gdao.onApplicationEvent(new EntityDeletedEvent<>(loginServer, null));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)",
            new Object[] {IdentityZoneHolder.get().getId()},
            Integer.class),
        is(4));
    assertThat(
        jdbcTemplate.queryForObject(
            "select count(*) from groups where identity_zone_id=?",
            new Object[] {IdentityZoneHolder.get().getId()},
            Integer.class),
        is(3));
  }

  @Test
  public void canGetGroupsForMemberEvenWhenCycleExistsInGroupHierarchy() {
    addMember("g1", "m3", "USER", "READER");
    addMember("g1", "g2", "GROUP", "READER");
    addMember("g2", "g3", "GROUP", "READER");
    addMember("g3", "g1", "GROUP", "READER");

    Set<ScimGroup> groups = dao.getGroupsWithMember("m3", true);
    assertNotNull(groups);
    assertEquals(4, groups.size());
  }

  @Test
  public void canAddMember() throws Exception {
    validateCount(0);
    ScimGroupMember m1 = new ScimGroupMember("m1", ScimGroupMember.Type.USER, null);
    ScimGroupMember m2 = dao.addMember("g2", m1);
    validateCount(1);
    assertEquals(ScimGroupMember.Type.USER, m2.getType());
    assertEquals(ScimGroupMember.GROUP_MEMBER, m2.getRoles());
    assertEquals("m1", m2.getMemberId());
    validateUserGroups("m1", "test2");
  }

  @Test(expected = ScimResourceNotFoundException.class)
  public void addMember_In_Different_Zone_Causes_Issues() throws Exception {
    String subdomain = generator.generate();
    IdentityZone otherZone = MultitenancyFixture.identityZone(subdomain, subdomain);
    IdentityZoneHolder.set(otherZone);
    ScimGroupMember m1 = new ScimGroupMember("m1", ScimGroupMember.Type.USER, null);
    m1.setOrigin(OriginKeys.UAA);
    dao.addMember("g2", m1);
  }

  @Test(expected = ScimResourceNotFoundException.class)
  public void canAddMember_Validate_Origin_and_ZoneId() throws Exception {
    String subdomain = generator.generate();
    IdentityZone otherZone = MultitenancyFixture.identityZone(subdomain, subdomain);
    IdentityZoneHolder.set(otherZone);
    validateCount(0);
    ScimGroupMember m1 = new ScimGroupMember("m1", ScimGroupMember.Type.USER, null);
    m1.setOrigin(OriginKeys.UAA);
    dao.addMember("g2", m1);
  }

  @Test
  public void canAddNestedGroupMember() {
    addMember("g2", "m1", "USER", "READER");

    ScimGroupMember g2 =
        new ScimGroupMember("g2", ScimGroupMember.Type.GROUP, ScimGroupMember.GROUP_ADMIN);
    g2 = dao.addMember("g1", g2);
    assertEquals(ScimGroupMember.Type.GROUP, g2.getType());
    assertEquals(ScimGroupMember.GROUP_ADMIN, g2.getRoles());
    assertEquals("g2", g2.getMemberId());
    validateUserGroups("m1", "test1.i", "test2");
  }

  @Test(expected = InvalidScimResourceException.class)
  public void cannotNestGroupWithinItself() {
    ScimGroupMember g2 =
        new ScimGroupMember("g2", ScimGroupMember.Type.GROUP, ScimGroupMember.GROUP_ADMIN);
    dao.addMember("g2", g2);
  }

  @Test
  public void canGetMembers() throws Exception {
    addMember("g1", "m1", "USER", "READER");
    addMember("g1", "g2", "GROUP", "READER");
    addMember("g3", "m2", "USER", "READER,WRITER");

    List<ScimGroupMember> members = dao.getMembers("g1", null, false);
    assertNotNull(members);
    assertEquals(2, members.size());

    members = dao.getMembers("g2", null, false);
    assertNotNull(members);
    assertEquals(0, members.size());
  }

  @Test
  public void canGetMembers_Fails_In_Other_Zone() throws Exception {
    addMember("g1", "m1", "USER", "READER");
    addMember("g1", "g2", "GROUP", "READER");
    addMember("g3", "m2", "USER", "READER,WRITER");
    IdentityZoneHolder.set(
        MultitenancyFixture.identityZone(generator.generate(), generator.generate()));
    assertEquals(0, dao.getMembers("g1", null, false).size());
  }

  @Test
  public void testBackwardsCompatibilityToMemberAuthorities() {
    addMember("g1", "m1", "USER", "READ");
    addMember("g1", "g2", "GROUP", "member");
    addMember("g1", "m2", "USER", "READER,write");

    List<ScimGroupMember> members = dao.getMembers("g1", null, false);
    assertNotNull(members);
    assertEquals(3, members.size());
    List<ScimGroupMember> readers = new ArrayList<ScimGroupMember>(),
        writers = new ArrayList<ScimGroupMember>();
    for (ScimGroupMember member : members) {
      if (member.getRoles().contains(ScimGroupMember.Role.READER)) {
        readers.add(member);
      }
      if (member.getRoles().contains(ScimGroupMember.Role.WRITER)) {
        writers.add(member);
      }
    }
    assertEquals(2, readers.size());
    assertEquals(1, writers.size());
  }

  @Test
  public void canGetDefaultGroupsUsingGetGroupsForMember() {
    Set<ScimGroup> groups = dao.getGroupsWithMember("m1", false);
    assertNotNull(groups);
    assertEquals(1, groups.size());
  }

  @Test
  public void canGetAdminMembers() {
    addMember("g1", "m3", "USER", "READER,WRITER");
    addMember("g1", "g2", "GROUP", "READER");

    assertEquals(1, dao.getMembers("g1", ScimGroupMember.Role.WRITER).size());
    assertTrue(
        dao.getMembers("g1", ScimGroupMember.Role.WRITER).contains(new ScimGroupMember("m3")));

    assertEquals(0, dao.getMembers("g2", ScimGroupMember.Role.WRITER).size());
  }

  @Test
  public void canGetMembersByAuthority() {
    addMember("g1", "m3", "USER", "READER,WRITER");
    addMember("g1", "g2", "GROUP", "READER,MEMBER");
    addMember("g2", "g3", "GROUP", "MEMBER");

    assertEquals(1, dao.getMembers("g1", ScimGroupMember.Role.MEMBER).size());
    assertEquals(2, dao.getMembers("g1", ScimGroupMember.Role.READER).size());
    assertEquals(1, dao.getMembers("g1", ScimGroupMember.Role.WRITER).size());

    assertEquals(1, dao.getMembers("g2", ScimGroupMember.Role.MEMBER).size());
    assertEquals(0, dao.getMembers("g2", ScimGroupMember.Role.WRITER).size());
  }

  @Test
  public void canGetMemberById() throws Exception {
    addMember("g3", "m2", "USER", "READER,WRITER");

    ScimGroupMember m = dao.getMemberById("g3", "m2");
    assertEquals(ScimGroupMember.Type.USER, m.getType());
    assertEquals(ScimGroupMember.GROUP_ADMIN, m.getRoles());
  }

  @Test
  public void canUpdateMember() throws Exception {
    addMember("g1", "m1", "USER", "READER");
    validateCount(1);
    ScimGroupMember m1 =
        new ScimGroupMember("m1", ScimGroupMember.Type.USER, ScimGroupMember.GROUP_ADMIN);
    ScimGroupMember m2 = dao.updateMember("g1", m1);
    assertEquals(ScimGroupMember.GROUP_ADMIN, m2.getRoles());
    assertNotSame(m1, m2);

    validateCount(1);
    validateUserGroups("m1", "test1");
  }

  @Test
  public void canUpdateOrAddMembers() {
    dao.addMember(
        "g1", new ScimGroupMember("m1", ScimGroupMember.Type.USER, ScimGroupMember.GROUP_MEMBER));
    dao.addMember(
        "g1", new ScimGroupMember("g2", ScimGroupMember.Type.GROUP, ScimGroupMember.GROUP_MEMBER));
    dao.addMember(
        "g2", new ScimGroupMember("m2", ScimGroupMember.Type.USER, ScimGroupMember.GROUP_ADMIN));
    validateCount(3);
    validateUserGroups("m1", "test1");
    validateUserGroups("m2", "test2", "test1.i");

    ScimGroupMember g2 =
        new ScimGroupMember("g2", ScimGroupMember.Type.GROUP, ScimGroupMember.GROUP_ADMIN);
    ScimGroupMember m3 =
        new ScimGroupMember("m3", ScimGroupMember.Type.USER, ScimGroupMember.GROUP_MEMBER);
    List<ScimGroupMember> members = dao.updateOrAddMembers("g1", Arrays.asList(g2, m3));

    validateCount(3);
    assertEquals(2, members.size());
    assertTrue(members.contains(new ScimGroupMember("g2", ScimGroupMember.Type.GROUP, null)));
    assertTrue(members.contains(new ScimGroupMember("m3", ScimGroupMember.Type.USER, null)));
    assertFalse(members.contains(new ScimGroupMember("m1", ScimGroupMember.Type.USER, null)));
    validateUserGroups("m3", "test1");
    validateUserGroups("m2", "test2", "test1.i");
    validateUserGroups("m1");
  }

  @Test
  public void canRemoveMemberById() throws Exception {
    addMember("g1", "m1", "USER", "READER");
    validateCount(1);

    dao.removeMemberById("g1", "m1");
    validateCount(0);
    try {
      dao.getMemberById("g1", "m1");
      fail("member should not exist");
    } catch (MemberNotFoundException ex) {

    }
  }

  @Test
  public void canRemoveNestedGroupMember() {
    dao.addMember(
        "g1", new ScimGroupMember("m1", ScimGroupMember.Type.USER, ScimGroupMember.GROUP_MEMBER));
    dao.addMember(
        "g1", new ScimGroupMember("g2", ScimGroupMember.Type.GROUP, ScimGroupMember.GROUP_MEMBER));
    dao.addMember(
        "g2", new ScimGroupMember("m2", ScimGroupMember.Type.USER, ScimGroupMember.GROUP_ADMIN));
    validateCount(3);
    validateUserGroups("m1", "test1");
    validateUserGroups("m2", "test2", "test1.i");

    dao.removeMemberById("g1", "g2");
    try {
      dao.getMemberById("g1", "g2");
      fail("member should not exist");
    } catch (MemberNotFoundException ex) {
    }
    validateCount(2);
    validateUserGroups("m1", "test1");
    validateUserGroups("m2", "test2");
  }

  @Test
  public void canRemoveAllMembers() {
    dao.addMember(
        "g1", new ScimGroupMember("m1", ScimGroupMember.Type.USER, ScimGroupMember.GROUP_MEMBER));
    dao.addMember(
        "g1", new ScimGroupMember("g2", ScimGroupMember.Type.GROUP, ScimGroupMember.GROUP_MEMBER));
    dao.addMember(
        "g2", new ScimGroupMember("m2", ScimGroupMember.Type.USER, ScimGroupMember.GROUP_ADMIN));
    validateCount(3);
    validateUserGroups("m1", "test1");
    validateUserGroups("m2", "test2", "test1.i");

    dao.removeMembersByGroupId("g1");
    validateCount(1);
    try {
      dao.getMemberById("g1", "m1");
      fail("member should not exist");
    } catch (MemberNotFoundException ex) {
    }
    validateUserGroups("m1");
    validateUserGroups("m2", "test2");
  }
}
 private IdentityZone getIdentityZone() throws Exception {
   String subdomain = generator.generate();
   return mockMvcUtils.createOtherIdentityZone(
       subdomain, getMockMvc(), getWebApplicationContext());
 }