Merge branch 'develop' of https://github.com/mifosio/identity into develop
diff --git a/api/src/main/java/io/mifos/identity/api/v1/client/IdentityManager.java b/api/src/main/java/io/mifos/identity/api/v1/client/IdentityManager.java
index 33b746b..295900e 100644
--- a/api/src/main/java/io/mifos/identity/api/v1/client/IdentityManager.java
+++ b/api/src/main/java/io/mifos/identity/api/v1/client/IdentityManager.java
@@ -168,6 +168,35 @@
   void deleteApplicationPermission(@PathVariable("applicationidentifier") String applicationIdentifier,
                                    @PathVariable("permissionidentifier") String permittableEndpointGroupIdentifier);
 
+  @RequestMapping(value = "/applications/{applicationidentifier}/callendpointset", method = RequestMethod.POST,
+          consumes = {MediaType.APPLICATION_JSON_VALUE},
+          produces = {MediaType.ALL_VALUE})
+  void createApplicationCallEndpointSet(@PathVariable("applicationidentifier") String applicationIdentifier, CallEndpointSet callEndpointSet);
+
+  @RequestMapping(value = "/applications/{applicationidentifier}/callendpointset/{callendpointsetidentifier}", method = RequestMethod.PUT,
+          consumes = {MediaType.APPLICATION_JSON_VALUE},
+          produces = {MediaType.ALL_VALUE})
+  void changeApplicationCallEndpointSet(@PathVariable("applicationidentifier") String applicationIdentifier,
+                                        @PathVariable("callendpointsetidentifier") String callEndpointSetIdentifier,
+                                        CallEndpointSet callEndpointSet);
+
+  @RequestMapping(value = "/applications/{applicationidentifier}/callendpointset", method = RequestMethod.GET,
+          consumes = {MediaType.APPLICATION_JSON_VALUE},
+          produces = {MediaType.ALL_VALUE})
+  List<CallEndpointSet> getApplicationCallEndpointSets(@PathVariable("applicationidentifier") String applicationIdentifier);
+
+  @RequestMapping(value = "/applications/{applicationidentifier}/callendpointset/{callendpointsetidentifier}", method = RequestMethod.GET,
+          consumes = {MediaType.APPLICATION_JSON_VALUE},
+          produces = {MediaType.ALL_VALUE})
+  CallEndpointSet getApplicationCallEndpointSet(@PathVariable("applicationidentifier") String applicationIdentifier,
+                                                @PathVariable("callendpointsetidentifier") String callEndpointSetIdentifier);
+
+  @RequestMapping(value = "/applications/{applicationidentifier}/callendpointset/{callendpointsetidentifier}", method = RequestMethod.DELETE,
+          consumes = {MediaType.APPLICATION_JSON_VALUE},
+          produces = {MediaType.ALL_VALUE})
+  void deleteApplicationCallEndpointSet(@PathVariable("applicationidentifier") String applicationIdentifier,
+                                    @PathVariable("callendpointsetidentifier") String callEndpointSetIdentifier);
+
   @RequestMapping(value = "/applications/{applicationidentifier}/permissions/{permissionidentifier}/users/{useridentifier}/enabled", method = RequestMethod.PUT,
           consumes = {MediaType.APPLICATION_JSON_VALUE},
           produces = {MediaType.ALL_VALUE})
diff --git a/api/src/main/java/io/mifos/identity/api/v1/domain/Authentication.java b/api/src/main/java/io/mifos/identity/api/v1/domain/Authentication.java
index 5faa63a..a5a4b53 100644
--- a/api/src/main/java/io/mifos/identity/api/v1/domain/Authentication.java
+++ b/api/src/main/java/io/mifos/identity/api/v1/domain/Authentication.java
@@ -17,6 +17,8 @@
 
 import org.hibernate.validator.constraints.NotBlank;
 
+import javax.annotation.Nullable;
+import javax.validation.constraints.Null;
 import java.util.Objects;
 
 /**
@@ -38,8 +40,9 @@
 
   /**
    * If password expiration is in the past, then the tokens provided only allow the user to change his/her password.
+   * If password expiration is null then password will never expire.
    */
-  @NotBlank
+  @Nullable
   private String passwordExpiration;
 
   public Authentication()
diff --git a/api/src/main/java/io/mifos/identity/api/v1/domain/CallEndpointSet.java b/api/src/main/java/io/mifos/identity/api/v1/domain/CallEndpointSet.java
new file mode 100644
index 0000000..ce4e402
--- /dev/null
+++ b/api/src/main/java/io/mifos/identity/api/v1/domain/CallEndpointSet.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.identity.api.v1.domain;
+
+import io.mifos.core.lang.validation.constraints.ValidIdentifier;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings({"unused", "WeakerAccess"})
+public class CallEndpointSet {
+  @ValidIdentifier
+  private String identifier;
+
+  @NotNull
+  private List<String> permittableEndpointGroupIdentifiers;
+
+  public CallEndpointSet() {
+  }
+
+  public String getIdentifier() {
+    return identifier;
+  }
+
+  public void setIdentifier(String identifier) {
+    this.identifier = identifier;
+  }
+
+  public List<String> getPermittableEndpointGroupIdentifiers() {
+    return permittableEndpointGroupIdentifiers;
+  }
+
+  public void setPermittableEndpointGroupIdentifiers(List<String> permittableEndpointGroupIdentifiers) {
+    this.permittableEndpointGroupIdentifiers = permittableEndpointGroupIdentifiers;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    CallEndpointSet that = (CallEndpointSet) o;
+    return Objects.equals(identifier, that.identifier) &&
+            Objects.equals(permittableEndpointGroupIdentifiers, that.permittableEndpointGroupIdentifiers);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(identifier, permittableEndpointGroupIdentifiers);
+  }
+
+  @Override
+  public String toString() {
+    return "CallEndpointSet{" +
+            "identifier='" + identifier + '\'' +
+            ", permittableEndpointGroupIdentifiers=" + permittableEndpointGroupIdentifiers +
+            '}';
+  }
+}
diff --git a/api/src/main/java/io/mifos/identity/api/v1/domain/Role.java b/api/src/main/java/io/mifos/identity/api/v1/domain/Role.java
index 04c55ee..a8d20bb 100644
--- a/api/src/main/java/io/mifos/identity/api/v1/domain/Role.java
+++ b/api/src/main/java/io/mifos/identity/api/v1/domain/Role.java
@@ -16,6 +16,7 @@
 package io.mifos.identity.api.v1.domain;
 
 import io.mifos.core.lang.validation.constraints.ValidIdentifier;
+import io.mifos.identity.api.v1.validation.ChangeableRole;
 import org.hibernate.validator.constraints.ScriptAssert;
 import org.springframework.util.Assert;
 
@@ -29,9 +30,9 @@
  * @author Myrle Krantz
  */
 @SuppressWarnings("unused")
-@ScriptAssert(lang = "javascript", script = "_this.identifier !== \"deactivated\"")
 public class Role {
   @ValidIdentifier
+  @ChangeableRole
   private String identifier;
 
   @NotNull
diff --git a/api/src/main/java/io/mifos/identity/api/v1/domain/RoleIdentifier.java b/api/src/main/java/io/mifos/identity/api/v1/domain/RoleIdentifier.java
index 6ba990d..ff18c63 100644
--- a/api/src/main/java/io/mifos/identity/api/v1/domain/RoleIdentifier.java
+++ b/api/src/main/java/io/mifos/identity/api/v1/domain/RoleIdentifier.java
@@ -15,6 +15,7 @@
  */
 package io.mifos.identity.api.v1.domain;
 
+import io.mifos.identity.api.v1.validation.NotRootRole;
 import org.hibernate.validator.constraints.NotBlank;
 
 import java.util.Objects;
@@ -25,6 +26,7 @@
 @SuppressWarnings({"unused", "WeakerAccess"})
 public class RoleIdentifier {
   @NotBlank
+  @NotRootRole
   private String identifier;
 
   public RoleIdentifier() {
diff --git a/api/src/main/java/io/mifos/identity/api/v1/events/ApplicationCallEndpointSetEvent.java b/api/src/main/java/io/mifos/identity/api/v1/events/ApplicationCallEndpointSetEvent.java
new file mode 100644
index 0000000..7d872be
--- /dev/null
+++ b/api/src/main/java/io/mifos/identity/api/v1/events/ApplicationCallEndpointSetEvent.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.identity.api.v1.events;
+
+import java.util.Objects;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings({"WeakerAccess", "unused"})
+public class ApplicationCallEndpointSetEvent {
+  private String applicationIdentifier;
+  private String callEndpointSetIdentifier;
+
+  public ApplicationCallEndpointSetEvent() {
+  }
+
+  public ApplicationCallEndpointSetEvent(String applicationIdentifier, String callEndpointSetIdentifier) {
+    this.applicationIdentifier = applicationIdentifier;
+    this.callEndpointSetIdentifier = callEndpointSetIdentifier;
+  }
+
+  public String getApplicationIdentifier() {
+    return applicationIdentifier;
+  }
+
+  public void setApplicationIdentifier(String applicationIdentifier) {
+    this.applicationIdentifier = applicationIdentifier;
+  }
+
+  public String getCallEndpointSetIdentifier() {
+    return callEndpointSetIdentifier;
+  }
+
+  public void setCallEndpointSetIdentifier(String callEndpointSetIdentifier) {
+    this.callEndpointSetIdentifier = callEndpointSetIdentifier;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    ApplicationCallEndpointSetEvent that = (ApplicationCallEndpointSetEvent) o;
+    return Objects.equals(applicationIdentifier, that.applicationIdentifier) &&
+            Objects.equals(callEndpointSetIdentifier, that.callEndpointSetIdentifier);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(applicationIdentifier, callEndpointSetIdentifier);
+  }
+
+  @Override
+  public String toString() {
+    return "ApplicationCallEndpointSetEvent{" +
+            "applicationIdentifier='" + applicationIdentifier + '\'' +
+            ", callEndpointSetIdentifier='" + callEndpointSetIdentifier + '\'' +
+            '}';
+  }
+}
diff --git a/api/src/main/java/io/mifos/identity/api/v1/events/EventConstants.java b/api/src/main/java/io/mifos/identity/api/v1/events/EventConstants.java
index c97ec76..5ebacb9 100644
--- a/api/src/main/java/io/mifos/identity/api/v1/events/EventConstants.java
+++ b/api/src/main/java/io/mifos/identity/api/v1/events/EventConstants.java
@@ -38,6 +38,9 @@
 
   String OPERATION_PUT_APPLICATION_SIGNATURE =   "put-application-signature";
   String OPERATION_DELETE_APPLICATION = "delete-application";
+  String OPERATION_POST_APPLICATION_CALLENDPOINTSET = "post-application-callendpointset";
+  String OPERATION_PUT_APPLICATION_CALLENDPOINTSET = "put-application-callendpointset";
+  String OPERATION_DELETE_APPLICATION_CALLENDPOINTSET = "delete-application-callendpointset";
   String OPERATION_POST_APPLICATION_PERMISSION = "post-application-permission";
   String OPERATION_DELETE_APPLICATION_PERMISSION = "delete-application-permission";
   String OPERATION_PUT_APPLICATION_PERMISSION_USER_ENABLED = "put-application-permission-user-enabled";
@@ -56,6 +59,9 @@
 
   String SELECTOR_PUT_APPLICATION_SIGNATURE = OPERATION_HEADER + " = '" + OPERATION_PUT_APPLICATION_SIGNATURE + "'";
   String SELECTOR_DELETE_APPLICATION = OPERATION_HEADER + " = '" + OPERATION_DELETE_APPLICATION + "'";
+  String SELECTOR_POST_APPLICATION_CALLENDPOINTSET = OPERATION_HEADER + " = '" + OPERATION_POST_APPLICATION_CALLENDPOINTSET + "'";
+  String SELECTOR_PUT_APPLICATION_CALLENDPOINTSET = OPERATION_HEADER + " = '" + OPERATION_PUT_APPLICATION_CALLENDPOINTSET + "'";
+  String SELECTOR_DELETE_APPLICATION_CALLENDPOINTSET = OPERATION_HEADER + " = '" + OPERATION_DELETE_APPLICATION_CALLENDPOINTSET + "'";
   String SELECTOR_POST_APPLICATION_PERMISSION = OPERATION_HEADER + " = '" + OPERATION_POST_APPLICATION_PERMISSION + "'";
   String SELECTOR_DELETE_APPLICATION_PERMISSION = OPERATION_HEADER + " = '" + OPERATION_DELETE_APPLICATION_PERMISSION + "'";
   String SELECTOR_PUT_APPLICATION_PERMISSION_USER_ENABLED = OPERATION_HEADER + " = '" + OPERATION_PUT_APPLICATION_PERMISSION_USER_ENABLED + "'";
diff --git a/api/src/main/java/io/mifos/identity/api/v1/validation/ChangeableRole.java b/api/src/main/java/io/mifos/identity/api/v1/validation/ChangeableRole.java
new file mode 100644
index 0000000..f2e0344
--- /dev/null
+++ b/api/src/main/java/io/mifos/identity/api/v1/validation/ChangeableRole.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.identity.api.v1.validation;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.*;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings({"unused", "WeakerAccess"})
+@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Constraint(
+        validatedBy = {CheckRoleChangeable.class}
+)
+public @interface ChangeableRole {
+  String message() default "The role 'pharaoh' cannot be changed or deleted.";
+
+  Class<?>[] groups() default {};
+
+  Class<? extends Payload>[] payload() default {};
+}
diff --git a/api/src/main/java/io/mifos/identity/api/v1/validation/CheckNotRootRole.java b/api/src/main/java/io/mifos/identity/api/v1/validation/CheckNotRootRole.java
new file mode 100644
index 0000000..5a2e1a9
--- /dev/null
+++ b/api/src/main/java/io/mifos/identity/api/v1/validation/CheckNotRootRole.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.identity.api.v1.validation;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+/**
+ * @author Myrle Krantz
+ */
+public class CheckNotRootRole implements ConstraintValidator<NotRootRole, String> {
+  @Override
+  public void initialize(final NotRootRole constraintAnnotation) {
+
+  }
+
+  @Override
+  public boolean isValid(final String value, final ConstraintValidatorContext context) {
+    return !value.equals("pharaoh");
+  }
+}
\ No newline at end of file
diff --git a/api/src/main/java/io/mifos/identity/api/v1/validation/CheckRoleChangeable.java b/api/src/main/java/io/mifos/identity/api/v1/validation/CheckRoleChangeable.java
new file mode 100644
index 0000000..5f09e9e
--- /dev/null
+++ b/api/src/main/java/io/mifos/identity/api/v1/validation/CheckRoleChangeable.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.identity.api.v1.validation;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings("WeakerAccess")
+public class CheckRoleChangeable implements ConstraintValidator<ChangeableRole, String> {
+  @Override
+  public void initialize(final ChangeableRole constraintAnnotation) {
+
+  }
+
+  @Override
+  public boolean isValid(final String value, final ConstraintValidatorContext context) {
+    return isChangeableRoleIdentifier(value);
+  }
+
+  public static boolean isChangeableRoleIdentifier(final String value) {
+    return !value.equals("pharaoh") && !value.equals("deactivated");
+  }
+}
diff --git a/api/src/main/java/io/mifos/identity/api/v1/validation/NotRootRole.java b/api/src/main/java/io/mifos/identity/api/v1/validation/NotRootRole.java
new file mode 100644
index 0000000..4bb3ec9
--- /dev/null
+++ b/api/src/main/java/io/mifos/identity/api/v1/validation/NotRootRole.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.identity.api.v1.validation;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.*;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings({"unused", "WeakerAccess"})
+@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Constraint(
+        validatedBy = {CheckNotRootRole.class}
+)
+public @interface NotRootRole {
+  String message() default "The role 'pharaoh' cannot be assigned to a user.";
+
+  Class<?>[] groups() default {};
+
+  Class<? extends Payload>[] payload() default {};
+}
diff --git a/api/src/test/java/io/mifos/identity/api/v1/domain/CallEndpointSetTest.java b/api/src/test/java/io/mifos/identity/api/v1/domain/CallEndpointSetTest.java
new file mode 100644
index 0000000..3bc4e1a
--- /dev/null
+++ b/api/src/test/java/io/mifos/identity/api/v1/domain/CallEndpointSetTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.identity.api.v1.domain;
+
+import io.mifos.core.test.domain.ValidationTest;
+import io.mifos.core.test.domain.ValidationTestCase;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * @author Myrle Krantz
+ */
+@RunWith(Parameterized.class)
+public class CallEndpointSetTest extends ValidationTest<CallEndpointSet> {
+
+  public CallEndpointSetTest(final ValidationTestCase<CallEndpointSet> testCase) {
+    super(testCase);
+  }
+
+  @Parameterized.Parameters
+  public static Collection testCases() {
+    final Collection<ValidationTestCase> ret = new ArrayList<>();
+
+    ret.add(new ValidationTestCase<CallEndpointSet>("validCase")
+            .adjustment(x -> {})
+            .valid(true));
+    ret.add(new ValidationTestCase<CallEndpointSet>("invalid identifier")
+            .adjustment(x -> x.setIdentifier(null))
+            .valid(false));
+    ret.add(new ValidationTestCase<CallEndpointSet>("null list")
+            .adjustment(x -> x.setPermittableEndpointGroupIdentifiers(null))
+            .valid(false));
+
+    return ret;
+  }
+
+  @Override
+  protected CallEndpointSet createValidTestSubject() {
+    final CallEndpointSet ret = new CallEndpointSet();
+    ret.setIdentifier("blah");
+    ret.setPermittableEndpointGroupIdentifiers(Collections.emptyList());
+    return ret;
+  }
+
+}
\ No newline at end of file
diff --git a/api/src/test/java/io/mifos/identity/v1/domain/RoleIdentifierTest.java b/api/src/test/java/io/mifos/identity/v1/domain/RoleIdentifierTest.java
index 3711c4c..f73fc24 100644
--- a/api/src/test/java/io/mifos/identity/v1/domain/RoleIdentifierTest.java
+++ b/api/src/test/java/io/mifos/identity/v1/domain/RoleIdentifierTest.java
@@ -41,6 +41,12 @@
     ret.add(new ValidationTestCase<RoleIdentifier>("spaceyIdentifier")
             .adjustment(x -> x.setIdentifier("   "))
             .valid(false));
+    ret.add(new ValidationTestCase<RoleIdentifier>("deactivated")
+            .adjustment(x -> x.setIdentifier("deactivated"))
+            .valid(true));
+    ret.add(new ValidationTestCase<RoleIdentifier>("pharaoh")
+            .adjustment(x -> x.setIdentifier("pharaoh"))
+            .valid(false));
 
     return ret;
   }
@@ -54,7 +60,7 @@
 
   private RoleIdentifier createValidTestSubject()
   {
-    return new RoleIdentifier("pharaoh");
+    return new RoleIdentifier("scribe");
   }
 
   @Test()
diff --git a/api/src/test/java/io/mifos/identity/v1/domain/RoleTest.java b/api/src/test/java/io/mifos/identity/v1/domain/RoleTest.java
new file mode 100644
index 0000000..517a5cb
--- /dev/null
+++ b/api/src/test/java/io/mifos/identity/v1/domain/RoleTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.identity.v1.domain;
+
+import io.mifos.core.test.domain.ValidationTest;
+import io.mifos.core.test.domain.ValidationTestCase;
+import io.mifos.identity.api.v1.domain.Role;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * @author Myrle Krantz
+ */
+@RunWith(Parameterized.class)
+public class RoleTest extends ValidationTest<Role> {
+
+  public RoleTest(final ValidationTestCase<Role> testCase) {
+    super(testCase);
+  }
+
+  @Parameterized.Parameters
+  public static Collection testCases() {
+    final Collection<ValidationTestCase> ret = new ArrayList<>();
+
+    ret.add(new ValidationTestCase<Role>("validCase")
+            .adjustment(x -> {})
+            .valid(true));
+    ret.add(new ValidationTestCase<Role>("deactivated")
+            .adjustment(x -> x.setIdentifier("deactivated"))
+            .valid(false));
+    ret.add(new ValidationTestCase<Role>("pharaoh")
+            .adjustment(x -> x.setIdentifier("pharaoh"))
+            .valid(false));
+
+    return ret;
+  }
+
+  @Override
+  protected Role createValidTestSubject() {
+    final Role ret = new Role();
+    ret.setIdentifier("blah");
+    ret.setPermissions(Collections.emptyList());
+    return ret;
+  }
+}
diff --git a/component-test/src/main/java/AbstractComponentTest.java b/component-test/src/main/java/AbstractComponentTest.java
index 63ce463..7f18da3 100644
--- a/component-test/src/main/java/AbstractComponentTest.java
+++ b/component-test/src/main/java/AbstractComponentTest.java
@@ -16,6 +16,7 @@
 import io.mifos.anubis.api.v1.domain.AllowedOperation;
 import io.mifos.anubis.test.v1.TenantApplicationSecurityEnvironmentTestRule;
 import io.mifos.core.api.config.EnableApiFactory;
+import io.mifos.core.api.context.AutoGuest;
 import io.mifos.core.api.context.AutoUserContext;
 import io.mifos.core.api.util.ApiFactory;
 import io.mifos.core.api.util.UserContextHolder;
@@ -105,7 +106,7 @@
     if (!alreadyInitialized) {
       try (final AutoUserContext ignored
                    = tenantApplicationSecurityEnvironment.createAutoSeshatContext()) {
-        identityManager.initialize(Helpers.encodePassword(ADMIN_PASSWORD));
+        identityManager.initialize(TestEnvironment.encodePassword(ADMIN_PASSWORD));
       }
       alreadyInitialized = true;
     }
@@ -123,30 +124,8 @@
   }
 
   AutoUserContext enableAndLoginAdmin() throws InterruptedException {
-    final Authentication adminAuthenticationToChangePassword =
-            getTestSubject().login(ADMIN_IDENTIFIER, Helpers.encodePassword(ADMIN_PASSWORD));
-    Assert.assertNotNull(adminAuthenticationToChangePassword);
-
-    {
-      final boolean found = eventRecorder
-              .wait(EventConstants.OPERATION_AUTHENTICATE, ADMIN_IDENTIFIER);
-      Assert.assertTrue(found);
-    }
-
-    //Change password, then re-authenticate
-    try (final AutoUserContext ignore = new AutoUserContext(ADMIN_IDENTIFIER, adminAuthenticationToChangePassword.getAccessToken()))
-    {
-      getTestSubject().changeUserPassword(ADMIN_IDENTIFIER, new Password(Helpers.encodePassword(ADMIN_PASSWORD)));
-
-      {
-        final boolean found = eventRecorder
-                .wait(EventConstants.OPERATION_PUT_USER_PASSWORD, ADMIN_IDENTIFIER);
-        Assert.assertTrue(found);
-      }
-    }
-
     final Authentication adminAuthentication =
-            getTestSubject().login(ADMIN_IDENTIFIER, Helpers.encodePassword(ADMIN_PASSWORD));
+            getTestSubject().login(ADMIN_IDENTIFIER, TestEnvironment.encodePassword(ADMIN_PASSWORD));
     Assert.assertNotNull(adminAuthentication);
 
     {
@@ -155,8 +134,6 @@
       Assert.assertTrue(found);
     }
 
-    eventRecorder.clear();
-
     return new AutoUserContext(ADMIN_IDENTIFIER, adminAuthentication.getAccessToken());
   }
 
@@ -165,20 +142,20 @@
    * to access any other endpoint.
    */
   String createUserWithNonexpiredPassword(final String password, final String role) throws InterruptedException {
-    final String username = Helpers.generateRandomIdentifier("Ahmes");
+    final String username = testEnvironment.generateUniqueIdentifer("Ahmes");
     try (final AutoUserContext ignore = enableAndLoginAdmin()) {
-      getTestSubject().createUser(new UserWithPassword(username, role, Helpers.encodePassword(password)));
+      getTestSubject().createUser(new UserWithPassword(username, role, TestEnvironment.encodePassword(password)));
 
       {
         final boolean found = eventRecorder.wait(EventConstants.OPERATION_POST_USER, username);
         Assert.assertTrue(found);
       }
 
-      final Authentication passwordOnlyAuthentication = getTestSubject().login(username, Helpers.encodePassword(password));
+      final Authentication passwordOnlyAuthentication = getTestSubject().login(username, TestEnvironment.encodePassword(password));
 
       try (final AutoUserContext ignore2 = new AutoUserContext(username, passwordOnlyAuthentication.getAccessToken()))
       {
-        getTestSubject().changeUserPassword(username, new Password(Helpers.encodePassword(password)));
+        getTestSubject().changeUserPassword(username, new Password(TestEnvironment.encodePassword(password)));
         final boolean found = eventRecorder.wait(EventConstants.OPERATION_PUT_USER_PASSWORD, username);
         Assert.assertTrue(found);
       }
@@ -187,7 +164,7 @@
   }
 
   String generateRoleIdentifier() {
-    return Helpers.generateRandomIdentifier("scribe");
+    return testEnvironment.generateUniqueIdentifer("scribe");
   }
 
   Role buildRole(final String identifier, final Permission... permission) {
@@ -211,7 +188,7 @@
     return permission;
   }
 
-  private Permission buildSelfPermission() {
+  Permission buildSelfPermission() {
     final Permission permission = new Permission();
     permission.setAllowedOperations(AllowedOperation.ALL);
     permission.setPermittableEndpointGroupIdentifier(PermittableGroupIds.SELF_MANAGEMENT);
@@ -238,7 +215,10 @@
   }
 
   AutoUserContext loginUser(final String userId, final String password) {
-    final Authentication authentication = getTestSubject().login(userId, Helpers.encodePassword(password));
+    final Authentication authentication;
+    try (AutoUserContext ignored = new AutoGuest()) {
+      authentication = getTestSubject().login(userId, TestEnvironment.encodePassword(password));
+    }
     return new AutoUserContext(userId, authentication.getAccessToken());
   }
 }
diff --git a/component-test/src/main/java/Helpers.java b/component-test/src/main/java/Helpers.java
index 5b43621..6251a7c 100644
--- a/component-test/src/main/java/Helpers.java
+++ b/component-test/src/main/java/Helpers.java
@@ -29,12 +29,4 @@
       final String identifier) {
     return users.stream().map(getIdentifier).filter(i -> i.equals(identifier)).findAny().isPresent();
   }
-
-  static String generateRandomIdentifier(final String prefix) {
-    return prefix + Math.abs(new Random().nextInt());
-  }
-
-  static String encodePassword(final String password) {
-    return Base64Utils.encodeToString(password.getBytes());
-  }
 }
diff --git a/component-test/src/main/java/TestApplications.java b/component-test/src/main/java/TestApplications.java
index a46a999..4dbf6f8 100644
--- a/component-test/src/main/java/TestApplications.java
+++ b/component-test/src/main/java/TestApplications.java
@@ -20,11 +20,9 @@
 import io.mifos.core.api.util.NotFoundException;
 import io.mifos.core.lang.security.RsaKeyPairFactory;
 import io.mifos.identity.api.v1.PermittableGroupIds;
+import io.mifos.identity.api.v1.domain.CallEndpointSet;
 import io.mifos.identity.api.v1.domain.Permission;
-import io.mifos.identity.api.v1.events.ApplicationPermissionEvent;
-import io.mifos.identity.api.v1.events.ApplicationPermissionUserEvent;
-import io.mifos.identity.api.v1.events.ApplicationSignatureEvent;
-import io.mifos.identity.api.v1.events.EventConstants;
+import io.mifos.identity.api.v1.events.*;
 import org.apache.commons.lang.RandomStringUtils;
 import org.junit.Assert;
 import org.junit.Test;
@@ -204,6 +202,56 @@
   }
 
   @Test
+  public void manageApplicationEndpointSet() throws InterruptedException {
+    try (final AutoUserContext ignored
+                 = tenantApplicationSecurityEnvironment.createAutoSeshatContext()) {
+      final ApplicationSignatureEvent appPlusSig = setApplicationSignature();
+
+      final String endpointSetIdentifier = testEnvironment.generateUniqueIdentifer("epset");
+      final CallEndpointSet endpointSet = new CallEndpointSet();
+      endpointSet.setIdentifier(endpointSetIdentifier);
+      endpointSet.setPermittableEndpointGroupIdentifiers(Collections.emptyList());
+
+      getTestSubject().createApplicationCallEndpointSet(appPlusSig.getApplicationIdentifier(), endpointSet);
+
+      Assert.assertTrue(eventRecorder.wait(EventConstants.OPERATION_POST_APPLICATION_CALLENDPOINTSET,
+              new ApplicationCallEndpointSetEvent(appPlusSig.getApplicationIdentifier(), endpointSetIdentifier)));
+
+      final List<CallEndpointSet> applicationEndpointSets = getTestSubject().getApplicationCallEndpointSets(appPlusSig.getApplicationIdentifier());
+      Assert.assertTrue(applicationEndpointSets.contains(endpointSet));
+
+      final CallEndpointSet storedEndpointSet = getTestSubject().getApplicationCallEndpointSet(
+              appPlusSig.getApplicationIdentifier(),
+              endpointSetIdentifier);
+      Assert.assertEquals(endpointSet, storedEndpointSet);
+
+      endpointSet.setPermittableEndpointGroupIdentifiers(Collections.singletonList(PermittableGroupIds.ROLE_MANAGEMENT));
+      getTestSubject().changeApplicationCallEndpointSet(
+              appPlusSig.getApplicationIdentifier(),
+              endpointSetIdentifier,
+              endpointSet);
+
+      Assert.assertTrue(eventRecorder.wait(EventConstants.OPERATION_PUT_APPLICATION_CALLENDPOINTSET,
+              new ApplicationCallEndpointSetEvent(appPlusSig.getApplicationIdentifier(), endpointSetIdentifier)));
+
+      final CallEndpointSet storedEndpointSet2 = getTestSubject().getApplicationCallEndpointSet(
+              appPlusSig.getApplicationIdentifier(),
+              endpointSetIdentifier);
+      Assert.assertEquals(endpointSet, storedEndpointSet2);
+
+      final List<CallEndpointSet> applicationEndpointSets2 = getTestSubject().getApplicationCallEndpointSets(appPlusSig.getApplicationIdentifier());
+      Assert.assertTrue(applicationEndpointSets2.size() == 1);
+
+      getTestSubject().deleteApplicationCallEndpointSet(appPlusSig.getApplicationIdentifier(), endpointSetIdentifier);
+      Assert.assertTrue(eventRecorder.wait(EventConstants.OPERATION_DELETE_APPLICATION_CALLENDPOINTSET,
+              new ApplicationCallEndpointSetEvent(appPlusSig.getApplicationIdentifier(), endpointSetIdentifier)));
+
+      final List<CallEndpointSet> applicationEndpointSets3 = getTestSubject().getApplicationCallEndpointSets(appPlusSig.getApplicationIdentifier());
+      Assert.assertTrue(applicationEndpointSets3.isEmpty());
+    }
+  }
+
+  @Test
   public void applicationIssuedRefreshTokenHappyCase() throws InterruptedException {
     final ApplicationSignatureEvent appPlusSig;
     final Permission rolePermission = buildRolePermission();
diff --git a/component-test/src/main/java/TestAuthentication.java b/component-test/src/main/java/TestAuthentication.java
index 0759568..0b58edd 100644
--- a/component-test/src/main/java/TestAuthentication.java
+++ b/component-test/src/main/java/TestAuthentication.java
@@ -22,6 +22,7 @@
 import io.mifos.core.api.util.NotFoundException;
 import io.mifos.core.lang.AutoTenantContext;
 import io.mifos.core.lang.security.RsaPublicKeyBuilder;
+import io.mifos.core.test.env.TestEnvironment;
 import io.mifos.identity.api.v1.domain.*;
 import org.junit.Assert;
 import org.junit.Test;
@@ -61,7 +62,7 @@
 
   @Test(expected = NotFoundException.class)
   public void testAdminIncorrectLogin() throws InterruptedException {
-    getTestSubject().login(ADMIN_IDENTIFIER, Helpers.encodePassword("set"));
+    getTestSubject().login(ADMIN_IDENTIFIER, TestEnvironment.encodePassword("set"));
     Assert.fail("login with wrong password should fail with not found exception.");
   }
 
@@ -70,7 +71,7 @@
     try (final AutoUserContext ignored = tenantApplicationSecurityEnvironment.createAutoSeshatContext()) {
       try (final AutoTenantContext ignored2 = new AutoTenantContext())
       {
-        getTestSubject().login(ADMIN_IDENTIFIER, Helpers.encodePassword(ADMIN_PASSWORD));
+        getTestSubject().login(ADMIN_IDENTIFIER, TestEnvironment.encodePassword(ADMIN_PASSWORD));
       }
     }
     Assert.fail("login without tenant header set should fail with bad request.");
@@ -80,7 +81,7 @@
   public void testPermissionsCorrectInAdminToken() throws InterruptedException {
     try (final AutoUserContext ignore = enableAndLoginAdmin()) {
       final Authentication adminAuthentication =
-              getTestSubject().login(ADMIN_IDENTIFIER, Helpers.encodePassword(ADMIN_PASSWORD));
+              getTestSubject().login(ADMIN_IDENTIFIER, TestEnvironment.encodePassword(ADMIN_PASSWORD));
       Assert.assertNotNull(adminAuthentication);
 
       final TokenContent tokenContent = SystemSecurityEnvironment.getTokenContent(adminAuthentication.getAccessToken(), getPublicKey());
diff --git a/component-test/src/main/java/TestKeyRotation.java b/component-test/src/main/java/TestKeyRotation.java
index 92719c4..22c1cbb 100644
--- a/component-test/src/main/java/TestKeyRotation.java
+++ b/component-test/src/main/java/TestKeyRotation.java
@@ -16,9 +16,11 @@
 import io.mifos.anubis.api.v1.client.Anubis;
 import io.mifos.anubis.api.v1.domain.ApplicationSignatureSet;
 import io.mifos.anubis.api.v1.domain.Signature;
+import io.mifos.core.api.context.AutoGuest;
 import io.mifos.core.api.context.AutoSeshat;
 import io.mifos.core.api.context.AutoUserContext;
 import io.mifos.core.api.util.NotFoundException;
+import io.mifos.core.test.env.TestEnvironment;
 import io.mifos.identity.api.v1.domain.Authentication;
 import org.junit.Assert;
 import org.junit.Test;
@@ -51,7 +53,7 @@
 
     final String systemToken = tenantApplicationSecurityEnvironment.getSystemSecurityEnvironment().systemToken(APP_NAME);
 
-    try (final AutoSeshat ignored1 = new AutoSeshat(systemToken)) {
+    try (final AutoUserContext ignored1 = new AutoSeshat(systemToken)) {
       //Create a signature set then test that it is listed.
       final String timestamp = getTestSubject().createSignatureSet().getTimestamp();
       {
@@ -60,8 +62,10 @@
       }
 
 
-      final Authentication adminAuthenticationOnFirstKeyset =
-              getTestSubject().login(ADMIN_IDENTIFIER, Helpers.encodePassword(ADMIN_PASSWORD));
+      final Authentication adminAuthenticationOnFirstKeyset;
+      try (final AutoUserContext ignored2 = new AutoGuest()) {
+        adminAuthenticationOnFirstKeyset = getTestSubject().login(ADMIN_IDENTIFIER, TestEnvironment.encodePassword(ADMIN_PASSWORD));
+      }
 
       //TODO: Assert.assertTrue(canAccessResources(adminAuthenticationOnFirstKeyset));
 
@@ -82,8 +86,10 @@
         Assert.assertTrue(signatureSets.contains(timestamp2));
       }
 
-      final Authentication adminAuthenticationOnSecondKeyset =
-              getTestSubject().login(ADMIN_IDENTIFIER, Helpers.encodePassword(ADMIN_PASSWORD));
+      //final Authentication adminAuthenticationOnSecondKeyset;
+      try (final AutoUserContext ignored2 = new AutoGuest()) {
+        getTestSubject().login(ADMIN_IDENTIFIER, TestEnvironment.encodePassword(ADMIN_PASSWORD));
+      }
 
       //TODO: Assert.assertTrue(canAccessResources(adminAuthenticationOnFirstKeyset));
       //TODO: Assert.assertTrue(canAccessResources(adminAuthenticationOnSecondKeyset));
@@ -119,7 +125,7 @@
     }
   }
 
-  private boolean canAccessResources(Authentication adminAuthentication) {
+  private boolean canAccessResources(final Authentication adminAuthentication) {
     try (final AutoUserContext ignored = new AutoUserContext(ADMIN_IDENTIFIER, adminAuthentication.getAccessToken())) {
       getTestSubject().getUsers();
       return true;
diff --git a/component-test/src/main/java/TestPasswords.java b/component-test/src/main/java/TestPasswords.java
index f4c0260..7e0e677 100644
--- a/component-test/src/main/java/TestPasswords.java
+++ b/component-test/src/main/java/TestPasswords.java
@@ -15,15 +15,19 @@
  */
 import io.mifos.core.api.context.AutoUserContext;
 import io.mifos.core.api.util.NotFoundException;
-import io.mifos.core.test.domain.DateStampChecker;
-import io.mifos.identity.api.v1.events.EventConstants;
+import io.mifos.core.test.domain.TimeStampChecker;
+import io.mifos.core.test.env.TestEnvironment;
 import io.mifos.identity.api.v1.domain.Authentication;
 import io.mifos.identity.api.v1.domain.Password;
 import io.mifos.identity.api.v1.domain.UserWithPassword;
+import io.mifos.identity.api.v1.events.EventConstants;
 import org.apache.commons.lang.RandomStringUtils;
 import org.junit.Assert;
 import org.junit.Test;
 
+import java.time.Duration;
+import java.util.concurrent.TimeUnit;
+
 /**
  * @author Myrle Krantz
  */
@@ -34,7 +38,7 @@
     final String username = createUserWithNonexpiredPassword(AHMES_PASSWORD, ADMIN_ROLE);
 
     try (final AutoUserContext ignore = enableAndLoginAdmin()) {
-      final String newPassword = Helpers.encodePassword(
+      final String newPassword = TestEnvironment.encodePassword(
               AHMES_PASSWORD + "make_it_a_little_longer");
 
       {
@@ -48,7 +52,7 @@
       try (final AutoUserContext ignore2 = new AutoUserContext(username, newPasswordAuthentication.getAccessToken()))
       {
         getTestSubject().createUser(new UserWithPassword("Ahmes_friend", "scribe",
-                Helpers.encodePassword(AHMES_FRIENDS_PASSWORD)));
+                TestEnvironment.encodePassword(AHMES_FRIENDS_PASSWORD)));
         Assert.fail("createUser should've thrown an exception because the password is admin reset.");
       }
       catch (final NotFoundException ex)
@@ -68,7 +72,7 @@
       {
         //Now it should be possible to create a user since the user changed the password herself.
         getTestSubject().createUser(new UserWithPassword("Ahmes_friend", "scribe",
-                Helpers.encodePassword(AHMES_FRIENDS_PASSWORD)));
+                TestEnvironment.encodePassword(AHMES_FRIENDS_PASSWORD)));
       }
     }
   }
@@ -76,7 +80,7 @@
   @Test
   public void testAdminChangeAdminPassword() throws InterruptedException {
     try (final AutoUserContext ignore = enableAndLoginAdmin()) {
-      final String newPassword = Helpers.encodePassword(
+      final String newPassword = TestEnvironment.encodePassword(
               ADMIN_PASSWORD + "make_it_a_little_longer");
 
       {
@@ -87,7 +91,7 @@
       }
 
       try {
-        final String oldPassword = Helpers.encodePassword(ADMIN_PASSWORD);
+        final String oldPassword = TestEnvironment.encodePassword(ADMIN_PASSWORD);
         getTestSubject().login(ADMIN_IDENTIFIER, oldPassword);
         Assert.fail("Login with the old password should not succeed.");
       } catch (final NotFoundException ignored) {
@@ -97,7 +101,7 @@
 
       {
         //Change the password back so the tests after this don't fail.
-        getTestSubject().changeUserPassword(ADMIN_IDENTIFIER, new Password(Helpers.encodePassword(ADMIN_PASSWORD)));
+        getTestSubject().changeUserPassword(ADMIN_IDENTIFIER, new Password(TestEnvironment.encodePassword(ADMIN_PASSWORD)));
         boolean found = eventRecorder.wait(EventConstants.OPERATION_PUT_USER_PASSWORD, ADMIN_IDENTIFIER);
         Assert.assertTrue(found);
       }
@@ -108,14 +112,11 @@
   public void testUserChangeOwnPasswordButNotAdminPassword() throws InterruptedException {
     final String username = createUserWithNonexpiredPassword(AHMES_PASSWORD, "scribe");
 
-    final Authentication userAuthentication =
-            getTestSubject().login(username, Helpers.encodePassword(AHMES_PASSWORD));
-
-    try (AutoUserContext ignored = new AutoUserContext(username, userAuthentication.getAccessToken()))
+    try (final AutoUserContext ignored = loginUser(username, AHMES_PASSWORD))
     {
       final String newPassword = "new password";
       {
-        getTestSubject().changeUserPassword(username, new Password(Helpers.encodePassword(newPassword)));
+        getTestSubject().changeUserPassword(username, new Password(TestEnvironment.encodePassword(newPassword)));
 
         boolean found = eventRecorder.wait(EventConstants.OPERATION_PUT_USER_PASSWORD, username);
         Assert.assertTrue(found);
@@ -123,14 +124,14 @@
 
       Thread.sleep(100);
 
-      final DateStampChecker passwordExpirationChecker = DateStampChecker.inTheFuture(93);
-      final Authentication userAuthenticationAfterPasswordChange = getTestSubject().login(username, Helpers.encodePassword(newPassword));
+      final TimeStampChecker passwordExpirationChecker = TimeStampChecker.inTheFutureWithWiggleRoom(Duration.ofDays(93), Duration.ofHours(24));
+      final Authentication userAuthenticationAfterPasswordChange = getTestSubject().login(username, TestEnvironment.encodePassword(newPassword));
       final String passwordExpiration = userAuthenticationAfterPasswordChange.getPasswordExpiration();
       passwordExpirationChecker.assertCorrect(passwordExpiration);
 
       //noinspection EmptyCatchBlock
       try {
-        getTestSubject().changeUserPassword(ADMIN_IDENTIFIER, new Password(Helpers.encodePassword(newPassword)));
+        getTestSubject().changeUserPassword(ADMIN_IDENTIFIER, new Password(TestEnvironment.encodePassword(newPassword)));
         Assert.fail("trying to change the admins password should fail.");
       }
       catch (final NotFoundException ex) {
@@ -140,7 +141,7 @@
 
 
       try {
-        getTestSubject().login(ADMIN_IDENTIFIER, Helpers.encodePassword(newPassword));
+        getTestSubject().login(ADMIN_IDENTIFIER, TestEnvironment.encodePassword(newPassword));
         Assert.fail("logging into admin with the new password should likewise fail.");
       }
       catch (final NotFoundException ex) {
@@ -166,4 +167,34 @@
     }
 
   }
+
+  @Test
+  public void activatedAntonyPasswordDoesntExpire() throws InterruptedException {
+
+    try (final AutoUserContext ignored = enableAndLoginAdmin()) {
+      final Authentication adminAuthentication =
+              getTestSubject().login(ADMIN_IDENTIFIER, TestEnvironment.encodePassword(ADMIN_PASSWORD));
+      Assert.assertEquals(null, adminAuthentication.getPasswordExpiration());
+    }
+  }
+
+  @Test
+  public void onlyAntonyCanSetAntonyPassword() throws InterruptedException {
+    try (final AutoUserContext ignored = enableAndLoginAdmin()) {
+
+      final String roleIdentifier = createRole(buildUserPermission(), buildSelfPermission(), buildRolePermission());
+      final String username = createUserWithNonexpiredPassword(AHMES_PASSWORD, roleIdentifier);
+
+      TimeUnit.SECONDS.sleep(1);
+      try (final AutoUserContext ignored2 = loginUser(username, AHMES_PASSWORD)) {
+        getTestSubject().changeUserPassword(ADMIN_IDENTIFIER, new Password(TestEnvironment.encodePassword(AHMES_FRIENDS_PASSWORD)));
+        Assert.fail("Should not be able to change antony's password from any account other than antony's.");
+      }
+      catch (final IllegalArgumentException expected) {
+        //noinspection EmptyCatchBlock
+      }
+    }
+
+    getTestSubject().login(ADMIN_IDENTIFIER, TestEnvironment.encodePassword(ADMIN_PASSWORD));
+  }
 }
diff --git a/component-test/src/main/java/TestPermittableGroups.java b/component-test/src/main/java/TestPermittableGroups.java
index cf1d462..d078849 100644
--- a/component-test/src/main/java/TestPermittableGroups.java
+++ b/component-test/src/main/java/TestPermittableGroups.java
@@ -51,7 +51,7 @@
   @Test(expected = IllegalArgumentException.class)
   public void createWithIllegalMethodThrows() throws InterruptedException {
     try (final AutoUserContext ignore = enableAndLoginAdmin()) {
-      final String identifier = Helpers.generateRandomIdentifier("group");
+      final String identifier = testEnvironment.generateUniqueIdentifer("group");
 
       final PermittableEndpoint permittableEndpoint = buildPermittableEndpoint();
       permittableEndpoint.setMethod("blah");
@@ -65,7 +65,7 @@
   @Test
   public void create() throws InterruptedException {
     try (final AutoUserContext ignore = enableAndLoginAdmin()) {
-      final String identifier = Helpers.generateRandomIdentifier("group");
+      final String identifier = testEnvironment.generateUniqueIdentifer("group");
 
       final PermittableEndpoint permittableEndpoint = buildPermittableEndpoint();
       final PermittableGroup group = buildPermittableGroup(identifier, permittableEndpoint);
diff --git a/component-test/src/main/java/TestProvisioning.java b/component-test/src/main/java/TestProvisioning.java
index d81a210..bef58b4 100644
--- a/component-test/src/main/java/TestProvisioning.java
+++ b/component-test/src/main/java/TestProvisioning.java
@@ -102,7 +102,7 @@
 
       final String invalidSeshatToken = "notBearer";
       try (final AutoSeshat ignored2 = new AutoSeshat(invalidSeshatToken)){
-        testSubject.initialize(Helpers.encodePassword(ADMIN_PASSWORD));
+        testSubject.initialize(TestEnvironment.encodePassword(ADMIN_PASSWORD));
         Assert.fail("The key had the wrong format.  This should've failed.");
       }
       catch (final InvalidTokenException ignored2)
@@ -112,7 +112,7 @@
 
       final String wrongSystemToken = systemTokenFromWrongKey();
       try (final AutoSeshat ignored2 = new AutoSeshat(wrongSystemToken)){
-        testSubject.initialize(Helpers.encodePassword(ADMIN_PASSWORD));
+        testSubject.initialize(TestEnvironment.encodePassword(ADMIN_PASSWORD));
         Assert.fail("The key was signed by the wrong source.  This should've failed.");
       }
       catch (final Exception e)
@@ -122,7 +122,7 @@
 
 
       try (final AutoUserContext ignored2 = tenantApplicationSecurityEnvironment.createAutoSeshatContext("goober")) {
-        testSubject.initialize(Helpers.encodePassword(ADMIN_PASSWORD));
+        testSubject.initialize(TestEnvironment.encodePassword(ADMIN_PASSWORD));
         Assert.fail("The key was intended for a different tenant.  This should've failed.");
       }
       catch (final Exception e)
@@ -131,7 +131,7 @@
       }
 
       try (final AutoUserContext ignored2 = tenantApplicationSecurityEnvironment.createAutoSeshatContext()) {
-        firstTenantSignatureSet = testSubject.initialize(Helpers.encodePassword(ADMIN_PASSWORD));
+        firstTenantSignatureSet = testSubject.initialize(TestEnvironment.encodePassword(ADMIN_PASSWORD));
 
         final Signature applicationSignature = tenantApplicationSecurityEnvironment.getAnubis().getApplicationSignature(firstTenantSignatureSet.getTimestamp());
         firstTenantIdentityManagerSignature = tenantApplicationSecurityEnvironment.getAnubis().getSignatureSet(firstTenantSignatureSet.getTimestamp()).getIdentityManagerSignature();
@@ -153,7 +153,7 @@
     try (final TenantDataStoreTestContext ignored = TenantDataStoreTestContext.forRandomTenantName(cassandraInitializer)) {
       try (final AutoUserContext ignored2
                    = tenantApplicationSecurityEnvironment.createAutoSeshatContext()) {
-        secondTenantSignatureSet = testSubject.initialize(Helpers.encodePassword(ADMIN_PASSWORD));
+        secondTenantSignatureSet = testSubject.initialize(TestEnvironment.encodePassword(ADMIN_PASSWORD));
         final Signature secondTenantIdentityManagerSignature = tenantApplicationSecurityEnvironment.getAnubis().getApplicationSignature(secondTenantSignatureSet.getTimestamp());
         Assert.assertNotEquals(firstTenantIdentityManagerSignature, secondTenantIdentityManagerSignature);
       }
diff --git a/component-test/src/main/java/TestRefreshToken.java b/component-test/src/main/java/TestRefreshToken.java
index 47a4040..f27eb95 100644
--- a/component-test/src/main/java/TestRefreshToken.java
+++ b/component-test/src/main/java/TestRefreshToken.java
@@ -17,6 +17,7 @@
 import io.mifos.core.api.context.AutoUserContext;
 import io.mifos.core.api.util.InvalidTokenException;
 import io.mifos.core.test.domain.TimeStampChecker;
+import io.mifos.core.test.env.TestEnvironment;
 import io.mifos.core.test.fixture.TenantDataStoreTestContext;
 import io.mifos.identity.api.v1.domain.Authentication;
 import io.mifos.identity.api.v1.domain.Password;
@@ -51,7 +52,7 @@
 
   @Test(expected = InvalidTokenException.class)
   public void adminLoginRefreshTokenShouldTimeOut() throws InterruptedException {
-    getTestSubject().login(ADMIN_IDENTIFIER, Helpers.encodePassword(ADMIN_PASSWORD));
+    getTestSubject().login(ADMIN_IDENTIFIER, TestEnvironment.encodePassword(ADMIN_PASSWORD));
 
     Thread.sleep(TimeUnit.SECONDS.toMillis(REFRESH_TOKEN_TIME_TO_LIVE + 1));
 
@@ -60,7 +61,7 @@
 
   @Test
   public void afterAccessTokenExpiresRefreshTokenShouldAcquireNewAccessToken() throws InterruptedException {
-    getTestSubject().login(ADMIN_IDENTIFIER, Helpers.encodePassword(ADMIN_PASSWORD));
+    getTestSubject().login(ADMIN_IDENTIFIER, TestEnvironment.encodePassword(ADMIN_PASSWORD));
 
     Thread.sleep(TimeUnit.SECONDS.toMillis(ACCESS_TOKEN_TIME_TO_LIVE + 1));
 
@@ -68,19 +69,19 @@
             getTestSubject().refresh();
 
     try (final AutoUserContext ignored = new AutoUserContext(ADMIN_IDENTIFIER, refreshAccessTokenAuthentication.getAccessToken())) {
-      getTestSubject().changeUserPassword(ADMIN_IDENTIFIER, new Password(Helpers.encodePassword(ADMIN_PASSWORD)));
+      getTestSubject().changeUserPassword(ADMIN_IDENTIFIER, new Password(TestEnvironment.encodePassword(ADMIN_PASSWORD)));
     }
   }
 
   @Test(expected = InvalidTokenException.class)
   public void refreshTokenShouldGrantAccessOnlyToOneTenant()
   {
-    getTestSubject().login(ADMIN_IDENTIFIER, Helpers.encodePassword(ADMIN_PASSWORD));
+    getTestSubject().login(ADMIN_IDENTIFIER, TestEnvironment.encodePassword(ADMIN_PASSWORD));
 
     try (final TenantDataStoreTestContext ignored = TenantDataStoreTestContext.forRandomTenantName(cassandraInitializer)) {
       try (final AutoUserContext ignored2
                    = tenantApplicationSecurityEnvironment.createAutoSeshatContext()) {
-        getTestSubject().initialize(Helpers.encodePassword(ADMIN_PASSWORD));
+        getTestSubject().initialize(TestEnvironment.encodePassword(ADMIN_PASSWORD));
       }
 
       getTestSubject().refresh();
@@ -90,7 +91,7 @@
   @Test
   public void expirationDatesShouldBeCorrectIsoDateTimes() throws InterruptedException {
     final Authentication authentication =
-            getTestSubject().login(ADMIN_IDENTIFIER, Helpers.encodePassword(ADMIN_PASSWORD));
+            getTestSubject().login(ADMIN_IDENTIFIER, TestEnvironment.encodePassword(ADMIN_PASSWORD));
 
     final TimeStampChecker preRefreshAccessTokenTimeStampChecker = TimeStampChecker.inTheFuture(Duration.ofSeconds(ACCESS_TOKEN_TIME_TO_LIVE));
     final TimeStampChecker refreshTokenTimeStampChecker = TimeStampChecker.inTheFuture(Duration.ofSeconds(REFRESH_TOKEN_TIME_TO_LIVE));
diff --git a/component-test/src/main/java/TestRoles.java b/component-test/src/main/java/TestRoles.java
index bc934f0..16db3b8 100644
--- a/component-test/src/main/java/TestRoles.java
+++ b/component-test/src/main/java/TestRoles.java
@@ -25,6 +25,8 @@
 import java.util.Collections;
 import java.util.List;
 
+import static io.mifos.identity.internal.util.IdentityConstants.SU_ROLE;
+
 /**
  * @author Myrle Krantz
  */
@@ -113,4 +115,41 @@
       Assert.assertEquals(role, changedRole);
     }
   }
-}
+
+  @Test
+  public void testChangePharaohRoleFails() throws InterruptedException {
+    try (final AutoUserContext ignore = enableAndLoginAdmin()) {
+      final Role referenceRole = getTestSubject().getRole(SU_ROLE);
+      final Role roleChangeRequest = buildRole(SU_ROLE, buildSelfPermission());
+
+      try {
+        getTestSubject().changeRole(SU_ROLE, roleChangeRequest);
+        Assert.fail("Should not be able to change the pharaoh role.");
+      }
+      catch (final IllegalArgumentException expected) {
+        //noinspection EmptyCatchBlock
+      }
+
+      final Role unChangedRole = getTestSubject().getRole(SU_ROLE);
+      Assert.assertEquals(referenceRole, unChangedRole);
+    }
+  }
+
+  @Test
+  public void testDeletePharaohRoleFails() throws InterruptedException {
+
+    try (final AutoUserContext ignore = enableAndLoginAdmin()) {
+      final Role adminRole = getTestSubject().getRole(ADMIN_ROLE);
+      try {
+        getTestSubject().deleteRole(ADMIN_ROLE);
+        Assert.fail("It should not be possible to delete the admin role.");
+      }
+      catch (final IllegalArgumentException expected) {
+        //noinspection EmptyCatchBlock
+      }
+
+      final Role adminRoleStillThere = getTestSubject().getRole(ADMIN_ROLE);
+      Assert.assertEquals(adminRole, adminRoleStillThere);
+    }
+  }
+}
\ No newline at end of file
diff --git a/component-test/src/main/java/TestUsers.java b/component-test/src/main/java/TestUsers.java
index 5d56549..90b7663 100644
--- a/component-test/src/main/java/TestUsers.java
+++ b/component-test/src/main/java/TestUsers.java
@@ -13,11 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 import io.mifos.anubis.api.v1.domain.AllowedOperation;
 import io.mifos.core.api.context.AutoUserContext;
-import io.mifos.identity.api.v1.events.EventConstants;
+import io.mifos.core.test.env.TestEnvironment;
 import io.mifos.identity.api.v1.PermittableGroupIds;
 import io.mifos.identity.api.v1.domain.*;
+import io.mifos.identity.api.v1.events.EventConstants;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -25,6 +27,9 @@
 import java.util.List;
 import java.util.Set;
 
+import static io.mifos.identity.internal.util.IdentityConstants.SU_NAME;
+import static io.mifos.identity.internal.util.IdentityConstants.SU_ROLE;
+
 /**
  * @author Myrle Krantz
  */
@@ -44,13 +49,13 @@
     }
 
     final Authentication userAuthentication =
-            getTestSubject().login(username, Helpers.encodePassword(AHMES_PASSWORD));
+            getTestSubject().login(username, TestEnvironment.encodePassword(AHMES_PASSWORD));
 
     Assert.assertNotNull(userAuthentication);
 
     try (final AutoUserContext ignored = new AutoUserContext(username, userAuthentication.getAccessToken())) {
       getTestSubject().createUser(new UserWithPassword("Ahmes_friend", "scribe",
-              Helpers.encodePassword(AHMES_FRIENDS_PASSWORD)));
+              TestEnvironment.encodePassword(AHMES_FRIENDS_PASSWORD)));
 
       final boolean found = eventRecorder.wait(EventConstants.OPERATION_POST_USER, "Ahmes_friend");
       Assert.assertTrue(found);
@@ -68,7 +73,7 @@
     final String userIdentifier = createUserWithNonexpiredPassword(AHMES_PASSWORD, ADMIN_ROLE);
 
     final Authentication ahmesAuthentication =
-            getTestSubject().login(userIdentifier, Helpers.encodePassword(AHMES_PASSWORD));
+            getTestSubject().login(userIdentifier, TestEnvironment.encodePassword(AHMES_PASSWORD));
 
     try (final AutoUserContext ignored = new AutoUserContext(userIdentifier, ahmesAuthentication.getAccessToken())) {
       List<User> users = getTestSubject().getUsers();
@@ -91,6 +96,27 @@
   }
 
   @Test
+  public void testChangeAntonyRoleFails() throws InterruptedException {
+    final String userIdentifier = createUserWithNonexpiredPassword(AHMES_PASSWORD, ADMIN_ROLE);
+
+    final Authentication ahmesAuthentication =
+            getTestSubject().login(userIdentifier, TestEnvironment.encodePassword(AHMES_PASSWORD));
+
+    try (final AutoUserContext ignored = new AutoUserContext(userIdentifier, ahmesAuthentication.getAccessToken())) {
+      try {
+        getTestSubject().changeUserRole(SU_NAME, new RoleIdentifier("scribe"));
+        Assert.fail("Should not be able to change the role set for antony.");
+      }
+      catch (final IllegalArgumentException expected) {
+        //noinspection EmptyCatchBlock
+      }
+
+      final User antony = getTestSubject().getUser(SU_NAME);
+      Assert.assertEquals(SU_ROLE, antony.getRole());
+    }
+  }
+
+  @Test
   public void testAdminProvisioning() throws InterruptedException {
     try (final AutoUserContext ignore = enableAndLoginAdmin()) {
       final List<Role> roleIdentifiers = getTestSubject().getRoles();
diff --git a/component-test/src/main/java/listener/ApplicationEventListener.java b/component-test/src/main/java/listener/ApplicationEventListener.java
index 936c95d..d56975a 100644
--- a/component-test/src/main/java/listener/ApplicationEventListener.java
+++ b/component-test/src/main/java/listener/ApplicationEventListener.java
@@ -17,10 +17,7 @@
 
 import io.mifos.core.lang.config.TenantHeaderFilter;
 import io.mifos.core.test.listener.EventRecorder;
-import io.mifos.identity.api.v1.events.ApplicationPermissionEvent;
-import io.mifos.identity.api.v1.events.ApplicationPermissionUserEvent;
-import io.mifos.identity.api.v1.events.ApplicationSignatureEvent;
-import io.mifos.identity.api.v1.events.EventConstants;
+import io.mifos.identity.api.v1.events.*;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.jms.annotation.JmsListener;
 import org.springframework.messaging.handler.annotation.Header;
@@ -95,4 +92,37 @@
           final String payload) throws Exception {
     eventRecorder.event(tenant, EventConstants.OPERATION_PUT_APPLICATION_PERMISSION_USER_ENABLED, payload, ApplicationPermissionUserEvent.class);
   }
-}
+
+  @JmsListener(
+          subscription = EventConstants.DESTINATION,
+          destination = EventConstants.DESTINATION,
+          selector = EventConstants.SELECTOR_POST_APPLICATION_CALLENDPOINTSET
+  )
+  public void onCreateApplicationEndpointSet(
+          @Header(TenantHeaderFilter.TENANT_HEADER)final String tenant,
+          final String payload) throws Exception {
+    eventRecorder.event(tenant, EventConstants.OPERATION_POST_APPLICATION_CALLENDPOINTSET, payload, ApplicationCallEndpointSetEvent.class);
+  }
+
+  @JmsListener(
+          subscription = EventConstants.DESTINATION,
+          destination = EventConstants.DESTINATION,
+          selector = EventConstants.SELECTOR_PUT_APPLICATION_CALLENDPOINTSET
+  )
+  public void onSetApplicationEndpointSet(
+          @Header(TenantHeaderFilter.TENANT_HEADER)final String tenant,
+          final String payload) throws Exception {
+    eventRecorder.event(tenant, EventConstants.OPERATION_PUT_APPLICATION_CALLENDPOINTSET, payload, ApplicationCallEndpointSetEvent.class);
+  }
+
+  @JmsListener(
+          subscription = EventConstants.DESTINATION,
+          destination = EventConstants.DESTINATION,
+          selector = EventConstants.SELECTOR_DELETE_APPLICATION_CALLENDPOINTSET
+  )
+  public void onDeleteApplicationEndpointSet(
+          @Header(TenantHeaderFilter.TENANT_HEADER)final String tenant,
+          final String payload) throws Exception {
+    eventRecorder.event(tenant, EventConstants.OPERATION_DELETE_APPLICATION_CALLENDPOINTSET, payload, ApplicationCallEndpointSetEvent.class);
+  }
+}
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/identity/internal/command/ChangeApplicationCallEndpointSetCommand.java b/service/src/main/java/io/mifos/identity/internal/command/ChangeApplicationCallEndpointSetCommand.java
new file mode 100644
index 0000000..c605cc9
--- /dev/null
+++ b/service/src/main/java/io/mifos/identity/internal/command/ChangeApplicationCallEndpointSetCommand.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.identity.internal.command;
+
+import io.mifos.identity.api.v1.domain.CallEndpointSet;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings("unused")
+public class ChangeApplicationCallEndpointSetCommand {
+  private String applicationIdentifier;
+  private String callEndpointSetIdentifier;
+  private CallEndpointSet callEndpointSet;
+
+  public ChangeApplicationCallEndpointSetCommand() {
+  }
+
+  public ChangeApplicationCallEndpointSetCommand(
+          String applicationIdentifier,
+          String callEndpointSetIdentifier,
+          CallEndpointSet callEndpointSet) {
+    this.applicationIdentifier = applicationIdentifier;
+    this.callEndpointSetIdentifier = callEndpointSetIdentifier;
+    this.callEndpointSet = callEndpointSet;
+  }
+
+  public String getApplicationIdentifier() {
+    return applicationIdentifier;
+  }
+
+  public void setApplicationIdentifier(String applicationIdentifier) {
+    this.applicationIdentifier = applicationIdentifier;
+  }
+
+  public String getCallEndpointSetIdentifier() {
+    return callEndpointSetIdentifier;
+  }
+
+  public void setCallEndpointSetIdentifier(String callEndpointSetIdentifier) {
+    this.callEndpointSetIdentifier = callEndpointSetIdentifier;
+  }
+
+  public CallEndpointSet getCallEndpointSet() {
+    return callEndpointSet;
+  }
+
+  public void setCallEndpointSet(CallEndpointSet callEndpointSet) {
+    this.callEndpointSet = callEndpointSet;
+  }
+}
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/identity/internal/command/CreateApplicationCallEndpointSetCommand.java b/service/src/main/java/io/mifos/identity/internal/command/CreateApplicationCallEndpointSetCommand.java
new file mode 100644
index 0000000..694d5fe
--- /dev/null
+++ b/service/src/main/java/io/mifos/identity/internal/command/CreateApplicationCallEndpointSetCommand.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.identity.internal.command;
+
+import io.mifos.identity.api.v1.domain.CallEndpointSet;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings("unused")
+public class CreateApplicationCallEndpointSetCommand {
+  private String applicationIdentifier;
+  private CallEndpointSet callEndpointSet;
+
+  public CreateApplicationCallEndpointSetCommand() {
+  }
+
+  public CreateApplicationCallEndpointSetCommand(String applicationIdentifier, CallEndpointSet callEndpointSet) {
+    this.applicationIdentifier = applicationIdentifier;
+    this.callEndpointSet = callEndpointSet;
+  }
+
+  public String getApplicationIdentifier() {
+    return applicationIdentifier;
+  }
+
+  public void setApplicationIdentifier(String applicationIdentifier) {
+    this.applicationIdentifier = applicationIdentifier;
+  }
+
+  public CallEndpointSet getCallEndpointSet() {
+    return callEndpointSet;
+  }
+
+  public void setEndpointSet(CallEndpointSet callEndpointSet) {
+    this.callEndpointSet = callEndpointSet;
+  }
+}
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/identity/internal/command/DeleteApplicationCallEndpointSetCommand.java b/service/src/main/java/io/mifos/identity/internal/command/DeleteApplicationCallEndpointSetCommand.java
new file mode 100644
index 0000000..bb8025d
--- /dev/null
+++ b/service/src/main/java/io/mifos/identity/internal/command/DeleteApplicationCallEndpointSetCommand.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.identity.internal.command;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings("unused")
+public class DeleteApplicationCallEndpointSetCommand {
+  private String applicationIdentifier;
+  private String callEndpointSetIdentifier;
+
+  public DeleteApplicationCallEndpointSetCommand() {
+  }
+
+  public DeleteApplicationCallEndpointSetCommand(String applicationIdentifier, String callEndpointSetIdentifier) {
+    this.applicationIdentifier = applicationIdentifier;
+    this.callEndpointSetIdentifier = callEndpointSetIdentifier;
+  }
+
+  public String getApplicationIdentifier() {
+    return applicationIdentifier;
+  }
+
+  public void setApplicationIdentifier(String applicationIdentifier) {
+    this.applicationIdentifier = applicationIdentifier;
+  }
+
+  public String getCallEndpointSetIdentifier() {
+    return callEndpointSetIdentifier;
+  }
+
+  public void setCallEndpointSetIdentifier(String callEndpointSetIdentifier) {
+    this.callEndpointSetIdentifier = callEndpointSetIdentifier;
+  }
+}
diff --git a/service/src/main/java/io/mifos/identity/internal/command/handler/ApplicationCommandHandler.java b/service/src/main/java/io/mifos/identity/internal/command/handler/ApplicationCommandHandler.java
index 9e4a62a..6b31a2f 100644
--- a/service/src/main/java/io/mifos/identity/internal/command/handler/ApplicationCommandHandler.java
+++ b/service/src/main/java/io/mifos/identity/internal/command/handler/ApplicationCommandHandler.java
@@ -18,11 +18,10 @@
 import io.mifos.core.command.annotation.Aggregate;
 import io.mifos.core.command.annotation.CommandHandler;
 import io.mifos.core.command.annotation.EventEmitter;
-import io.mifos.identity.api.v1.events.ApplicationPermissionEvent;
-import io.mifos.identity.api.v1.events.ApplicationPermissionUserEvent;
-import io.mifos.identity.api.v1.events.ApplicationSignatureEvent;
-import io.mifos.identity.api.v1.events.EventConstants;
+import io.mifos.core.lang.ServiceException;
+import io.mifos.identity.api.v1.events.*;
 import io.mifos.identity.internal.command.*;
+import io.mifos.identity.internal.mapper.ApplicationCallEndpointSetMapper;
 import io.mifos.identity.internal.mapper.PermissionMapper;
 import io.mifos.identity.internal.repository.*;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -31,20 +30,24 @@
 /**
  * @author Myrle Krantz
  */
+@SuppressWarnings("unused")
 @Aggregate
 @Component
 public class ApplicationCommandHandler {
   private final ApplicationSignatures applicationSignatures;
   private final ApplicationPermissions applicationPermissions;
   private final ApplicationPermissionUsers applicationPermissionUsers;
+  private final ApplicationCallEndpointSets applicationCallEndpointSets;
 
   @Autowired
   public ApplicationCommandHandler(final ApplicationSignatures applicationSignatures,
                                    final ApplicationPermissions applicationPermissions,
-                                   final ApplicationPermissionUsers applicationPermissionUsers) {
+                                   final ApplicationPermissionUsers applicationPermissionUsers,
+                                   final ApplicationCallEndpointSets applicationCallEndpointSets) {
     this.applicationSignatures = applicationSignatures;
     this.applicationPermissions = applicationPermissions;
     this.applicationPermissionUsers = applicationPermissionUsers;
+    this.applicationCallEndpointSets = applicationCallEndpointSets;
   }
 
   @CommandHandler
@@ -90,4 +93,42 @@
     applicationPermissionUsers.setEnabled(command.getApplicationIdentifier(), command.getPermittableGroupIdentifier(), command.getUserIdentifier(), command.isEnabled());
     return new ApplicationPermissionUserEvent(command.getApplicationIdentifier(), command.getPermittableGroupIdentifier(), command.getUserIdentifier());
   }
+
+  @CommandHandler
+  @EventEmitter(selectorName = EventConstants.OPERATION_HEADER, selectorValue = EventConstants.OPERATION_PUT_APPLICATION_CALLENDPOINTSET)
+  public ApplicationCallEndpointSetEvent process(final ChangeApplicationCallEndpointSetCommand command) {
+    applicationCallEndpointSets.get(command.getApplicationIdentifier(), command.getCallEndpointSetIdentifier())
+            .orElseThrow(() -> ServiceException.notFound("No application call endpoint '"
+                    + command.getApplicationIdentifier() + "." + command.getCallEndpointSetIdentifier() + "'."));
+
+    final ApplicationCallEndpointSetEntity toSave  = ApplicationCallEndpointSetMapper.mapToEntity(
+            command.getApplicationIdentifier(),
+            command.getCallEndpointSet());
+    applicationCallEndpointSets.change(toSave);
+    return new ApplicationCallEndpointSetEvent(command.getApplicationIdentifier(), command.getCallEndpointSetIdentifier());
+  }
+
+  @CommandHandler
+  @EventEmitter(selectorName = EventConstants.OPERATION_HEADER, selectorValue = EventConstants.OPERATION_POST_APPLICATION_CALLENDPOINTSET)
+  public ApplicationCallEndpointSetEvent process(final CreateApplicationCallEndpointSetCommand command) {
+    if (!applicationSignatures.signaturesExistForApplication(command.getApplicationIdentifier()))
+      throw ServiceException.notFound("No application '" + command.getApplicationIdentifier() + "'.");
+
+    final ApplicationCallEndpointSetEntity toSave  = ApplicationCallEndpointSetMapper.mapToEntity(
+            command.getApplicationIdentifier(),
+            command.getCallEndpointSet());
+    applicationCallEndpointSets.add(toSave);
+    return new ApplicationCallEndpointSetEvent(command.getApplicationIdentifier(), command.getCallEndpointSet().getIdentifier());
+  }
+
+  @CommandHandler
+  @EventEmitter(selectorName = EventConstants.OPERATION_HEADER, selectorValue = EventConstants.OPERATION_DELETE_APPLICATION_CALLENDPOINTSET)
+  public ApplicationCallEndpointSetEvent process(final DeleteApplicationCallEndpointSetCommand command) {
+    applicationCallEndpointSets.get(command.getApplicationIdentifier(), command.getCallEndpointSetIdentifier())
+            .orElseThrow(() -> ServiceException.notFound("No application call endpoint '"
+                    + command.getApplicationIdentifier() + "." + command.getCallEndpointSetIdentifier() + "'."));
+
+    applicationCallEndpointSets.delete(command.getApplicationIdentifier(), command.getCallEndpointSetIdentifier());
+    return new ApplicationCallEndpointSetEvent(command.getApplicationIdentifier(), command.getCallEndpointSetIdentifier());
+  }
 }
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/identity/internal/command/handler/AuthenticationCommandHandler.java b/service/src/main/java/io/mifos/identity/internal/command/handler/AuthenticationCommandHandler.java
index 390a01c..e5bcdca 100644
--- a/service/src/main/java/io/mifos/identity/internal/command/handler/AuthenticationCommandHandler.java
+++ b/service/src/main/java/io/mifos/identity/internal/command/handler/AuthenticationCommandHandler.java
@@ -19,11 +19,10 @@
 import io.mifos.anubis.api.v1.domain.AllowedOperation;
 import io.mifos.anubis.api.v1.domain.TokenContent;
 import io.mifos.anubis.api.v1.domain.TokenPermission;
+import io.mifos.anubis.provider.InvalidKeyTimestampException;
+import io.mifos.anubis.provider.TenantRsaKeyProvider;
 import io.mifos.anubis.security.AmitAuthenticationException;
-import io.mifos.anubis.token.TenantAccessTokenSerializer;
-import io.mifos.anubis.token.TenantRefreshTokenSerializer;
-import io.mifos.anubis.token.TokenDeserializationResult;
-import io.mifos.anubis.token.TokenSerializationResult;
+import io.mifos.anubis.token.*;
 import io.mifos.core.command.annotation.Aggregate;
 import io.mifos.core.command.annotation.CommandHandler;
 import io.mifos.core.lang.ApplicationName;
@@ -49,7 +48,10 @@
 import org.springframework.util.Base64Utils;
 
 import java.security.PrivateKey;
+import java.security.PublicKey;
 import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.util.*;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -69,6 +71,7 @@
   private final HashGenerator hashGenerator;
   private final TenantAccessTokenSerializer tenantAccessTokenSerializer;
   private final TenantRefreshTokenSerializer tenantRefreshTokenSerializer;
+  private final TenantRsaKeyProvider tenantRsaKeyProvider;
   private final JmsTemplate jmsTemplate;
   private final Gson gson;
   private final Logger logger;
@@ -93,6 +96,8 @@
                                       final TenantAccessTokenSerializer tenantAccessTokenSerializer,
                                       @SuppressWarnings("SpringJavaAutowiringInspection")
                                         final TenantRefreshTokenSerializer tenantRefreshTokenSerializer,
+                                      @SuppressWarnings("SpringJavaAutowiringInspection")
+                                        final TenantRsaKeyProvider tenantRsaKeyProvider,
                                       final JmsTemplate jmsTemplate,
                                       final ApplicationName applicationName,
                                       @Qualifier(IdentityConstants.JSON_SERIALIZER_NAME) final Gson gson,
@@ -105,6 +110,7 @@
     this.hashGenerator = hashGenerator;
     this.tenantAccessTokenSerializer = tenantAccessTokenSerializer;
     this.tenantRefreshTokenSerializer = tenantRefreshTokenSerializer;
+    this.tenantRsaKeyProvider = tenantRsaKeyProvider;
     this.jmsTemplate = jmsTemplate;
     this.gson = gson;
     this.logger = logger;
@@ -141,7 +147,7 @@
       throw AmitAuthenticationException.userPasswordCombinationNotFound();
     }
 
-    final LocalDate passwordExpiration = getExpiration(user);
+    final Optional<LocalDateTime> passwordExpiration = getExpiration(user);
 
     final TokenSerializationResult accessToken = getAccessToken(
             user.getIdentifier(),
@@ -155,7 +161,7 @@
     return new AuthenticationCommandResponse(
         accessToken.getToken(), DateConverter.toIsoString(accessToken.getExpiration()),
         refreshToken.getToken(), DateConverter.toIsoString(refreshToken.getExpiration()),
-            DateConverter.toIsoString(passwordExpiration));
+            passwordExpiration.map(DateConverter::toIsoString).orElse(null));
   }
 
   private PrivateSignatureEntity checkedGetPrivateSignature() {
@@ -176,19 +182,28 @@
     return privateTenantInfo.get();
   }
 
+  private class TenantIdentityRsaKeyProvider implements TenantApplicationRsaKeyProvider {
+    @Override
+    public PublicKey getApplicationPublicKey(final String tokenApplicationName, final String timestamp) throws InvalidKeyTimestampException {
+      if (!applicationName.toString().equals(tokenApplicationName))
+        throw new IllegalArgumentException("Currently only supporting refresh tokens issued by identity");
+      return tenantRsaKeyProvider.getPublicKey(timestamp);
+    }
+  }
+
   @CommandHandler
   public AuthenticationCommandResponse process(final RefreshTokenAuthenticationCommand command)
       throws AmitAuthenticationException
   {
     final TokenDeserializationResult deserializedRefreshToken =
-        tenantRefreshTokenSerializer.deserialize(command.getRefreshToken());
+        tenantRefreshTokenSerializer.deserialize(new TenantIdentityRsaKeyProvider(), command.getRefreshToken());
 
     final PrivateTenantInfoEntity privateTenantInfo = checkedGetPrivateTenantInfo();
     final PrivateSignatureEntity privateSignature = checkedGetPrivateSignature();
 
     final UserEntity user = getUser(deserializedRefreshToken.getUserIdentifier());
 
-    final LocalDate passwordExpiration = getExpiration(user);
+    final Optional<LocalDateTime> passwordExpiration = getExpiration(user);
 
     final TokenSerializationResult accessToken = getAccessToken(
             user.getIdentifier(),
@@ -198,12 +213,17 @@
     return new AuthenticationCommandResponse(
         accessToken.getToken(), DateConverter.toIsoString(accessToken.getExpiration()),
         command.getRefreshToken(), DateConverter.toIsoString(deserializedRefreshToken.getExpiration()),
-        DateConverter.toIsoString(passwordExpiration));
+        passwordExpiration.map(DateConverter::toIsoString).orElse(null));
   }
 
-  private LocalDate getExpiration(final UserEntity user)
+  private Optional<LocalDateTime> getExpiration(final UserEntity user)
   {
-    return LocalDate.ofEpochDay(user.getPasswordExpiresOn().getDaysSinceEpoch());
+    if (user.getIdentifier().equals(IdentityConstants.SU_NAME))
+      return Optional.empty();
+    else
+      return Optional.of(LocalDateTime.of(
+              LocalDate.ofEpochDay(user.getPasswordExpiresOn().getDaysSinceEpoch()), //Convert from cassandra LocalDate to java LocalDate.
+              LocalTime.MIDNIGHT));
   }
 
   private UserEntity getUser(final String identifier) throws AmitAuthenticationException {
@@ -257,7 +277,8 @@
 
   private Set<TokenPermission> getTokenPermissions(
           final UserEntity user,
-          final LocalDate passwordExpiration,
+          @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
+          final Optional<LocalDateTime> passwordExpiration,
           final long gracePeriod) throws AmitAuthenticationException {
     final Optional<RoleEntity> userRole = roles.get(user.getRole());
     final Set<TokenPermission> tokenPermissions;
@@ -293,12 +314,15 @@
     return tokenPermissions;
   }
 
-  static boolean pastExpiration(final LocalDate passwordExpiration) {
-    return LocalDate.now().compareTo(passwordExpiration) >= 0;
+  static boolean pastExpiration(
+          @SuppressWarnings("OptionalUsedAsFieldOrParameterType") final Optional<LocalDateTime> passwordExpiration) {
+    return passwordExpiration.map(x -> LocalDateTime.now().compareTo(x) >= 0).orElse(false);
   }
 
-  static boolean pastGracePeriod(final LocalDate passwordExpiration, final long gracePeriod) {
-    return LocalDate.now().compareTo(passwordExpiration.plusDays(gracePeriod)) >= 0;
+  static boolean pastGracePeriod(
+          @SuppressWarnings("OptionalUsedAsFieldOrParameterType") final Optional<LocalDateTime> passwordExpiration,
+          final long gracePeriod) {
+    return passwordExpiration.map(x -> (LocalDateTime.now().compareTo(x.plusDays(gracePeriod)) >= 0)).orElse(false);
   }
 
   private Stream<TokenPermission> mapPermissions(final PermissionType permission) {
diff --git a/service/src/main/java/io/mifos/identity/internal/command/handler/Provisioner.java b/service/src/main/java/io/mifos/identity/internal/command/handler/Provisioner.java
index 4dd0d10..66cf6fd 100644
--- a/service/src/main/java/io/mifos/identity/internal/command/handler/Provisioner.java
+++ b/service/src/main/java/io/mifos/identity/internal/command/handler/Provisioner.java
@@ -50,6 +50,7 @@
   private final ApplicationSignatures applicationSignatures;
   private final ApplicationPermissions applicationPermissions;
   private final ApplicationPermissionUsers applicationPermissionUsers;
+  private final ApplicationCallEndpointSets applicationCallEndpointSets;
   private final UserEntityCreator userEntityCreator;
   private final Logger logger;
   private final SaltGenerator saltGenerator;
@@ -74,6 +75,7 @@
           final ApplicationSignatures applicationSignatures,
           final ApplicationPermissions applicationPermissions,
           final ApplicationPermissionUsers applicationPermissionUsers,
+          final ApplicationCallEndpointSets applicationCallEndpointSets,
           final UserEntityCreator userEntityCreator,
           @Qualifier(IdentityConstants.LOGGER_NAME) final Logger logger,
           final SaltGenerator saltGenerator)
@@ -87,6 +89,7 @@
     this.applicationSignatures = applicationSignatures;
     this.applicationPermissions = applicationPermissions;
     this.applicationPermissionUsers = applicationPermissionUsers;
+    this.applicationCallEndpointSets = applicationCallEndpointSets;
     this.userEntityCreator = userEntityCreator;
     this.logger = logger;
     this.saltGenerator = saltGenerator;
@@ -107,6 +110,7 @@
       applicationSignatures.buildTable();
       applicationPermissions.buildTable();
       applicationPermissionUsers.buildTable();
+      applicationCallEndpointSets.buildTable();
 
       final SignatureEntity signatureEntity = signature.add(keys);
       tenant.add(fixedSalt, passwordExpiresInDays, timeToChangePasswordAfterExpirationInDays);
diff --git a/service/src/main/java/io/mifos/identity/internal/mapper/ApplicationCallEndpointSetMapper.java b/service/src/main/java/io/mifos/identity/internal/mapper/ApplicationCallEndpointSetMapper.java
new file mode 100644
index 0000000..aba4685
--- /dev/null
+++ b/service/src/main/java/io/mifos/identity/internal/mapper/ApplicationCallEndpointSetMapper.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.identity.internal.mapper;
+
+import io.mifos.identity.api.v1.domain.CallEndpointSet;
+import io.mifos.identity.internal.repository.ApplicationCallEndpointSetEntity;
+
+import java.util.Collections;
+
+/**
+ * @author Myrle Krantz
+ */
+public interface ApplicationCallEndpointSetMapper {
+  static ApplicationCallEndpointSetEntity mapToEntity(
+          final String applicationIdentifier,
+          final CallEndpointSet callEndpointSet)
+  {
+    final ApplicationCallEndpointSetEntity ret = new ApplicationCallEndpointSetEntity();
+    ret.setApplicationIdentifier(applicationIdentifier);
+    ret.setCallEndpointSetIdentifier(callEndpointSet.getIdentifier());
+    ret.setCallEndpointGroupIdentifiers(callEndpointSet.getPermittableEndpointGroupIdentifiers());
+    return ret;
+  }
+
+  static CallEndpointSet map(final ApplicationCallEndpointSetEntity entity) {
+    final CallEndpointSet ret = new CallEndpointSet();
+    ret.setIdentifier(entity.getCallEndpointSetIdentifier());
+    if (entity.getCallEndpointGroupIdentifiers() == null)
+      ret.setPermittableEndpointGroupIdentifiers(Collections.emptyList());
+    else
+      ret.setPermittableEndpointGroupIdentifiers(entity.getCallEndpointGroupIdentifiers());
+    return ret;
+  }
+}
diff --git a/service/src/main/java/io/mifos/identity/internal/repository/ApplicationCallEndpointSetEntity.java b/service/src/main/java/io/mifos/identity/internal/repository/ApplicationCallEndpointSetEntity.java
new file mode 100644
index 0000000..f692ae0
--- /dev/null
+++ b/service/src/main/java/io/mifos/identity/internal/repository/ApplicationCallEndpointSetEntity.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.identity.internal.repository;
+
+import com.datastax.driver.mapping.annotations.ClusteringColumn;
+import com.datastax.driver.mapping.annotations.Column;
+import com.datastax.driver.mapping.annotations.PartitionKey;
+import com.datastax.driver.mapping.annotations.Table;
+
+import java.util.List;
+
+/**
+ * @author Myrle Krantz
+ */
+@Table(name = ApplicationCallEndpointSets.TABLE_NAME)
+public class ApplicationCallEndpointSetEntity {
+  @PartitionKey
+  @Column(name = ApplicationCallEndpointSets.APPLICATION_IDENTIFIER_COLUMN)
+  private String applicationIdentifier;
+
+  @ClusteringColumn
+  @Column(name = ApplicationCallEndpointSets.CALLENDPOINTSET_IDENTIFIER_COLUMN)
+  private String callEndpointSetIdentifier;
+
+  @Column(name = ApplicationCallEndpointSets.CALLENDPOINT_GROUP_IDENTIFIERS_COLUMN)
+  private List<String> callEndpointGroupIdentifiers;
+
+  public ApplicationCallEndpointSetEntity() {
+  }
+
+  public String getApplicationIdentifier() {
+    return applicationIdentifier;
+  }
+
+  public void setApplicationIdentifier(String applicationIdentifier) {
+    this.applicationIdentifier = applicationIdentifier;
+  }
+
+  public String getCallEndpointSetIdentifier() {
+    return callEndpointSetIdentifier;
+  }
+
+  public void setCallEndpointSetIdentifier(String callEndpointSetIdentifier) {
+    this.callEndpointSetIdentifier = callEndpointSetIdentifier;
+  }
+
+  public List<String> getCallEndpointGroupIdentifiers() {
+    return callEndpointGroupIdentifiers;
+  }
+
+  public void setCallEndpointGroupIdentifiers(List<String> callEndpointGroupIdentifiers) {
+    this.callEndpointGroupIdentifiers = callEndpointGroupIdentifiers;
+  }
+}
diff --git a/service/src/main/java/io/mifos/identity/internal/repository/ApplicationCallEndpointSets.java b/service/src/main/java/io/mifos/identity/internal/repository/ApplicationCallEndpointSets.java
new file mode 100644
index 0000000..83bd201
--- /dev/null
+++ b/service/src/main/java/io/mifos/identity/internal/repository/ApplicationCallEndpointSets.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.identity.internal.repository;
+
+import com.datastax.driver.core.DataType;
+import com.datastax.driver.core.Session;
+import com.datastax.driver.core.Statement;
+import com.datastax.driver.core.querybuilder.QueryBuilder;
+import com.datastax.driver.core.schemabuilder.Create;
+import com.datastax.driver.core.schemabuilder.SchemaBuilder;
+import com.datastax.driver.mapping.Mapper;
+import io.mifos.core.cassandra.core.CassandraSessionProvider;
+import io.mifos.core.cassandra.core.TenantAwareCassandraMapperProvider;
+import io.mifos.core.cassandra.core.TenantAwareEntityTemplate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.util.Assert;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @author Myrle Krantz
+ */
+@Component
+public class ApplicationCallEndpointSets {
+  static final String TABLE_NAME = "isis_application_callendpointsets";
+  static final String APPLICATION_IDENTIFIER_COLUMN = "application_identifier";
+  static final String CALLENDPOINTSET_IDENTIFIER_COLUMN = "call_endpoint_set_identifier";
+  static final String CALLENDPOINT_GROUP_IDENTIFIERS_COLUMN = "call_endpoint_group_identifiers";
+
+  private final CassandraSessionProvider cassandraSessionProvider;
+  private final TenantAwareEntityTemplate tenantAwareEntityTemplate;
+  private final TenantAwareCassandraMapperProvider tenantAwareCassandraMapperProvider;
+
+  @Autowired
+  public ApplicationCallEndpointSets(
+          final CassandraSessionProvider cassandraSessionProvider,
+          final TenantAwareEntityTemplate tenantAwareEntityTemplate,
+          final TenantAwareCassandraMapperProvider tenantAwareCassandraMapperProvider) {
+    this.cassandraSessionProvider = cassandraSessionProvider;
+    this.tenantAwareEntityTemplate = tenantAwareEntityTemplate;
+    this.tenantAwareCassandraMapperProvider = tenantAwareCassandraMapperProvider;
+  }
+
+  public void buildTable() {
+
+    final Create createTableStatement =
+            SchemaBuilder.createTable(TABLE_NAME)
+                    .addPartitionKey(APPLICATION_IDENTIFIER_COLUMN, DataType.text())
+                    .addClusteringColumn(CALLENDPOINTSET_IDENTIFIER_COLUMN, DataType.text())
+                    .addColumn(CALLENDPOINT_GROUP_IDENTIFIERS_COLUMN, DataType.list(DataType.text()));
+
+    cassandraSessionProvider.getTenantSession().execute(createTableStatement);
+  }
+
+  public void add(final ApplicationCallEndpointSetEntity entity) {
+    tenantAwareEntityTemplate.save(entity);
+  }
+
+  public void change(final ApplicationCallEndpointSetEntity instance) {
+    tenantAwareEntityTemplate.save(instance);
+  }
+
+  public Optional<ApplicationCallEndpointSetEntity> get(final String applicationIdentifier, final String callEndpointSetIdentifier)
+  {
+    final ApplicationCallEndpointSetEntity entity =
+            tenantAwareCassandraMapperProvider.getMapper(ApplicationCallEndpointSetEntity.class).get(applicationIdentifier, callEndpointSetIdentifier);
+
+    if (entity != null) {
+      Assert.notNull(entity.getApplicationIdentifier());
+      Assert.notNull(entity.getCallEndpointSetIdentifier());
+    }
+
+    return Optional.ofNullable(entity);
+  }
+
+  public List<ApplicationCallEndpointSetEntity> getAllForApplication(final String applicationIdentifier) {
+    final Mapper<ApplicationCallEndpointSetEntity> entityMapper = tenantAwareCassandraMapperProvider.getMapper(ApplicationCallEndpointSetEntity.class);
+    final Session tenantSession = cassandraSessionProvider.getTenantSession();
+
+    final Statement statement = QueryBuilder.select().from(TABLE_NAME).where(QueryBuilder.eq(APPLICATION_IDENTIFIER_COLUMN, applicationIdentifier));
+
+    return entityMapper.map(tenantSession.execute(statement)).all();
+  }
+
+  public void delete(final String applicationIdentifier, final String callEndpointSetIdentifier) {
+    final Optional<ApplicationCallEndpointSetEntity> toDelete = tenantAwareEntityTemplate.findById(ApplicationCallEndpointSetEntity.class, applicationIdentifier, callEndpointSetIdentifier);
+    toDelete.ifPresent(tenantAwareEntityTemplate::delete);
+  }
+}
diff --git a/service/src/main/java/io/mifos/identity/internal/service/ApplicationService.java b/service/src/main/java/io/mifos/identity/internal/service/ApplicationService.java
index 076baa5..9f001e7 100644
--- a/service/src/main/java/io/mifos/identity/internal/service/ApplicationService.java
+++ b/service/src/main/java/io/mifos/identity/internal/service/ApplicationService.java
@@ -16,13 +16,12 @@
 package io.mifos.identity.internal.service;
 
 import io.mifos.anubis.api.v1.domain.Signature;
+import io.mifos.identity.api.v1.domain.CallEndpointSet;
 import io.mifos.identity.api.v1.domain.Permission;
+import io.mifos.identity.internal.mapper.ApplicationCallEndpointSetMapper;
 import io.mifos.identity.internal.mapper.PermissionMapper;
 import io.mifos.identity.internal.mapper.SignatureMapper;
-import io.mifos.identity.internal.repository.ApplicationPermissionUsers;
-import io.mifos.identity.internal.repository.ApplicationPermissions;
-import io.mifos.identity.internal.repository.ApplicationSignatureEntity;
-import io.mifos.identity.internal.repository.ApplicationSignatures;
+import io.mifos.identity.internal.repository.*;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -40,14 +39,17 @@
   private final ApplicationSignatures applicationSignaturesRepository;
   private final ApplicationPermissions applicationPermissionsRepository;
   private final ApplicationPermissionUsers applicationPermissionsUserRepository;
+  private final ApplicationCallEndpointSets applicationCallEndpointSets;
 
   @Autowired
   public ApplicationService(final ApplicationSignatures applicationSignaturesRepository,
                             final ApplicationPermissions applicationPermissionsRepository,
-                            final ApplicationPermissionUsers applicationPermissionsUserRepository) {
+                            final ApplicationPermissionUsers applicationPermissionsUserRepository,
+                            final ApplicationCallEndpointSets applicationCallEndpointSets) {
     this.applicationSignaturesRepository = applicationSignaturesRepository;
     this.applicationPermissionsRepository = applicationPermissionsRepository;
     this.applicationPermissionsUserRepository = applicationPermissionsUserRepository;
+    this.applicationCallEndpointSets = applicationCallEndpointSets;
   }
 
   public List<String> getAllApplications() {
@@ -81,4 +83,21 @@
                                                      final String userIdentifier) {
     return applicationPermissionsUserRepository.enabled(applicationIdentifier, permittableEndpointGroupIdentifier, userIdentifier);
   }
+
+  public List<CallEndpointSet> getAllCallEndpointSetsForApplication(final String applicationIdentifier) {
+    return applicationCallEndpointSets.getAllForApplication(applicationIdentifier).stream()
+            .map(ApplicationCallEndpointSetMapper::map)
+            .collect(Collectors.toList());
+  }
+
+  public Optional<CallEndpointSet> getCallEndpointSetForApplication(
+          final String applicationIdentifier,
+          final String callEndpointSetIdentifier) {
+    return applicationCallEndpointSets.get(applicationIdentifier, callEndpointSetIdentifier)
+            .map(ApplicationCallEndpointSetMapper::map);
+  }
+
+  public boolean applicationCallEndpointSetExists(String applicationIdentifier, String callEndpointSetIdentifier) {
+    return applicationCallEndpointSets.get(applicationIdentifier, callEndpointSetIdentifier).isPresent();
+  }
 }
diff --git a/service/src/main/java/io/mifos/identity/internal/service/TenantService.java b/service/src/main/java/io/mifos/identity/internal/service/TenantService.java
index b02279b..dee14ca 100644
--- a/service/src/main/java/io/mifos/identity/internal/service/TenantService.java
+++ b/service/src/main/java/io/mifos/identity/internal/service/TenantService.java
@@ -20,13 +20,17 @@
 import io.mifos.anubis.api.v1.domain.Signature;
 import io.mifos.anubis.config.TenantSignatureRepository;
 import io.mifos.core.lang.security.RsaKeyPairFactory;
+import io.mifos.core.lang.security.RsaPrivateKeyBuilder;
 import io.mifos.identity.internal.mapper.SignatureMapper;
+import io.mifos.identity.internal.repository.PrivateSignatureEntity;
 import io.mifos.identity.internal.repository.SignatureEntity;
 import io.mifos.identity.internal.repository.Signatures;
 import io.mifos.identity.internal.repository.Tenants;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.security.PrivateKey;
+import java.security.interfaces.RSAPrivateKey;
 import java.util.List;
 import java.util.Optional;
 
@@ -100,6 +104,19 @@
     return timestamp.flatMap(this::getApplicationSignature);
   }
 
+  @Override
+  public Optional<RsaKeyPairFactory.KeyPairHolder> getLatestApplicationSigningKeyPair() {
+    final Optional<PrivateSignatureEntity> privateSignatureEntity = signatures.getPrivateSignature();
+    return privateSignatureEntity.map(x -> {
+      final String timestamp = x.getKeyTimestamp();
+      final PrivateKey privateKey = new RsaPrivateKeyBuilder()
+              .setPrivateKeyExp(x.getPrivateKeyExp())
+              .setPrivateKeyMod(x.getPrivateKeyMod())
+              .build();
+      return new RsaKeyPairFactory.KeyPairHolder(timestamp, null, (RSAPrivateKey)privateKey);
+    });
+  }
+
   private Optional<String> getMostRecentTimestamp() {
     return getAllSignatureSetKeyTimestamps().stream()
             .max(String::compareTo);
diff --git a/service/src/main/java/io/mifos/identity/rest/ApplicationCallEndpointSetRestController.java b/service/src/main/java/io/mifos/identity/rest/ApplicationCallEndpointSetRestController.java
new file mode 100644
index 0000000..94812b1
--- /dev/null
+++ b/service/src/main/java/io/mifos/identity/rest/ApplicationCallEndpointSetRestController.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.identity.rest;
+
+import io.mifos.anubis.annotation.AcceptedTokenType;
+import io.mifos.anubis.annotation.Permittable;
+import io.mifos.core.command.gateway.CommandGateway;
+import io.mifos.core.lang.ServiceException;
+import io.mifos.identity.api.v1.domain.CallEndpointSet;
+import io.mifos.identity.internal.command.ChangeApplicationCallEndpointSetCommand;
+import io.mifos.identity.internal.command.CreateApplicationCallEndpointSetCommand;
+import io.mifos.identity.internal.command.DeleteApplicationCallEndpointSetCommand;
+import io.mifos.identity.internal.service.ApplicationService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings("unused")
+@RestController
+@RequestMapping("/applications/{applicationidentifier}/callendpointset")
+public class ApplicationCallEndpointSetRestController {
+  private final CommandGateway commandGateway;
+  private final ApplicationService applicationService;
+
+  @Autowired
+  public ApplicationCallEndpointSetRestController(
+          final CommandGateway commandGateway,
+          final ApplicationService applicationService) {
+    this.commandGateway = commandGateway;
+    this.applicationService = applicationService;
+  }
+
+  @RequestMapping(method = RequestMethod.POST,
+          produces = {MediaType.APPLICATION_JSON_VALUE},
+          consumes = {MediaType.ALL_VALUE})
+  @Permittable(value = AcceptedTokenType.SYSTEM)
+  public @ResponseBody
+  ResponseEntity<Void> createApplicationCallEndpointSet(
+          @PathVariable("applicationidentifier") final String applicationIdentifier,
+          @RequestBody final CallEndpointSet instance)
+  {
+    if (!applicationService.applicationExists(applicationIdentifier))
+      throw ServiceException.notFound("Application '"
+              + applicationIdentifier + "' doesn't exist.");
+
+    commandGateway.process(new CreateApplicationCallEndpointSetCommand(applicationIdentifier, instance));
+    return ResponseEntity.accepted().build();
+  }
+
+  @RequestMapping(value = "/{callendpointsetidentifier}", method = RequestMethod.PUT,
+          produces = {MediaType.APPLICATION_JSON_VALUE},
+          consumes = {MediaType.ALL_VALUE})
+  @Permittable(value = AcceptedTokenType.SYSTEM)
+  public @ResponseBody
+  ResponseEntity<Void> changeApplicationCallEndpointSet(
+          @PathVariable("applicationidentifier") String applicationIdentifier,
+          @PathVariable("callendpointsetidentifier") String callEndpointSetIdentifier,
+          @RequestBody final CallEndpointSet instance)
+  {
+    if (!applicationService.applicationCallEndpointSetExists(applicationIdentifier, callEndpointSetIdentifier))
+      throw ServiceException.notFound("Application call endpointset '"
+              + applicationIdentifier + "." + callEndpointSetIdentifier + "' doesn't exist.");
+
+    if (!callEndpointSetIdentifier.equals(instance.getIdentifier()))
+      throw ServiceException.badRequest("Instance identifiers may not be changed.");
+
+    commandGateway.process(new ChangeApplicationCallEndpointSetCommand(applicationIdentifier, callEndpointSetIdentifier, instance));
+    return ResponseEntity.accepted().build();
+  }
+
+  @RequestMapping(value = "/{callendpointsetidentifier}", method = RequestMethod.GET,
+          produces = {MediaType.APPLICATION_JSON_VALUE},
+          consumes = {MediaType.ALL_VALUE})
+  @Permittable(value = AcceptedTokenType.SYSTEM)
+  public @ResponseBody
+  ResponseEntity<CallEndpointSet> getApplicationCallEndpointSet(
+          @PathVariable("applicationidentifier") String applicationIdentifier,
+          @PathVariable("callendpointsetidentifier") String callEndpointSetIdentifier)
+  {
+    if (!applicationService.applicationCallEndpointSetExists(applicationIdentifier, callEndpointSetIdentifier))
+      throw ServiceException.notFound("Application call endpointset '"
+              + applicationIdentifier + "." + callEndpointSetIdentifier + "' doesn't exist.");
+
+    return applicationService.getCallEndpointSetForApplication(applicationIdentifier, callEndpointSetIdentifier)
+            .map(ResponseEntity::ok)
+            .orElseThrow(() -> ServiceException.notFound("Call endpointset for application " + applicationIdentifier + " and call endpointset identifier " + callEndpointSetIdentifier + "doesn't exist."));
+  }
+
+  @RequestMapping(method = RequestMethod.GET,
+          produces = {MediaType.APPLICATION_JSON_VALUE},
+          consumes = {MediaType.ALL_VALUE})
+  @Permittable(value = AcceptedTokenType.SYSTEM)
+  public @ResponseBody
+  ResponseEntity<List<CallEndpointSet>>
+  getApplicationCallEndpointSets(
+          @PathVariable("applicationidentifier") final String applicationIdentifier)
+  {
+    return ResponseEntity.ok(applicationService.getAllCallEndpointSetsForApplication(applicationIdentifier));
+  }
+
+  @RequestMapping(value = "/{callendpointsetidentifier}", method = RequestMethod.DELETE,
+          produces = {MediaType.APPLICATION_JSON_VALUE},
+          consumes = {MediaType.ALL_VALUE})
+  @Permittable(value = AcceptedTokenType.SYSTEM)
+  public @ResponseBody
+  ResponseEntity<Void> deleteApplicationCallEndpointSet(
+          @PathVariable("applicationidentifier") final String applicationIdentifier,
+          @PathVariable("callendpointsetidentifier") final String callEndpointSetIdentifier)
+  {
+    if (!applicationService.applicationCallEndpointSetExists(applicationIdentifier, callEndpointSetIdentifier))
+      throw ServiceException.notFound("Application call endpointset '"
+              + applicationIdentifier + "." + callEndpointSetIdentifier + "' doesn't exist.");
+    commandGateway.process(new DeleteApplicationCallEndpointSetCommand(applicationIdentifier, callEndpointSetIdentifier));
+    return ResponseEntity.accepted().build();
+  }
+}
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/identity/rest/RoleRestController.java b/service/src/main/java/io/mifos/identity/rest/RoleRestController.java
index cf6b450..410a90b 100644
--- a/service/src/main/java/io/mifos/identity/rest/RoleRestController.java
+++ b/service/src/main/java/io/mifos/identity/rest/RoleRestController.java
@@ -21,10 +21,11 @@
 import io.mifos.core.lang.ServiceException;
 import io.mifos.identity.api.v1.PermittableGroupIds;
 import io.mifos.identity.api.v1.domain.Role;
+import io.mifos.identity.api.v1.validation.CheckRoleChangeable;
+import io.mifos.identity.internal.command.ChangeRoleCommand;
 import io.mifos.identity.internal.command.CreateRoleCommand;
 import io.mifos.identity.internal.command.DeleteRoleCommand;
 import io.mifos.identity.internal.service.RoleService;
-import io.mifos.identity.internal.command.ChangeRoleCommand;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
@@ -94,6 +95,9 @@
   @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.ROLE_MANAGEMENT)
   public @ResponseBody ResponseEntity<Void> delete(@PathVariable(PathConstants.IDENTIFIER_PATH_VARIABLE) final String identifier)
   {
+    if (!CheckRoleChangeable.isChangeableRoleIdentifier(identifier))
+      throw ServiceException.badRequest("Role with identifier: " + identifier + " cannot be deleted.");
+
     checkIdentifier(identifier);
 
     final DeleteRoleCommand deleteCommand = new DeleteRoleCommand(identifier);
@@ -108,6 +112,9 @@
   public @ResponseBody ResponseEntity<Void> change(
           @PathVariable(PathConstants.IDENTIFIER_PATH_VARIABLE) final String identifier, @RequestBody @Valid final Role instance)
   {
+    if (!CheckRoleChangeable.isChangeableRoleIdentifier(identifier))
+      throw ServiceException.badRequest("Role with identifier: " + identifier + " cannot be changed.");
+
     checkIdentifier(identifier);
 
     if (!identifier.equals(instance.getIdentifier()))
diff --git a/service/src/main/java/io/mifos/identity/rest/UserRestController.java b/service/src/main/java/io/mifos/identity/rest/UserRestController.java
index 6e97b25..b20a38e 100644
--- a/service/src/main/java/io/mifos/identity/rest/UserRestController.java
+++ b/service/src/main/java/io/mifos/identity/rest/UserRestController.java
@@ -17,6 +17,7 @@
 
 import io.mifos.anubis.annotation.AcceptedTokenType;
 import io.mifos.anubis.annotation.Permittable;
+import io.mifos.core.api.util.UserContextHolder;
 import io.mifos.core.command.gateway.CommandGateway;
 import io.mifos.core.lang.ServiceException;
 import io.mifos.identity.api.v1.PermittableGroupIds;
@@ -35,6 +36,7 @@
 import java.util.List;
 import java.util.Set;
 
+import static io.mifos.identity.internal.util.IdentityConstants.SU_NAME;
 import static io.mifos.identity.rest.PathConstants.IDENTIFIER_PATH_VARIABLE;
 
 
@@ -98,6 +100,9 @@
       @PathVariable(IDENTIFIER_PATH_VARIABLE) final String userIdentifier,
       @RequestBody @Valid final RoleIdentifier roleIdentifier)
   {
+    if (userIdentifier.equals(SU_NAME))
+      throw ServiceException.badRequest("Role of user with identifier: " + userIdentifier + " cannot be changed.");
+
     checkIdentifier(userIdentifier);
 
     final ChangeUserRoleCommand changeCommand = new ChangeUserRoleCommand(userIdentifier, roleIdentifier.getIdentifier());
@@ -127,6 +132,9 @@
       @PathVariable(IDENTIFIER_PATH_VARIABLE) final String userIdentifier,
       @RequestBody @Valid final Password password)
   {
+    if (userIdentifier.equals(SU_NAME) && !UserContextHolder.checkedGetUser().equals(SU_NAME))
+      throw ServiceException.badRequest("Role of user with identifier: " + userIdentifier + " cannot be changed.");
+
     checkIdentifier(userIdentifier);
 
     checkPassword(password);