fineract-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From my...@apache.org
Subject [fineract-cn-provisioner] 01/50: Open Sourcing provisioner service.
Date Mon, 22 Jan 2018 15:15:59 GMT
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-provisioner.git

commit e3038defe8cee355c2b60d62d92e2453f751173d
Author: myrle-krantz <mkrantz@mifos.org>
AuthorDate: Wed Mar 15 17:52:55 2017 +0100

    Open Sourcing provisioner service.
---
 .gitignore                                         |  14 +
 HEADER                                             |  13 +
 LICENSE                                            | 201 ++++++++++++
 README.md                                          |  31 ++
 api/build.gradle                                   |  34 ++
 api/settings.gradle                                |   1 +
 .../v1/client/DuplicateIdentifierException.java    |  23 ++
 .../InvalidProvisionerCredentialsException.java    |  26 ++
 .../api/v1/client/ProvisionerService.java          | 202 ++++++++++++
 .../provisioner/api/v1/domain/Application.java     |  67 ++++
 .../api/v1/domain/AssignedApplication.java         |  40 +++
 .../api/v1/domain/AuthenticationResponse.java      |  37 +++
 .../api/v1/domain/CassandraConnectionInfo.java     | 119 +++++++
 .../io/mifos/provisioner/api/v1/domain/Client.java |  83 +++++
 .../api/v1/domain/ClientCredentials.java           |  37 +++
 .../api/v1/domain/DatabaseConnectionInfo.java      |  93 ++++++
 .../v1/domain/IdentityManagerInitialization.java   |  35 +++
 .../provisioner/api/v1/domain/PasswordPolicy.java  |  43 +++
 .../io/mifos/provisioner/api/v1/domain/Tenant.java |  76 +++++
 build.gradle                                       |  36 +++
 component-test/build.gradle                        |  41 +++
 component-test/settings.gradle                     |   1 +
 .../io/mifos/provisioner/AbstractServiceTest.java  |  87 ++++++
 .../ProvisionerCassandraInitializer.java           |  38 +++
 .../provisioner/ProvisionerMariaDBInitializer.java |  41 +++
 .../io/mifos/provisioner/application/Fixture.java  |  38 +++
 .../provisioner/application/TestApplications.java  | 115 +++++++
 .../java/io/mifos/provisioner/client/Fixture.java  |  39 +++
 .../io/mifos/provisioner/client/TestClients.java   | 116 +++++++
 .../mifos/provisioner/internal/TestProvision.java  |  63 ++++
 .../provisioner/security/TestAuthentication.java   |  55 ++++
 .../provisioner/security/TestPasswordPolicy.java   |  99 ++++++
 .../java/io/mifos/provisioner/tenant/Fixture.java  |  57 ++++
 .../tenant/TestTenantApplicationAssignment.java    | 335 ++++++++++++++++++++
 .../io/mifos/provisioner/tenant/TestTenants.java   |  94 ++++++
 gradle/wrapper/gradle-wrapper.jar                  | Bin 0 -> 54212 bytes
 gradle/wrapper/gradle-wrapper.properties           |   6 +
 gradlew                                            | 172 +++++++++++
 gradlew.bat                                        |  84 +++++
 service/build.gradle                               |  66 ++++
 service/settings.gradle                            |   1 +
 .../mifos/provisioner/ProvisionerApplication.java  |  32 ++
 .../provisioner/config/ProvisionerConstants.java   |  25 ++
 .../config/ProvisionerServiceConfig.java           |  97 ++++++
 .../internal/repository/ApplicationEntity.java     |  92 ++++++
 .../internal/repository/ClientEntity.java          | 103 +++++++
 .../internal/repository/ConfigEntity.java          |  71 +++++
 .../internal/repository/Provisioner.java           | 223 ++++++++++++++
 .../repository/TenantApplicationEntity.java        |  72 +++++
 .../repository/TenantCassandraRepository.java      | 134 ++++++++
 .../provisioner/internal/repository/TenantDAO.java | 225 ++++++++++++++
 .../internal/repository/TenantDAOHackEntity.java   | 108 +++++++
 .../internal/repository/TenantEntity.java          | 158 ++++++++++
 .../internal/repository/UserEntity.java            | 117 +++++++
 .../internal/service/ApplicationService.java       | 100 ++++++
 .../internal/service/AuthenticationService.java    | 184 +++++++++++
 .../internal/service/ClientService.java            |  84 +++++
 .../internal/service/TenantApplicationService.java | 210 +++++++++++++
 .../internal/service/TenantService.java            | 324 +++++++++++++++++++
 .../service/applications/AnubisInitializer.java    |  62 ++++
 .../ApplicationCallContextProvider.java            |  85 +++++
 .../applications/IdentityServiceInitializer.java   | 198 ++++++++++++
 .../internal/util/ContactPointUtils.java           |  46 +++
 .../provisioner/internal/util/DataSourceUtils.java |  67 ++++
 .../provisioner/internal/util/DataStoreOption.java |  30 ++
 .../provisioner/internal/util/JdbcUrlBuilder.java  |  74 +++++
 .../provisioner/internal/util/TokenProvider.java   |  61 ++++
 .../rest/controller/SeshatRestController.java      | 343 +++++++++++++++++++++
 .../provisioner/rest/mapper/ApplicationMapper.java |  44 +++
 .../rest/mapper/AssignedApplicationMapper.java     |  56 ++++
 .../provisioner/rest/mapper/ClientMapper.java      |  47 +++
 service/src/main/resources/application.yaml        |  55 ++++
 service/src/main/resources/bootstrap.yaml          |  19 ++
 service/src/main/resources/logback.xml             |  40 +++
 .../io/mifos/provisioner/GenerateRsaKeyPair.java   |  62 ++++
 .../IdentityServiceInitializerTest.java            | 127 ++++++++
 .../internal/util/ContactPointUtilsTest.java       |  66 ++++
 .../internal/util/DataStoreOptionTest.java         |  56 ++++
 .../internal/util/JdbcUrlBuilderTest.java          |  41 +++
 settings.gradle                                    |   6 +
 shared.gradle                                      |  70 +++++
 81 files changed, 6773 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..789f943
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@
+.gradle
+.idea
+**/build/
+**/target/
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
+
+*.iml
+
+*.log
\ No newline at end of file
diff --git a/HEADER b/HEADER
new file mode 100644
index 0000000..4b2eadf
--- /dev/null
+++ b/HEADER
@@ -0,0 +1,13 @@
+Copyright ${year} ${name}.
+
+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.
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..8dada3e
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright {yyyy} {name of copyright owner}
+
+   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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..6ac8870
--- /dev/null
+++ b/README.md
@@ -0,0 +1,31 @@
+# Mifos I/O Provisioner
+
+[![Join the chat at https://gitter.im/mifos-initiative/mifos.io](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/mifos-initiative/mifos.io?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+
+This project provides functionality for provisioning services for tenants.
+
+## Abstract
+Mifos I/O is an application framework for digital financial services, a system to support nationwide and cross-national financial transactions and help to level and speed the creation of an inclusive, interconnected digital economy for every nation in the world.
+
+### Needed environment
+Before building and starting the Docker container you need to make sure that needed environment is started too:
+
+## Versioning
+The version numbers follow the [Semantic Versioning](http://semver.org/) scheme.
+
+In addition to MAJOR.MINOR.PATCH the following postfixes are used to indicate the development state.
+
+* BUILD-SNAPSHOT - A release currently in development.
+* M - A _milestone_ release include specific sets of functions and are released as soon as the functionality is complete.
+* RC - A _release candidate_ is a version with potential to be a final product, considered _code complete_.
+* RELEASE - indicates that this release is the best available version and is recommended for all usage.
+
+The versioning layout is {MAJOR}.{MINOR}.{PATCH}-{INDICATOR}[.{PATCH}]. Only milestones and release candidates can  have patch versions. Some examples:
+
+1.2.3-BUILD-SNAPSHOT
+1.3.5-M.1
+1.5.7-RC.2
+2.0.0-RELEASE
+
+## License
+See [LICENSE](LICENSE) file.
\ No newline at end of file
diff --git a/api/build.gradle b/api/build.gradle
new file mode 100644
index 0000000..681d0c7
--- /dev/null
+++ b/api/build.gradle
@@ -0,0 +1,34 @@
+buildscript {
+    repositories {
+        jcenter()
+    }
+
+    dependencies {
+        classpath 'io.spring.gradle:dependency-management-plugin:0.6.0.RELEASE'
+    }
+}
+
+plugins {
+    id 'com.github.hierynomus.license' version '0.13.1'
+}
+
+apply from: '../shared.gradle'
+
+dependencies {
+    compile(
+            [group: 'org.springframework.cloud', name: 'spring-cloud-starter-feign'],
+            [group: 'io.mifos.core', name: 'api', version: versions.frameworkapi],
+            [group: 'org.hibernate', name: 'hibernate-validator', version: versions.validator]
+    )
+}
+
+publishing {
+    publications {
+        api(MavenPublication) {
+            from components.java
+            groupId project.group
+            artifactId project.name
+            version project.version
+        }
+    }
+}
\ No newline at end of file
diff --git a/api/settings.gradle b/api/settings.gradle
new file mode 100644
index 0000000..7c8e3dc
--- /dev/null
+++ b/api/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'api'
\ No newline at end of file
diff --git a/api/src/main/java/io/mifos/provisioner/api/v1/client/DuplicateIdentifierException.java b/api/src/main/java/io/mifos/provisioner/api/v1/client/DuplicateIdentifierException.java
new file mode 100644
index 0000000..932fc12
--- /dev/null
+++ b/api/src/main/java/io/mifos/provisioner/api/v1/client/DuplicateIdentifierException.java
@@ -0,0 +1,23 @@
+/*
+ * 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.provisioner.api.v1.client;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings("WeakerAccess")
+public class DuplicateIdentifierException extends RuntimeException {
+}
diff --git a/api/src/main/java/io/mifos/provisioner/api/v1/client/InvalidProvisionerCredentialsException.java b/api/src/main/java/io/mifos/provisioner/api/v1/client/InvalidProvisionerCredentialsException.java
new file mode 100644
index 0000000..459ff79
--- /dev/null
+++ b/api/src/main/java/io/mifos/provisioner/api/v1/client/InvalidProvisionerCredentialsException.java
@@ -0,0 +1,26 @@
+/*
+ * 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.provisioner.api.v1.client;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings("WeakerAccess")
+public class InvalidProvisionerCredentialsException extends RuntimeException {
+  public InvalidProvisionerCredentialsException() {
+    super();
+  }
+}
diff --git a/api/src/main/java/io/mifos/provisioner/api/v1/client/ProvisionerService.java b/api/src/main/java/io/mifos/provisioner/api/v1/client/ProvisionerService.java
new file mode 100644
index 0000000..15bc6d4
--- /dev/null
+++ b/api/src/main/java/io/mifos/provisioner/api/v1/client/ProvisionerService.java
@@ -0,0 +1,202 @@
+/*
+ * 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.provisioner.api.v1.client;
+
+import io.mifos.core.api.util.CustomFeignClientsConfiguration;
+import io.mifos.provisioner.api.v1.domain.*;
+import org.springframework.cloud.netflix.feign.FeignClient;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import java.util.List;
+
+import javax.validation.Valid;
+
+import io.mifos.core.api.annotation.ThrowsException;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings("unused")
+@FeignClient(name="provisioner-v1", path="/provisioner/v1", configuration=CustomFeignClientsConfiguration.class)
+public interface ProvisionerService {
+
+
+  @RequestMapping(
+      value = "/auth/token?grant_type=password",
+      method = RequestMethod.POST,
+      produces = {MediaType.ALL_VALUE},
+      consumes = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  @ThrowsException(status = HttpStatus.NOT_FOUND, exception = InvalidProvisionerCredentialsException.class)
+  AuthenticationResponse authenticate(@RequestParam("client_id") final String clientId,
+                                      @RequestParam("username") final String username,
+                                      @RequestParam("password") final String password);
+
+
+  @RequestMapping(
+      value = "/auth/user/{useridentifier}/password",
+      method = RequestMethod.PUT,
+      produces = {MediaType.APPLICATION_JSON_VALUE},
+      consumes = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  void updatePasswordPolicy(@PathVariable("useridentifier") final String userIdentifier,
+                            @RequestBody final PasswordPolicy passwordPolicy);
+
+  @RequestMapping(
+      value = "/clients",
+      method = RequestMethod.GET,
+      produces = {MediaType.ALL_VALUE},
+      consumes = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  List<Client> getClients();
+
+
+  @RequestMapping(
+      value = "/clients",
+      method = RequestMethod.POST,
+      produces = {MediaType.APPLICATION_JSON_VALUE},
+      consumes = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  @ThrowsException(status = HttpStatus.CONFLICT, exception = DuplicateIdentifierException.class)
+  void createClient(@RequestBody @Valid final Client client);
+
+
+
+  @RequestMapping(
+      value = "/clients/{clientidentifier}",
+      method = RequestMethod.GET,
+      produces = {MediaType.ALL_VALUE},
+      consumes = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  Client getClient(@PathVariable("clientidentifier") final String clientIdentifier);
+
+  @RequestMapping(
+      value = "/clients/{clientidentifier}",
+      method = RequestMethod.DELETE,
+      produces = {MediaType.ALL_VALUE},
+      consumes = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  void deleteClient(@PathVariable("clientidentifier") final String clientIdentifier);
+
+
+  @RequestMapping(
+      value = "/tenants",
+      method = RequestMethod.POST,
+      produces = {MediaType.APPLICATION_JSON_VALUE},
+      consumes = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  @ThrowsException(status = HttpStatus.CONFLICT, exception = DuplicateIdentifierException.class)
+  void createTenant(@RequestBody final Tenant tenant);
+
+
+
+  @RequestMapping(
+      value = "/tenants",
+      method = RequestMethod.GET,
+      produces = {MediaType.ALL_VALUE},
+      consumes = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  List<Tenant> getTenants();
+
+
+  @RequestMapping(
+      value = "/tenants/{tenantidentifier}",
+      method = RequestMethod.GET,
+      produces = {MediaType.ALL_VALUE},
+      consumes = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  Tenant getTenant(@PathVariable("tenantidentifier") final String tenantIdentifier);
+
+
+  @RequestMapping(
+      value = "/tenants/{tenantidentifier}",
+      method = RequestMethod.DELETE,
+      produces = {MediaType.ALL_VALUE},
+      consumes = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  void deleteTenant(@PathVariable("tenantidentifier") final String tenantIdentifier);
+
+
+  @RequestMapping(
+      value = "/applications",
+      method = RequestMethod.POST,
+      produces = {MediaType.APPLICATION_JSON_VALUE},
+      consumes = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  @ThrowsException(status = HttpStatus.CONFLICT, exception = DuplicateIdentifierException.class)
+  void createApplication(@RequestBody final Application application);
+
+
+  @RequestMapping(
+      value = "/applications",
+      method = RequestMethod.GET,
+      produces = {MediaType.ALL_VALUE},
+      consumes = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  List<Application> getApplications();
+
+
+  @RequestMapping(
+      value = "/applications/{name}",
+      method = RequestMethod.GET,
+      produces = {MediaType.ALL_VALUE},
+      consumes = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  Application getApplication(@PathVariable("name") final String name);
+
+
+  @RequestMapping(
+      value = "/applications/{name}",
+      method = RequestMethod.DELETE,
+      produces = {MediaType.ALL_VALUE},
+      consumes = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  void deleteApplication(@PathVariable("name") final String name);
+
+  @RequestMapping(
+          value = "tenants/{tenantidentifier}/identityservice",
+          method = RequestMethod.POST,
+          produces = {MediaType.APPLICATION_JSON_VALUE},
+          consumes = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  IdentityManagerInitialization assignIdentityManager(@PathVariable("tenantidentifier") final String tenantIdentifier,
+                                                      @RequestBody final AssignedApplication assignedApplications);
+
+  @RequestMapping(
+          value = "tenants/{tenantidentifier}/applications",
+          method = RequestMethod.PUT,
+          produces = {MediaType.APPLICATION_JSON_VALUE},
+          consumes = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  void assignApplications(@PathVariable("tenantidentifier") final String tenantIdentifier,
+                          @RequestBody final List<AssignedApplication> assignedApplications);
+
+
+  @RequestMapping(
+      value = "tenants/{tenantidentifier}/applications",
+      method = RequestMethod.GET,
+      produces = {MediaType.ALL_VALUE},
+      consumes = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  List<AssignedApplication> getAssignedApplications(
+      @PathVariable("tenantidentifier") final String tenantIdentifier);
+}
\ No newline at end of file
diff --git a/api/src/main/java/io/mifos/provisioner/api/v1/domain/Application.java b/api/src/main/java/io/mifos/provisioner/api/v1/domain/Application.java
new file mode 100644
index 0000000..4c9bfdb
--- /dev/null
+++ b/api/src/main/java/io/mifos/provisioner/api/v1/domain/Application.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.provisioner.api.v1.domain;
+
+import io.mifos.core.lang.validation.constraints.ValidIdentifier;
+
+import javax.validation.constraints.NotNull;
+
+@SuppressWarnings("unused")
+public class Application {
+
+  @NotNull
+  @ValidIdentifier //name should be URL-safe.
+  private String name;
+  private String description;
+  private String vendor;
+  private String homepage;
+
+  public Application() {
+    super();
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  public String getDescription() {
+    return description;
+  }
+
+  public void setDescription(String description) {
+    this.description = description;
+  }
+
+  public String getVendor() {
+    return vendor;
+  }
+
+  public void setVendor(String vendor) {
+    this.vendor = vendor;
+  }
+
+  public String getHomepage() {
+    return homepage;
+  }
+
+  public void setHomepage(String homepage) {
+    this.homepage = homepage;
+  }
+}
diff --git a/api/src/main/java/io/mifos/provisioner/api/v1/domain/AssignedApplication.java b/api/src/main/java/io/mifos/provisioner/api/v1/domain/AssignedApplication.java
new file mode 100644
index 0000000..76e1898
--- /dev/null
+++ b/api/src/main/java/io/mifos/provisioner/api/v1/domain/AssignedApplication.java
@@ -0,0 +1,40 @@
+/*
+ * 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.provisioner.api.v1.domain;
+
+import io.mifos.core.lang.validation.constraints.ValidIdentifier;
+
+import javax.validation.constraints.NotNull;
+
+@SuppressWarnings("unused")
+public class AssignedApplication {
+
+  @NotNull
+  @ValidIdentifier
+  private String name;
+
+  public AssignedApplication() {
+    super();
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+}
diff --git a/api/src/main/java/io/mifos/provisioner/api/v1/domain/AuthenticationResponse.java b/api/src/main/java/io/mifos/provisioner/api/v1/domain/AuthenticationResponse.java
new file mode 100644
index 0000000..8db6148
--- /dev/null
+++ b/api/src/main/java/io/mifos/provisioner/api/v1/domain/AuthenticationResponse.java
@@ -0,0 +1,37 @@
+/*
+ * 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.provisioner.api.v1.domain;
+
+@SuppressWarnings("unused")
+public class AuthenticationResponse {
+
+  private final String token;
+  private final String accessTokenExpiration;
+
+  public AuthenticationResponse(final String token, final String accessTokenExpiration) {
+    super();
+    this.token = token;
+    this.accessTokenExpiration = accessTokenExpiration;
+  }
+
+  public String getToken() {
+    return token;
+  }
+
+  public String getAccessTokenExpiration() {
+    return accessTokenExpiration;
+  }
+}
diff --git a/api/src/main/java/io/mifos/provisioner/api/v1/domain/CassandraConnectionInfo.java b/api/src/main/java/io/mifos/provisioner/api/v1/domain/CassandraConnectionInfo.java
new file mode 100644
index 0000000..d2c70ac
--- /dev/null
+++ b/api/src/main/java/io/mifos/provisioner/api/v1/domain/CassandraConnectionInfo.java
@@ -0,0 +1,119 @@
+/*
+ * 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.provisioner.api.v1.domain;
+
+import javax.annotation.Nonnull;
+import javax.validation.constraints.NotNull;
+
+@SuppressWarnings({"unused", "WeakerAccess"})
+public final class CassandraConnectionInfo {
+
+  @NotNull
+  private String clusterName;
+  @NotNull
+  private String contactPoints;
+  @NotNull
+  private String keyspace;
+  @NotNull
+  private String replicationType;
+  @NotNull
+  private String replicas;
+
+  public CassandraConnectionInfo() {
+    super();
+  }
+
+  @Nonnull
+  public String getClusterName() {
+    return clusterName;
+  }
+
+  public void setClusterName(@Nonnull final String clusterName) {
+    this.clusterName = clusterName;
+  }
+
+  @Nonnull
+  public String getContactPoints() {
+    return contactPoints;
+  }
+
+  public void setContactPoints(@Nonnull final String contactPoints) {
+    this.contactPoints = contactPoints;
+  }
+
+  @Nonnull
+  public String getKeyspace() {
+    return keyspace;
+  }
+
+  public void setKeyspace(@Nonnull final String keyspace) {
+    this.keyspace = keyspace;
+  }
+
+  @Nonnull
+  public String getReplicationType() {
+    return replicationType;
+  }
+
+  public void setReplicationType(@Nonnull final String replicationType) {
+    this.replicationType = replicationType;
+  }
+
+  @Nonnull
+  public String getReplicas() {
+    return replicas;
+  }
+
+  public void setReplicas(@Nonnull final String replicas) {
+    this.replicas = replicas;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    CassandraConnectionInfo that = (CassandraConnectionInfo) o;
+
+    return clusterName.equals(that.clusterName)
+        && contactPoints.equals(that.contactPoints)
+        && keyspace.equals(that.keyspace)
+        && replicationType.equals(that.replicationType)
+        && replicas.equals(that.replicas);
+
+  }
+
+  @Override
+  public int hashCode() {
+    int result = clusterName.hashCode();
+    result = 31 * result + contactPoints.hashCode();
+    result = 31 * result + keyspace.hashCode();
+    result = 31 * result + replicationType.hashCode();
+    result = 31 * result + replicas.hashCode();
+    return result;
+  }
+
+  @Override
+  public String toString() {
+    return "CassandraConnectionInfo{" +
+        "clusterName='" + clusterName + '\'' +
+        ", contactPoints='" + contactPoints + '\'' +
+        ", keyspace='" + keyspace + '\'' +
+        ", replicationType='" + replicationType + '\'' +
+        ", replicas='" + replicas + '\'' +
+        '}';
+  }
+}
diff --git a/api/src/main/java/io/mifos/provisioner/api/v1/domain/Client.java b/api/src/main/java/io/mifos/provisioner/api/v1/domain/Client.java
new file mode 100644
index 0000000..f2a36b0
--- /dev/null
+++ b/api/src/main/java/io/mifos/provisioner/api/v1/domain/Client.java
@@ -0,0 +1,83 @@
+/*
+ * 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.provisioner.api.v1.domain;
+
+import org.springframework.util.Assert;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.validation.constraints.NotNull;
+
+@SuppressWarnings("unused")
+public final class Client {
+
+  @NotNull
+  private String name;
+  private String description;
+  private String redirectUri;
+  private String vendor;
+  private String homepage;
+
+  public Client() {
+    super();
+  }
+
+  @Nonnull
+  public String getName() {
+    return name;
+  }
+
+  public void setName(@Nonnull final String name) {
+    Assert.notNull(name, "Client name must be given!");
+    this.name = name;
+  }
+
+  @Nullable
+  public String getDescription() {
+    return description;
+  }
+
+  public void setDescription(@Nullable final String description) {
+    this.description = description;
+  }
+
+  @Nullable
+  public String getRedirectUri() {
+    return redirectUri;
+  }
+
+  public void setRedirectUri(@Nullable final String redirectUri) {
+    this.redirectUri = redirectUri;
+  }
+
+  @Nullable
+  public String getVendor() {
+    return vendor;
+  }
+
+  public void setVendor(@Nullable final String vendor) {
+    this.vendor = vendor;
+  }
+
+  @Nullable
+  public String getHomepage() {
+    return homepage;
+  }
+
+  public void setHomepage(@Nullable final String homepage) {
+    this.homepage = homepage;
+  }
+}
diff --git a/api/src/main/java/io/mifos/provisioner/api/v1/domain/ClientCredentials.java b/api/src/main/java/io/mifos/provisioner/api/v1/domain/ClientCredentials.java
new file mode 100644
index 0000000..63cbca6
--- /dev/null
+++ b/api/src/main/java/io/mifos/provisioner/api/v1/domain/ClientCredentials.java
@@ -0,0 +1,37 @@
+/*
+ * 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.provisioner.api.v1.domain;
+
+@SuppressWarnings("unused")
+public final class ClientCredentials {
+
+  private final String id;
+  private final String secret;
+
+  public ClientCredentials(String id, String secret) {
+    super();
+    this.id = id;
+    this.secret = secret;
+  }
+
+  public String getId() {
+    return id;
+  }
+
+  public String getSecret() {
+    return secret;
+  }
+}
diff --git a/api/src/main/java/io/mifos/provisioner/api/v1/domain/DatabaseConnectionInfo.java b/api/src/main/java/io/mifos/provisioner/api/v1/domain/DatabaseConnectionInfo.java
new file mode 100644
index 0000000..762ad32
--- /dev/null
+++ b/api/src/main/java/io/mifos/provisioner/api/v1/domain/DatabaseConnectionInfo.java
@@ -0,0 +1,93 @@
+/*
+ * 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.provisioner.api.v1.domain;
+
+import javax.annotation.Nonnull;
+import javax.validation.constraints.NotNull;
+
+@SuppressWarnings({"unused", "WeakerAccess"})
+public final class DatabaseConnectionInfo {
+
+  @NotNull
+  private String driverClass;
+  @NotNull
+  private String databaseName;
+  @NotNull
+  private String host;
+  @NotNull
+  private String port;
+  @NotNull
+  private String user;
+  @NotNull
+  private String password;
+
+  public DatabaseConnectionInfo() {
+    super();
+  }
+
+  @Nonnull
+  public String getDriverClass() {
+    return driverClass;
+  }
+
+  public void setDriverClass(@Nonnull final String driverClass) {
+    this.driverClass = driverClass;
+  }
+
+  public String getDatabaseName() {
+    return databaseName;
+  }
+
+  public void setDatabaseName(final String databaseName) {
+    this.databaseName = databaseName;
+  }
+
+  @Nonnull
+  public String getHost() {
+    return host;
+  }
+
+  public void setHost(@Nonnull final String host) {
+    this.host = host;
+  }
+
+  @Nonnull
+  public String getPort() {
+    return port;
+  }
+
+  public void setPort(@Nonnull final String port) {
+    this.port = port;
+  }
+
+  @Nonnull
+  public String getUser() {
+    return user;
+  }
+
+  public void setUser(@Nonnull final String user) {
+    this.user = user;
+  }
+
+  @Nonnull
+  public String getPassword() {
+    return password;
+  }
+
+  public void setPassword(@Nonnull final String password) {
+    this.password = password;
+  }
+}
diff --git a/api/src/main/java/io/mifos/provisioner/api/v1/domain/IdentityManagerInitialization.java b/api/src/main/java/io/mifos/provisioner/api/v1/domain/IdentityManagerInitialization.java
new file mode 100644
index 0000000..5a32694
--- /dev/null
+++ b/api/src/main/java/io/mifos/provisioner/api/v1/domain/IdentityManagerInitialization.java
@@ -0,0 +1,35 @@
+/*
+ * 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.provisioner.api.v1.domain;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings("unused")
+public final class IdentityManagerInitialization {
+
+  private String adminPassword;
+
+  public IdentityManagerInitialization() { }
+
+  public String getAdminPassword() {
+    return adminPassword;
+  }
+
+  public void setAdminPassword(String adminPassword) {
+    this.adminPassword = adminPassword;
+  }
+}
diff --git a/api/src/main/java/io/mifos/provisioner/api/v1/domain/PasswordPolicy.java b/api/src/main/java/io/mifos/provisioner/api/v1/domain/PasswordPolicy.java
new file mode 100644
index 0000000..426f4b6
--- /dev/null
+++ b/api/src/main/java/io/mifos/provisioner/api/v1/domain/PasswordPolicy.java
@@ -0,0 +1,43 @@
+/*
+ * 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.provisioner.api.v1.domain;
+
+@SuppressWarnings("unused")
+public class PasswordPolicy {
+
+  private String newPassword;
+  private Integer expiresInDays;
+
+  public PasswordPolicy() {
+    super();
+  }
+
+  public String getNewPassword() {
+    return newPassword;
+  }
+
+  public void setNewPassword(String newPassword) {
+    this.newPassword = newPassword;
+  }
+
+  public Integer getExpiresInDays() {
+    return expiresInDays;
+  }
+
+  public void setExpiresInDays(Integer expiresInDays) {
+    this.expiresInDays = expiresInDays;
+  }
+}
diff --git a/api/src/main/java/io/mifos/provisioner/api/v1/domain/Tenant.java b/api/src/main/java/io/mifos/provisioner/api/v1/domain/Tenant.java
new file mode 100644
index 0000000..da7a1ad
--- /dev/null
+++ b/api/src/main/java/io/mifos/provisioner/api/v1/domain/Tenant.java
@@ -0,0 +1,76 @@
+/*
+ * 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.provisioner.api.v1.domain;
+
+import javax.validation.constraints.NotNull;
+
+@SuppressWarnings("unused")
+public final class Tenant {
+
+  @NotNull
+  private String identifier;
+  @NotNull
+  private String name;
+  private String description;
+  @NotNull
+  private CassandraConnectionInfo cassandraConnectionInfo;
+  @NotNull
+  private DatabaseConnectionInfo databaseConnectionInfo;
+
+  public Tenant() {
+    super();
+  }
+
+  public String getIdentifier() {
+    return identifier;
+  }
+
+  public void setIdentifier(String identifier) {
+    this.identifier = identifier;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  public String getDescription() {
+    return description;
+  }
+
+  public void setDescription(String description) {
+    this.description = description;
+  }
+
+  public CassandraConnectionInfo getCassandraConnectionInfo() {
+    return cassandraConnectionInfo;
+  }
+
+  public void setCassandraConnectionInfo(CassandraConnectionInfo cassandraConnectionInfo) {
+    this.cassandraConnectionInfo = cassandraConnectionInfo;
+  }
+
+  public DatabaseConnectionInfo getDatabaseConnectionInfo() {
+    return databaseConnectionInfo;
+  }
+
+  public void setDatabaseConnectionInfo(DatabaseConnectionInfo databaseConnectionInfo) {
+    this.databaseConnectionInfo = databaseConnectionInfo;
+  }
+}
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..d622dae
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,36 @@
+group 'io.mifos'
+
+task publishApiToMavenLocal {
+    dependsOn gradle.includedBuild('api').task(':publishToMavenLocal')
+}
+
+task publishServiceToMavenLocal {
+    mustRunAfter publishApiToMavenLocal
+    dependsOn gradle.includedBuild('service').task(':publishToMavenLocal')
+}
+
+task publishComponentTestToMavenLocal {
+    mustRunAfter publishApiToMavenLocal
+    mustRunAfter publishServiceToMavenLocal
+    dependsOn gradle.includedBuild('component-test').task(':publishToMavenLocal')
+}
+
+task publishToMavenLocal {
+    group 'all'
+    dependsOn publishApiToMavenLocal
+    dependsOn publishServiceToMavenLocal
+    dependsOn publishComponentTestToMavenLocal
+}
+
+task prepareForTest {
+    group 'all'
+    dependsOn publishToMavenLocal
+    dependsOn gradle.includedBuild('component-test').task(':build')
+}
+
+task licenseFormat {
+    group 'all'
+    dependsOn gradle.includedBuild('api').task(':licenseFormat')
+    dependsOn gradle.includedBuild('service').task(':licenseFormat')
+    dependsOn gradle.includedBuild('component-test').task(':licenseFormat')
+}
\ No newline at end of file
diff --git a/component-test/build.gradle b/component-test/build.gradle
new file mode 100644
index 0000000..4c8056e
--- /dev/null
+++ b/component-test/build.gradle
@@ -0,0 +1,41 @@
+buildscript {
+    ext {
+        springBootVersion = '1.4.1.RELEASE'
+    }
+
+    repositories {
+        jcenter()
+    }
+
+    dependencies {
+        classpath ("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
+    }
+}
+
+plugins {
+    id 'com.github.hierynomus.license' version '0.13.1'
+}
+
+apply from: '../shared.gradle'
+
+dependencies {
+    compile(
+            [group: 'io.mifos.provisioner', name: 'api', version: project.version],
+            [group: 'io.mifos.provisioner', name: 'service', version: project.version],
+            [group: 'io.mifos.anubis', name: 'test', version: versions.frameworkanubis],
+            [group: 'io.mifos.anubis', name: 'api', version: versions.frameworkanubis],
+            [group: 'io.mifos.identity', name: 'api', version: versions.mifosidentityservice],
+            [group: 'io.mifos.core', name: 'api', version: versions.frameworkapi],
+            [group: 'io.mifos.core', name: 'test', version: versions.frameworktest],
+            [group: 'io.mifos.core', name: 'lang', version: versions.frameworklang],
+            [group: 'org.springframework.boot', name: 'spring-boot-starter-test'],
+    )
+}
+
+publishing {
+    publications {
+        mavenJava(MavenPublication) {
+            from components.java
+        }
+    }
+}
\ No newline at end of file
diff --git a/component-test/settings.gradle b/component-test/settings.gradle
new file mode 100644
index 0000000..07867cb
--- /dev/null
+++ b/component-test/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'component-test'
\ No newline at end of file
diff --git a/component-test/src/main/java/io/mifos/provisioner/AbstractServiceTest.java b/component-test/src/main/java/io/mifos/provisioner/AbstractServiceTest.java
new file mode 100644
index 0000000..d9fb284
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/provisioner/AbstractServiceTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.provisioner;
+
+import io.mifos.core.test.env.TestEnvironment;
+import io.mifos.provisioner.api.v1.client.ProvisionerService;
+import io.mifos.provisioner.config.ProvisionerServiceConfig;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.cloud.netflix.feign.EnableFeignClients;
+import org.springframework.cloud.netflix.ribbon.RibbonClient;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT,
+        classes = {AbstractServiceTest.TestConfiguration.class})
+public class AbstractServiceTest {
+  private static final String APP_NAME = "provisioner-v1";
+  private static final String CLIENT_ID = "sillyRabbit";
+
+  @Configuration
+  @EnableFeignClients(basePackages = {"io.mifos.provisioner.api.v1.client"})
+  @RibbonClient(name = APP_NAME)
+  @Import({ProvisionerServiceConfig.class})
+  public static class TestConfiguration {
+    public TestConfiguration() {
+      super();
+    }
+
+    @Bean()
+    public Logger logger() {
+      return LoggerFactory.getLogger("test-logger");
+    }
+  }
+
+
+  private static TestEnvironment testEnvironment = new TestEnvironment(APP_NAME);
+  private static ProvisionerMariaDBInitializer mariaDBInitializer = new ProvisionerMariaDBInitializer();
+  private static ProvisionerCassandraInitializer cassandraInitializer = new ProvisionerCassandraInitializer();
+
+  @ClassRule
+  public static TestRule orderClassRules = RuleChain
+          .outerRule(testEnvironment)
+          .around(mariaDBInitializer)
+          .around(cassandraInitializer);
+
+  @Autowired
+  protected ProvisionerService provisionerService;
+
+  public AbstractServiceTest() {
+    super();
+  }
+
+  @BeforeClass
+  public static void setup() throws Exception {
+    System.setProperty("provisioner.privateKey.modulus", testEnvironment.getSeshatPrivateKey().getModulus().toString());
+    System.setProperty("provisioner.privateKey.exponent", testEnvironment.getSeshatPrivateKey().getPrivateExponent().toString());
+    System.setProperty("provisioner.initialclientid", CLIENT_ID);
+  }
+
+  protected String getClientId() {
+    return CLIENT_ID;
+  }
+}
diff --git a/component-test/src/main/java/io/mifos/provisioner/ProvisionerCassandraInitializer.java b/component-test/src/main/java/io/mifos/provisioner/ProvisionerCassandraInitializer.java
new file mode 100644
index 0000000..89d2e33
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/provisioner/ProvisionerCassandraInitializer.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.provisioner;
+
+import org.apache.thrift.transport.TTransportException;
+import org.cassandraunit.utils.EmbeddedCassandraServerHelper;
+import org.junit.rules.ExternalResource;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author Myrle Krantz
+ */
+public class ProvisionerCassandraInitializer extends ExternalResource {
+  @Override
+  protected void before() throws InterruptedException, IOException, TTransportException {
+    EmbeddedCassandraServerHelper.startEmbeddedCassandra(TimeUnit.SECONDS.toMillis(30L));
+  }
+
+  @Override
+  protected void after() {
+    EmbeddedCassandraServerHelper.cleanEmbeddedCassandra();
+  }
+}
diff --git a/component-test/src/main/java/io/mifos/provisioner/ProvisionerMariaDBInitializer.java b/component-test/src/main/java/io/mifos/provisioner/ProvisionerMariaDBInitializer.java
new file mode 100644
index 0000000..ade2b11
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/provisioner/ProvisionerMariaDBInitializer.java
@@ -0,0 +1,41 @@
+/*
+ * 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.provisioner;
+
+import ch.vorburger.exec.ManagedProcessException;
+import ch.vorburger.mariadb4j.DB;
+import org.junit.rules.ExternalResource;
+
+/**
+ * @author Myrle Krantz
+ */
+public class ProvisionerMariaDBInitializer extends ExternalResource {
+  private static DB EMBEDDED_MARIA_DB;
+  @Override
+  protected void before() throws ManagedProcessException {
+    EMBEDDED_MARIA_DB = DB.newEmbeddedDB(3306);
+    EMBEDDED_MARIA_DB.start();
+  }
+
+  @Override
+  protected void after() {
+    try {
+      EMBEDDED_MARIA_DB.stop();
+    } catch (ManagedProcessException e) {
+      throw new RuntimeException(e);
+    }
+  }
+}
diff --git a/component-test/src/main/java/io/mifos/provisioner/application/Fixture.java b/component-test/src/main/java/io/mifos/provisioner/application/Fixture.java
new file mode 100644
index 0000000..446335b
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/provisioner/application/Fixture.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.provisioner.application;
+
+import io.mifos.provisioner.api.v1.domain.Application;
+
+class Fixture {
+
+  private static Application application = new Application();
+
+  static {
+    application.setName("comp-test-app");
+    application.setDescription("Component Test Application");
+    application.setHomepage("http://www.example.org");
+    application.setVendor("Component Test");
+  }
+
+  private Fixture() {
+    super();
+  }
+
+  static Application getApplication() {
+    return application;
+  }
+}
diff --git a/component-test/src/main/java/io/mifos/provisioner/application/TestApplications.java b/component-test/src/main/java/io/mifos/provisioner/application/TestApplications.java
new file mode 100644
index 0000000..fd89597
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/provisioner/application/TestApplications.java
@@ -0,0 +1,115 @@
+/*
+ * 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.provisioner.application;
+
+
+import io.mifos.core.api.context.AutoSeshat;
+import io.mifos.core.api.util.ApiConstants;
+import io.mifos.core.api.util.NotFoundException;
+import io.mifos.provisioner.AbstractServiceTest;
+import io.mifos.provisioner.api.v1.client.DuplicateIdentifierException;
+import io.mifos.provisioner.api.v1.domain.Application;
+import io.mifos.provisioner.api.v1.domain.AuthenticationResponse;
+import io.mifos.provisioner.config.ProvisionerConstants;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestApplications extends AbstractServiceTest {
+
+  private AutoSeshat autoSeshat;
+
+  public TestApplications() {
+    super();
+  }
+
+
+  @Before
+  public void before()
+  {
+    final AuthenticationResponse authentication = provisionerService.authenticate(
+            this.getClientId(), ApiConstants.SYSTEM_SU, ProvisionerConstants.INITIAL_PWD);
+    autoSeshat = new AutoSeshat(authentication.getToken());
+  }
+
+  @After
+  public void after() {
+    provisionerService.deleteApplication(Fixture.getApplication().getName());
+    autoSeshat.close();
+  }
+
+  @Test
+  public void shouldCreateApplication() {
+    final Application application = Fixture.getApplication();
+    provisionerService.createApplication(application);
+
+    final Application createdApplication = provisionerService.getApplication(application.getName());
+
+    Assert.assertNotNull(createdApplication);
+    Assert.assertEquals(application.getName(), createdApplication.getName());
+    Assert.assertEquals(application.getDescription(), createdApplication.getDescription());
+    Assert.assertEquals(application.getVendor(), createdApplication.getVendor());
+    Assert.assertEquals(application.getHomepage(), createdApplication.getHomepage());
+  }
+
+  @Test
+  public void shouldFindApplication() {
+    provisionerService.createApplication(Fixture.getApplication());
+    Assert.assertNotNull(provisionerService.getApplication(Fixture.getApplication().getName()));
+  }
+
+  @Test
+  public void shouldFetchAll() {
+    provisionerService.createApplication(Fixture.getApplication());
+    Assert.assertFalse(provisionerService.getApplications().isEmpty());
+  }
+
+  @Test(expected = DuplicateIdentifierException.class)
+  public void shouldFailCreateDuplicate() {
+    provisionerService.createApplication(Fixture.getApplication());
+    provisionerService.createApplication(Fixture.getApplication());
+  }
+
+  @Test(expected = NotFoundException.class)
+  public void shouldFailFindUnknown() {
+    provisionerService.getApplication("unknown");
+  }
+
+  @Test
+  public void shouldDeleteApplication() {
+    final Application applicationToDelete = new Application();
+    applicationToDelete.setName("deleteme");
+
+    provisionerService.createApplication(applicationToDelete);
+
+    try {
+      provisionerService.getApplication(applicationToDelete.getName());
+    } catch (final RuntimeException ignored) {
+      Assert.fail();
+    }
+
+    provisionerService.deleteApplication(applicationToDelete.getName());
+
+    try {
+      provisionerService.getApplication(applicationToDelete.getName());
+      Assert.fail();
+    }
+    catch (final RuntimeException ex) {
+      Assert.assertTrue(ex instanceof NotFoundException);
+    }
+  }
+}
diff --git a/component-test/src/main/java/io/mifos/provisioner/client/Fixture.java b/component-test/src/main/java/io/mifos/provisioner/client/Fixture.java
new file mode 100644
index 0000000..e32bc8f
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/provisioner/client/Fixture.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.provisioner.client;
+
+import io.mifos.provisioner.api.v1.domain.Client;
+
+class Fixture {
+
+  private static Client compTestClient = new Client();
+
+  static {
+    compTestClient.setName("comp-test");
+    compTestClient.setDescription("Component Test Client");
+    compTestClient.setHomepage("http://www.example.org");
+    compTestClient.setVendor("Component Test");
+    compTestClient.setRedirectUri("http://redirect.me");
+  }
+
+  private Fixture() {
+    super();
+  }
+
+  static Client getCompTestClient() {
+    return compTestClient;
+  }
+}
diff --git a/component-test/src/main/java/io/mifos/provisioner/client/TestClients.java b/component-test/src/main/java/io/mifos/provisioner/client/TestClients.java
new file mode 100644
index 0000000..73aac42
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/provisioner/client/TestClients.java
@@ -0,0 +1,116 @@
+/*
+ * 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.provisioner.client;
+
+import io.mifos.core.api.context.AutoSeshat;
+import io.mifos.core.api.util.ApiConstants;
+import io.mifos.core.api.util.NotFoundException;
+import io.mifos.provisioner.AbstractServiceTest;
+import io.mifos.provisioner.api.v1.client.DuplicateIdentifierException;
+import io.mifos.provisioner.api.v1.domain.AuthenticationResponse;
+import io.mifos.provisioner.api.v1.domain.Client;
+import io.mifos.provisioner.config.ProvisionerConstants;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestClients extends AbstractServiceTest {
+
+  private AutoSeshat autoSeshat;
+
+  public TestClients() {
+    super();
+  }
+  @Before
+  public void before()
+  {
+    final AuthenticationResponse authentication = provisionerService.authenticate(
+        this.getClientId(), ApiConstants.SYSTEM_SU, ProvisionerConstants.INITIAL_PWD);
+    autoSeshat = new AutoSeshat(authentication.getToken());
+  }
+
+  @After
+  public void after() {
+    provisionerService.deleteClient(Fixture.getCompTestClient().getName());
+    autoSeshat.close();
+  }
+
+  @Test
+  public void shouldCreateClient() {
+    final Client client = Fixture.getCompTestClient();
+
+    provisionerService.createClient(client);
+    //TODO: add waiting?
+
+    final Client newlyCreatedClient = provisionerService.getClient(client.getName());
+
+    Assert.assertEquals(client.getName(), newlyCreatedClient.getName());
+    Assert.assertEquals(client.getDescription(), newlyCreatedClient.getDescription());
+    Assert.assertEquals(client.getHomepage(), newlyCreatedClient.getHomepage());
+    Assert.assertEquals(client.getVendor(), newlyCreatedClient.getVendor());
+    Assert.assertEquals(client.getRedirectUri(), newlyCreatedClient.getRedirectUri());
+  }
+
+  @Test(expected = DuplicateIdentifierException.class)
+  public void shouldFailCreateClientAlreadyExists() {
+    final Client client = new Client();
+    client.setName("duplicate-client");
+
+    provisionerService.createClient(client);
+    provisionerService.createClient(client);
+  }
+
+  @Test
+  public void shouldFindClient() {
+    provisionerService.createClient(Fixture.getCompTestClient());
+    Assert.assertNotNull(provisionerService.getClient(Fixture.getCompTestClient().getName()));
+  }
+
+  @Test(expected = NotFoundException.class)
+  public void shouldNotFindClientUnknown() {
+    provisionerService.getClient("unknown-client");
+  }
+
+  @Test
+  public void shouldFetchAllClients() {
+    Assert.assertFalse(provisionerService.getClients().isEmpty());
+  }
+
+  @Test
+  public void shouldDeleteClient() {
+    final Client clientToDelete = new Client();
+    clientToDelete.setName("deleteme");
+
+    provisionerService.createClient(clientToDelete);
+
+    try {
+      provisionerService.getClient(clientToDelete.getName());
+    } catch (final Exception ex) {
+      Assert.fail();
+    }
+
+    provisionerService.deleteClient(clientToDelete.getName());
+
+    try {
+      provisionerService.getClient(clientToDelete.getName());
+      Assert.fail();
+    }
+    catch (final RuntimeException ex) {
+      Assert.assertTrue(ex instanceof NotFoundException);
+    }
+  }
+}
\ No newline at end of file
diff --git a/component-test/src/main/java/io/mifos/provisioner/internal/TestProvision.java b/component-test/src/main/java/io/mifos/provisioner/internal/TestProvision.java
new file mode 100644
index 0000000..1b13a80
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/provisioner/internal/TestProvision.java
@@ -0,0 +1,63 @@
+/*
+ * 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.provisioner.internal;
+
+import com.datastax.driver.core.KeyspaceMetadata;
+import com.datastax.driver.core.ResultSet;
+import com.datastax.driver.core.Session;
+
+import io.mifos.core.cassandra.core.CassandraSessionProvider;
+import io.mifos.core.cassandra.util.CassandraConnectorConstants;
+import io.mifos.provisioner.AbstractServiceTest;
+import org.junit.Assert;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+public class TestProvision extends AbstractServiceTest {
+
+  public TestProvision() {
+    super();
+  }
+
+  @Autowired
+  protected CassandraSessionProvider cassandraSessionProvider;
+
+  @Test
+  public void dataModelExists() throws Exception {
+    try (final Session session = cassandraSessionProvider.getAdminSession()) {
+      final KeyspaceMetadata keyspace = session.getCluster().getMetadata().getKeyspace(CassandraConnectorConstants.KEYSPACE_PROP_DEFAULT);
+
+      Assert.assertTrue(keyspace != null);
+      Assert.assertTrue(keyspace.getTable("config") != null);
+      Assert.assertTrue(keyspace.getTable("users") != null);
+      Assert.assertTrue(keyspace.getTable("tenants") != null);
+      Assert.assertTrue(keyspace.getTable("applications") != null);
+      Assert.assertTrue(keyspace.getTable("tenant_applications") != null);
+      Assert.assertTrue(keyspace.getTable("clients") != null);
+
+      session.execute("USE " + CassandraConnectorConstants.KEYSPACE_PROP_DEFAULT);
+
+      final ResultSet configResultSet = session.execute("SELECT * FROM config WHERE name = 'io.mifos.provisioner.internal'");
+      Assert.assertNotNull(configResultSet.one());
+
+      final ResultSet userResultSet = session.execute("SELECT * FROM users WHERE name = 'wepemnefret'");
+      Assert.assertNotNull(userResultSet.one());
+
+      final ResultSet clientResultSet = session.execute("SELECT * FROM clients");
+      Assert.assertNotNull(clientResultSet.one());
+    }
+  }
+}
diff --git a/component-test/src/main/java/io/mifos/provisioner/security/TestAuthentication.java b/component-test/src/main/java/io/mifos/provisioner/security/TestAuthentication.java
new file mode 100644
index 0000000..7a17509
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/provisioner/security/TestAuthentication.java
@@ -0,0 +1,55 @@
+/*
+ * 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.provisioner.security;
+
+
+import io.mifos.core.api.util.ApiConstants;
+import io.mifos.provisioner.AbstractServiceTest;
+import io.mifos.provisioner.api.v1.client.InvalidProvisionerCredentialsException;
+import io.mifos.provisioner.api.v1.domain.AuthenticationResponse;
+import io.mifos.provisioner.config.ProvisionerConstants;
+import org.junit.Assert;
+import org.junit.Test;
+import org.springframework.util.Base64Utils;
+
+public class TestAuthentication extends AbstractServiceTest {
+
+  public TestAuthentication() {
+    super();
+  }
+
+  @Test
+  public void shouldLoginAdmin() {
+    final AuthenticationResponse authenticate
+        = provisionerService.authenticate(this.getClientId(), ApiConstants.SYSTEM_SU, ProvisionerConstants.INITIAL_PWD);
+    Assert.assertNotNull(authenticate.getToken());
+  }
+
+  @Test(expected = InvalidProvisionerCredentialsException.class)
+  public void shouldFailLoginWrongClientId() {
+    provisionerService.authenticate("wrong-client", ApiConstants.SYSTEM_SU, ProvisionerConstants.INITIAL_PWD);
+  }
+
+  @Test(expected = InvalidProvisionerCredentialsException.class)
+  public void shouldFailLoginWrongUser() {
+    provisionerService.authenticate(this.getClientId(), "wrong-user", ProvisionerConstants.INITIAL_PWD);
+  }
+
+  @Test(expected = InvalidProvisionerCredentialsException.class)
+  public void shouldFailLoginWrongPassword() {
+    provisionerService.authenticate(this.getClientId(), ApiConstants.SYSTEM_SU, Base64Utils.encodeToString("wrong-pwd".getBytes()));
+  }
+}
diff --git a/component-test/src/main/java/io/mifos/provisioner/security/TestPasswordPolicy.java b/component-test/src/main/java/io/mifos/provisioner/security/TestPasswordPolicy.java
new file mode 100644
index 0000000..e54e572
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/provisioner/security/TestPasswordPolicy.java
@@ -0,0 +1,99 @@
+/*
+ * 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.provisioner.security;
+
+import io.mifos.core.api.context.AutoUserContext;
+import io.mifos.core.api.util.ApiConstants;
+import io.mifos.provisioner.AbstractServiceTest;
+import io.mifos.provisioner.api.v1.domain.AuthenticationResponse;
+import io.mifos.provisioner.api.v1.domain.PasswordPolicy;
+import io.mifos.provisioner.config.ProvisionerConstants;
+import org.junit.Assert;
+import org.junit.Test;
+import org.springframework.util.Base64Utils;
+
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * @author Myrle Krantz
+ */
+public class TestPasswordPolicy extends AbstractServiceTest {
+
+  static private String currentPassword = ProvisionerConstants.INITIAL_PWD;
+
+  @Test
+  public void shouldUpdatePassword() {
+    final PasswordPolicy passwordPolicy = new PasswordPolicy();
+    passwordPolicy.setNewPassword(Base64Utils.encodeToString("new-pwd".getBytes()));
+
+    setPasswordPolicy(passwordPolicy);
+    currentPassword = Base64Utils.encodeToString("new-pwd".getBytes());
+
+    final AuthenticationResponse authenticate =
+        provisionerService.authenticate(this.getClientId(), ApiConstants.SYSTEM_SU, currentPassword);
+
+    checkAuthenticationResponse(authenticate);
+  }
+
+  @Test
+  public void shouldUpdatePasswordExpiration() {
+    final PasswordPolicy passwordPolicy = new PasswordPolicy();
+    passwordPolicy.setExpiresInDays(10);
+
+    setPasswordPolicy(passwordPolicy);
+
+    final AuthenticationResponse authenticate =
+        provisionerService.authenticate(this.getClientId(), ApiConstants.SYSTEM_SU, currentPassword);
+
+    checkAuthenticationResponse(authenticate);
+  }
+
+  @Test
+  public void shouldUpdatePasswordPolicy() {
+    final PasswordPolicy passwordPolicy = new PasswordPolicy();
+    passwordPolicy.setNewPassword(Base64Utils.encodeToString("new-pwd".getBytes()));
+    passwordPolicy.setExpiresInDays(10);
+
+    setPasswordPolicy(passwordPolicy);
+    currentPassword = Base64Utils.encodeToString("new-pwd".getBytes());
+
+    final AuthenticationResponse authenticate =
+        provisionerService.authenticate(this.getClientId(), ApiConstants.SYSTEM_SU, currentPassword);
+
+    checkAuthenticationResponse(authenticate);
+  }
+
+  private void setPasswordPolicy(final PasswordPolicy passwordPolicy)
+  {
+    final AuthenticationResponse authenticate = provisionerService.authenticate(this.getClientId(), ApiConstants.SYSTEM_SU, currentPassword);
+    try (final AutoUserContext ignore = new AutoUserContext(ApiConstants.SYSTEM_SU, authenticate.getToken())) {
+      provisionerService.updatePasswordPolicy(ApiConstants.SYSTEM_SU, passwordPolicy);
+    }
+  }
+
+  private void checkAuthenticationResponse(final AuthenticationResponse authenticate)
+  {
+    Assert.assertNotNull(authenticate.getToken());
+
+    final String passwordExpiresAt = authenticate.getAccessTokenExpiration();
+    Assert.assertNotNull(passwordExpiresAt);
+    final LocalDateTime expires
+        = LocalDateTime.parse(passwordExpiresAt, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
+    Assert.assertTrue(expires.isAfter(LocalDateTime.now(ZoneId.of("UTC"))));
+  }
+}
\ No newline at end of file
diff --git a/component-test/src/main/java/io/mifos/provisioner/tenant/Fixture.java b/component-test/src/main/java/io/mifos/provisioner/tenant/Fixture.java
new file mode 100644
index 0000000..a7cf7f2
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/provisioner/tenant/Fixture.java
@@ -0,0 +1,57 @@
+/*
+ * 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.provisioner.tenant;
+
+
+import io.mifos.provisioner.api.v1.domain.CassandraConnectionInfo;
+import io.mifos.provisioner.api.v1.domain.DatabaseConnectionInfo;
+import io.mifos.provisioner.api.v1.domain.Tenant;
+
+class Fixture {
+
+  private static Tenant compTestTenant = new Tenant();
+
+  static final String TENANT_IDENTIFIER = "comp-test";
+
+  static final String TENANT_NAME = "Comp Test";
+
+  static {
+    compTestTenant.setIdentifier(TENANT_IDENTIFIER);
+    compTestTenant.setName(TENANT_NAME);
+    compTestTenant.setDescription("Component Test Tenant");
+
+    final CassandraConnectionInfo cassandraConnectionInfo = new CassandraConnectionInfo();
+    compTestTenant.setCassandraConnectionInfo(cassandraConnectionInfo);
+    cassandraConnectionInfo.setClusterName("Test Cluster");
+    cassandraConnectionInfo.setContactPoints("127.0.0.1:9142");
+    cassandraConnectionInfo.setKeyspace("comp_test");
+    cassandraConnectionInfo.setReplicas("3");
+    cassandraConnectionInfo.setReplicationType("Simple");
+
+    final DatabaseConnectionInfo databaseConnectionInfo = new DatabaseConnectionInfo();
+    compTestTenant.setDatabaseConnectionInfo(databaseConnectionInfo);
+    databaseConnectionInfo.setDriverClass("org.mariadb.jdbc.Driver");
+    databaseConnectionInfo.setDatabaseName("comp_test");
+    databaseConnectionInfo.setHost("localhost");
+    databaseConnectionInfo.setPort("3306");
+    databaseConnectionInfo.setUser("root");
+    databaseConnectionInfo.setPassword("mysql");
+  }
+
+  static Tenant getCompTestTenant() {
+    return compTestTenant;
+  }
+}
diff --git a/component-test/src/main/java/io/mifos/provisioner/tenant/TestTenantApplicationAssignment.java b/component-test/src/main/java/io/mifos/provisioner/tenant/TestTenantApplicationAssignment.java
new file mode 100644
index 0000000..6f895be
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/provisioner/tenant/TestTenantApplicationAssignment.java
@@ -0,0 +1,335 @@
+/*
+ * 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.provisioner.tenant;
+
+import io.mifos.anubis.api.v1.client.Anubis;
+import io.mifos.anubis.api.v1.domain.PermittableEndpoint;
+import io.mifos.anubis.api.v1.domain.Signature;
+import io.mifos.anubis.provider.SystemRsaKeyProvider;
+import io.mifos.anubis.test.v1.SystemSecurityEnvironment;
+import io.mifos.anubis.token.TokenSerializationResult;
+import io.mifos.core.api.context.AutoSeshat;
+import io.mifos.core.api.util.ApiConstants;
+import io.mifos.core.api.util.ApiFactory;
+import io.mifos.core.lang.AutoTenantContext;
+import io.mifos.core.test.env.TestEnvironment;
+import io.mifos.identity.api.v1.client.IdentityService;
+import io.mifos.identity.api.v1.domain.PermittableGroup;
+import io.mifos.provisioner.ProvisionerCassandraInitializer;
+import io.mifos.provisioner.ProvisionerMariaDBInitializer;
+import io.mifos.provisioner.api.v1.client.ProvisionerService;
+import io.mifos.provisioner.api.v1.domain.*;
+import io.mifos.provisioner.config.ProvisionerConstants;
+import io.mifos.provisioner.config.ProvisionerServiceConfig;
+import io.mifos.provisioner.internal.service.applications.ApplicationCallContextProvider;
+import io.mifos.provisioner.internal.util.TokenProvider;
+import org.junit.*;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.cloud.netflix.feign.EnableFeignClients;
+import org.springframework.cloud.netflix.ribbon.RibbonClient;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.*;
+
+/**
+ * @author Myrle Krantz
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
+public class TestTenantApplicationAssignment {
+  private static final String APP_NAME = "provisioner-v1";
+  private static final String CLIENT_ID = "sillyRabbit";
+
+  @Configuration
+  @EnableFeignClients(basePackages = {"io.mifos.provisioner.api.v1.client"})
+  @RibbonClient(name = APP_NAME)
+  @Import({ProvisionerServiceConfig.class})
+  public static class TestConfiguration {
+    public TestConfiguration() {
+      super();
+    }
+
+    @Bean()
+    public Logger logger() {
+      return LoggerFactory.getLogger("test-logger");
+    }
+
+    @Bean()
+    public ApplicationCallContextProvider applicationCallContextProvider(
+            final ApiFactory apiFactory,
+            final @Qualifier("tokenProviderSpy") TokenProvider tokenProviderSpy)
+    {
+      return Mockito.spy(new ApplicationCallContextProvider(apiFactory, tokenProviderSpy));
+    }
+
+    @Bean(name = "tokenProviderSpy")
+    public TokenProvider tokenProviderSpy(final @Qualifier("tokenProvider") TokenProvider tokenProvider)
+    {
+      return Mockito.spy(tokenProvider);
+    }
+  }
+
+
+  private static TestEnvironment testEnvironment = new TestEnvironment(APP_NAME);
+  private static ProvisionerMariaDBInitializer mariaDBInitializer = new ProvisionerMariaDBInitializer();
+  private static ProvisionerCassandraInitializer cassandraInitializer = new ProvisionerCassandraInitializer();
+  private static SystemSecurityEnvironment systemSecurityEnvironment
+          = new SystemSecurityEnvironment(testEnvironment.getSeshatPublicKey(), testEnvironment.getSeshatPrivateKey());
+
+  @ClassRule
+  public static TestRule orderClassRules = RuleChain
+          .outerRule(testEnvironment)
+          .around(mariaDBInitializer)
+          .around(cassandraInitializer);
+
+  @Autowired
+  private ProvisionerService provisionerService;
+
+  @Autowired
+  @Qualifier("tokenProviderSpy")
+  protected TokenProvider tokenProviderSpy;
+
+  @Autowired
+  protected ApplicationCallContextProvider applicationCallContextProviderSpy;
+
+  @Autowired
+  protected SystemRsaKeyProvider systemRsaKeyProvider;
+
+  private AutoSeshat autoSeshat;
+
+  public TestTenantApplicationAssignment() {
+    super();
+  }
+
+  @BeforeClass
+  public static void setup() throws Exception {
+
+    System.setProperty("provisioner.privateKey.modulus", testEnvironment.getSeshatPrivateKey().getModulus().toString());
+    System.setProperty("provisioner.privateKey.exponent", testEnvironment.getSeshatPrivateKey().getPrivateExponent().toString());
+    System.setProperty("provisioner.initialclientid", CLIENT_ID);
+  }
+
+  @Before
+  public void before()
+  {
+    final AuthenticationResponse authentication = this.provisionerService.authenticate(
+            CLIENT_ID, ApiConstants.SYSTEM_SU, ProvisionerConstants.INITIAL_PWD);
+    autoSeshat = new AutoSeshat(authentication.getToken());
+  }
+
+  @After
+  public void after() throws InterruptedException {
+    this.provisionerService.deleteTenant(Fixture.getCompTestTenant().getIdentifier());
+    Thread.sleep(500L);
+    autoSeshat.close();
+  }
+
+  private static class TokenChecker implements Answer<TokenSerializationResult> {
+    TokenSerializationResult result = null;
+
+    @Override
+    public TokenSerializationResult answer(final InvocationOnMock invocation) throws Throwable {
+      result = (TokenSerializationResult) invocation.callRealMethod();
+      Assert.assertNotNull(result);
+      return result;
+    }
+  }
+
+  private class VerifyIsisInitializeContext implements Answer<Signature> {
+
+    private final BigInteger modulus;
+    private final BigInteger exponent;
+
+    private boolean validSecurityContext = false;
+
+    VerifyIsisInitializeContext(final BigInteger modulus, final BigInteger exponent) {
+      this.modulus = modulus;
+      this.exponent = exponent;
+    }
+
+    @Override
+    public Signature answer(final InvocationOnMock invocation) throws Throwable {
+      validSecurityContext = systemSecurityEnvironment.isValidSystemSecurityContext("identity", "1", Fixture.TENANT_IDENTIFIER);
+
+      final Signature fakeSignature = new Signature();
+      fakeSignature.setPublicKeyMod(modulus);
+      fakeSignature.setPublicKeyExp(exponent);
+
+      return fakeSignature;
+    }
+
+    boolean isValidSecurityContext() {
+      return validSecurityContext;
+    }
+  }
+
+  private class VerifyAnubisInitializeContext implements Answer<Void> {
+
+    private boolean validSecurityContext = false;
+    final private String target;
+
+    private VerifyAnubisInitializeContext(final String target) {
+      this.target = target;
+    }
+
+    @Override
+    public Void answer(final InvocationOnMock invocation) throws Throwable {
+      validSecurityContext = systemSecurityEnvironment.isValidSystemSecurityContext(target, "1", Fixture.TENANT_IDENTIFIER);
+      return null;
+    }
+
+    boolean isValidSecurityContext() {
+      return validSecurityContext;
+    }
+  }
+
+
+  private class VerifyAnubisPermittablesContext implements Answer<List<PermittableEndpoint>> {
+
+    private boolean validSecurityContext = false;
+    private final List<PermittableEndpoint> answer;
+
+    private VerifyAnubisPermittablesContext(final List<PermittableEndpoint> answer) {
+      this.answer = answer;
+    }
+
+    @Override
+    public List<PermittableEndpoint> answer(final InvocationOnMock invocation) throws Throwable {
+      validSecurityContext = systemSecurityEnvironment.isValidGuestSecurityContext(Fixture.TENANT_IDENTIFIER);
+      return answer;
+    }
+
+    boolean isValidSecurityContext() {
+      return validSecurityContext;
+    }
+  }
+
+  @Test
+  public void testTenantApplicationAssignment() throws InterruptedException {
+    //Create io.mifos.provisioner.tenant
+    final Tenant tenant = Fixture.getCompTestTenant();
+    provisionerService.createTenant(tenant);
+
+
+    //Create identity service application
+    final Application identityServiceApp = new Application();
+    identityServiceApp.setName("identity-v1");
+    identityServiceApp.setHomepage("http://xyz.identity:2020/v1");
+    identityServiceApp.setDescription("identity manager");
+    identityServiceApp.setVendor("fineract");
+
+    provisionerService.createApplication(identityServiceApp);
+
+
+    //Assign identity service application.  This requires some mocking since we can't actually call initialize in a component test.
+    final AssignedApplication identityServiceAssigned = new AssignedApplication();
+    identityServiceAssigned.setName("identity-v1");
+
+
+    final IdentityService identityServiceMock = Mockito.mock(IdentityService.class);
+    when(applicationCallContextProviderSpy.getApplication(IdentityService.class, "http://xyz.identity:2020/v1")).thenReturn(identityServiceMock);
+
+    final VerifyIsisInitializeContext verifyInitializeContextAndReturnSignature;
+    try (final AutoTenantContext ignored = new AutoTenantContext(Fixture.TENANT_IDENTIFIER)) {
+      verifyInitializeContextAndReturnSignature = new VerifyIsisInitializeContext(
+              systemSecurityEnvironment.tenantPublicKey().getModulus(),
+              systemSecurityEnvironment.tenantPublicKey().getPublicExponent());
+    }
+    doAnswer(verifyInitializeContextAndReturnSignature).when(identityServiceMock).initialize(anyString());
+
+    final TokenChecker tokenChecker = new TokenChecker();
+    doAnswer(tokenChecker).when(tokenProviderSpy).createToken(Fixture.TENANT_IDENTIFIER, "identity-v1", 2L, TimeUnit.MINUTES);
+
+    {
+      final IdentityManagerInitialization identityServiceAdminInitialization
+              = provisionerService.assignIdentityManager(tenant.getIdentifier(), identityServiceAssigned);
+
+      Assert.assertTrue(verifyInitializeContextAndReturnSignature.isValidSecurityContext());
+      Assert.assertNotNull(identityServiceAdminInitialization);
+      Assert.assertNotNull(identityServiceAdminInitialization.getAdminPassword());
+    }
+
+    verify(applicationCallContextProviderSpy).getApplicationCallContext(Fixture.TENANT_IDENTIFIER, "identity-v1");
+
+
+    //Create horus application.
+    final Application officeApp = new Application();
+    officeApp.setName("office-v1");
+    officeApp.setHomepage("http://xyz.office:2021/v1");
+    officeApp.setDescription("organization manager");
+    officeApp.setVendor("fineract");
+
+    provisionerService.createApplication(officeApp);
+
+
+    //Assign horus application.
+    final AssignedApplication officeAssigned = new AssignedApplication();
+    officeAssigned.setName("office-v1");
+
+    final Anubis anubisMock = Mockito.mock(Anubis.class);
+    when(applicationCallContextProviderSpy.getApplication(Anubis.class, "http://xyz.office:2021/v1")).thenReturn(anubisMock);
+
+
+    final PermittableEndpoint xxPermittableEndpoint = new PermittableEndpoint("/x/y", "POST", "x");
+    final PermittableEndpoint xyPermittableEndpoint = new PermittableEndpoint("/y/z", "POST", "x");
+    final PermittableEndpoint xyGetPermittableEndpoint = new PermittableEndpoint("/y/z", "GET", "x");
+    final PermittableEndpoint mPermittableEndpoint = new PermittableEndpoint("/m/n", "GET", "m");
+
+    final VerifyAnubisInitializeContext verifyAnubisInitializeContext;
+    final VerifyAnubisPermittablesContext verifyAnubisPermittablesContext;
+    try (final AutoTenantContext ignored = new AutoTenantContext(Fixture.TENANT_IDENTIFIER)) {
+      verifyAnubisInitializeContext = new VerifyAnubisInitializeContext("office");
+      verifyAnubisPermittablesContext = new VerifyAnubisPermittablesContext(Arrays.asList(xxPermittableEndpoint, xxPermittableEndpoint, xyPermittableEndpoint, xyGetPermittableEndpoint, mPermittableEndpoint));
+    }
+    doAnswer(verifyAnubisInitializeContext).when(anubisMock).initialize(anyObject(), anyObject());
+    doAnswer(verifyAnubisPermittablesContext).when(anubisMock).getPermittableEndpoints();
+
+    {
+      provisionerService.assignApplications(tenant.getIdentifier(), Collections.singletonList(officeAssigned));
+      Thread.sleep(500L); //Application assigning is asynchronous.
+    }
+
+    verify(applicationCallContextProviderSpy).getApplicationCallContext(Fixture.TENANT_IDENTIFIER, "office-v1");
+    verify(applicationCallContextProviderSpy, never()).getApplicationCallContext(eq(Fixture.TENANT_NAME), Mockito.anyString());
+    verify(tokenProviderSpy).createToken(Fixture.TENANT_IDENTIFIER, "office-v1", 2L, TimeUnit.MINUTES);
+
+    verify(identityServiceMock).createPermittableGroup(new PermittableGroup("x", Arrays.asList(xxPermittableEndpoint, xyPermittableEndpoint, xyGetPermittableEndpoint)));
+    verify(identityServiceMock).createPermittableGroup(new PermittableGroup("m", Collections.singletonList(mPermittableEndpoint)));
+
+    Assert.assertTrue(verifyAnubisInitializeContext.isValidSecurityContext());
+    Assert.assertTrue(verifyAnubisPermittablesContext.isValidSecurityContext());
+  }
+}
diff --git a/component-test/src/main/java/io/mifos/provisioner/tenant/TestTenants.java b/component-test/src/main/java/io/mifos/provisioner/tenant/TestTenants.java
new file mode 100644
index 0000000..361243b
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/provisioner/tenant/TestTenants.java
@@ -0,0 +1,94 @@
+/*
+ * 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.provisioner.tenant;
+
+
+import io.mifos.core.api.context.AutoSeshat;
+import io.mifos.core.api.util.ApiConstants;
+import io.mifos.core.api.util.NotFoundException;
+import io.mifos.provisioner.AbstractServiceTest;
+import io.mifos.provisioner.api.v1.client.DuplicateIdentifierException;
+import io.mifos.provisioner.api.v1.domain.AuthenticationResponse;
+import io.mifos.provisioner.api.v1.domain.Tenant;
+import io.mifos.provisioner.config.ProvisionerConstants;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestTenants extends AbstractServiceTest {
+
+  public TestTenants() {
+    super();
+  }
+
+  private AutoSeshat autoSeshat;
+
+  @Before
+  public void before()
+  {
+    final AuthenticationResponse authentication = provisionerService.authenticate(
+        this.getClientId(), ApiConstants.SYSTEM_SU, ProvisionerConstants.INITIAL_PWD);
+    autoSeshat = new AutoSeshat(authentication.getToken());
+  }
+
+  @After
+  public void after() throws InterruptedException {
+    provisionerService.deleteTenant(Fixture.getCompTestTenant().getIdentifier());
+    Thread.sleep(1200L);
+    autoSeshat.close();
+  }
+
+  @Test
+  public void shouldCreateTenant() throws Exception {
+    final Tenant tenant = Fixture.getCompTestTenant();
+    provisionerService.createTenant(tenant);
+
+    final Tenant tenantCreated = provisionerService.getTenant(tenant.getIdentifier());
+
+    Assert.assertNotNull(tenantCreated);
+    Assert.assertEquals(tenant.getIdentifier(), tenantCreated.getIdentifier());
+    Assert.assertEquals(tenant.getName(), tenantCreated.getName());
+    Assert.assertEquals(tenant.getDescription(), tenantCreated.getDescription());
+    Assert.assertEquals(tenant.getCassandraConnectionInfo(), tenantCreated.getCassandraConnectionInfo());
+
+  }
+
+  @Test(expected = DuplicateIdentifierException.class)
+  public void shouldFailCreateDuplicate() {
+    provisionerService.createTenant(Fixture.getCompTestTenant());
+    provisionerService.createTenant(Fixture.getCompTestTenant());
+  }
+
+  @Test
+  public void shouldFindTenant() {
+    final Tenant tenant = Fixture.getCompTestTenant();
+    provisionerService.createTenant(tenant);
+    final Tenant foundTenant = provisionerService.getTenant(tenant.getIdentifier());
+    Assert.assertNotNull(foundTenant);
+  }
+
+  @Test(expected = NotFoundException.class)
+  public void shouldFailFindUnknown() {
+    provisionerService.getTenant("unknown");
+  }
+
+  @Test
+  public void shouldFetchAll() {
+    provisionerService.createTenant(Fixture.getCompTestTenant());
+    Assert.assertFalse(provisionerService.getTenants().isEmpty());
+  }
+}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..d7acaf6
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..9021a9e
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Mar 15 16:20:12 CET 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-bin.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..4453cce
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save ( ) {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/service/build.gradle b/service/build.gradle
new file mode 100644
index 0000000..fea755c
--- /dev/null
+++ b/service/build.gradle
@@ -0,0 +1,66 @@
+buildscript {
+    ext {
+        springBootVersion = '1.4.1.RELEASE'
+    }
+
+    repositories {
+        jcenter()
+    }
+
+    dependencies {
+        classpath ("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
+    }
+}
+
+plugins {
+    id 'com.github.hierynomus.license' version '0.13.1'
+}
+
+apply from: '../shared.gradle'
+
+apply plugin: 'spring-boot'
+
+springBoot {
+    executable = true
+    classifier = 'boot'
+}
+
+dependencies {
+    compile(
+            [group: 'org.springframework.cloud', name: 'spring-cloud-starter-config'],
+            [group: 'org.springframework.cloud', name: 'spring-cloud-starter-eureka'],
+            [group: 'org.springframework.boot', name: 'spring-boot-starter-jetty'],
+            [group: 'io.mifos.provisioner', name: 'api', version: project.version],
+            [group: 'io.mifos.anubis', name: 'library', version: versions.frameworkanubis],
+            [group: 'io.mifos.anubis', name: 'api', version: versions.frameworkanubis],
+            [group: 'io.mifos.identity', name: 'api', version: versions.mifosidentityservice],
+            [group: 'com.google.code.gson', name: 'gson', version: versions.gson],
+            [group: 'io.mifos.core', name: 'api', version: versions.frameworkapi],
+            [group: 'io.mifos.core', name: 'lang', version: versions.frameworklang],
+            [group: 'io.mifos.core', name: 'async', version: versions.frameworkasync],
+            [group: 'io.mifos.core', name: 'cassandra', version: versions.frameworkcassandra],
+            [group: 'io.mifos.core', name: 'mariadb', version: versions.frameworkmariadb],
+            [group: 'org.hibernate', name: 'hibernate-validator', version: versions.validator],
+            [group: 'io.mifos.tools', name: 'crypto', version: versions.frameworkcrypto],
+    )
+}
+
+publishToMavenLocal.dependsOn bootRepackage
+
+publishing {
+    publications {
+        service(MavenPublication) {
+            from components.java
+            groupId project.group
+            artifactId project.name
+            version project.version
+        }
+        bootService(MavenPublication) {
+            // "boot" jar
+            artifact ("$buildDir/libs/$project.name-$version-boot.jar")
+            groupId project.group
+            artifactId ("$project.name-boot")
+            version project.version
+        }
+    }
+}
\ No newline at end of file
diff --git a/service/settings.gradle b/service/settings.gradle
new file mode 100644
index 0000000..081feb4
--- /dev/null
+++ b/service/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'service'
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/provisioner/ProvisionerApplication.java b/service/src/main/java/io/mifos/provisioner/ProvisionerApplication.java
new file mode 100644
index 0000000..bc15648
--- /dev/null
+++ b/service/src/main/java/io/mifos/provisioner/ProvisionerApplication.java
@@ -0,0 +1,32 @@
+/*
+ * 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.provisioner;
+
+import io.mifos.provisioner.config.ProvisionerServiceConfig;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class ProvisionerApplication {
+
+  public ProvisionerApplication() {
+    super();
+  }
+
+  public static void main(String[] args) {
+    SpringApplication.run(ProvisionerServiceConfig.class, args);
+  }
+}
diff --git a/service/src/main/java/io/mifos/provisioner/config/ProvisionerConstants.java b/service/src/main/java/io/mifos/provisioner/config/ProvisionerConstants.java
new file mode 100644
index 0000000..88832ae
--- /dev/null
+++ b/service/src/main/java/io/mifos/provisioner/config/ProvisionerConstants.java
@@ -0,0 +1,25 @@
+/*
+ * 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.provisioner.config;
+
+public interface ProvisionerConstants {
+
+  String LOGGER_NAME = "provisioner-logger";
+  String INITIAL_PWD = "oS/0IiAME/2unkN1momDrhAdNKOhGykYFH/mJN20";
+  String CONFIG_INTERNAL = "io.mifos.provisioner.internal";
+  int ITERATION_COUNT = 4096;
+  int HASH_LENGTH = 256;
+}
diff --git a/service/src/main/java/io/mifos/provisioner/config/ProvisionerServiceConfig.java b/service/src/main/java/io/mifos/provisioner/config/ProvisionerServiceConfig.java
new file mode 100644
index 0000000..6abfd19
--- /dev/null
+++ b/service/src/main/java/io/mifos/provisioner/config/ProvisionerServiceConfig.java
@@ -0,0 +1,97 @@
+/*
+ * 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.provisioner.config;
+
+import io.mifos.anubis.config.EnableAnubis;
+import io.mifos.anubis.config.TenantSignatureProvider;
+import io.mifos.anubis.repository.TenantAuthorizationDataRepository;
+import io.mifos.anubis.token.SystemAccessTokenSerializer;
+import io.mifos.core.api.util.ApiFactory;
+import io.mifos.core.async.config.EnableAsync;
+import io.mifos.core.cassandra.config.EnableCassandra;
+import io.mifos.core.cassandra.core.CassandraSessionProvider;
+import io.mifos.core.lang.ApplicationName;
+import io.mifos.core.lang.config.EnableApplicationName;
+import io.mifos.core.lang.config.EnableServiceException;
+import io.mifos.core.mariadb.config.EnableMariaDB;
+import io.mifos.provisioner.internal.util.TokenProvider;
+import io.mifos.tool.crypto.config.EnableCrypto;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.env.Environment;
+
+import java.math.BigInteger;
+
+@Configuration
+@EnableAutoConfiguration
+@ComponentScan({
+    "io.mifos.provisioner.internal.service",
+    "io.mifos.provisioner.internal.service.applications",
+    "io.mifos.provisioner.internal.repository",
+    "io.mifos.provisioner.rest.controller",
+})
+@EnableCrypto
+@EnableAsync
+@EnableAnubis(storeTenantKeysAtInitialization = false)
+@EnableMariaDB
+@EnableCassandra
+@EnableServiceException
+@EnableApplicationName
+public class ProvisionerServiceConfig {
+
+  public ProvisionerServiceConfig() {
+    super();
+  }
+
+  @Bean(name = ProvisionerConstants.LOGGER_NAME)
+  public Logger logger() {
+    return LoggerFactory.getLogger(ProvisionerConstants.LOGGER_NAME);
+  }
+
+  @Bean(name = "tokenProvider")
+  public TokenProvider tokenProvider(final Environment environment,
+                                     @SuppressWarnings("SpringJavaAutowiringInspection") final SystemAccessTokenSerializer tokenSerializer) {
+    return new TokenProvider(
+        new BigInteger(environment.getProperty("provisioner.privateKey.modulus")),
+        new BigInteger(environment.getProperty("provisioner.privateKey.exponent")), tokenSerializer);
+  }
+
+  @Bean
+  public ApiFactory apiFactory(@Qualifier(ProvisionerConstants.LOGGER_NAME) final Logger logger) {
+    return new ApiFactory(logger);
+  }
+
+  @Bean
+  public TenantSignatureProvider tenantSignatureProvider()
+  {
+    return tenant -> {
+      throw new IllegalArgumentException("no io.mifos.provisioner.tenant signatures here.");
+    };
+  }
+
+  @Bean
+  public TenantAuthorizationDataRepository tenantAuthorizationDataRepository(
+          final ApplicationName applicationName,
+          final CassandraSessionProvider cassandraSessionProvider)
+  {
+    return new TenantAuthorizationDataRepository(applicationName, cassandraSessionProvider);
+  }
+}
diff --git a/service/src/main/java/io/mifos/provisioner/internal/repository/ApplicationEntity.java b/service/src/main/java/io/mifos/provisioner/internal/repository/ApplicationEntity.java
new file mode 100644
index 0000000..fafa821
--- /dev/null
+++ b/service/src/main/java/io/mifos/provisioner/internal/repository/ApplicationEntity.java
@@ -0,0 +1,92 @@
+/*
+ * 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.provisioner.internal.repository;
+
+import com.datastax.driver.mapping.annotations.Column;
+import com.datastax.driver.mapping.annotations.PartitionKey;
+import com.datastax.driver.mapping.annotations.Table;
+
+@Table(name = ApplicationEntity.TABLE_NAME)
+public class ApplicationEntity {
+
+  static final String TABLE_NAME = "applications";
+  static final String NAME_COLUMN = "name";
+  static final String DESCRIPTION_COLUMN = "description";
+  static final String VENDOR_COLUMN = "vendor";
+  static final String HOMEPAGE_COLUMN = "homepage";
+
+  @PartitionKey
+  @Column(name = NAME_COLUMN)
+  private String name;
+  @Column(name = DESCRIPTION_COLUMN)
+  private String description;
+  @Column(name = VENDOR_COLUMN)
+  private String vendor;
+  @Column(name = HOMEPAGE_COLUMN)
+  private String homepage;
+
+  public ApplicationEntity() {
+    super();
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  public String getDescription() {
+    return description;
+  }
+
+  public void setDescription(String description) {
+    this.description = description;
+  }
+
+  public String getVendor() {
+    return vendor;
+  }
+
+  public void setVendor(String vendor) {
+    this.vendor = vendor;
+  }
+
+  public String getHomepage() {
+    return homepage;
+  }
+
+  public void setHomepage(String homepage) {
+    this.homepage = homepage;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    ApplicationEntity that = (ApplicationEntity) o;
+
+    return name.equals(that.name);
+
+  }
+
+  @Override
+  public int hashCode() {
+    return name.hashCode();
+  }
+}
diff --git a/service/src/main/java/io/mifos/provisioner/internal/repository/ClientEntity.java b/service/src/main/java/io/mifos/provisioner/internal/repository/ClientEntity.java
new file mode 100644
index 0000000..4b87976
--- /dev/null
+++ b/service/src/main/java/io/mifos/provisioner/internal/repository/ClientEntity.java
@@ -0,0 +1,103 @@
+/*
+ * 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.provisioner.internal.repository;
+
+import com.datastax.driver.mapping.annotations.Column;
+import com.datastax.driver.mapping.annotations.PartitionKey;
+import com.datastax.driver.mapping.annotations.Table;
+
+@Table(name = ClientEntity.TABLE_NAME)
+public class ClientEntity {
+
+  static final String TABLE_NAME = "clients";
+  static final String NAME_COLUMN = "name";
+  static final String DESCRIPTION_COLUMN = "description";
+  static final String REDIRECT_URI_COLUMN = "redirect_uri";
+  static final String VENDOR_COLUMN = "vendor";
+  static final String HOMEPAGE_COLUMN = "homepage";
+
+  @PartitionKey
+  @Column(name = NAME_COLUMN)
+  private String name;
+  @Column(name = DESCRIPTION_COLUMN)
+  private String description;
+  @Column(name = REDIRECT_URI_COLUMN)
+  private String redirectUri;
+  @Column(name = VENDOR_COLUMN)
+  private String vendor;
+  @Column(name = HOMEPAGE_COLUMN)
+  private String homepage;
+
+  public ClientEntity() {
+    super();
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  public String getDescription() {
+    return description;
+  }
+
+  public void setDescription(String description) {
+    this.description = description;
+  }
+
+  public String getRedirectUri() {
+    return redirectUri;
+  }
+
+  public void setRedirectUri(String redirectUri) {
+    this.redirectUri = redirectUri;
+  }
+
+  public String getVendor() {
+    return vendor;
+  }
+
+  public void setVendor(String vendor) {
+    this.vendor = vendor;
+  }
+
+  public String getHomepage() {
+    return homepage;
+  }
+
+  public void setHomepage(String homepage) {
+    this.homepage = homepage;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    ClientEntity that = (ClientEntity) o;
+
+    return name.equals(that.name);
+
+  }
+
+  @Override
+  public int hashCode() {
+    return name.hashCode();
+  }
+}
diff --git a/service/src/main/java/io/mifos/provisioner/internal/repository/ConfigEntity.java b/service/src/main/java/io/mifos/provisioner/internal/repository/ConfigEntity.java
new file mode 100644
index 0000000..fd23c36
--- /dev/null
+++ b/service/src/main/java/io/mifos/provisioner/internal/repository/ConfigEntity.java
@@ -0,0 +1,71 @@
+/*
+ * 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.provisioner.internal.repository;
+
+import com.datastax.driver.mapping.annotations.Column;
+import com.datastax.driver.mapping.annotations.PartitionKey;
+import com.datastax.driver.mapping.annotations.Table;
+
+@SuppressWarnings("unused")
+@Table(name = ConfigEntity.TABLE_NAME)
+public class ConfigEntity {
+
+  static final String TABLE_NAME = "config";
+  static final String NAME_COLUMN = "name";
+  static final String SECRET_COLUMN = "secret";
+
+  @PartitionKey
+  @Column(name = NAME_COLUMN)
+  private String name;
+  @Column(name = SECRET_COLUMN)
+  private byte[] secret;
+
+  public ConfigEntity() {
+    super();
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  public byte[] getSecret() {
+    return secret;
+  }
+
+  public void setSecret(byte[] secret) {
+    this.secret = secret;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    ConfigEntity that = (ConfigEntity) o;
+
+    return name.equals(that.name);
+
+  }
+
+  @Override
+  public int hashCode() {
+    return name.hashCode();
+  }
+}
diff --git a/service/src/main/java/io/mifos/provisioner/internal/repository/Provisioner.java b/service/src/main/java/io/mifos/provisioner/internal/repository/Provisioner.java
new file mode 100644
index 0000000..2b19b65
--- /dev/null
+++ b/service/src/main/java/io/mifos/provisioner/internal/repository/Provisioner.java
@@ -0,0 +1,223 @@
+/*
+ * 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.provisioner.internal.repository;
+
+import com.datastax.driver.core.BoundStatement;
+import com.datastax.driver.core.DataType;
+import com.datastax.driver.core.Session;
+import com.datastax.driver.core.schemabuilder.SchemaBuilder;
+import io.mifos.core.api.util.ApiConstants;
+import io.mifos.core.cassandra.core.CassandraSessionProvider;
+import io.mifos.core.cassandra.util.CassandraConnectorConstants;
+import io.mifos.core.mariadb.util.MariaDBConstants;
+import io.mifos.provisioner.config.ProvisionerConstants;
+import io.mifos.provisioner.internal.util.DataSourceUtils;
+import io.mifos.tool.crypto.HashGenerator;
+import io.mifos.tool.crypto.SaltGenerator;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.env.Environment;
+import org.springframework.security.crypto.util.EncodingUtils;
+import org.springframework.stereotype.Component;
+import org.springframework.util.Base64Utils;
+
+import javax.annotation.PostConstruct;
+import java.nio.ByteBuffer;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Date;
+import java.util.UUID;
+
+@SuppressWarnings({"SqlNoDataSourceInspection", "SqlDialectInspection"})
+@Component
+public class Provisioner {
+
+  private final Environment environment;
+  private final Logger logger;
+  private final CassandraSessionProvider cassandraSessionProvider;
+  private final SaltGenerator saltGenerator;
+  private final HashGenerator hashGenerator;
+  private final String initialClientId;
+  private String metaKeySpaceName;
+  private String mariaDBName;
+
+  @Autowired
+  public Provisioner(final Environment environment, @Qualifier(ProvisionerConstants.LOGGER_NAME) final Logger logger,
+                     final CassandraSessionProvider cassandraSessionProvider,
+                     final SaltGenerator saltGenerator, final HashGenerator hashGenerator,
+                     @Value("${provisioner.initialclientid}") final String initialClientId) {
+    super();
+    this.environment = environment;
+    this.logger = logger;
+    this.cassandraSessionProvider = cassandraSessionProvider;
+    this.saltGenerator = saltGenerator;
+    this.hashGenerator = hashGenerator;
+    this.initialClientId = initialClientId;
+  }
+
+  @PostConstruct
+  public void initialize() {
+    try {
+      metaKeySpaceName = this.environment.getProperty(
+          CassandraConnectorConstants.KEYSPACE_PROP,
+          CassandraConnectorConstants.KEYSPACE_PROP_DEFAULT);
+      mariaDBName = this.environment.getProperty(
+          MariaDBConstants.MARIADB_DATABASE_NAME_PROP,
+          MariaDBConstants.MARIADB_DATABASE_NAME_DEFAULT);
+
+      this.initializeCassandra();
+      this.initializeDatabase();
+    } catch (final Exception ex) {
+      throw new IllegalStateException("Could not initialize service!", ex);
+    }
+
+  }
+
+  private void initializeCassandra() throws Exception {
+    final Session session = this.cassandraSessionProvider.getAdminSession();
+
+    if (session.getCluster().getMetadata().getKeyspace(metaKeySpaceName).getTable(ConfigEntity.TABLE_NAME) == null) {
+      //create config family
+      final String createConfigTableStatement = SchemaBuilder.createTable(ConfigEntity.TABLE_NAME)
+          .addPartitionKey(ConfigEntity.NAME_COLUMN, DataType.text())
+          .addColumn(ConfigEntity.SECRET_COLUMN, DataType.blob())
+          .buildInternal();
+
+      session.execute(createConfigTableStatement);
+
+      final byte[] secret = this.saltGenerator.createRandomSalt();
+      final BoundStatement configBoundStatement = session.prepare("INSERT INTO config (name, secret) VALUES (?, ?)").bind();
+      configBoundStatement.setString("name", ProvisionerConstants.CONFIG_INTERNAL);
+      configBoundStatement.setBytes("secret", ByteBuffer.wrap(secret));
+      session.execute(configBoundStatement);
+
+      //create users family
+      final String createUsersTableStatement = SchemaBuilder.createTable(UserEntity.TABLE_NAME)
+          .addPartitionKey(UserEntity.NAME_COLUMN, DataType.text())
+          .addColumn(UserEntity.PASSWORD_COLUMN, DataType.blob())
+          .addColumn(UserEntity.SALT_COLUMN, DataType.blob())
+          .addColumn(UserEntity.ITERATION_COUNT_COLUMN, DataType.cint())
+          .addColumn(UserEntity.EXPIRES_IN_DAYS_COLUMN, DataType.cint())
+          .addColumn(UserEntity.PASSWORD_RESET_ON_COLUMN, DataType.timestamp())
+          .buildInternal();
+
+      session.execute(createUsersTableStatement);
+
+      final String username = ApiConstants.SYSTEM_SU;
+      final byte[] hashedPassword = Base64Utils.decodeFromString(ProvisionerConstants.INITIAL_PWD);
+      final byte[] variableSalt = this.saltGenerator.createRandomSalt();
+      final BoundStatement userBoundStatement =
+          session.prepare("INSERT INTO users (name, passwordWord, salt, iteration_count, password_reset_on) VALUES (?, ?, ?, ?, ?)").bind();
+      userBoundStatement.setString("name", username);
+      userBoundStatement.setBytes("passwordWord", ByteBuffer.wrap(
+          this.hashGenerator.hash(Base64Utils.encodeToString(hashedPassword), EncodingUtils.concatenate(variableSalt, secret),
+              ProvisionerConstants.ITERATION_COUNT, ProvisionerConstants.HASH_LENGTH)));
+      userBoundStatement.setBytes("salt", ByteBuffer.wrap(variableSalt));
+      userBoundStatement.setInt("iteration_count", ProvisionerConstants.ITERATION_COUNT);
+      userBoundStatement.setTimestamp("password_reset_on", new Date());
+      session.execute(userBoundStatement);
+
+      //create tenants family
+      final String createTenantsTableStatement = SchemaBuilder.createTable(TenantEntity.TABLE_NAME)
+          .addPartitionKey(TenantEntity.IDENTIFIER_COLUMN, DataType.text())
+          .addColumn(TenantEntity.CLUSTER_NAME_COLUMN, DataType.text())
+          .addColumn(TenantEntity.CONTACT_POINTS_COLUMN, DataType.text())
+          .addColumn(TenantEntity.KEYSPACE_NAME_COLUMN, DataType.text())
+          .addColumn(TenantEntity.REPLICATION_TYPE_COLUMN, DataType.text())
+          .addColumn(TenantEntity.REPLICAS_COLUMN, DataType.text())
+          .addColumn(TenantEntity.NAME_COLUMN, DataType.text())
+          .addColumn(TenantEntity.DESCRIPTION_COLUMN, DataType.text())
+          .addColumn(TenantEntity.IDENTITY_MANAGER_APPLICATION_NAME_COLUMN, DataType.text())
+          .addColumn(TenantEntity.IDENTITY_MANAGER_APPLICATION_URI_COLUMN, DataType.text())
+          .buildInternal();
+
+      session.execute(createTenantsTableStatement);
+
+
+      //create services family
+      final String createApplicationsTableStatement =
+          SchemaBuilder.createTable(ApplicationEntity.TABLE_NAME)
+              .addPartitionKey(ApplicationEntity.NAME_COLUMN, DataType.text())
+              .addColumn(ApplicationEntity.DESCRIPTION_COLUMN, DataType.text())
+              .addColumn(ApplicationEntity.VENDOR_COLUMN, DataType.text())
+              .addColumn(ApplicationEntity.HOMEPAGE_COLUMN, DataType.text())
+              .buildInternal();
+
+      session.execute(createApplicationsTableStatement);
+
+
+      //create io.mifos.provisioner.tenant services family
+      final String createTenantApplicationsTableStatement =
+          SchemaBuilder.createTable(TenantApplicationEntity.TABLE_NAME)
+              .addPartitionKey(TenantApplicationEntity.TENANT_IDENTIFIER_COLUMN, DataType.text())
+              .addColumn(TenantApplicationEntity.ASSIGNED_APPLICATIONS_COLUMN, DataType.set(DataType.text()))
+              .buildInternal();
+
+      session.execute(createTenantApplicationsTableStatement);
+
+
+      //create clients family
+      final String createClientsTableStatement =
+          SchemaBuilder.createTable(ClientEntity.TABLE_NAME)
+              .addPartitionKey(ClientEntity.NAME_COLUMN, DataType.text())
+              .addColumn(ClientEntity.DESCRIPTION_COLUMN, DataType.text())
+              .addColumn(ClientEntity.REDIRECT_URI_COLUMN, DataType.text())
+              .addColumn(ClientEntity.VENDOR_COLUMN, DataType.text())
+              .addColumn(ClientEntity.HOMEPAGE_COLUMN, DataType.text())
+              .buildInternal();
+      session.execute(createClientsTableStatement);
+      final String clientId = StringUtils.isEmpty(initialClientId) ? UUID.randomUUID().toString() : initialClientId;
+      this.logger.info(clientId);
+
+
+      final BoundStatement clientBoundStatement = session.prepare("INSERT INTO clients (name, description, vendor, homepage) VALUES (?, ?, ?, ?)").bind();
+      clientBoundStatement.setString("name", clientId);
+      clientBoundStatement.setString("description", "REST Console");
+      clientBoundStatement.setString("vendor", "Mifos Initiative");
+      clientBoundStatement.setString("homepage", "https://mifos.org");
+      session.execute(clientBoundStatement);
+    }
+  }
+
+  private void initializeDatabase() throws Exception {
+    final Connection connection = DataSourceUtils.createProvisionerConnection(this.environment);
+    try (final Statement statement = connection.createStatement()) {
+      // create meta data database
+      statement.execute("CREATE DATABASE IF NOT EXISTS " + mariaDBName);
+      statement.execute("USE " + mariaDBName);
+      // create tenants table
+      statement.execute("CREATE TABLE IF NOT EXISTS tenants (" +
+          "  identifier    VARCHAR(32) NOT NULL," +
+          "  driver_class  VARCHAR(255) NOT NULL," +
+          "  database_name VARCHAR(32) NOT NULL," +
+          "  host          VARCHAR(32) NOT NULL," +
+          "  port          VARCHAR(5)  NOT NULL," +
+          "  a_user        VARCHAR(32) NOT NULL," +
+          "  pwd           VARCHAR(32) NOT NULL," +
+          "  PRIMARY KEY (identifier)" +
+          ")");
+      connection.commit();
+    } catch (final SQLException sqlex) {
+      throw new IllegalStateException(sqlex.getMessage(), sqlex);
+    } finally {
+      connection.close();
+    }
+  }
+}
diff --git a/service/src/main/java/io/mifos/provisioner/internal/repository/TenantApplicationEntity.java b/service/src/main/java/io/mifos/provisioner/internal/repository/TenantApplicationEntity.java
new file mode 100644
index 0000000..b93a79f
--- /dev/null
+++ b/service/src/main/java/io/mifos/provisioner/internal/repository/TenantApplicationEntity.java
@@ -0,0 +1,72 @@
+/*
+ * 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.provisioner.internal.repository;
+
+import com.datastax.driver.mapping.annotations.Column;
+import com.datastax.driver.mapping.annotations.PartitionKey;
+import com.datastax.driver.mapping.annotations.Table;
+
+import java.util.Set;
+
+@Table(name = TenantApplicationEntity.TABLE_NAME)
+public class TenantApplicationEntity {
+
+  static final String TABLE_NAME = "tenant_applications";
+  static final String TENANT_IDENTIFIER_COLUMN = "tenant_identifier";
+  static final String ASSIGNED_APPLICATIONS_COLUMN = "assigned_applications";
+
+  @PartitionKey
+  @Column(name = TENANT_IDENTIFIER_COLUMN)
+  private String tenantIdentifier;
+  @Column(name = ASSIGNED_APPLICATIONS_COLUMN)
+  private Set<String> applications;
+
+  public TenantApplicationEntity() {
+    super();
+  }
+
+  public String getTenantIdentifier() {
+    return tenantIdentifier;
+  }
+
+  public void setTenantIdentifier(String tenantIdentifier) {
+    this.tenantIdentifier = tenantIdentifier;
+  }
+
+  public Set<String> getApplications() {
+    return applications;
+  }
+
+  public void setApplications(Set<String> applications) {
+    this.applications = applications;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    TenantApplicationEntity that = (TenantApplicationEntity) o;
+
+    return tenantIdentifier.equals(that.tenantIdentifier);
+
+  }
+
+  @Override
+  public int hashCode() {
+    return tenantIdentifier.hashCode();
+  }
+}
diff --git a/service/src/main/java/io/mifos/provisioner/internal/repository/TenantCassandraRepository.java b/service/src/main/java/io/mifos/provisioner/internal/repository/TenantCassandraRepository.java
new file mode 100644
index 0000000..4af47cd
--- /dev/null
+++ b/service/src/main/java/io/mifos/provisioner/internal/repository/TenantCassandraRepository.java
@@ -0,0 +1,134 @@
+/*
+ * 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.provisioner.internal.repository;
+
+import com.datastax.driver.core.Cluster;
+import com.datastax.driver.core.DataType;
+import com.datastax.driver.core.ResultSet;
+import com.datastax.driver.core.Session;
+import com.datastax.driver.core.schemabuilder.SchemaBuilder;
+import com.datastax.driver.mapping.Mapper;
+import com.datastax.driver.mapping.MappingManager;
+import com.datastax.driver.mapping.Result;
+import io.mifos.core.cassandra.core.CassandraSessionProvider;
+import io.mifos.core.cassandra.core.ReplicationStrategyResolver;
+import io.mifos.core.lang.ServiceException;
+import io.mifos.provisioner.internal.util.ContactPointUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Consumer;
+
+/**
+ * @author Myrle Krantz
+ */
+@Component
+public class TenantCassandraRepository {
+  private final CassandraSessionProvider cassandraSessionProvider;
+  private MappingManager mappingManager;
+
+  @Autowired
+  public TenantCassandraRepository(
+          final @Nonnull CassandraSessionProvider cassandraSessionProvider) {
+    this.cassandraSessionProvider = cassandraSessionProvider;
+  }
+
+  public Optional<TenantEntity> get(final @Nonnull String tenantIdentifier) {
+    final Mapper<TenantEntity> tenantEntityMapper = this.getMappingManager().mapper(TenantEntity.class);
+    return Optional.ofNullable(tenantEntityMapper.get(tenantIdentifier));
+  }
+
+  public void adjust(final @Nonnull String tenantIdentifier, final @Nonnull Consumer<TenantEntity> adjustment)
+  {
+    final Mapper<TenantEntity> tenantEntityMapper = this.getMappingManager().mapper(TenantEntity.class);
+    final TenantEntity tenantEntity = tenantEntityMapper.get(tenantIdentifier);
+    if (tenantEntity == null) {
+      throw ServiceException.notFound("Tenant {0} not found!", tenantIdentifier);
+    }
+
+    adjustment.accept(tenantEntity);
+
+    tenantEntityMapper.save(tenantEntity);
+  }
+
+  public List<TenantEntity> fetchAll() {
+    final ResultSet resultSet = this.cassandraSessionProvider.getAdminSession().execute(" SELECT * FROM tenants");
+    final Mapper<TenantEntity> tenantEntityMapper = this.getMappingManager().mapper(TenantEntity.class);
+    final Result<TenantEntity> map = tenantEntityMapper.map(resultSet);
+    return map.all();
+  }
+
+  public void delete(final @Nonnull String identifier) {
+    final Mapper<TenantEntity> tenantEntityMapper = this.getMappingManager().mapper(TenantEntity.class);
+    final TenantEntity tenantEntity = tenantEntityMapper.get(identifier);
+    if (tenantEntity != null) {
+      final Session session = this.getCluster(tenantEntity).connect();
+
+      // drop io.mifos.provisioner.tenant keyspace
+      session.execute("DROP KEYSPACE " + tenantEntity.getKeyspaceName());
+      session.close();
+
+      tenantEntityMapper.delete(identifier);
+    }
+  }
+
+  public void create(final @Nonnull TenantEntity tenant) {
+    final Mapper<TenantEntity> tenantEntityMapper = this.getMappingManager().mapper(TenantEntity.class);
+    if (tenantEntityMapper.get(tenant.getIdentifier()) != null) {
+      throw ServiceException.conflict("Tenant {0} already exists!", tenant.getIdentifier());
+    }
+    final Session session = this.getCluster(tenant).connect();
+    session.execute("CREATE KEYSPACE " + tenant.getKeyspaceName() + " WITH REPLICATION = " +
+            ReplicationStrategyResolver.replicationStrategy(
+                    tenant.getReplicationType(),
+                    tenant.getReplicas()));
+
+    final String createCommandSourceTable =
+            SchemaBuilder.createTable(tenant.getKeyspaceName(), "command_source")
+                    .addPartitionKey("source", DataType.text())
+                    .addPartitionKey("bucket", DataType.text())
+                    .addClusteringColumn("created_on", DataType.timestamp())
+                    .addColumn("command", DataType.text())
+                    .addColumn("processed", DataType.cboolean())
+                    .addColumn("failed", DataType.cboolean())
+                    .addColumn("failure_message", DataType.text())
+                    .buildInternal();
+    session.execute(createCommandSourceTable);
+    session.close();
+
+    tenantEntityMapper.save(tenant);
+  }
+
+  private Cluster getCluster(final @Nonnull TenantEntity tenantEntity) {
+    final Cluster.Builder clusterBuilder = Cluster
+            .builder()
+            .withClusterName(tenantEntity.getClusterName());
+
+    ContactPointUtils.process(clusterBuilder, tenantEntity.getContactPoints());
+
+    return clusterBuilder.build();
+  }
+
+  private MappingManager getMappingManager() {
+    if (this.mappingManager == null) {
+      this.mappingManager = new MappingManager(this.cassandraSessionProvider.getAdminSession());
+    }
+    return this.mappingManager;
+  }
+}
diff --git a/service/src/main/java/io/mifos/provisioner/internal/repository/TenantDAO.java b/service/src/main/java/io/mifos/provisioner/internal/repository/TenantDAO.java
new file mode 100644
index 0000000..0bcb23b
--- /dev/null
+++ b/service/src/main/java/io/mifos/provisioner/internal/repository/TenantDAO.java
@@ -0,0 +1,225 @@
+/*
+ * 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.provisioner.internal.repository;
+
+import io.mifos.provisioner.api.v1.domain.DatabaseConnectionInfo;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+@SuppressWarnings({"SqlDialectInspection", "SqlNoDataSourceInspection"})
+public class TenantDAO {
+
+  private static final class Builder {
+
+    private final ResultSet resultSet;
+
+    private Builder(final ResultSet resultSet) {
+      super();
+      this.resultSet = resultSet;
+    }
+
+    Optional<TenantDAO> build() throws SQLException {
+      if (this.resultSet.next()) {
+        final TenantDAO tenantDAO = new TenantDAO();
+        tenantDAO.setIdentifier(this.resultSet.getString("identifier"));
+        tenantDAO.setDriverClass(this.resultSet.getString("driver_class"));
+        tenantDAO.setDatabaseName(this.resultSet.getString("database_name"));
+        tenantDAO.setHost(this.resultSet.getString("host"));
+        tenantDAO.setPort(this.resultSet.getString("port"));
+        tenantDAO.setUser(this.resultSet.getString("a_user"));
+        tenantDAO.setPassword(this.resultSet.getString("pwd"));
+        return Optional.of(tenantDAO);
+      } else {
+        return Optional.empty();
+      }
+    }
+
+    List<TenantDAO> collect() throws SQLException {
+      final ArrayList<TenantDAO> tenantDAOs = new ArrayList<>();
+      while (this.resultSet.next()) {
+        final TenantDAO tenantDAO = new TenantDAO();
+        tenantDAOs.add(tenantDAO);
+        tenantDAO.setIdentifier(this.resultSet.getString("identifier"));
+        tenantDAO.setDriverClass(this.resultSet.getString("driver_class"));
+        tenantDAO.setDatabaseName(this.resultSet.getString("database_name"));
+        tenantDAO.setHost(this.resultSet.getString("host"));
+        tenantDAO.setPort(this.resultSet.getString("port"));
+        tenantDAO.setUser(this.resultSet.getString("a_user"));
+        tenantDAO.setPassword(this.resultSet.getString("pwd"));
+      }
+      return tenantDAOs;
+    }
+  }
+
+  private static final int INDEX_IDENTIFIER = 1;
+  private static final int INDEX_DRIVER_CLASS = 2;
+  private static final int INDEX_DATABASE_NAME = 3;
+  private static final int INDEX_HOST = 4;
+  private static final int INDEX_PORT = 5;
+  private static final int INDEX_USER = 6;
+  private static final int INDEX_PASSWORD = 7;
+
+  private static final String TABLE_NAME = "tenants";
+  private static final String META_KEYSPACE = "seshat"; //TODO: read MariaDB name from the configuration.
+  private static final String FETCH_ALL_STMT = " SELECT * FROM " +
+      META_KEYSPACE +
+      "." +
+      TenantDAO.TABLE_NAME;
+  private static final String FIND_ONE_STMT = " SELECT * FROM " +
+      META_KEYSPACE +
+      "." +
+      TenantDAO.TABLE_NAME +
+      " WHERE identifier = ?";
+  private static final String INSERT_STMT = " INSERT INTO " +
+      META_KEYSPACE +
+      "." +
+      TenantDAO.TABLE_NAME +
+      " (identifier, driver_class, database_name, host, port, a_user, pwd) " +
+      " values " +
+      " (?, ?, ?, ?, ?, ?, ?) ";
+  private static final String DELETE_STMT = " DELETE FROM " +
+      META_KEYSPACE +
+      "." +
+      TenantDAO.TABLE_NAME +
+      " WHERE identifier = ? ";
+
+  private String identifier;
+  private String driverClass;
+  private String databaseName;
+  private String host;
+  private String port;
+  private String user;
+  private String password;
+
+  public TenantDAO() {
+    super();
+  }
+
+  private static Builder create(final ResultSet resultSet) {
+    return new Builder(resultSet);
+  }
+
+  public static Optional<TenantDAO> find(final Connection connection, final String identifier) throws SQLException {
+    try (final PreparedStatement findOneTenantStatement = connection.prepareStatement(TenantDAO.FIND_ONE_STMT)) {
+      findOneTenantStatement.setString(INDEX_IDENTIFIER, identifier);
+      try (final ResultSet resultSet = findOneTenantStatement.executeQuery()) {
+        return TenantDAO.create(resultSet).build();
+      }
+    }
+  }
+
+  public static List<TenantDAO> fetchAll(final Connection connection) throws SQLException {
+    try (final Statement fetchAllTenantsStatement = connection.createStatement()) {
+      try (final ResultSet resultSet = fetchAllTenantsStatement.executeQuery(TenantDAO.FETCH_ALL_STMT)) {
+        return TenantDAO.create(resultSet).collect();
+      }
+    }
+  }
+
+  public static void delete(final Connection connection, final String identifier) throws SQLException {
+    try (final PreparedStatement deleteTenantStatement = connection.prepareStatement(TenantDAO.DELETE_STMT)) {
+      deleteTenantStatement.setString(INDEX_IDENTIFIER, identifier);
+      deleteTenantStatement.execute();
+    }
+  }
+
+  public void insert(final Connection connection) throws SQLException {
+    try (final PreparedStatement insertTenantStatement = connection.prepareStatement(TenantDAO.INSERT_STMT)) {
+      insertTenantStatement.setString(INDEX_IDENTIFIER, this.getIdentifier());
+      insertTenantStatement.setString(INDEX_DRIVER_CLASS, this.getDriverClass());
+      insertTenantStatement.setString(INDEX_DATABASE_NAME, this.getDatabaseName());
+      insertTenantStatement.setString(INDEX_HOST, this.getHost());
+      insertTenantStatement.setString(INDEX_PORT, this.getPort());
+      insertTenantStatement.setString(INDEX_USER, this.getUser());
+      insertTenantStatement.setString(INDEX_PASSWORD, this.getPassword());
+      insertTenantStatement.execute();
+    }
+  }
+
+  public DatabaseConnectionInfo map() {
+    final DatabaseConnectionInfo databaseConnectionInfo = new DatabaseConnectionInfo();
+    databaseConnectionInfo.setDriverClass(this.getDriverClass());
+    databaseConnectionInfo.setDatabaseName(this.getDatabaseName());
+    databaseConnectionInfo.setHost(this.getHost());
+    databaseConnectionInfo.setPort(this.getPort());
+    databaseConnectionInfo.setUser(this.getUser());
+    databaseConnectionInfo.setPassword(this.getPassword());
+    return databaseConnectionInfo;
+  }
+
+  public String getIdentifier() {
+    return identifier;
+  }
+
+  public void setIdentifier(String identifier) {
+    this.identifier = identifier;
+  }
+
+  private String getDriverClass() {
+    return driverClass;
+  }
+
+  public void setDriverClass(String driverClass) {
+    this.driverClass = driverClass;
+  }
+
+  private String getDatabaseName() {
+    return databaseName;
+  }
+
+  public void setDatabaseName(String databaseName) {
+    this.databaseName = databaseName;
+  }
+
+  private String getHost() {
+    return host;
+  }
+
+  public void setHost(String host) {
+    this.host = host;
+  }
+
+  private String getPort() {
+    return port;
+  }
+
+  public void setPort(String port) {
+    this.port = port;
+  }
+
+  private String getUser() {
+    return user;
+  }
+
+  public void setUser(String user) {
+    this.user = user;
+  }
+
+  private String getPassword() {
+    return password;
+  }
+
+  public void setPassword(String password) {
+    this.password = password;
+  }
+}
diff --git a/service/src/main/java/io/mifos/provisioner/internal/repository/TenantDAOHackEntity.java b/service/src/main/java/io/mifos/provisioner/internal/repository/TenantDAOHackEntity.java
new file mode 100644
index 0000000..cae5fa8
--- /dev/null
+++ b/service/src/main/java/io/mifos/provisioner/internal/repository/TenantDAOHackEntity.java
@@ -0,0 +1,108 @@
+/*
+ * 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.provisioner.internal.repository;
+
+
+import javax.persistence.*;
+
+/** See https://github.com/spring-projects/spring-boot/issues/6314 */
+@SuppressWarnings("unused") //To remove exception that persistence unit is missing.
+@Entity
+@Table(name = "tenants")
+public class TenantDAOHackEntity {
+  @Id
+  @GeneratedValue(strategy = GenerationType.IDENTITY)
+  @Column(name = "id")
+  private String id;
+  @Column(name = "identifier")
+  private String identifier;
+  @Column(name = "driver_class")
+  private String driverClass;
+  @Column(name = "database_name")
+  private String databaseName;
+  @Column(name = "host")
+  private String host;
+  @Column(name = "port")
+  private String port;
+  @Column(name = "a_user")
+  private String user;
+  @Column(name = "pwd")
+  private String password;
+
+  public String getId() {
+    return id;
+  }
+
+  public void setId(String id) {
+    this.id = id;
+  }
+
+  public String getIdentifier() {
+    return identifier;
+  }
+
+  public void setIdentifier(String identifier) {
+    this.identifier = identifier;
+  }
+
+  public String getDriverClass() {
+    return driverClass;
+  }
+
+  public void setDriverClass(String driverClass) {
+    this.driverClass = driverClass;
+  }
+
+  public String getDatabaseName() {
+    return databaseName;
+  }
+
+  public void setDatabaseName(String databaseName) {
+    this.databaseName = databaseName;
+  }
+
+  public String getHost() {
+    return host;
+  }
+
+  public void setHost(String host) {
+    this.host = host;
+  }
+
+  public String getPort() {
+    return port;
+  }
+
+  public void setPort(String port) {
+    this.port = port;
+  }
+
+  public String getUser() {
+    return user;
+  }
+
+  public void setUser(String user) {
+    this.user = user;
+  }
+
+  public String getPassword() {
+    return password;
+  }
+
+  public void setPassword(String password) {
+    this.password = password;
+  }
+}
diff --git a/service/src/main/java/io/mifos/provisioner/internal/repository/TenantEntity.java b/service/src/main/java/io/mifos/provisioner/internal/repository/TenantEntity.java
new file mode 100644
index 0000000..0cd65a2
--- /dev/null
+++ b/service/src/main/java/io/mifos/provisioner/internal/repository/TenantEntity.java
@@ -0,0 +1,158 @@
+/*
+ * 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.provisioner.internal.repository;
+
+import com.datastax.driver.mapping.annotations.Column;
+import com.datastax.driver.mapping.annotations.PartitionKey;
+import com.datastax.driver.mapping.annotations.Table;
+
+@Table(name = TenantEntity.TABLE_NAME)
+public class TenantEntity {
+
+  static final String TABLE_NAME = "tenants";
+  static final String IDENTIFIER_COLUMN = "identifier";
+  static final String CLUSTER_NAME_COLUMN = "cluster_name";
+  static final String CONTACT_POINTS_COLUMN = "contact_points";
+  static final String KEYSPACE_NAME_COLUMN = "keyspace_name";
+  static final String REPLICATION_TYPE_COLUMN = "replication_type";
+  static final String REPLICAS_COLUMN = "replicas";
+  static final String NAME_COLUMN = "name";
+  static final String DESCRIPTION_COLUMN = "description";
+  static final String IDENTITY_MANAGER_APPLICATION_NAME_COLUMN = "identity_manager_application_name";
+  static final String IDENTITY_MANAGER_APPLICATION_URI_COLUMN = "identity_manager_application_uri";
+
+  @PartitionKey
+  @Column(name = IDENTIFIER_COLUMN)
+  private String identifier;
+  @Column(name = CLUSTER_NAME_COLUMN)
+  private String clusterName;
+  @Column(name = CONTACT_POINTS_COLUMN)
+  private String contactPoints;
+  @Column(name = KEYSPACE_NAME_COLUMN)
+  private String keyspaceName;
+  @Column(name = REPLICATION_TYPE_COLUMN)
+  private String replicationType;
+  @Column(name = REPLICAS_COLUMN)
+  private String replicas;
+  @Column(name = NAME_COLUMN)
+  private String name;
+  @Column(name = DESCRIPTION_COLUMN)
+  private String description;
+  @Column(name = IDENTITY_MANAGER_APPLICATION_NAME_COLUMN)
+  private String identityManagerApplicationName;
+  @Column(name = IDENTITY_MANAGER_APPLICATION_URI_COLUMN)
+  private String identityManagerApplicationUri;
+
+  public TenantEntity() {
+    super();
+  }
+
+  public String getIdentifier() {
+    return identifier;
+  }
+
+  public void setIdentifier(String identifier) {
+    this.identifier = identifier;
+  }
+
+  public String getClusterName() {
+    return clusterName;
+  }
+
+  public void setClusterName(String clusterName) {
+    this.clusterName = clusterName;
+  }
+
+  public String getContactPoints() {
+    return contactPoints;
+  }
+
+  public void setContactPoints(String contactPoints) {
+    this.contactPoints = contactPoints;
+  }
+
+  public String getKeyspaceName() {
+    return keyspaceName;
+  }
+
+  public void setKeyspaceName(String keyspaceName) {
+    this.keyspaceName = keyspaceName;
+  }
+
+  public String getReplicationType() {
+    return replicationType;
+  }
+
+  public void setReplicationType(String replicationType) {
+    this.replicationType = replicationType;
+  }
+
+  public String getReplicas() {
+    return replicas;
+  }
+
+  public void setReplicas(String replicas) {
+    this.replicas = replicas;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  public String getDescription() {
+    return description;
+  }
+
+  public void setDescription(String description) {
+    this.description = description;
+  }
+
+  public String getIdentityManagerApplicationName() {
+    return identityManagerApplicationName;
+  }
+
+  public void setIdentityManagerApplicationName(String identityManagerApplicationName) {
+    this.identityManagerApplicationName = identityManagerApplicationName;
+  }
+
+  public String getIdentityManagerApplicationUri() {
+    return identityManagerApplicationUri;
+  }
+
+  public void setIdentityManagerApplicationUri(String identityManagerApplicationUri) {
+    this.identityManagerApplicationUri = identityManagerApplicationUri;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    TenantEntity that = (TenantEntity) o;
+
+    return identifier.equals(that.identifier);
+
+  }
+
+  @Override
+  public int hashCode() {
+    return identifier.hashCode();
+  }
+}
diff --git a/service/src/main/java/io/mifos/provisioner/internal/repository/UserEntity.java b/service/src/main/java/io/mifos/provisioner/internal/repository/UserEntity.java
new file mode 100644
index 0000000..36b7083
--- /dev/null
+++ b/service/src/main/java/io/mifos/provisioner/internal/repository/UserEntity.java
@@ -0,0 +1,117 @@
+/*
+ * 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.provisioner.internal.repository;
+
+import com.datastax.driver.mapping.annotations.Column;
+import com.datastax.driver.mapping.annotations.PartitionKey;
+import com.datastax.driver.mapping.annotations.Table;
+
+import java.util.Date;
+
+@SuppressWarnings("unused")
+@Table(name = UserEntity.TABLE_NAME)
+public class UserEntity {
+
+  static final java.lang.String TABLE_NAME = "users";
+  static final String NAME_COLUMN = "name";
+  static final String PASSWORD_COLUMN = "passwordWord";
+  static final String SALT_COLUMN = "salt";
+  static final String ITERATION_COUNT_COLUMN = "iteration_count";
+  static final String EXPIRES_IN_DAYS_COLUMN = "expires_in_days";
+  static final String PASSWORD_RESET_ON_COLUMN = "password_reset_on";
+
+  @PartitionKey
+  @Column(name = NAME_COLUMN)
+  private String name;
+  @Column(name = PASSWORD_COLUMN)
+  private byte[] password;
+  @Column(name = SALT_COLUMN)
+  private byte[] salt;
+  @Column(name = ITERATION_COUNT_COLUMN)
+  private int iterationCount;
+  @Column(name = EXPIRES_IN_DAYS_COLUMN)
+  private int expiresInDays;
+  @Column(name = PASSWORD_RESET_ON_COLUMN)
+  private Date passwordResetOn;
+
+  public UserEntity() {
+    super();
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  public byte[] getPassword() {
+    return password;
+  }
+
+  public void setPassword(byte[] password) {
+    this.password = password;
+  }
+
+  public byte[] getSalt() {
+    return salt;
+  }
+
+  public void setSalt(byte[] salt) {
+    this.salt = salt;
+  }
+
+  public int getIterationCount() {
+    return iterationCount;
+  }
+
+  public void setIterationCount(int iterationCount) {
+    this.iterationCount = iterationCount;
+  }
+
+  public int getExpiresInDays() {
+    return expiresInDays;
+  }
+
+  public void setExpiresInDays(int expiresInDays) {
+    this.expiresInDays = expiresInDays;
+  }
+
+  public Date getPasswordResetOn() {
+    return passwordResetOn;
+  }
+
+  public void setPasswordResetOn(Date passwordResetOn) {
+    this.passwordResetOn = passwordResetOn;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    UserEntity that = (UserEntity) o;
+
+    return name.equals(that.name);
+
+  }
+
+  @Override
+  public int hashCode() {
+    return name.hashCode();
+  }
+}
diff --git a/service/src/main/java/io/mifos/provisioner/internal/service/ApplicationService.java b/service/src/main/java/io/mifos/provisioner/internal/service/ApplicationService.java
new file mode 100644
index 0000000..336167d
--- /dev/null
+++ b/service/src/main/java/io/mifos/provisioner/internal/service/ApplicationService.java
@@ -0,0 +1,100 @@
+/*
+ * 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.provisioner.internal.service;
+
+import com.datastax.driver.core.ResultSet;
+import com.datastax.driver.mapping.Mapper;
+import com.datastax.driver.mapping.Result;
+
+import io.mifos.core.cassandra.core.CassandraSessionProvider;
+import io.mifos.core.lang.ServiceException;
+import io.mifos.provisioner.internal.repository.ApplicationEntity;
+import io.mifos.provisioner.config.ProvisionerConstants;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Component
+public class ApplicationService {
+
+  private final Logger logger;
+  private final CassandraSessionProvider cassandraSessionProvider;
+  private final TenantApplicationService tenantApplicationService;
+
+  @Autowired
+  public ApplicationService(@Qualifier(ProvisionerConstants.LOGGER_NAME) final Logger logger,
+                            final CassandraSessionProvider cassandraSessionProvider,
+                            final TenantApplicationService tenantApplicationService) {
+    super();
+    this.logger = logger;
+    this.cassandraSessionProvider = cassandraSessionProvider;
+    this.tenantApplicationService = tenantApplicationService;
+  }
+
+  public void create(final ApplicationEntity applicationEntity) {
+    final Mapper<ApplicationEntity> applicationEntityMapper =
+        this.cassandraSessionProvider.getAdminSessionMappingManager().mapper(ApplicationEntity.class);
+
+    if (applicationEntityMapper.get(applicationEntity.getName()) != null) {
+      this.logger.warn("Tried to create duplicate application {}!", applicationEntity.getName());
+      throw ServiceException.conflict("Application {0} already exists!", applicationEntity.getName());
+    }
+
+    applicationEntityMapper.save(applicationEntity);
+  }
+
+  public ApplicationEntity find(final String name) {
+    final Mapper<ApplicationEntity> applicationEntityMapper =
+        this.cassandraSessionProvider.getAdminSessionMappingManager().mapper(ApplicationEntity.class);
+
+    final ApplicationEntity applicationEntity = applicationEntityMapper.get(name);
+    if (applicationEntity == null) {
+      this.logger.warn("Tried to find unknown application {}!", name);
+      throw ServiceException.notFound("Application {0} not found!", name);
+    }
+
+    return applicationEntity;
+  }
+
+  public List<ApplicationEntity> fetchAll() {
+    final ArrayList<ApplicationEntity> applicationEntities = new ArrayList<>();
+
+    final ResultSet resultSet =
+        this.cassandraSessionProvider.getAdminSession().execute(" SELECT * FROM applications ");
+
+    final Mapper<ApplicationEntity> applicationEntityMapper =
+        this.cassandraSessionProvider.getAdminSessionMappingManager().mapper(ApplicationEntity.class);
+
+    if (resultSet != null) {
+      final Result<ApplicationEntity> mappedApplicationEntities = applicationEntityMapper.map(resultSet);
+      applicationEntities.addAll(mappedApplicationEntities.all());
+    }
+
+    return applicationEntities;
+  }
+
+  public void delete(final String name) {
+    final Mapper<ApplicationEntity> applicationEntityMapper =
+        this.cassandraSessionProvider.getAdminSessionMappingManager().mapper(ApplicationEntity.class);
+
+    applicationEntityMapper.delete(name);
+    this.tenantApplicationService.removeApplication(name);
+  }
+}
diff --git a/service/src/main/java/io/mifos/provisioner/internal/service/AuthenticationService.java b/service/src/main/java/io/mifos/provisioner/internal/service/AuthenticationService.java
new file mode 100644
index 0000000..8c9b103
--- /dev/null
+++ b/service/src/main/java/io/mifos/provisioner/internal/service/AuthenticationService.java
@@ -0,0 +1,184 @@
+/*
+ * 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.provisioner.internal.service;
+
+import com.datastax.driver.core.BoundStatement;
+import com.datastax.driver.core.ResultSet;
+import com.datastax.driver.core.Row;
+import com.datastax.driver.core.Session;
+import com.datastax.driver.core.Statement;
+import com.datastax.driver.core.utils.Bytes;
+import com.datastax.driver.mapping.Mapper;
+import com.datastax.driver.mapping.MappingManager;
+
+import io.mifos.anubis.token.TokenSerializationResult;
+import io.mifos.core.cassandra.core.CassandraSessionProvider;
+import io.mifos.core.lang.ServiceException;
+import io.mifos.provisioner.internal.repository.UserEntity;
+import io.mifos.provisioner.api.v1.domain.AuthenticationResponse;
+import io.mifos.provisioner.api.v1.domain.PasswordPolicy;
+import io.mifos.provisioner.internal.repository.ClientEntity;
+import io.mifos.provisioner.internal.repository.ConfigEntity;
+import io.mifos.provisioner.internal.util.TokenProvider;
+import io.mifos.provisioner.config.ProvisionerConstants;
+import io.mifos.tool.crypto.HashGenerator;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.crypto.util.EncodingUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.util.Base64Utils;
+
+import java.nio.ByteBuffer;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.Nonnull;
+
+@Service
+public class AuthenticationService {
+
+  @Value("${spring.application.name}")
+  private String applicationName;
+  @Value("${provisioner.token.ttl}")
+  private Integer ttl;
+  private final Logger logger;
+  private final CassandraSessionProvider cassandraSessionProvider;
+  private final HashGenerator hashGenerator;
+  private final TokenProvider tokenProvider;
+
+
+  @Autowired
+  public AuthenticationService(@Qualifier(ProvisionerConstants.LOGGER_NAME) final Logger logger,
+                               final CassandraSessionProvider cassandraSessionProvider,
+                               final HashGenerator hashGenerator,
+                               final TokenProvider tokenProvider) {
+    super();
+    this.logger = logger;
+    this.cassandraSessionProvider = cassandraSessionProvider;
+    this.hashGenerator = hashGenerator;
+    this.tokenProvider = tokenProvider;
+  }
+
+  public AuthenticationResponse authenticate(
+      final @Nonnull String clientId,
+      final @Nonnull String username,
+      final @Nonnull String password) {
+    final Session session = this.cassandraSessionProvider.getAdminSession();
+    final MappingManager mappingManager = new MappingManager(session);
+
+    final Mapper<ClientEntity> clientEntityMapper = mappingManager.mapper(ClientEntity.class);
+    if (clientEntityMapper.get(clientId) == null) {
+      this.logger.warn("Authentication attempt with unknown client: " + clientId);
+      throw ServiceException.notFound("Requested resource not found!");
+    }
+
+    final Mapper<UserEntity> userEntityMapper = mappingManager.mapper(UserEntity.class);
+    final Statement userQuery = userEntityMapper.getQuery(username);
+    final ResultSet userResult = session.execute(userQuery);
+    final Row userRow = userResult.one();
+    if (userRow == null) {
+      this.logger.warn("Authentication attempt with unknown user: " + username);
+      throw ServiceException.notFound("Requested resource not found!");
+    }
+    final byte[] storedPassword = Bytes.getArray(userRow.getBytes(1));
+    final byte[] salt = Bytes.getArray(userRow.getBytes(2));
+    final int iterationCount = userRow.getInt(3);
+    final int expiresInDays = userRow.getInt(4);
+    final Date passwordResetOn = userRow.getTimestamp(5);
+
+    final Mapper<ConfigEntity> configEntityMapper = mappingManager.mapper(ConfigEntity.class);
+    final Statement configQuery = configEntityMapper.getQuery(ProvisionerConstants.CONFIG_INTERNAL);
+    final ResultSet configResult = session.execute(configQuery);
+    final Row configRow = configResult.one();
+    final byte[] secret = Bytes.getArray(configRow.getBytes(1));
+
+    if (this.hashGenerator.isEqual(
+        storedPassword,
+        Base64Utils.decodeFromString(password),
+        secret,
+        salt,
+        iterationCount,
+        256)) {
+
+      if (expiresInDays > 0) {
+        final LocalDate ld = passwordResetOn.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+        final LocalDate expiresOn = ld.plusDays(expiresInDays);
+        if (LocalDate.now().isAfter(expiresOn)) {
+          throw ServiceException.badRequest("Password expired");
+        }
+      }
+
+      final TokenSerializationResult authToken = this.tokenProvider.createToken(username, this.applicationName, this.ttl, TimeUnit.MINUTES);
+      return new AuthenticationResponse(authToken.getToken(), dateTimeToString(authToken.getExpiration()));
+    } else {
+      throw ServiceException.notFound("Requested resource not found!");
+    }
+  }
+
+  private String dateTimeToString(final LocalDateTime dateTime) {
+    return dateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
+  }
+
+  public void updatePasswordPolicy(final String username, final PasswordPolicy passwordPolicy) {
+    try {
+      final Session session = this.cassandraSessionProvider.getAdminSession();
+      final MappingManager mappingManager = new MappingManager(session);
+      final Mapper<UserEntity> userEntityMapper = mappingManager.mapper(UserEntity.class);
+      final Statement userQuery = userEntityMapper.getQuery(username);
+      final ResultSet userResult = session.execute(userQuery);
+      final Row userRow = userResult.one();
+      if (userRow == null) {
+        this.logger.warn("Authentication attempt with unknown user: " + username);
+        throw ServiceException.notFound("Requested resource not found!");
+      }
+      final byte[] salt = Bytes.getArray(userRow.getBytes(2));
+      final int iterationCount = userRow.getInt(3);
+
+      final Mapper<ConfigEntity> configEntityMapper = mappingManager.mapper(ConfigEntity.class);
+      final Statement configQuery = configEntityMapper.getQuery(ProvisionerConstants.CONFIG_INTERNAL);
+      final ResultSet configResult = session.execute(configQuery);
+      final Row configRow = configResult.one();
+      final byte[] secret = Bytes.getArray(configRow.getBytes(1));
+
+      if (passwordPolicy.getNewPassword() != null) {
+        final byte[] newPasswordHash = this.hashGenerator.hash(passwordPolicy.getNewPassword(), EncodingUtils.concatenate(salt, secret), iterationCount, ProvisionerConstants.HASH_LENGTH);
+        final BoundStatement updateStatement = session.prepare(
+            "UPDATE users SET passwordWord = ?, password_reset_on = ? WHERE name = ?").bind();
+        updateStatement.setBytes(0, ByteBuffer.wrap(newPasswordHash));
+        updateStatement.setTimestamp(1, new Date());
+        updateStatement.setString(2, username);
+        session.execute(updateStatement);
+      }
+
+      if (passwordPolicy.getExpiresInDays() != null) {
+        final BoundStatement updateStatement = session.prepare(
+            "UPDATE users SET expires_in_days = ? WHERE name = ?").bind();
+        updateStatement.setInt(0, passwordPolicy.getExpiresInDays());
+        updateStatement.setString(1, username);
+        session.execute(updateStatement);
+      }
+    } catch (final Exception ex) {
+      this.logger.error("Error updating password policy!", ex);
+      throw ServiceException.internalError(ex.getMessage());
+    }
+  }
+}
diff --git a/service/src/main/java/io/mifos/provisioner/internal/service/ClientService.java b/service/src/main/java/io/mifos/provisioner/internal/service/ClientService.java
new file mode 100644
index 0000000..3b3c4dd
--- /dev/null
+++ b/service/src/main/java/io/mifos/provisioner/internal/service/ClientService.java
@@ -0,0 +1,84 @@
+/*
+ * 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.provisioner.internal.service;
+
+import com.datastax.driver.core.ResultSet;
+import com.datastax.driver.mapping.Mapper;
+import com.datastax.driver.mapping.Result;
+
+import io.mifos.core.cassandra.core.CassandraSessionProvider;
+import io.mifos.core.lang.ServiceException;
+import io.mifos.provisioner.internal.repository.ClientEntity;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Service
+public class ClientService {
+
+  private final CassandraSessionProvider cassandraSessionProvider;
+
+  @Autowired
+  public ClientService(final CassandraSessionProvider cassandraSessionProvider) {
+    super();
+    this.cassandraSessionProvider = cassandraSessionProvider;
+  }
+
+  public List<ClientEntity> fetchAll() {
+    final ArrayList<ClientEntity> result = new ArrayList<>();
+
+    final ResultSet clientResult =
+        this.cassandraSessionProvider.getAdminSession().execute("SELECT * FROM clients");
+
+    final Mapper<ClientEntity> clientEntityMapper =
+        this.cassandraSessionProvider.getAdminSessionMappingManager().mapper(ClientEntity.class);
+
+    final Result<ClientEntity> mappedClientEntities = clientEntityMapper.map(clientResult);
+    if (mappedClientEntities != null) {
+      result.addAll(mappedClientEntities.all());
+    }
+
+    return result;
+  }
+
+  public void create(final ClientEntity clientEntity) {
+    final Mapper<ClientEntity> clientEntityMapper =
+        this.cassandraSessionProvider.getAdminSessionMappingManager().mapper(ClientEntity.class);
+    if (clientEntityMapper.get(clientEntity.getName()) != null) {
+      throw ServiceException.conflict("Client {0} already exists!", clientEntity.getName());
+    }
+    clientEntityMapper.save(clientEntity);
+  }
+
+  public void delete(final String name) {
+    final Mapper<ClientEntity> clientEntityMapper =
+        this.cassandraSessionProvider.getAdminSessionMappingManager().mapper(ClientEntity.class);
+    clientEntityMapper.delete(name);
+  }
+
+  public ClientEntity findByName(final String name) {
+    final Mapper<ClientEntity> clientEntityMapper =
+        this.cassandraSessionProvider.getAdminSessionMappingManager().mapper(ClientEntity.class);
+    final ClientEntity clientEntity = clientEntityMapper.get(name);
+    if (clientEntity == null) {
+      throw ServiceException.notFound("Client {0} not found!", name);
+    }
+    return clientEntity;
+  }
+}
diff --git a/service/src/main/java/io/mifos/provisioner/internal/service/TenantApplicationService.java b/service/src/main/java/io/mifos/provisioner/internal/service/TenantApplicationService.java
new file mode 100644
index 0000000..222c4e4
--- /dev/null
+++ b/service/src/main/java/io/mifos/provisioner/internal/service/TenantApplicationService.java
@@ -0,0 +1,210 @@
+/*
+ * 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.provisioner.internal.service;
+
+import com.datastax.driver.core.ResultSet;
+import com.datastax.driver.mapping.Mapper;
+import com.datastax.driver.mapping.Result;
+
+import io.mifos.anubis.api.v1.TokenConstants;
+import io.mifos.anubis.api.v1.domain.Signature;
+import io.mifos.anubis.repository.TenantAuthorizationDataRepository;
+import io.mifos.core.cassandra.core.CassandraSessionProvider;
+import io.mifos.core.lang.AutoTenantContext;
+import io.mifos.core.lang.ServiceException;
+import io.mifos.provisioner.internal.repository.ApplicationEntity;
+import io.mifos.provisioner.internal.repository.TenantCassandraRepository;
+import io.mifos.provisioner.internal.repository.TenantApplicationEntity;
+import io.mifos.provisioner.internal.repository.TenantEntity;
+
+import io.mifos.provisioner.internal.service.applications.AnubisInitializer;
+import io.mifos.provisioner.internal.service.applications.IdentityServiceInitializer;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Component;
+import org.springframework.util.Assert;
+
+import javax.annotation.Nonnull;
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Component
+public class TenantApplicationService {
+
+  private final CassandraSessionProvider cassandraSessionProvider;
+  private final AnubisInitializer anubisInitializer;
+  private final IdentityServiceInitializer identityServiceInitializer;
+  private final TenantAuthorizationDataRepository tenantAuthorizationDataRepository;
+  private final TenantCassandraRepository tenantCassandraRepository;
+
+  @Autowired
+  public TenantApplicationService(final CassandraSessionProvider cassandraSessionProvider,
+                                  final AnubisInitializer anubisInitializer,
+                                  final IdentityServiceInitializer identityServiceInitializer,
+                                  final TenantAuthorizationDataRepository tenantAuthorizationDataRepository,
+                                  final TenantCassandraRepository tenantCassandraRepository) {
+    super();
+    this.cassandraSessionProvider = cassandraSessionProvider;
+    this.anubisInitializer = anubisInitializer;
+    this.identityServiceInitializer = identityServiceInitializer;
+    this.tenantAuthorizationDataRepository = tenantAuthorizationDataRepository;
+    this.tenantCassandraRepository = tenantCassandraRepository;
+  }
+
+  @Async
+  public void assign(final @Nonnull TenantApplicationEntity tenantApplicationEntity, final @Nonnull Map<String, String> appNameToUriMap) {
+    Assert.notNull(tenantApplicationEntity);
+    Assert.notNull(appNameToUriMap);
+
+    final Optional<TenantEntity> tenantEntity = tenantCassandraRepository.get(tenantApplicationEntity.getTenantIdentifier());
+    tenantEntity.ifPresent(x -> {
+      checkApplications(tenantApplicationEntity.getApplications());
+
+      saveTenantApplicationAssignment(tenantApplicationEntity);
+
+      final Set<ApplicationNameToUriPair> applicationNameToUriPairs =
+              getApplicationNameToUriPairs(tenantApplicationEntity, appNameToUriMap);
+
+      initializeIsis(x, applicationNameToUriPairs);
+
+      getIsisSignature(x).ifPresent(y -> initializeAnubis(x, y, applicationNameToUriPairs));
+    });
+
+    tenantEntity.orElseThrow(
+            () -> ServiceException.notFound("Tenant {0} not found.", tenantApplicationEntity.getTenantIdentifier()));
+  }
+
+  private void saveTenantApplicationAssignment(final @Nonnull TenantApplicationEntity tenantApplicationEntity) {
+    final Mapper<TenantApplicationEntity> tenantApplicationEntityMapper =
+            this.cassandraSessionProvider.getAdminSessionMappingManager().mapper(TenantApplicationEntity.class);
+
+    tenantApplicationEntityMapper.save(tenantApplicationEntity);
+  }
+
+  private Set<ApplicationNameToUriPair> getApplicationNameToUriPairs(
+          final @Nonnull TenantApplicationEntity tenantApplicationEntity,
+          final @Nonnull Map<String, String> appNameToUriMap) {
+    return tenantApplicationEntity.getApplications().stream()
+            .map(x -> new TenantApplicationService.ApplicationNameToUriPair(x, appNameToUriMap.get(x)))
+            .collect(Collectors.toSet());
+  }
+
+  private static class ApplicationNameToUriPair
+  {
+    String name;
+    String uri;
+
+    ApplicationNameToUriPair(String name, String uri) {
+      this.name = name;
+      this.uri = uri;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) return true;
+      if (o == null || getClass() != o.getClass()) return false;
+      ApplicationNameToUriPair that = (ApplicationNameToUriPair) o;
+      return Objects.equals(name, that.name) &&
+              Objects.equals(uri, that.uri);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(name, uri);
+    }
+  }
+
+  private Optional<Signature> getIsisSignature(final @Nonnull TenantEntity tenantEntity) {
+    try (final AutoTenantContext ignored = new AutoTenantContext(tenantEntity.getIdentifier())) {
+      return tenantAuthorizationDataRepository.getSignature(TokenConstants.VERSION);
+    }
+  }
+
+  private void initializeIsis(
+          final @Nonnull TenantEntity tenantEntity,
+          final @Nonnull Set<ApplicationNameToUriPair> applicationNameToUriPairs) {
+    applicationNameToUriPairs.forEach(applicationNameUriPair ->
+            identityServiceInitializer.postPermittableGroups(
+                    tenantEntity.getIdentifier(),
+                    tenantEntity.getIdentityManagerApplicationName(),
+                    tenantEntity.getIdentityManagerApplicationUri(),
+                    applicationNameUriPair.uri));
+  }
+
+  private void initializeAnubis(
+          final @Nonnull TenantEntity tenantEntity,
+          final @Nonnull Signature identityServiceTenantSignature,
+          final @Nonnull Set<ApplicationNameToUriPair> applicationNameToUriPairs) {
+    applicationNameToUriPairs.forEach(applicationNameUriPair ->
+            anubisInitializer.initializeAnubis(
+                    tenantEntity.getIdentifier(),
+                    applicationNameUriPair.name,
+                    applicationNameUriPair.uri,
+                    identityServiceTenantSignature)
+    );
+  }
+
+  public TenantApplicationEntity find(final String tenantIdentifier) {
+    checkTenant(tenantIdentifier);
+
+    final Mapper<TenantApplicationEntity> tenantApplicationEntityMapper =
+        this.cassandraSessionProvider.getAdminSessionMappingManager().mapper(TenantApplicationEntity.class);
+
+    return tenantApplicationEntityMapper.get(tenantIdentifier);
+  }
+
+  void deleteTenant(final String tenantIdentifier) {
+    final Mapper<TenantApplicationEntity> tenantApplicationEntityMapper =
+        this.cassandraSessionProvider.getAdminSessionMappingManager().mapper(TenantApplicationEntity.class);
+
+    tenantApplicationEntityMapper.delete(tenantIdentifier);
+  }
+
+  void removeApplication(final String name) {
+    final ResultSet tenantApplicationResultSet =
+        this.cassandraSessionProvider.getAdminSession().execute("SELECT * FROM tenant_applications");
+
+    if (tenantApplicationResultSet != null) {
+      final Mapper<TenantApplicationEntity> tenantApplicationEntityMapper =
+          this.cassandraSessionProvider.getAdminSessionMappingManager().mapper(TenantApplicationEntity.class);
+
+      final Result<TenantApplicationEntity> mappedTenantApplications = tenantApplicationEntityMapper.map(tenantApplicationResultSet);
+
+      for (TenantApplicationEntity tenantApplicationEntity : mappedTenantApplications) {
+        if (tenantApplicationEntity.getApplications().contains(name)) {
+          tenantApplicationEntity.getApplications().remove(name);
+          tenantApplicationEntityMapper.save(tenantApplicationEntity);
+        }
+      }
+    }
+  }
+
+  private void checkApplications(final Set<String> applications) {
+    final Mapper<ApplicationEntity> applicationEntityMapper =
+            this.cassandraSessionProvider.getAdminSessionMappingManager().mapper(ApplicationEntity.class);
+
+    for (final String name : applications) {
+      if (applicationEntityMapper.get(name) == null) {
+        throw ServiceException.badRequest("Application {0} not found!", name);
+      }
+    }
+  }
+
+  private void checkTenant(final @Nonnull String tenantIdentifier) {
+    final Optional<TenantEntity> tenantEntity = tenantCassandraRepository.get(tenantIdentifier);
+    tenantEntity.orElseThrow(() -> ServiceException.notFound("Tenant {0} not found.", tenantIdentifier));
+  }
+}
diff --git a/service/src/main/java/io/mifos/provisioner/internal/service/TenantService.java b/service/src/main/java/io/mifos/provisioner/internal/service/TenantService.java
new file mode 100644
index 0000000..7409f00
--- /dev/null
+++ b/service/src/main/java/io/mifos/provisioner/internal/service/TenantService.java
@@ -0,0 +1,324 @@
+/*
+ * 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.provisioner.internal.service;
+
+import io.mifos.anubis.api.v1.domain.Signature;
+import io.mifos.anubis.repository.TenantAuthorizationDataRepository;
+import io.mifos.core.lang.AutoTenantContext;
+import io.mifos.core.lang.ServiceException;
+import io.mifos.provisioner.internal.repository.TenantCassandraRepository;
+import io.mifos.provisioner.internal.util.DataSourceUtils;
+import io.mifos.provisioner.internal.util.DataStoreOption;
+import io.mifos.provisioner.api.v1.domain.CassandraConnectionInfo;
+import io.mifos.provisioner.api.v1.domain.DatabaseConnectionInfo;
+import io.mifos.provisioner.api.v1.domain.Tenant;
+import io.mifos.provisioner.config.ProvisionerConstants;
+import io.mifos.provisioner.internal.repository.TenantDAO;
+import io.mifos.provisioner.internal.repository.TenantEntity;
+import io.mifos.provisioner.internal.service.applications.IdentityServiceInitializer;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.core.env.Environment;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Nonnull;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+@SuppressWarnings({"SqlNoDataSourceInspection", "SqlDialectInspection"})
+@Component
+public class TenantService {
+  private static final String META_KEYSPACE = "seshat"; //TODO: read MariaDB name from the configuration.
+
+  private final Logger logger;
+  private final Environment environment;
+  private final TenantApplicationService tenantApplicationService;
+  private final TenantAuthorizationDataRepository tenantAuthorizationDataRepository;
+  private final TenantCassandraRepository tenantCassandraRepository;
+  private final IdentityServiceInitializer identityServiceInitializer;
+
+
+  @Autowired
+  public TenantService(@Qualifier(ProvisionerConstants.LOGGER_NAME) final Logger logger,
+                       final Environment environment,
+                       final TenantApplicationService tenantApplicationService,
+                       final TenantAuthorizationDataRepository tenantAuthorizationDataRepository,
+                       final TenantCassandraRepository tenantCassandraRepository,
+                       final IdentityServiceInitializer identityServiceInitializer) {
+    super();
+    this.logger = logger;
+    this.environment = environment;
+    this.tenantApplicationService = tenantApplicationService;
+    this.tenantAuthorizationDataRepository = tenantAuthorizationDataRepository;
+    this.tenantCassandraRepository = tenantCassandraRepository;
+    this.identityServiceInitializer = identityServiceInitializer;
+  }
+
+  public void create(final Tenant tenant) {
+    this.initializeKeyspace(tenant);
+    this.initializeDatabase(tenant);
+  }
+
+  private void initializeKeyspace(final @Nonnull Tenant tenant) {
+    final DataStoreOption dataStoreOption = DataStoreOption.valueOf(
+            this.environment.getProperty(DataStoreOption.PROPERTY_NAME, DataStoreOption.PROPERTY_DEFAULT_VALUE));
+    if (dataStoreOption.isEnabled(DataStoreOption.CASSANDRA)) {
+      final CassandraConnectionInfo cassandraConnectionInfo = tenant.getCassandraConnectionInfo();
+
+      final TenantEntity tenantEntity = new TenantEntity();
+      tenantEntity.setIdentifier(tenant.getIdentifier());
+      tenantEntity.setClusterName(cassandraConnectionInfo.getClusterName());
+      tenantEntity.setContactPoints(cassandraConnectionInfo.getContactPoints());
+      tenantEntity.setKeyspaceName(cassandraConnectionInfo.getKeyspace());
+      tenantEntity.setReplicationType(cassandraConnectionInfo.getReplicationType());
+      tenantEntity.setReplicas(cassandraConnectionInfo.getReplicas());
+      tenantEntity.setName(tenant.getName());
+      tenantEntity.setDescription(tenant.getDescription());
+      tenantEntity.setIdentityManagerApplicationName(null); //Identity manager can't be spun up till the io.mifos.provisioner.tenant is provisioned.
+      tenantEntity.setIdentityManagerApplicationUri(null); //Identity manager can't be spun up till the io.mifos.provisioner.tenant is provisioned.
+
+      tenantCassandraRepository.create(tenantEntity);
+    }
+  }
+
+  public Optional<String> assignIdentityManager(
+          final String tenantIdentifier,
+          final String identityManagerAppName,
+          final String identityManagerUri)
+  {
+    tenantCassandraRepository.adjust(tenantIdentifier, x -> {
+      x.setIdentityManagerApplicationName(identityManagerAppName);
+      x.setIdentityManagerApplicationUri(identityManagerUri);
+    });
+
+    IdentityServiceInitializer.IdentityServiceInitializationResult identityServiceInitializationResult = identityServiceInitializer.initializeIsis(tenantIdentifier, identityManagerAppName, identityManagerUri);
+    final Signature identityServiceTenantSignature = identityServiceInitializationResult.getSignature();
+
+    try (final AutoTenantContext ignored = new AutoTenantContext(tenantIdentifier)) {
+      tenantAuthorizationDataRepository.provisionTenant(identityServiceTenantSignature.getPublicKeyMod(), identityServiceTenantSignature.getPublicKeyExp());
+    }
+
+    return identityServiceInitializationResult.getAdminPassword();
+  }
+
+  public List<Tenant> fetchAll() {
+    final ArrayList<Tenant> result = new ArrayList<>();
+    this.fetchAllCassandra(result);
+    this.fetchAllDatabase(result);
+    return result;
+  }
+
+  private void fetchAllCassandra(final @Nonnull List<Tenant> tenants) {
+    final DataStoreOption dataStoreOption = DataStoreOption.valueOf(
+            this.environment.getProperty(DataStoreOption.PROPERTY_NAME, DataStoreOption.PROPERTY_DEFAULT_VALUE));
+    if (dataStoreOption.isEnabled(DataStoreOption.CASSANDRA)) {
+      List<TenantEntity> tenantEntities = tenantCassandraRepository.fetchAll();
+
+      for (final TenantEntity tenantEntity : tenantEntities) {
+        final Tenant tenant = new Tenant();
+        tenants.add(tenant);
+        tenant.setIdentifier(tenantEntity.getIdentifier());
+        tenant.setName(tenantEntity.getName());
+        tenant.setDescription(tenantEntity.getDescription());
+
+        tenant.setCassandraConnectionInfo(getCassandraConnectionInfoFromTenantEntity(tenantEntity));
+      }
+    }
+  }
+
+  @SuppressWarnings("UnnecessaryLocalVariable")
+  public Optional<Tenant> find(final @Nonnull String identifier) {
+    final Optional<Tenant> tenantInCassandra = this.findCassandra(identifier);
+    final Optional<Tenant> tenantInDatabase = tenantInCassandra.map(x -> this.findInDatabase(x, identifier));
+
+    return tenantInDatabase;
+  }
+
+  public void delete(final String identifier) {
+    this.deleteFromCassandra(identifier);
+    this.deleteDatabase(identifier);
+  }
+
+  private void fetchAllDatabase(final ArrayList<Tenant> tenants) {
+    final DataStoreOption dataStoreOption = DataStoreOption.valueOf(
+        this.environment.getProperty(DataStoreOption.PROPERTY_NAME, DataStoreOption.PROPERTY_DEFAULT_VALUE));
+    if (dataStoreOption.isEnabled(DataStoreOption.RDBMS)) {
+      if (tenants.size() > 0) {
+        try (final Connection connection = DataSourceUtils.createProvisionerConnection(this.environment)) {
+          for (final Tenant tenant : tenants) {
+            final Optional<TenantDAO> optionalTenantDAO = TenantDAO.find(connection, tenant.getIdentifier());
+            if (optionalTenantDAO.isPresent()) {
+              tenant.setDatabaseConnectionInfo(optionalTenantDAO.get().map());
+            }
+          }
+        } catch (final SQLException sqlex) {
+          this.logger.error(sqlex.getMessage(), sqlex);
+          throw new IllegalStateException("Could not load io.mifos.provisioner.tenant data!");
+        }
+      } else {
+        try (final Connection connection = DataSourceUtils.createProvisionerConnection(this.environment)) {
+          final List<TenantDAO> tenantDAOs = TenantDAO.fetchAll(connection);
+          for (final TenantDAO tenantDAO : tenantDAOs) {
+            final Tenant tenant = new Tenant();
+            tenants.add(tenant);
+            tenant.setIdentifier(tenantDAO.getIdentifier());
+            tenant.setDatabaseConnectionInfo(tenantDAO.map());
+          }
+        } catch (final SQLException sqlex) {
+          this.logger.error(sqlex.getMessage(), sqlex);
+          throw new IllegalStateException("Could not load io.mifos.provisioner.tenant data!");
+        }
+      }
+    }
+  }
+
+  private Optional<Tenant> findCassandra(final String identifier) {
+    final DataStoreOption dataStoreOption = DataStoreOption.valueOf(
+        this.environment.getProperty(DataStoreOption.PROPERTY_NAME, DataStoreOption.PROPERTY_DEFAULT_VALUE));
+    if (dataStoreOption.isEnabled(DataStoreOption.CASSANDRA)) {
+      return tenantCassandraRepository.get(identifier).map(x -> {
+                final Tenant tenant = new Tenant();
+                tenant.setIdentifier(x.getIdentifier());
+                tenant.setName(x.getName());
+                tenant.setDescription(x.getDescription());
+                tenant.setCassandraConnectionInfo(getCassandraConnectionInfoFromTenantEntity(x));
+                return tenant;
+              }
+      );
+    }
+    return Optional.empty();
+  }
+
+  private Tenant findInDatabase(final @Nonnull Tenant tenant, final @Nonnull String identifier) {
+    final DataStoreOption dataStoreOption = DataStoreOption.valueOf(
+        this.environment.getProperty(DataStoreOption.PROPERTY_NAME, DataStoreOption.PROPERTY_DEFAULT_VALUE));
+    if (dataStoreOption.isEnabled(DataStoreOption.RDBMS)) {
+      try (final Connection connection = DataSourceUtils.createProvisionerConnection(this.environment)) {
+        final Optional<TenantDAO> optionalTenantDAO = TenantDAO.find(connection, identifier);
+        if (optionalTenantDAO.isPresent()) {
+          tenant.setDatabaseConnectionInfo(optionalTenantDAO.get().map());
+          return tenant;
+        }
+      } catch (final SQLException sqlex) {
+        this.logger.error(sqlex.getMessage(), sqlex);
+        throw new IllegalStateException("Could not load io.mifos.provisioner.tenant data!");
+      }
+    }
+    return tenant;
+  }
+
+  private void initializeDatabase(final Tenant tenant) {
+    final DataStoreOption dataStoreOption = DataStoreOption.valueOf(
+        this.environment.getProperty(DataStoreOption.PROPERTY_NAME, DataStoreOption.PROPERTY_DEFAULT_VALUE));
+    if (dataStoreOption.isEnabled(DataStoreOption.RDBMS)) {
+
+      try (
+              final Connection provisionerConnection = DataSourceUtils.createProvisionerConnection(this.environment);
+              final Statement statement = provisionerConnection.createStatement()
+      ) {
+        final java.sql.ResultSet resultSet = statement.executeQuery(
+            " SELECT * FROM " + META_KEYSPACE + ".tenants WHERE identifier = '" + tenant.getIdentifier() + "' ");
+        if (resultSet.next()) {
+          throw ServiceException.conflict("Tenant {0} already exists!", tenant.getIdentifier());
+        }
+      } catch (SQLException sqlex) {
+        this.logger.error(sqlex.getMessage(), sqlex);
+        throw new IllegalStateException("Could not insert io.mifos.provisioner.tenant info!", sqlex);
+      }
+      final DatabaseConnectionInfo databaseConnectionInfo = tenant.getDatabaseConnectionInfo();
+      try (
+          final Connection connection = DataSourceUtils.create(databaseConnectionInfo);
+          final Statement statement = connection.createStatement()
+      ) {
+        statement.execute("CREATE DATABASE IF NOT EXISTS " + databaseConnectionInfo.getDatabaseName());
+        statement.close();
+      } catch (final SQLException sqlex) {
+        this.logger.error(sqlex.getMessage(), sqlex);
+        throw new IllegalStateException("Could not create database!", sqlex);
+      }
+
+      try (final Connection provisionerConnection = DataSourceUtils.createProvisionerConnection(this.environment)) {
+        final TenantDAO tenantDAO = new TenantDAO();
+        tenantDAO.setIdentifier(tenant.getIdentifier());
+        tenantDAO.setDriverClass(databaseConnectionInfo.getDriverClass());
+        tenantDAO.setDatabaseName(databaseConnectionInfo.getDatabaseName());
+        tenantDAO.setHost(databaseConnectionInfo.getHost());
+        tenantDAO.setPort(databaseConnectionInfo.getPort());
+        tenantDAO.setUser(databaseConnectionInfo.getUser());
+        tenantDAO.setPassword(databaseConnectionInfo.getPassword());
+        tenantDAO.insert(provisionerConnection);
+        provisionerConnection.commit();
+      } catch (SQLException sqlex) {
+        this.logger.error(sqlex.getMessage(), sqlex);
+        throw new IllegalStateException("Could not insert io.mifos.provisioner.tenant info!", sqlex);
+      }
+    }
+  }
+
+  private void deleteFromCassandra(final @Nonnull String identifier) {
+    final DataStoreOption dataStoreOption = DataStoreOption.valueOf(
+        this.environment.getProperty(DataStoreOption.PROPERTY_NAME, DataStoreOption.PROPERTY_DEFAULT_VALUE));
+    if (dataStoreOption.isEnabled(DataStoreOption.CASSANDRA)) {
+      final Optional<TenantEntity> tenantEntity = tenantCassandraRepository.get(identifier);
+      tenantEntity.ifPresent(x ->
+      {
+        tenantCassandraRepository.delete(identifier);
+        this.tenantApplicationService.deleteTenant(identifier);
+      });
+    }
+  }
+
+  private void deleteDatabase(final String identifier) {
+    final DataStoreOption dataStoreOption = DataStoreOption.valueOf(
+        this.environment.getProperty(DataStoreOption.PROPERTY_NAME, DataStoreOption.PROPERTY_DEFAULT_VALUE));
+    if (dataStoreOption.isEnabled(DataStoreOption.RDBMS)) {
+
+      try (final Connection provisionerConnection = DataSourceUtils.createProvisionerConnection(this.environment)) {
+        final Optional<TenantDAO> optionalTenantDAO = TenantDAO.find(provisionerConnection, identifier);
+        if (optionalTenantDAO.isPresent()) {
+          final DatabaseConnectionInfo databaseConnectionInfo = optionalTenantDAO.get().map();
+          try (
+              final Connection connection = DataSourceUtils.create(databaseConnectionInfo);
+              final Statement dropStatement = connection.createStatement()
+          ) {
+            dropStatement.execute("DROP DATABASE " + databaseConnectionInfo.getDatabaseName());
+            connection.commit();
+          }
+          TenantDAO.delete(provisionerConnection, identifier);
+          provisionerConnection.commit();
+        }
+      } catch (final SQLException sqlex) {
+        this.logger.error(sqlex.getMessage(), sqlex);
+        throw new IllegalStateException("Could not delete database!");
+      }
+    }
+  }
+
+  private static CassandraConnectionInfo getCassandraConnectionInfoFromTenantEntity(final TenantEntity tenantEntity) {
+    final CassandraConnectionInfo cassandraConnectionInfo = new CassandraConnectionInfo();
+    cassandraConnectionInfo.setClusterName(tenantEntity.getClusterName());
+    cassandraConnectionInfo.setContactPoints(tenantEntity.getContactPoints());
+    cassandraConnectionInfo.setKeyspace(tenantEntity.getKeyspaceName());
+    cassandraConnectionInfo.setReplicationType(tenantEntity.getReplicationType());
+    cassandraConnectionInfo.setReplicas(tenantEntity.getReplicas());
+    return cassandraConnectionInfo;
+  }
+}
diff --git a/service/src/main/java/io/mifos/provisioner/internal/service/applications/AnubisInitializer.java b/service/src/main/java/io/mifos/provisioner/internal/service/applications/AnubisInitializer.java
new file mode 100644
index 0000000..c3f31de
--- /dev/null
+++ b/service/src/main/java/io/mifos/provisioner/internal/service/applications/AnubisInitializer.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.provisioner.internal.service.applications;
+
+import io.mifos.anubis.api.v1.client.Anubis;
+import io.mifos.anubis.api.v1.domain.Signature;
+import io.mifos.provisioner.config.ProvisionerConstants;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Nonnull;
+
+/**
+ * @author Myrle Krantz
+ */
+@Component
+public class AnubisInitializer {
+  private final ApplicationCallContextProvider applicationCallContextProvider;
+  private final Logger logger;
+
+  @Autowired
+  public AnubisInitializer(
+          final ApplicationCallContextProvider applicationCallContextProvider,
+          @Qualifier(ProvisionerConstants.LOGGER_NAME) final Logger logger) {
+    this.applicationCallContextProvider = applicationCallContextProvider;
+    this.logger = logger;
+  }
+
+  @Async
+  public void initializeAnubis(final @Nonnull String tenantIdentifier,
+                               final @Nonnull String applicationName,
+                               final @Nonnull String uri,
+                               final @Nonnull Signature signature) {
+    try (final AutoCloseable ignored
+                 = this.applicationCallContextProvider.getApplicationCallContext(tenantIdentifier, applicationName))
+    {
+      final Anubis anubis = this.applicationCallContextProvider.getApplication(Anubis.class, uri);
+      anubis.initialize(signature.getPublicKeyMod(), signature.getPublicKeyExp());
+      logger.info("Anubis initialization for io.mifos.provisioner.tenant '{}' and application '{}' succeeded with signature '{}'.",
+              tenantIdentifier, applicationName, signature);
+
+    } catch (final Exception e) {
+      throw new IllegalStateException(e);
+    }
+  }
+}
diff --git a/service/src/main/java/io/mifos/provisioner/internal/service/applications/ApplicationCallContextProvider.java b/service/src/main/java/io/mifos/provisioner/internal/service/applications/ApplicationCallContextProvider.java
new file mode 100644
index 0000000..9577393
--- /dev/null
+++ b/service/src/main/java/io/mifos/provisioner/internal/service/applications/ApplicationCallContextProvider.java
@@ -0,0 +1,85 @@
+/*
+ * 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.provisioner.internal.service.applications;
+
+import io.mifos.anubis.api.v1.RoleConstants;
+import io.mifos.anubis.api.v1.TokenConstants;
+import io.mifos.core.api.context.AutoSeshat;
+import io.mifos.core.api.context.AutoUserContext;
+import io.mifos.core.api.util.ApiFactory;
+import io.mifos.core.lang.AutoTenantContext;
+import io.mifos.provisioner.internal.util.TokenProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.TimeUnit;
+
+@SuppressWarnings("WeakerAccess")
+@Component
+public class ApplicationCallContextProvider {
+
+  private final ApiFactory apiFactory;
+  private final TokenProvider tokenProvider;
+
+  static private class ApplicationCallContext implements AutoCloseable
+  {
+    private final AutoTenantContext tenantContext;
+    private final AutoUserContext userContext;
+
+
+    private ApplicationCallContext(final AutoTenantContext tenantContext, final AutoUserContext userContext) {
+      this.tenantContext = tenantContext;
+      this.userContext = userContext;
+    }
+
+    @Override
+    public void close() {
+      tenantContext.close();
+      userContext.close();
+    }
+  }
+
+  @Autowired
+  public ApplicationCallContextProvider(final ApiFactory apiFactory,
+                                        final TokenProvider tokenProvider) {
+    super();
+    this.apiFactory = apiFactory;
+    this.tokenProvider = tokenProvider;
+  }
+
+  public AutoCloseable getApplicationCallContext(final String tenantIdentifier, final String applicationName)
+  {
+    final String token = this.tokenProvider.createToken(tenantIdentifier, applicationName, 2L, TimeUnit.MINUTES).getToken();
+    final AutoTenantContext tenantContext = new AutoTenantContext(tenantIdentifier);
+    final AutoUserContext userContext = new AutoSeshat(token);
+
+    return new ApplicationCallContext(tenantContext, userContext);
+  }
+
+  public AutoCloseable getApplicationCallGuestContext(final String tenantIdentifier)
+  {
+    final AutoTenantContext tenantContext = new AutoTenantContext(tenantIdentifier);
+    final AutoUserContext userContext = new AutoUserContext(RoleConstants.GUEST_USER_IDENTIFIER, TokenConstants.NO_AUTHENTICATION);
+
+    return new ApplicationCallContext(tenantContext, userContext);
+
+  }
+
+  public <T> T getApplication(final Class<T> clazz, final String applicationUri)
+  {
+    return this.apiFactory.create(clazz, applicationUri);
+  }
+}
diff --git a/service/src/main/java/io/mifos/provisioner/internal/service/applications/IdentityServiceInitializer.java b/service/src/main/java/io/mifos/provisioner/internal/service/applications/IdentityServiceInitializer.java
new file mode 100644
index 0000000..1be58d8
--- /dev/null
+++ b/service/src/main/java/io/mifos/provisioner/internal/service/applications/IdentityServiceInitializer.java
@@ -0,0 +1,198 @@
+/*
+ * 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.provisioner.internal.service.applications;
+
+
+import io.mifos.anubis.api.v1.client.Anubis;
+import io.mifos.anubis.api.v1.domain.PermittableEndpoint;
+import io.mifos.anubis.api.v1.domain.Signature;
+import io.mifos.identity.api.v1.client.IdentityService;
+import io.mifos.identity.api.v1.client.PermittableGroupAlreadyExistsException;
+import io.mifos.identity.api.v1.client.TenantAlreadyInitializedException;
+import io.mifos.identity.api.v1.domain.PermittableGroup;
+import io.mifos.provisioner.config.ProvisionerConstants;
+import io.mifos.tool.crypto.HashGenerator;
+import org.apache.commons.lang.RandomStringUtils;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.util.Base64Utils;
+
+import javax.annotation.Nonnull;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @author Myrle Krantz
+ */
+@Component
+public class IdentityServiceInitializer {
+
+  private final ApplicationCallContextProvider applicationCallContextProvider;
+  private final HashGenerator hashGenerator;
+  private final Logger logger;
+
+  @Value("${provisioner.domain}")
+  private String domain;
+
+  public class IdentityServiceInitializationResult {
+    private final Signature signature;
+    @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
+    private final Optional<String> adminPassword;
+
+    private IdentityServiceInitializationResult(final Signature signature, final String adminPassword) {
+      this.signature = signature;
+      this.adminPassword = Optional.of(adminPassword);
+    }
+
+    private IdentityServiceInitializationResult(final Signature signature) {
+      this.signature = signature;
+      this.adminPassword = Optional.empty();
+    }
+
+    public Signature getSignature() {
+      return signature;
+    }
+
+    public Optional<String> getAdminPassword() {
+      return adminPassword;
+    }
+  }
+
+  @Autowired
+  public IdentityServiceInitializer(
+          final ApplicationCallContextProvider applicationCallContextProvider,
+          final HashGenerator hashGenerator,
+          @Qualifier(ProvisionerConstants.LOGGER_NAME) final Logger logger) {
+    this.applicationCallContextProvider = applicationCallContextProvider;
+    this.hashGenerator = hashGenerator;
+    this.logger = logger;
+  }
+
+  public IdentityServiceInitializationResult initializeIsis(
+          final @Nonnull String tenantIdentifier,
+          final @Nonnull String applicationName,
+          final @Nonnull String identityManagerUri) {
+    try (final AutoCloseable ignored
+                 = applicationCallContextProvider.getApplicationCallContext(tenantIdentifier, applicationName))
+    {
+      final IdentityService identityService = applicationCallContextProvider.getApplication(IdentityService.class, identityManagerUri);
+      try {
+        final String randomPassword = RandomStringUtils.random(8, true, true);
+
+        final byte[] salt = Base64Utils.encode(("antony" + tenantIdentifier + this.domain).getBytes());
+
+        final String encodedPassword = Base64Utils.encodeToString(randomPassword.getBytes());
+
+        final byte[] hash = this.hashGenerator.hash(encodedPassword, salt, ProvisionerConstants.ITERATION_COUNT, ProvisionerConstants.HASH_LENGTH);
+        final String encodedPasswordHash = Base64Utils.encodeToString(hash);
+
+        final Signature signature = identityService.initialize(encodedPasswordHash);
+        logger.info("Isis initialization for io.mifos.provisioner.tenant '{}' succeeded with signature '{}'.", tenantIdentifier, signature);
+
+        return new IdentityServiceInitializationResult(signature, encodedPasswordHash);
+      } catch (final TenantAlreadyInitializedException aiex) {
+        final Signature signature = identityService.getSignature();
+        logger.info("Isis initialization for io.mifos.provisioner.tenant '{}' failed because it was already initialized.  Pre-existing signature '{}'.",
+                tenantIdentifier, signature);
+
+        return new IdentityServiceInitializationResult(signature);
+      }
+    } catch (final Exception e) {
+      throw new IllegalStateException(e);
+    }
+  }
+
+  public void postPermittableGroups(
+          final @Nonnull String tenantIdentifier,
+          final @Nonnull String identityManagerApplicationName,
+          final @Nonnull String identityManagerApplicationUri,
+          final @Nonnull String applicationUri)
+  {
+    final List<PermittableEndpoint> permittables;
+    try (final AutoCloseable ignored = applicationCallContextProvider.getApplicationCallGuestContext(tenantIdentifier)) {
+      permittables = getPermittables(applicationUri);
+    } catch (final Exception e) {
+      throw new IllegalStateException(e);
+    }
+
+    try (final AutoCloseable ignored
+                 = applicationCallContextProvider.getApplicationCallContext(tenantIdentifier, identityManagerApplicationName))
+    {
+      final IdentityService identityService = applicationCallContextProvider.getApplication(IdentityService.class, identityManagerApplicationUri);
+
+      final List<PermittableGroup> permittableGroups = getPermittableGroups(permittables);
+
+      permittableGroups.forEach(x -> createOrFindPermittableGroup(identityService, x));
+    } catch (final Exception e) {
+      throw new IllegalStateException(e);
+    }
+  }
+
+  List<PermittableEndpoint> getPermittables(final @Nonnull String applicationUri)
+  {
+    try {
+      final Anubis anubis = this.applicationCallContextProvider.getApplication(Anubis.class, applicationUri);
+      return anubis.getPermittableEndpoints();
+    }
+    catch (final RuntimeException unexpected)
+    {
+      logger.error("Request for permittable endpoints to '{}' failed.", applicationUri, unexpected);
+      return Collections.emptyList();
+    }
+  }
+
+  static List<PermittableGroup> getPermittableGroups(final @Nonnull List<PermittableEndpoint> permittables)
+  {
+    final Map<String, Set<PermittableEndpoint>> groupedPermittables = new HashMap<>();
+
+    permittables.forEach(x -> groupedPermittables.computeIfAbsent(x.getGroupId(), y -> new LinkedHashSet<>()).add(x));
+
+    return groupedPermittables.entrySet().stream()
+            .map(entry -> new PermittableGroup(entry.getKey(), entry.getValue().stream().collect(Collectors.toList())))
+            .collect(Collectors.toList());
+  }
+
+  void createOrFindPermittableGroup(
+          final @Nonnull IdentityService identityService,
+          final @Nonnull PermittableGroup permittableGroup) {
+    try {
+      identityService.createPermittableGroup(permittableGroup);
+      logger.info("Group '{}' successfully created in identity service.", permittableGroup.getIdentifier());
+    }
+    catch (final PermittableGroupAlreadyExistsException groupAlreadyExistsException)
+    {
+      //if the group already exists, read out and compare.  If the group is the same, there is nothing left to do.
+      final PermittableGroup existingGroup = identityService.getPermittableGroup(permittableGroup.getIdentifier());
+      if (!existingGroup.getIdentifier().equals(permittableGroup.getIdentifier())) {
+        logger.error("Group '{}' already exists, but has a different name{} (strange).", permittableGroup.getIdentifier(), existingGroup.getIdentifier());
+      }
+
+      //Compare as sets because I'm not going to get into a hissy fit over order.
+      final Set<PermittableEndpoint> existingGroupPermittables = new HashSet<>(existingGroup.getPermittables());
+      final Set<PermittableEndpoint> newGroupPermittables = new HashSet<>(permittableGroup.getPermittables());
+      if (!existingGroupPermittables.equals(newGroupPermittables)) {
+        logger.error("Group '{}' already exists, but has different contents.", permittableGroup.getIdentifier());
+      }
+    }
+    catch (final RuntimeException unexpected)
+    {
+      logger.error("Creating group '{}' failed.", permittableGroup.getIdentifier(), unexpected);
+    }
+  }
+}
diff --git a/service/src/main/java/io/mifos/provisioner/internal/util/ContactPointUtils.java b/service/src/main/java/io/mifos/provisioner/internal/util/ContactPointUtils.java
new file mode 100644
index 0000000..b636d82
--- /dev/null
+++ b/service/src/main/java/io/mifos/provisioner/internal/util/ContactPointUtils.java
@@ -0,0 +1,46 @@
+/*
+ * 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.provisioner.internal.util;
+
+import com.datastax.driver.core.Cluster;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+
+public class ContactPointUtils {
+
+  private ContactPointUtils() {
+    super();
+  }
+
+  public static void process(final Cluster.Builder clusterBuilder, final String contactPoints) {
+    final String[] splitContactPoints = contactPoints.split(",");
+    for (final String contactPoint : splitContactPoints) {
+      if (contactPoint.contains(":")) {
+        final String[] address = contactPoint.split(":");
+        clusterBuilder.addContactPointsWithPorts(
+            new InetSocketAddress(address[0].trim(), Integer.valueOf(address[1].trim())));
+      } else {
+        try {
+          clusterBuilder.addContactPoints(InetAddress.getByName(contactPoint.trim()));
+        } catch (final UnknownHostException uhex) {
+          throw new IllegalArgumentException("Host not found!", uhex);
+        }
+      }
+    }
+  }
+}
diff --git a/service/src/main/java/io/mifos/provisioner/internal/util/DataSourceUtils.java b/service/src/main/java/io/mifos/provisioner/internal/util/DataSourceUtils.java
new file mode 100644
index 0000000..a138b75
--- /dev/null
+++ b/service/src/main/java/io/mifos/provisioner/internal/util/DataSourceUtils.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.provisioner.internal.util;
+
+import org.springframework.core.env.Environment;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+import io.mifos.provisioner.api.v1.domain.DatabaseConnectionInfo;
+
+public class DataSourceUtils {
+
+  private DataSourceUtils() {
+    super();
+  }
+
+  public static Connection create(final DatabaseConnectionInfo databaseConnectionInfo) {
+    try {
+      Class.forName(databaseConnectionInfo.getDriverClass());
+    } catch (ClassNotFoundException cnfex) {
+      throw new IllegalArgumentException(cnfex.getMessage(), cnfex);
+    }
+    final String jdbcUrl = JdbcUrlBuilder
+        .create(JdbcUrlBuilder.DatabaseType.MARIADB)
+        .host(databaseConnectionInfo.getHost())
+        .port(databaseConnectionInfo.getPort())
+        .build();
+    try {
+      final Connection connection = DriverManager.getConnection(jdbcUrl, databaseConnectionInfo.getUser(), databaseConnectionInfo.getPassword());
+      connection.setAutoCommit(false);
+      return connection;
+    } catch (SQLException sqlex) {
+      throw new IllegalStateException(sqlex.getMessage(), sqlex);
+    }
+  }
+
+  public static Connection createProvisionerConnection(final Environment environment) {
+    final DatabaseConnectionInfo databaseConnectionInfo = new DatabaseConnectionInfo();
+    databaseConnectionInfo.setDriverClass(environment.getProperty("mariadb.driverClass"));
+    databaseConnectionInfo.setHost(environment.getProperty("mariadb.host"));
+    databaseConnectionInfo.setPort(environment.getProperty("mariadb.port"));
+    databaseConnectionInfo.setUser(environment.getProperty("mariadb.user"));
+    databaseConnectionInfo.setPassword(environment.getProperty("mariadb.password"));
+    final Connection connection = DataSourceUtils.create(databaseConnectionInfo);
+    try {
+      connection.setAutoCommit(false);
+    } catch (SQLException e) {
+      // do nothing
+    }
+    return connection;
+  }
+}
diff --git a/service/src/main/java/io/mifos/provisioner/internal/util/DataStoreOption.java b/service/src/main/java/io/mifos/provisioner/internal/util/DataStoreOption.java
new file mode 100644
index 0000000..e399e87
--- /dev/null
+++ b/service/src/main/java/io/mifos/provisioner/internal/util/DataStoreOption.java
@@ -0,0 +1,30 @@
+/*
+ * 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.provisioner.internal.util;
+
+public enum DataStoreOption {
+
+  ALL,
+  CASSANDRA,
+  RDBMS;
+
+  public static final String PROPERTY_NAME = "provisioner.dataStoreOption";
+  public static final String PROPERTY_DEFAULT_VALUE = "ALL";
+
+  public boolean isEnabled(final DataStoreOption dataStoreOption) {
+    return this == ALL || this == dataStoreOption;
+  }
+}
diff --git a/service/src/main/java/io/mifos/provisioner/internal/util/JdbcUrlBuilder.java b/service/src/main/java/io/mifos/provisioner/internal/util/JdbcUrlBuilder.java
new file mode 100644
index 0000000..450082a
--- /dev/null
+++ b/service/src/main/java/io/mifos/provisioner/internal/util/JdbcUrlBuilder.java
@@ -0,0 +1,74 @@
+/*
+ * 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.provisioner.internal.util;
+
+final class JdbcUrlBuilder {
+
+  enum DatabaseType {
+    MARIADB("jdbc:mariadb://");
+
+    private final String prefix;
+
+    DatabaseType(final String prefix) {
+      this.prefix = prefix;
+    }
+
+    String prefix() {
+      return this.prefix;
+    }
+  }
+
+  private final DatabaseType type;
+  private String host;
+  private String port;
+  private String instanceName;
+
+  private JdbcUrlBuilder(final DatabaseType type) {
+    super();
+    this.type = type;
+  }
+
+  static JdbcUrlBuilder create(final DatabaseType type) {
+    return new JdbcUrlBuilder(type);
+  }
+
+  JdbcUrlBuilder host(final String host) {
+    this.host = host;
+    return this;
+  }
+
+  JdbcUrlBuilder port(final String port) {
+    this.port = port;
+    return this;
+  }
+
+  JdbcUrlBuilder instanceName(final String instanceName) {
+    this.instanceName = instanceName;
+    return this;
+  }
+
+  String build() {
+    switch (this.type) {
+      case MARIADB:
+        return this.type.prefix()
+            + this.host + ":"
+            + this.port
+            + (this.instanceName != null ? "/" + this.instanceName : "");
+      default:
+        throw new IllegalArgumentException("Unknown database type '" + this.type.name() + "'");
+    }
+  }
+}
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/provisioner/internal/util/TokenProvider.java b/service/src/main/java/io/mifos/provisioner/internal/util/TokenProvider.java
new file mode 100644
index 0000000..a1081b7
--- /dev/null
+++ b/service/src/main/java/io/mifos/provisioner/internal/util/TokenProvider.java
@@ -0,0 +1,61 @@
+/*
+ * 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.provisioner.internal.util;
+
+import io.mifos.anubis.api.v1.RoleConstants;
+import io.mifos.anubis.token.SystemAccessTokenSerializer;
+import io.mifos.anubis.token.TokenSerializationResult;
+
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.spec.RSAPrivateKeySpec;
+import java.util.concurrent.TimeUnit;
+
+public class TokenProvider {
+  private PrivateKey privateKey;
+  private final SystemAccessTokenSerializer tokenSerializer;
+
+  public TokenProvider(
+      final BigInteger privateKeyModulus,
+      final BigInteger privateKeyExponent,
+      final SystemAccessTokenSerializer tokenSerializer) {
+    super();
+    this.tokenSerializer = tokenSerializer;
+
+    try {
+      final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+
+      final RSAPrivateKeySpec rsaPrivateKeySpec
+          = new RSAPrivateKeySpec(privateKeyModulus, privateKeyExponent);
+      this.privateKey = keyFactory.generatePrivate(rsaPrivateKeySpec);
+
+    } catch (final Exception ex) {
+      throw new IllegalStateException("Could not read RSA key pair!", ex);
+    }
+  }
+
+  public TokenSerializationResult createToken(final String subject, final String audience, final long ttl, final TimeUnit timeUnit) {
+    SystemAccessTokenSerializer.Specification specification = new SystemAccessTokenSerializer.Specification();
+    specification.setTenant(subject);
+    specification.setTargetApplicationName(audience);
+    specification.setSecondsToLive(timeUnit.toSeconds(ttl));
+    specification.setRole(RoleConstants.SYSTEM_ADMIN_ROLE_IDENTIFIER);
+    specification.setPrivateKey(privateKey);
+
+    return this.tokenSerializer.build(specification);
+  }
+}
diff --git a/service/src/main/java/io/mifos/provisioner/rest/controller/SeshatRestController.java b/service/src/main/java/io/mifos/provisioner/rest/controller/SeshatRestController.java
new file mode 100644
index 0000000..8646152
--- /dev/null
+++ b/service/src/main/java/io/mifos/provisioner/rest/controller/SeshatRestController.java
@@ -0,0 +1,343 @@
+/*
+ * 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.provisioner.rest.controller;
+
+import io.mifos.anubis.annotation.AcceptedTokenType;
+import io.mifos.anubis.annotation.Permittable;
+import io.mifos.core.lang.ServiceException;
+import io.mifos.provisioner.api.v1.domain.*;
+import io.mifos.provisioner.internal.repository.ClientEntity;
+import io.mifos.provisioner.internal.repository.TenantApplicationEntity;
+import io.mifos.provisioner.rest.mapper.ApplicationMapper;
+import io.mifos.provisioner.rest.mapper.AssignedApplicationMapper;
+import io.mifos.provisioner.rest.mapper.ClientMapper;
+import io.mifos.provisioner.internal.service.ApplicationService;
+import io.mifos.provisioner.internal.service.AuthenticationService;
+import io.mifos.provisioner.internal.service.ClientService;
+import io.mifos.provisioner.internal.service.TenantApplicationService;
+import io.mifos.provisioner.internal.service.TenantService;
+import io.mifos.provisioner.config.ProvisionerConstants;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.Valid;
+import java.util.*;
+import java.util.stream.Collectors;
+
+@SuppressWarnings("unused")
+@RestController
+@RequestMapping("/")
+public class SeshatRestController {
+
+  private final Logger logger;
+  private final AuthenticationService authenticationService;
+  private final ClientService clientService;
+  private final TenantService tenantService;
+  private final ApplicationService applicationService;
+  private final TenantApplicationService tenantApplicationService;
+
+  @Autowired
+  public SeshatRestController(@Qualifier(ProvisionerConstants.LOGGER_NAME) final Logger logger,
+                              final AuthenticationService authenticationService,
+                              final ClientService clientService,
+                              final TenantService tenantService,
+                              final ApplicationService applicationService,
+                              final TenantApplicationService tenantApplicationService) {
+    super();
+    this.logger = logger;
+    this.authenticationService = authenticationService;
+    this.clientService = clientService;
+    this.tenantService = tenantService;
+    this.applicationService = applicationService;
+    this.tenantApplicationService = tenantApplicationService;
+  }
+
+  @Permittable(AcceptedTokenType.GUEST)
+  @RequestMapping(
+      value = "/auth/token",
+      method = RequestMethod.POST,
+      consumes = {MediaType.ALL_VALUE},
+      produces = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  public
+  @ResponseBody
+  ResponseEntity<AuthenticationResponse> authenticate(@RequestParam("grant_type") final String grantType,
+                                                      @RequestParam("client_id") final String clientId,
+                                                      @RequestParam("username") final String username,
+                                                      @RequestParam("password") final String password) {
+    if (!grantType.equals("password")) {
+      this.logger.info("Authentication attempt with unknown grant type: " + grantType);
+      throw ServiceException.badRequest("Authentication attempt with unknown grant type: {0}", grantType);
+    }
+    return ResponseEntity.ok(this.authenticationService.authenticate(clientId, username, password));
+  }
+
+  @Permittable(AcceptedTokenType.SYSTEM)
+  @RequestMapping(
+      value = "/auth/user/{useridentifier}/password",
+      method = RequestMethod.PUT,
+      consumes = {MediaType.APPLICATION_JSON_VALUE},
+      produces = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Void> updatePasswordPolicy(@PathVariable("useridentifier") final String username,
+                                            @RequestBody final PasswordPolicy passwordPolicy) {
+    this.authenticationService.updatePasswordPolicy(username, passwordPolicy);
+    return ResponseEntity.accepted().build();
+  }
+
+  @Permittable(AcceptedTokenType.SYSTEM)
+  @RequestMapping(
+      value = "/clients",
+      method = RequestMethod.GET,
+      consumes = {MediaType.ALL_VALUE},
+      produces = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  public
+  @ResponseBody
+  ResponseEntity<List<Client>> getClients() {
+    final ArrayList<Client> result = new ArrayList<>();
+    final List<ClientEntity> clientEntities = this.clientService.fetchAll();
+    result.addAll(clientEntities
+        .stream().map(ClientMapper::map)
+        .collect(Collectors.toList()));
+    return ResponseEntity.ok(result);
+  }
+
+  @Permittable(AcceptedTokenType.SYSTEM)
+  @RequestMapping(
+      value = "/clients",
+      method = RequestMethod.POST,
+      consumes = {MediaType.APPLICATION_JSON_VALUE},
+      produces = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Void> createClient(@RequestBody @Valid final Client client) {
+    this.clientService.create(ClientMapper.map(client));
+    return ResponseEntity.accepted().build();
+  }
+
+  @Permittable(AcceptedTokenType.SYSTEM)
+  @RequestMapping(
+      value = "/clients/{clientidentifier}",
+      method = RequestMethod.GET,
+      consumes = {MediaType.ALL_VALUE},
+      produces = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Client> getClient(@PathVariable("clientidentifier") final String clientIdentifier) {
+    return ResponseEntity.ok(ClientMapper.map(this.clientService.findByName(clientIdentifier)));
+  }
+
+  @Permittable(AcceptedTokenType.SYSTEM)
+  @RequestMapping(
+      value = "/clients/{clientidentifier}",
+      method = RequestMethod.DELETE,
+      consumes = {MediaType.ALL_VALUE},
+      produces = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Void> deleteClient(@PathVariable("clientidentifier") final String clientIdentifier) {
+    this.clientService.delete(clientIdentifier);
+    return ResponseEntity.accepted().build();
+  }
+
+  @Permittable(AcceptedTokenType.SYSTEM)
+  @RequestMapping(
+      value = "/tenants",
+      method = RequestMethod.POST,
+      consumes = {MediaType.APPLICATION_JSON_VALUE},
+      produces = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Void> createTenant(@RequestBody final Tenant tenant) {
+    this.tenantService.create(tenant);
+    return ResponseEntity.accepted().build();
+  }
+
+
+  @Permittable(AcceptedTokenType.SYSTEM)
+  @RequestMapping(
+      value = "/tenants",
+      method = RequestMethod.GET,
+      consumes = {MediaType.ALL_VALUE},
+      produces = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  public
+  @ResponseBody
+  ResponseEntity<List<Tenant>> getTenants() {
+    return ResponseEntity.ok(this.tenantService.fetchAll());
+  }
+
+  @Permittable(AcceptedTokenType.SYSTEM)
+  @RequestMapping(
+      value = "/tenants/{tenantidentifier}",
+      method = RequestMethod.GET,
+      consumes = {MediaType.ALL_VALUE},
+      produces = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Tenant> getTenant(@PathVariable("tenantidentifier") final String tenantIdentifier) {
+    final Optional<Tenant> result = this.tenantService.find(tenantIdentifier);
+    if (result.isPresent()) {
+      return ResponseEntity.ok(result.get());
+    } else {
+      throw ServiceException.notFound("Tenant {0} not found!", tenantIdentifier);
+    }
+  }
+
+  @Permittable(AcceptedTokenType.SYSTEM)
+  @RequestMapping(
+      value = "/tenants/{tenantidentifier}",
+      method = RequestMethod.DELETE,
+      consumes = {MediaType.ALL_VALUE},
+      produces = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Void> deleteTenant(@PathVariable("tenantidentifier") final String tenantIdentifier) {
+    this.tenantService.delete(tenantIdentifier);
+    return ResponseEntity.accepted().build();
+  }
+
+  @RequestMapping(
+          value = "tenants/{tenantidentifier}/identityservice",
+          method = RequestMethod.POST,
+          produces = {MediaType.APPLICATION_JSON_VALUE},
+          consumes = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  ResponseEntity<IdentityManagerInitialization> assignIdentityManager(@PathVariable("tenantidentifier") final String tenantIdentifier,
+                                             @RequestBody final AssignedApplication assignedApplication)
+  {
+    final String identityManagerUri = applicationService.find(assignedApplication.getName()).getHomepage();
+
+    final Optional<String> adminPassword = tenantService.assignIdentityManager(
+            tenantIdentifier,
+            assignedApplication.getName(),
+            identityManagerUri);
+    final IdentityManagerInitialization ret = new IdentityManagerInitialization();
+    ret.setAdminPassword(adminPassword.orElse(""));
+    return ResponseEntity.ok(ret);
+  }
+
+  @Permittable(AcceptedTokenType.SYSTEM)
+  @RequestMapping(
+      value = "/applications",
+      method = RequestMethod.POST,
+      consumes = {MediaType.APPLICATION_JSON_VALUE},
+      produces = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Void> createApplication(@RequestBody final Application application) {
+    this.applicationService.create(ApplicationMapper.map(application));
+    return ResponseEntity.accepted().build();
+  }
+
+  @Permittable(AcceptedTokenType.SYSTEM)
+  @RequestMapping(
+      value = "/applications",
+      method = RequestMethod.GET,
+      consumes = {MediaType.ALL_VALUE},
+      produces = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  public
+  @ResponseBody
+  ResponseEntity<List<Application>> getApplications() {
+    return ResponseEntity.ok(
+        this.applicationService.fetchAll()
+            .stream().map(ApplicationMapper::map)
+            .collect(Collectors.toList()));
+  }
+
+  @Permittable(AcceptedTokenType.SYSTEM)
+  @RequestMapping(
+      value = "/applications/{name}",
+      method = RequestMethod.GET,
+      consumes = {MediaType.ALL_VALUE},
+      produces = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Application> getApplication(@PathVariable("name") final String name) {
+    return ResponseEntity.ok(ApplicationMapper.map(this.applicationService.find(name)));
+  }
+
+  @Permittable(AcceptedTokenType.SYSTEM)
+  @RequestMapping(
+      value = "/applications/{name}",
+      method = RequestMethod.DELETE,
+      consumes = {MediaType.ALL_VALUE},
+      produces = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Void> deleteApplication(@PathVariable("name") final String name) {
+    this.applicationService.delete(name);
+    return ResponseEntity.accepted().build();
+  }
+
+  @Permittable(AcceptedTokenType.SYSTEM)
+  @RequestMapping(
+      value = "tenants/{tenantidentifier}/applications",
+      method = RequestMethod.PUT,
+      consumes = {MediaType.APPLICATION_JSON_VALUE},
+      produces = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Void> assignApplications(@PathVariable("tenantidentifier") final String tenantIdentifier,
+                                          @RequestBody final List<AssignedApplication> assignedApplications) {
+    final TenantApplicationEntity tenantApplicationEntity = AssignedApplicationMapper.map(tenantIdentifier, assignedApplications);
+
+    final Map<String, String> appNameToUriMap = new HashMap<>();
+    tenantApplicationEntity.getApplications().forEach(
+            appName -> appNameToUriMap.put(appName, applicationService.find(appName).getHomepage()));
+
+    tenantApplicationService.assign(tenantApplicationEntity, appNameToUriMap);
+    return ResponseEntity.accepted().build();
+  }
+
+  @Permittable(AcceptedTokenType.SYSTEM)
+  @RequestMapping(
+      value = "tenants/{tenantidentifier}/applications",
+      method = RequestMethod.GET,
+      consumes = {MediaType.ALL_VALUE},
+      produces = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  public
+  @ResponseBody
+  ResponseEntity<List<AssignedApplication>> getAssignedApplications(@PathVariable("tenantidentifier") final String tenantIdentifier) {
+    final TenantApplicationEntity tenantApplicationEntity = this.tenantApplicationService.find(tenantIdentifier);
+    return ResponseEntity.ok(AssignedApplicationMapper.map(tenantApplicationEntity));
+  }
+}
diff --git a/service/src/main/java/io/mifos/provisioner/rest/mapper/ApplicationMapper.java b/service/src/main/java/io/mifos/provisioner/rest/mapper/ApplicationMapper.java
new file mode 100644
index 0000000..a986686
--- /dev/null
+++ b/service/src/main/java/io/mifos/provisioner/rest/mapper/ApplicationMapper.java
@@ -0,0 +1,44 @@
+/*
+ * 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.provisioner.rest.mapper;
+
+import io.mifos.provisioner.api.v1.domain.Application;
+import io.mifos.provisioner.internal.repository.ApplicationEntity;
+
+public final class ApplicationMapper {
+
+  private ApplicationMapper() {
+    super();
+  }
+
+  public static Application map(final ApplicationEntity applicationEntity) {
+    final Application application = new Application();
+    application.setName(applicationEntity.getName());
+    application.setDescription(applicationEntity.getDescription());
+    application.setVendor(applicationEntity.getVendor());
+    application.setHomepage(applicationEntity.getHomepage());
+    return application;
+  }
+
+  public static ApplicationEntity map(final Application application) {
+    final ApplicationEntity applicationEntity = new ApplicationEntity();
+    applicationEntity.setName(application.getName());
+    applicationEntity.setDescription(application.getDescription());
+    applicationEntity.setVendor(application.getVendor());
+    applicationEntity.setHomepage(application.getHomepage());
+    return applicationEntity;
+  }
+}
diff --git a/service/src/main/java/io/mifos/provisioner/rest/mapper/AssignedApplicationMapper.java b/service/src/main/java/io/mifos/provisioner/rest/mapper/AssignedApplicationMapper.java
new file mode 100644
index 0000000..dfcc908
--- /dev/null
+++ b/service/src/main/java/io/mifos/provisioner/rest/mapper/AssignedApplicationMapper.java
@@ -0,0 +1,56 @@
+/*
+ * 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.provisioner.rest.mapper;
+
+import io.mifos.provisioner.api.v1.domain.AssignedApplication;
+import io.mifos.provisioner.internal.repository.TenantApplicationEntity;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class AssignedApplicationMapper {
+
+  private AssignedApplicationMapper() {
+    super();
+  }
+
+  public static TenantApplicationEntity map(final String identifier, final List<AssignedApplication> assignedApplications) {
+    final TenantApplicationEntity tenantApplicationEntity = new TenantApplicationEntity();
+    tenantApplicationEntity.setTenantIdentifier(identifier);
+
+    final HashSet<String> applicationNames = new HashSet<>();
+    tenantApplicationEntity.setApplications(applicationNames);
+    applicationNames.addAll(assignedApplications
+        .stream().map(AssignedApplication::getName)
+        .collect(Collectors.toList()));
+    return tenantApplicationEntity;
+  }
+
+  public static List<AssignedApplication> map(final TenantApplicationEntity tenantApplicationEntity) {
+    final ArrayList<AssignedApplication> assignedApplications = new ArrayList<>();
+    if (tenantApplicationEntity != null) {
+      for (final String name : tenantApplicationEntity.getApplications()) {
+        final AssignedApplication assignedApplication = new AssignedApplication();
+        assignedApplications.add(assignedApplication);
+        assignedApplication.setName(name);
+      }
+
+    }
+    return assignedApplications;
+  }
+}
diff --git a/service/src/main/java/io/mifos/provisioner/rest/mapper/ClientMapper.java b/service/src/main/java/io/mifos/provisioner/rest/mapper/ClientMapper.java
new file mode 100644
index 0000000..f3f19dc
--- /dev/null
+++ b/service/src/main/java/io/mifos/provisioner/rest/mapper/ClientMapper.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.provisioner.rest.mapper;
+
+
+import io.mifos.provisioner.api.v1.domain.Client;
+import io.mifos.provisioner.internal.repository.ClientEntity;
+
+public class ClientMapper {
+
+  private ClientMapper() {
+    super();
+  }
+
+  public static Client map(final ClientEntity clientEntity) {
+    final Client client = new Client();
+    client.setName(clientEntity.getName());
+    client.setDescription(clientEntity.getDescription());
+    client.setRedirectUri(clientEntity.getRedirectUri());
+    client.setVendor(clientEntity.getVendor());
+    client.setHomepage(clientEntity.getHomepage());
+    return client;
+  }
+
+  public static ClientEntity map(final Client client) {
+    final ClientEntity clientEntity = new ClientEntity();
+    clientEntity.setName(client.getName());
+    clientEntity.setDescription(client.getDescription());
+    clientEntity.setRedirectUri(client.getRedirectUri());
+    clientEntity.setVendor(client.getVendor());
+    clientEntity.setHomepage(client.getHomepage());
+    return clientEntity;
+  }
+}
diff --git a/service/src/main/resources/application.yaml b/service/src/main/resources/application.yaml
new file mode 100644
index 0000000..3285ffe
--- /dev/null
+++ b/service/src/main/resources/application.yaml
@@ -0,0 +1,55 @@
+#
+# 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.
+#
+
+eureka:
+  client:
+    serviceUrl:
+      defaultZone: http://localhost:8761/eureka/
+
+server:
+  port: 9090
+  contextPath: /provisioner/v1/*
+
+flyway:
+  enabled: false
+
+cassandra:
+  clusterName: staging_cluster
+  contactPoints: 127.0.0.1:9042,127.0.0.2:9042,127.0.0.3:9042
+  replication:
+    default:
+      type: Network
+      replicas: datacenter1:3
+
+mariadb:
+  driverClass: org.mariadb.jdbc.Driver
+  database: seshat
+  host: localhost
+  port: 3306
+  user: root
+  password: mysql
+
+provisioner:
+  domain: mifos.io
+  dataStoreOption: ALL # possible values ALL, CASSANDRA, RDBMS
+  token:
+    ttl: 60
+  publicKey:
+    modulus: 18127979232651385577366788312577367809882840493309321947218444859734692803519322053118166861938127116063250592470870009582066787630638146674578444578864162263848522570791848618846268461050665448704495233021189752693589550011013299642312910333176350540133789870795905610030842272086304844975800905158104573387446873659409802855678797448220593733004510515015951396676579423158731638742125820984712730134997911206145523653040544527593404472473700394782702820939632486955986794980759 [...]
+    exponent: 65537
+  privateKey:
+    modulus: 21809400075083175962379439196636583774179615274445790777150424827246273931119109792802133084303807180138431535925399383578649396318773218549617349819790628104138404726662789219140029410886208143215123139141187664102456961514206129348474466709463496369346914088323798703048475755436555987223468203468553348311906047130177940096755897503185024960411862964691833266166883793657798456516931118758087021056538684569379725109236404256856114833181540549166096374949146511824467497157749 [...]
+    exponent: 2139642014781799413578815665329425805796195345660873344534634961460894500320787163339801865798552857857669093846009855145296308323102740222875928290909346208376463479111815761529864551325723848781847746088807546959933431187752563120969092743872415524914978086001597511956917156982389800806445384454909602336150973378503597186273413132363029529116825739138629507572323287073370284360633026515093536587446958309887954535219537879934808188484160721754899140220055724762318034081988 [...]
diff --git a/service/src/main/resources/bootstrap.yaml b/service/src/main/resources/bootstrap.yaml
new file mode 100644
index 0000000..b2c042b
--- /dev/null
+++ b/service/src/main/resources/bootstrap.yaml
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+
+spring:
+    application:
+        name: provisioner-v1
\ No newline at end of file
diff --git a/service/src/main/resources/logback.xml b/service/src/main/resources/logback.xml
new file mode 100644
index 0000000..3f27e34
--- /dev/null
+++ b/service/src/main/resources/logback.xml
@@ -0,0 +1,40 @@
+<!--
+
+    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.
+
+-->
+<configuration>
+    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>provisioner.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>provisioner.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <maxHistory>30</maxHistory>
+            <totalSizeCap>3GB</totalSizeCap>
+        </rollingPolicy>
+        <encoder>
+            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <root level="info">
+        <appender-ref ref="FILE"/>
+        <appender-ref ref="STDOUT"/>
+    </root>
+</configuration>
\ No newline at end of file
diff --git a/service/src/test/java/io/mifos/provisioner/GenerateRsaKeyPair.java b/service/src/test/java/io/mifos/provisioner/GenerateRsaKeyPair.java
new file mode 100644
index 0000000..8aed5da
--- /dev/null
+++ b/service/src/test/java/io/mifos/provisioner/GenerateRsaKeyPair.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.provisioner;
+
+import java.io.BufferedWriter;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.spec.RSAPrivateKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+
+public class GenerateRsaKeyPair {
+
+  private GenerateRsaKeyPair() {
+    super();
+  }
+
+  public static void main(String[] args) throws Exception {
+    final GenerateRsaKeyPair generateRsaKeyPair = new GenerateRsaKeyPair();
+    generateRsaKeyPair.createKeyPair();
+  }
+
+  private void createKeyPair() throws Exception {
+    final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+
+    final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+    keyPairGenerator.initialize(2048);
+    final KeyPair keyPair = keyPairGenerator.genKeyPair();
+
+    final RSAPublicKeySpec rsaPublicKeySpec = keyFactory.getKeySpec(keyPair.getPublic(), RSAPublicKeySpec.class);
+    final BufferedWriter bufferedWriterPubKey = Files.newBufferedWriter(Paths.get("/home/mage/seshat.pub"));
+    bufferedWriterPubKey.write(rsaPublicKeySpec.getModulus().toString());
+    bufferedWriterPubKey.newLine();
+    bufferedWriterPubKey.write(rsaPublicKeySpec.getPublicExponent().toString());
+    bufferedWriterPubKey.flush();
+    bufferedWriterPubKey.close();
+
+    final RSAPrivateKeySpec rsaPrivateKeySpec = keyFactory.getKeySpec(keyPair.getPrivate(), RSAPrivateKeySpec.class);
+    final BufferedWriter bufferedWriterPrivateKey = Files.newBufferedWriter(Paths.get("/home/mage/seshat"));
+    bufferedWriterPrivateKey.write(rsaPrivateKeySpec.getModulus().toString());
+    bufferedWriterPrivateKey.newLine();
+    bufferedWriterPrivateKey.write(rsaPrivateKeySpec.getPrivateExponent().toString());
+    bufferedWriterPrivateKey.flush();
+    bufferedWriterPrivateKey.close();
+
+  }
+}
diff --git a/service/src/test/java/io/mifos/provisioner/internal/service/applications/IdentityServiceInitializerTest.java b/service/src/test/java/io/mifos/provisioner/internal/service/applications/IdentityServiceInitializerTest.java
new file mode 100644
index 0000000..4c849ab
--- /dev/null
+++ b/service/src/test/java/io/mifos/provisioner/internal/service/applications/IdentityServiceInitializerTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.provisioner.internal.service.applications;
+
+import io.mifos.anubis.api.v1.client.Anubis;
+import io.mifos.anubis.api.v1.domain.PermittableEndpoint;
+import io.mifos.identity.api.v1.client.IdentityService;
+import io.mifos.identity.api.v1.client.PermittableGroupAlreadyExistsException;
+import io.mifos.identity.api.v1.domain.PermittableGroup;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.slf4j.Logger;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.*;
+
+/**
+ * @author Myrle Krantz
+ */
+public class IdentityServiceInitializerTest {
+
+  private final PermittableEndpoint abcPost1 = new PermittableEndpoint("/a/b/c", "POST", "1");
+  private final PermittableEndpoint abcGet1 = new PermittableEndpoint("/a/b/c", "GET", "1");
+  private final PermittableEndpoint defGet1 = new PermittableEndpoint("/d/e/f", "POST", "1");
+  private final PermittableGroup group1 = new PermittableGroup("1", Arrays.asList(abcPost1, abcGet1, defGet1));
+  private final PermittableGroup reorderedGroup1 = new PermittableGroup("1", Arrays.asList(abcGet1, abcPost1, defGet1));
+  private final PermittableGroup changedGroup1 = new PermittableGroup("1", Arrays.asList(abcPost1, defGet1));
+
+  private final PermittableEndpoint abcPost2 = new PermittableEndpoint("/a/b/c", "POST", "2");
+  private final PermittableEndpoint abcGet2 = new PermittableEndpoint("/a/b/c", "GET", "2");
+  private final PermittableEndpoint defGet2 = new PermittableEndpoint("/d/e/f", "POST", "2");
+  private final PermittableGroup group2 = new PermittableGroup("2", Arrays.asList(abcPost2, abcGet2, defGet2));
+
+  private final PermittableEndpoint defGet3 = new PermittableEndpoint("/d/e/f", "POST", "3");
+  private final PermittableGroup group3 = new PermittableGroup("3", Collections.singletonList(defGet3));
+
+  @Test
+  public void getPermittablesAnubisCallFails() throws Exception {
+    final ApplicationCallContextProvider applicationCallContextProviderMock = Mockito.mock(ApplicationCallContextProvider.class);
+    final Logger loggerMock = Mockito.mock(Logger.class);
+    final Anubis anubisMock = Mockito.mock(Anubis.class);
+
+    when(applicationCallContextProviderMock.getApplication(Anubis.class, "blah")).thenReturn(anubisMock);
+    //noinspection unchecked
+    when(anubisMock.getPermittableEndpoints()).thenThrow(IllegalStateException.class);
+
+    final List<PermittableEndpoint> ret = new IdentityServiceInitializer(applicationCallContextProviderMock, null, loggerMock)
+            .getPermittables("blah");
+
+    Assert.assertEquals(ret, Collections.emptyList());
+    verify(loggerMock).error(anyString(), anyString(), isA(IllegalStateException.class));
+  }
+
+
+
+  @Test
+  public void getPermittableGroups() throws Exception {
+
+    final List<PermittableEndpoint> permittableEndpoints = Arrays.asList(abcPost1, abcGet1, defGet1, abcPost2, abcGet2, defGet2, defGet3);
+    final List<PermittableGroup> ret = IdentityServiceInitializer.getPermittableGroups(permittableEndpoints);
+    Assert.assertEquals(ret, Arrays.asList(group1, group2, group3));
+  }
+
+  @Test
+  public void getPermittableGroupsOnEmptyList() throws Exception {
+    final List<PermittableGroup> ret = IdentityServiceInitializer.getPermittableGroups(Collections.emptyList());
+    Assert.assertEquals(ret, Collections.emptyList());
+  }
+
+  @Test
+  public void createOrFindPermittableGroupThatAlreadyExists() throws Exception {
+    final Logger loggerMock = Mockito.mock(Logger.class);
+
+    final IdentityService identityServiceMock = Mockito.mock(IdentityService.class);
+    doThrow(PermittableGroupAlreadyExistsException.class).when(identityServiceMock).createPermittableGroup(group1);
+    doReturn(reorderedGroup1).when(identityServiceMock).getPermittableGroup(group1.getIdentifier());
+
+    new IdentityServiceInitializer(null, null, loggerMock).createOrFindPermittableGroup(identityServiceMock, group1);
+  }
+
+  @Test
+  public void createOrFindPermittableGroupThatAlreadyExistsDifferently() throws Exception {
+    final Logger loggerMock = Mockito.mock(Logger.class);
+
+    final IdentityService identityServiceMock = Mockito.mock(IdentityService.class);
+    doThrow(PermittableGroupAlreadyExistsException.class).when(identityServiceMock).createPermittableGroup(group1);
+    doReturn(changedGroup1).when(identityServiceMock).getPermittableGroup(group1.getIdentifier());
+
+    new IdentityServiceInitializer(null, null, loggerMock).createOrFindPermittableGroup(identityServiceMock, group1);
+
+    verify(loggerMock).error(anyString(), anyString());
+  }
+
+  @Test
+  public void createOrFindPermittableGroupWhenIsisCallFails() throws Exception {
+    final Logger loggerMock = Mockito.mock(Logger.class);
+
+    final IdentityService identityServiceMock = Mockito.mock(IdentityService.class);
+    doThrow(IllegalStateException.class).when(identityServiceMock).createPermittableGroup(group1);
+    doReturn(changedGroup1).when(identityServiceMock).getPermittableGroup(group1.getIdentifier());
+
+    new IdentityServiceInitializer(null, null, loggerMock).createOrFindPermittableGroup(identityServiceMock, group1);
+
+    verify(loggerMock).error(anyString(), anyString(), isA(IllegalStateException.class));
+  }
+
+
+}
\ No newline at end of file
diff --git a/service/src/test/java/io/mifos/provisioner/internal/util/ContactPointUtilsTest.java b/service/src/test/java/io/mifos/provisioner/internal/util/ContactPointUtilsTest.java
new file mode 100644
index 0000000..3cd5bd7
--- /dev/null
+++ b/service/src/test/java/io/mifos/provisioner/internal/util/ContactPointUtilsTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.provisioner.internal.util;
+
+import com.datastax.driver.core.Cluster;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.net.InetSocketAddress;
+import java.util.List;
+
+public class ContactPointUtilsTest {
+
+  public ContactPointUtilsTest() {
+    super();
+  }
+
+  @Test
+  public void shouldAddSimpleContactPoints() {
+    final String contactPoints = "127.0.0.1,127.0.0.2,127.0.0.3";
+
+    final Cluster.Builder clusterBuilder = Cluster.builder();
+    ContactPointUtils.process(clusterBuilder, contactPoints);
+    final List<InetSocketAddress> addedClusterPoints = clusterBuilder.getContactPoints();
+    Assert.assertTrue(addedClusterPoints.size() == 3);
+    for (final InetSocketAddress address : addedClusterPoints) {
+      Assert.assertTrue(contactPoints.contains(address.getAddress().getHostAddress()));
+    }
+  }
+
+  @Test
+  public void shouldAddComplexContactPoints() {
+    final String contactPoints = "127.0.0.1:1234,127.0.0.2:2345,127.0.0.3:3456";
+
+    final Cluster.Builder clusterBuilder = Cluster.builder();
+    ContactPointUtils.process(clusterBuilder, contactPoints);
+    final List<InetSocketAddress> addedClusterPoints = clusterBuilder.getContactPoints();
+
+    Assert.assertTrue(addedClusterPoints.size() == 3);
+
+    final InetSocketAddress firstAddress = addedClusterPoints.get(0);
+    Assert.assertEquals("127.0.0.1", firstAddress.getAddress().getHostAddress());
+    Assert.assertEquals(1234, firstAddress.getPort());
+
+    final InetSocketAddress secondAddress = addedClusterPoints.get(1);
+    Assert.assertEquals("127.0.0.2", secondAddress.getAddress().getHostAddress());
+    Assert.assertEquals(2345, secondAddress.getPort());
+
+    final InetSocketAddress thirdAddress = addedClusterPoints.get(2);
+    Assert.assertEquals("127.0.0.3", thirdAddress.getAddress().getHostAddress());
+    Assert.assertEquals(3456, thirdAddress.getPort());
+  }
+}
diff --git a/service/src/test/java/io/mifos/provisioner/internal/util/DataStoreOptionTest.java b/service/src/test/java/io/mifos/provisioner/internal/util/DataStoreOptionTest.java
new file mode 100644
index 0000000..49b510a
--- /dev/null
+++ b/service/src/test/java/io/mifos/provisioner/internal/util/DataStoreOptionTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.provisioner.internal.util;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class DataStoreOptionTest {
+
+  public DataStoreOptionTest() {
+    super();
+  }
+
+  @Test
+  public void givenAllShouldBeEnabled() {
+    final DataStoreOption all = DataStoreOption.ALL;
+    Assert.assertTrue(all.isEnabled(DataStoreOption.CASSANDRA));
+    Assert.assertTrue(all.isEnabled(DataStoreOption.RDBMS));
+    Assert.assertTrue(all.isEnabled(DataStoreOption.CASSANDRA));
+  }
+
+  @Test
+  public void shouldOnlyCassandraEnabled() {
+    final DataStoreOption cassandra = DataStoreOption.CASSANDRA;
+    Assert.assertTrue(cassandra.isEnabled(DataStoreOption.CASSANDRA));
+    Assert.assertFalse(cassandra.isEnabled(DataStoreOption.RDBMS));
+    Assert.assertFalse(cassandra.isEnabled(DataStoreOption.ALL));
+  }
+
+  @Test
+  public void shouldOnlyRdbmsEnabled() {
+    final DataStoreOption rdbms = DataStoreOption.RDBMS;
+    Assert.assertFalse(rdbms.isEnabled(DataStoreOption.CASSANDRA));
+    Assert.assertTrue(rdbms.isEnabled(DataStoreOption.RDBMS));
+    Assert.assertFalse(rdbms.isEnabled(DataStoreOption.ALL));
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void shouldFailUnknownType() {
+    DataStoreOption.valueOf("unknown");
+    Assert.fail();
+  }
+}
diff --git a/service/src/test/java/io/mifos/provisioner/internal/util/JdbcUrlBuilderTest.java b/service/src/test/java/io/mifos/provisioner/internal/util/JdbcUrlBuilderTest.java
new file mode 100644
index 0000000..8bb26b4
--- /dev/null
+++ b/service/src/test/java/io/mifos/provisioner/internal/util/JdbcUrlBuilderTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.provisioner.internal.util;
+
+import io.mifos.provisioner.internal.util.JdbcUrlBuilder;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class JdbcUrlBuilderTest {
+
+  private final static String MARAIDB_JDBC_URL = "jdbc:mariadb://localhost:3306/comp_test";
+
+  public JdbcUrlBuilderTest() {
+    super();
+  }
+
+  @Test
+  public void shouldCreateMysqlUrl() {
+    final String mariaDbJdbcUrl = JdbcUrlBuilder
+        .create(JdbcUrlBuilder.DatabaseType.MARIADB)
+        .host("localhost")
+        .port("3306")
+        .instanceName("comp_test")
+        .build();
+
+    Assert.assertEquals(MARAIDB_JDBC_URL, mariaDbJdbcUrl);
+  }
+}
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..63fc056
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,6 @@
+rootProject.name = 'provisioner'
+
+includeBuild 'api'
+includeBuild 'service'
+includeBuild 'component-test'
+
diff --git a/shared.gradle b/shared.gradle
new file mode 100644
index 0000000..4535a5d
--- /dev/null
+++ b/shared.gradle
@@ -0,0 +1,70 @@
+group 'io.mifos.provisioner'
+version '0.1.0-BUILD-SNAPSHOT'
+
+ext.versions = [
+        mifosidentityservice : '0.1.0-BUILD-SNAPSHOT',
+        frameworkanubis      : '0.1.0-BUILD-SNAPSHOT',
+        frameworkapi         : '0.1.0-BUILD-SNAPSHOT',
+        frameworklang        : '0.1.0-BUILD-SNAPSHOT',
+        frameworkasync       : '0.1.0-BUILD-SNAPSHOT',
+        frameworkcassandra   : '0.1.0-BUILD-SNAPSHOT',
+        frameworkmariadb     : '0.1.0-BUILD-SNAPSHOT',
+        frameworkcrypto      : '0.1.0-BUILD-SNAPSHOT',
+        validator            : '5.3.0.Final'
+]
+
+
+apply plugin: 'java'
+apply plugin: 'idea'
+apply plugin: 'maven-publish'
+apply plugin: 'io.spring.dependency-management'
+
+tasks.withType(JavaCompile) {
+    sourceCompatibility = JavaVersion.VERSION_1_8
+    targetCompatibility = JavaVersion.VERSION_1_8
+}
+
+repositories {
+    jcenter()
+    mavenLocal()
+}
+
+dependencyManagement {
+    imports {
+        mavenBom 'io.spring.platform:platform-bom:Athens-RELEASE'
+        mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Camden.SR1'
+    }
+}
+
+// override certain dependency provided by Spring platform using newer releases
+ext['cassandra.version'] = '3.4'
+ext['cassandra-driver.version'] = '3.0.1'
+ext['activemq.version'] = '5.13.2'
+ext['spring-data-releasetrain.version'] = 'Gosling-SR2A'
+
+dependencies {
+    compile(
+            [group: 'com.google.code.findbugs', name: 'jsr305']
+    )
+
+    testCompile(
+            [group: 'org.springframework.boot', name: 'spring-boot-starter-test']
+    )
+}
+
+jar {
+    from sourceSets.main.allSource
+}
+
+license {
+    header rootProject.file('../HEADER')
+    strictCheck true
+    mapping {
+        java = 'SLASHSTAR_STYLE'
+        xml = 'XML_STYLE'
+        yml = 'SCRIPT_STYLE'
+        yaml = 'SCRIPT_STYLE'
+    }
+    ext.year = Calendar.getInstance().get(Calendar.YEAR)
+    ext.name = 'The Mifos Initiative'
+}
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

Mime
View raw message