You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by vo...@apache.org on 2021/12/12 08:37:49 UTC

[fineract] branch develop updated: Integration tests for twofactor

This is an automated email from the ASF dual-hosted git repository.

vorburger pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


The following commit(s) were added to refs/heads/develop by this push:
     new 5dbde0f  Integration tests for twofactor
5dbde0f is described below

commit 5dbde0f2ede60f45a39d5321b2faf541bf75aacc
Author: Petri Tuomola <pe...@tuomola.org>
AuthorDate: Sat Dec 4 18:05:05 2021 +0800

    Integration tests for twofactor
---
 .../workflows/{build.yml => build-twofactor.yml}   |   4 +-
 .github/workflows/build.yml                        |   4 +-
 build.gradle                                       |   2 +
 ...lServicesPropertiesReadPlatformServiceImpl.java |   2 +-
 .../infrastructure/core/service/DateUtils.java     |   3 +-
 .../security/domain/TFAccessToken.java             |   2 +-
 .../V265__modify_external_service_schema.sql       |   4 +-
 settings.gradle                                    |   1 +
 twofactor-tests/build.gradle                       |  67 +++++
 .../dependencies.gradle                            |  24 +-
 .../TwoFactorAuthenticationTest.java               | 304 +++++++++++++++++++++
 11 files changed, 403 insertions(+), 14 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build-twofactor.yml
similarity index 91%
copy from .github/workflows/build.yml
copy to .github/workflows/build-twofactor.yml
index cfcc71c..c0e054d 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build-twofactor.yml
@@ -1,4 +1,4 @@
-name: Fineract Gradle build
+name: Fineract Gradle build - twofactor
 on: [push, pull_request]
 
 jobs:
@@ -39,4 +39,4 @@ jobs:
             sudo apt-get update
             sudo apt-get install ghostscript -y
       - name: Build & Test
-        run: ./gradlew --no-daemon -q --console=plain licenseMain licenseTest check build test --fail-fast doc
+        run: ./gradlew --no-daemon -q --console=plain licenseMain licenseTest check build test --fail-fast doc -x :integration-tests:test -Ptwofactor=enabled
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index cfcc71c..fe53337 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,4 +1,4 @@
-name: Fineract Gradle build
+name: Fineract Gradle build - basicauth
 on: [push, pull_request]
 
 jobs:
@@ -39,4 +39,4 @@ jobs:
             sudo apt-get update
             sudo apt-get install ghostscript -y
       - name: Build & Test
-        run: ./gradlew --no-daemon -q --console=plain licenseMain licenseTest check build test --fail-fast doc
+        run: ./gradlew --no-daemon -q --console=plain licenseMain licenseTest check build test --fail-fast doc -x :twofactor-tests:test
diff --git a/build.gradle b/build.gradle
index 37013f1..0de0c8b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -28,6 +28,7 @@ buildscript {
                 'fineract-api',
                 'fineract-provider',
                 'integration-tests',
+                'twofactor-tests',
                 'fineract-client'
             ].contains(it.name)
         }
@@ -144,6 +145,7 @@ allprojects  {
             dependency 'org.webjars.npm:swagger-ui-dist:4.0.1'
             dependency 'org.webjars:webjars-locator-core:0.48'
             dependency 'org.springframework.boot:spring-boot-starter-mail:2.6.0'
+            dependency 'com.icegreen:greenmail-junit5:1.6.5'
 
             // fineract client dependencies
             dependency "com.squareup.retrofit2:retrofit:$retrofitVersion"
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformServiceImpl.java
index 8bde327..c66cda9 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformServiceImpl.java
@@ -83,7 +83,7 @@ public class ExternalServicesPropertiesReadPlatformServiceImpl implements Extern
 
                 if (ExternalServicesConstants.SMTP_USERNAME.equalsIgnoreCase(name)) {
                     username = value;
-                } else if (ExternalServicesConstants.SMTP_PORT.equalsIgnoreCase(port)) {
+                } else if (ExternalServicesConstants.SMTP_PORT.equalsIgnoreCase(name)) {
                     port = value;
                 } else if (ExternalServicesConstants.SMTP_PASSWORD.equalsIgnoreCase(name)) {
                     password = value;
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java
index 53c6fad..bd08a6c 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java
@@ -25,6 +25,7 @@ import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
@@ -69,7 +70,7 @@ public final class DateUtils {
 
     public static LocalDateTime getLocalDateTimeOfTenant() {
         final ZoneId zone = getDateTimeZoneOfTenant();
-        LocalDateTime today = LocalDateTime.now(zone);
+        LocalDateTime today = LocalDateTime.now(zone).truncatedTo(ChronoUnit.SECONDS);
         return today;
     }
 
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/TFAccessToken.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/TFAccessToken.java
index c9165e7..5593475 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/TFAccessToken.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/domain/TFAccessToken.java
@@ -128,6 +128,6 @@ public class TFAccessToken extends AbstractPersistableCustom {
     }
 
     private boolean isDateInThePast(LocalDateTime dateTime) {
-        return dateTime.isBefore(DateUtils.getLocalDateTimeOfTenant());
+        return (dateTime.isBefore(DateUtils.getLocalDateTimeOfTenant()) || dateTime.isEqual(DateUtils.getLocalDateTimeOfTenant()));
     }
 }
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V265__modify_external_service_schema.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V265__modify_external_service_schema.sql
index b4e8d2c..b83799c 100644
--- a/fineract-provider/src/main/resources/sql/migrations/core_db/V265__modify_external_service_schema.sql
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V265__modify_external_service_schema.sql
@@ -53,9 +53,9 @@ insert into c_external_service_properties (`name`, `value`, `external_service_id
 
 insert into c_external_service_properties (`name`, `value`, `external_service_id`) values('password', 'support80', (select id from c_external_service where name = 'SMTP_Email_Account'));
 
-insert into c_external_service_properties (`name`, `value`, `external_service_id`) values('host', 'smtp.gmail.com', (select id from c_external_service where name = 'SMTP_Email_Account'));
+insert into c_external_service_properties (`name`, `value`, `external_service_id`) values('host', 'localhost', (select id from c_external_service where name = 'SMTP_Email_Account'));
 
-insert into c_external_service_properties (`name`, `value`, `external_service_id`) values('port', '587', (select id from c_external_service where name = 'SMTP_Email_Account'));
+insert into c_external_service_properties (`name`, `value`, `external_service_id`) values('port', '3025', (select id from c_external_service where name = 'SMTP_Email_Account'));
 
 insert into c_external_service_properties (`name`, `value`, `external_service_id`) values('useTLS', 'true', (select id from c_external_service where name = 'SMTP_Email_Account'));
 
diff --git a/settings.gradle b/settings.gradle
index fcb6dc0..596dc4e 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -19,5 +19,6 @@
 rootProject.name='fineract'
 include ':fineract-provider'
 include ':integration-tests'
+include ':twofactor-tests'
 include ':fineract-client'
 include ':fineract-doc'
diff --git a/twofactor-tests/build.gradle b/twofactor-tests/build.gradle
new file mode 100644
index 0000000..bddfd3d
--- /dev/null
+++ b/twofactor-tests/build.gradle
@@ -0,0 +1,67 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+description = 'Fineract Integration Tests'
+
+apply plugin: 'com.bmuschko.cargo'
+
+// Configuration for the Gradle Cargo plugin
+// https://github.com/bmuschko/gradle-cargo-plugin
+configurations {
+    tomcat
+}
+
+apply from: 'dependencies.gradle'
+
+cargo {
+    containerId "tomcat9x"
+
+    // looks like Cargo doesn't detect the WAR file automatically in the multi-module setup
+    deployable {
+        file = file("$rootDir/fineract-provider/build/libs/fineract-provider.war")
+        context = 'fineract-provider'
+    }
+
+    local {
+        installer {
+            installConfiguration = configurations.tomcat
+            downloadDir = file("$buildDir/download")
+            extractDir = file("$buildDir/tomcat")
+        }
+        startStopTimeout = 240000
+        containerProperties {
+            property 'cargo.start.jvmargs', '-Dspring.profiles.active=twofactor,basicauth'
+            property 'cargo.tomcat.connector.keystoreFile', file("$rootDir/fineract-provider/src/main/resources/keystore.jks")
+            property 'cargo.tomcat.connector.keystorePass', 'openmf'
+            property 'cargo.tomcat.httpSecure', true
+            property 'cargo.tomcat.connector.sslProtocol', 'TLS'
+            property 'cargo.tomcat.connector.clientAuth', false
+            property 'cargo.protocol', 'https'
+            property 'cargo.servlet.port', 8443
+        }
+    }
+}
+
+cargoRunLocal.dependsOn ':fineract-provider:war'
+cargoStartLocal.dependsOn ':fineract-provider:war'
+cargoStartLocal.mustRunAfter 'testClasses'
+
+test {
+    dependsOn cargoStartLocal
+    finalizedBy cargoStopLocal
+}
diff --git a/settings.gradle b/twofactor-tests/dependencies.gradle
similarity index 50%
copy from settings.gradle
copy to twofactor-tests/dependencies.gradle
index fcb6dc0..d99bb86 100644
--- a/settings.gradle
+++ b/twofactor-tests/dependencies.gradle
@@ -16,8 +16,22 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-rootProject.name='fineract'
-include ':fineract-provider'
-include ':integration-tests'
-include ':fineract-client'
-include ':fineract-doc'
+dependencies {
+    // testCompile dependencies are ONLY used in src/test, not src/main.
+    // Do NOT repeat dependencies which are ALREADY in implementation or runtimeOnly!
+    //
+    tomcat 'org.apache.tomcat:tomcat:9.0.54@zip'
+    testImplementation( files("$rootDir/fineract-provider/build/classes/java/main/"),
+            project(path: ':fineract-provider', configuration: 'runtimeElements'),
+            'org.junit.jupiter:junit-jupiter-api',
+            'com.icegreen:greenmail-junit5'
+            )
+    testImplementation ('io.rest-assured:rest-assured') {
+        exclude group: 'commons-logging'
+        exclude group: 'org.apache.sling'
+        exclude group: 'com.sun.xml.bind'
+    }
+    testRuntimeOnly(
+            'org.junit.jupiter:junit-jupiter-engine'
+            )
+}
diff --git a/twofactor-tests/src/test/java/org/apache/fineract/twofactortests/TwoFactorAuthenticationTest.java b/twofactor-tests/src/test/java/org/apache/fineract/twofactortests/TwoFactorAuthenticationTest.java
new file mode 100644
index 0000000..e3703b4
--- /dev/null
+++ b/twofactor-tests/src/test/java/org/apache/fineract/twofactortests/TwoFactorAuthenticationTest.java
@@ -0,0 +1,304 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.fineract.twofactortests;
+
+import static io.restassured.RestAssured.given;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import com.icegreen.greenmail.configuration.GreenMailConfiguration;
+import com.icegreen.greenmail.junit5.GreenMailExtension;
+import com.icegreen.greenmail.util.ServerSetupTest;
+import io.restassured.RestAssured;
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.builder.ResponseSpecBuilder;
+import io.restassured.http.ContentType;
+import io.restassured.path.json.JsonPath;
+import io.restassured.response.Response;
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.mail.MessagingException;
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class TwoFactorAuthenticationTest {
+
+    private ResponseSpecification responseSpec;
+    private ResponseSpecification responseSpec403;
+    private ResponseSpecification responseSpec401;
+    private RequestSpecification requestSpecWithoutBasic;
+    private RequestSpecification requestSpec;
+    private String basicAuthenticationKey;
+
+    public static final String TENANT_PARAM_NAME = "tenantIdentifier";
+    public static final String DEFAULT_TENANT = "default";
+    public static final String TENANT_IDENTIFIER = TENANT_PARAM_NAME + '=' + DEFAULT_TENANT;
+    private static final String LOGIN_URL = "/fineract-provider/api/v1/authentication?" + TENANT_IDENTIFIER;
+    private static final String HEALTH_URL = "/fineract-provider/actuator/health";
+
+    @RegisterExtension
+    static GreenMailExtension greenMail = new GreenMailExtension(ServerSetupTest.SMTP)
+            .withConfiguration(GreenMailConfiguration.aConfig().withUser("support@cloudmicrofinance.com", "support81"))
+            .withPerMethodLifecycle(true);
+
+    @BeforeEach
+    public void setup() throws InterruptedException {
+        initializeRestAssured();
+
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+
+        // Login with basic authentication
+        awaitSpringBootActuatorHealthyUp();
+        String json = RestAssured.given().contentType(ContentType.JSON).body("{\"username\":\"mifos\", \"password\":\"password\"}").expect()
+                .log().ifError().when().post(LOGIN_URL).asString();
+        assertFalse(StringUtils.isBlank(json));
+
+        this.basicAuthenticationKey = JsonPath.with(json).get("base64EncodedAuthenticationKey");
+        assertFalse(StringUtils.isBlank(this.basicAuthenticationKey));
+
+        this.requestSpec.header("Authorization", "Basic " + basicAuthenticationKey);
+
+        this.requestSpecWithoutBasic = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.responseSpec403 = new ResponseSpecBuilder().expectStatusCode(403).build();
+        this.responseSpec401 = new ResponseSpecBuilder().expectStatusCode(401).build();
+    }
+
+    @Test
+    public void testActuatorAccess() {
+        performServerGet(requestSpecWithoutBasic, responseSpec, "/fineract-provider/actuator/info", null);
+    }
+
+    @Test
+    public void testApiDocsAccess() {
+        performServerGet(requestSpecWithoutBasic, responseSpec, "/fineract-provider/api-docs/apiLive.htm", null);
+    }
+
+    @Test
+    public void testAccessWithoutTwofactor() {
+        performServerGet(requestSpec, responseSpec403, "/fineract-provider/api/v1/offices/1?" + TENANT_IDENTIFIER, "");
+    }
+
+    @Test
+    public void testCheckTwofactorEnabled() {
+        String json = RestAssured.given().contentType(ContentType.JSON).body("{\"username\":\"mifos\", \"password\":\"password\"}").expect()
+                .log().ifError().when().post("/fineract-provider/api/v1/authentication?" + TENANT_IDENTIFIER).asString();
+        assertFalse(StringUtils.isBlank(json));
+        Boolean key = JsonPath.with(json).get("isTwoFactorAuthenticationRequired");
+        assertEquals(key, true);
+    }
+
+    @Test
+    public void testGetTwofactorMethods() {
+        String json = RestAssured.given().spec(requestSpec).expect().log().ifError().when()
+                .get("/fineract-provider/api/v1/twofactor?" + TENANT_IDENTIFIER).asString();
+        assertFalse(StringUtils.isBlank(json));
+        List<HashMap<String, Object>> twoFactorMethods = JsonPath.with(json).getList("$");
+        assertEquals("email", twoFactorMethods.get(0).get("name"));
+        assertEquals("demomfi@mifos.org", twoFactorMethods.get(0).get("target"));
+    }
+
+    @Test
+    public void testTwofactorLogin() throws IOException, MessagingException {
+        assertEquals(greenMail.getReceivedMessages().length, 0);
+        performServerPost(requestSpec, responseSpec,
+                "/fineract-provider/api/v1/twofactor?deliveryMethod=email&extendedToken=false&" + TENANT_IDENTIFIER, "", "");
+        assertEquals(greenMail.getReceivedMessages().length, 1);
+
+        Pattern p = Pattern.compile("token is (.+).");
+        Matcher m = p.matcher((CharSequence) greenMail.getReceivedMessages()[0].getContent());
+
+        String token = null;
+
+        while (m.find()) {
+            token = m.group(1);
+        }
+
+        assertNotNull(token);
+        String tfaToken = performServerPost(requestSpec, responseSpec,
+                "/fineract-provider/api/v1/twofactor/validate?token=" + token + "&" + TENANT_IDENTIFIER, "", "token");
+        assertNotNull(tfaToken);
+
+        RequestSpecification requestSpecWithTFA = new RequestSpecBuilder() //
+                .setContentType(ContentType.JSON) //
+                .addHeader("Fineract-Platform-TFA-Token", tfaToken) //
+                .addHeader("Authorization", "Basic " + basicAuthenticationKey) //
+                .build();
+
+        performServerGet(requestSpecWithTFA, responseSpec, "/fineract-provider/api/v1/offices/1?" + TENANT_IDENTIFIER, "");
+
+        performServerPost(requestSpecWithTFA, responseSpec, "/fineract-provider/api/v1/twofactor/invalidate?" + TENANT_IDENTIFIER,
+                "{ \"token\": \"" + tfaToken + "\" }", "");
+
+        performServerGet(requestSpecWithTFA, responseSpec401, "/fineract-provider/api/v1/offices/1?" + TENANT_IDENTIFIER, "");
+    }
+
+    @Test
+    public void testTfaConfigSettings() throws IOException, MessagingException {
+        assertEquals(greenMail.getReceivedMessages().length, 0);
+        performServerPost(requestSpec, responseSpec,
+                "/fineract-provider/api/v1/twofactor?deliveryMethod=email&extendedToken=false&" + TENANT_IDENTIFIER, "", "");
+        assertEquals(greenMail.getReceivedMessages().length, 1);
+
+        Pattern p = Pattern.compile("token is (.+).");
+        Matcher m = p.matcher((CharSequence) greenMail.getReceivedMessages()[0].getContent());
+
+        String token = null;
+
+        while (m.find()) {
+            token = m.group(1);
+        }
+
+        assertNotNull(token);
+        String tfaToken = performServerPost(requestSpec, responseSpec,
+                "/fineract-provider/api/v1/twofactor/validate?token=" + token + "&" + TENANT_IDENTIFIER, "", "token");
+        assertNotNull(tfaToken);
+
+        RequestSpecification requestSpecWithTFA = new RequestSpecBuilder() //
+                .setContentType(ContentType.JSON) //
+                .addHeader("Fineract-Platform-TFA-Token", tfaToken) //
+                .addHeader("Authorization", "Basic " + basicAuthenticationKey) //
+                .build();
+
+        // Get the configuration and check one of the values (OTP token length)
+        LinkedHashMap<String, Object> json = performServerGet(requestSpecWithTFA, responseSpec,
+                "/fineract-provider/api/v1/twofactor/configure?" + TENANT_IDENTIFIER, "");
+        assertEquals(json.get("otp-token-length"), token.length());
+
+        // Update OTP token length
+        performServerPut(requestSpecWithTFA, responseSpec, "/fineract-provider/api/v1/twofactor/configure?" + TENANT_IDENTIFIER,
+                "{\"otp-token-length\": 10 }", "");
+
+        // Invalidate token for re-login
+        performServerPost(requestSpecWithTFA, responseSpec, "/fineract-provider/api/v1/twofactor/invalidate?" + TENANT_IDENTIFIER,
+                "{ \"token\": \"" + tfaToken + "\" }", "");
+
+        // Login again
+        performServerPost(requestSpec, responseSpec,
+                "/fineract-provider/api/v1/twofactor?deliveryMethod=email&extendedToken=false&" + TENANT_IDENTIFIER, "", "");
+        assertEquals(greenMail.getReceivedMessages().length, 2);
+
+        Matcher m2 = p.matcher((CharSequence) greenMail.getReceivedMessages()[1].getContent());
+
+        String token2 = null;
+
+        while (m2.find()) {
+            token2 = m2.group(1);
+        }
+
+        assertNotNull(token2);
+
+        // Check that the configuration has worked and length is now 10
+        assertEquals(10, token2.length());
+
+        tfaToken = performServerPost(requestSpec, responseSpec,
+                "/fineract-provider/api/v1/twofactor/validate?token=" + token2 + "&" + TENANT_IDENTIFIER, "", "token");
+        assertNotNull(tfaToken);
+
+        requestSpecWithTFA = new RequestSpecBuilder() //
+                .setContentType(ContentType.JSON) //
+                .addHeader("Fineract-Platform-TFA-Token", tfaToken) //
+                .addHeader("Authorization", "Basic " + basicAuthenticationKey) //
+                .build();
+
+        // Get the configuration and check one of the values (OTP token length)
+        json = performServerGet(requestSpecWithTFA, responseSpec, "/fineract-provider/api/v1/twofactor/configure?" + TENANT_IDENTIFIER, "");
+        assertEquals(json.get("otp-token-length"), token2.length());
+
+        // Update OTP token length back to original value
+        performServerPut(requestSpecWithTFA, responseSpec, "/fineract-provider/api/v1/twofactor/configure?" + TENANT_IDENTIFIER,
+                "{\"otp-token-length\": " + token.length() + "}", "");
+
+        // Check that configuration has been reset
+        json = performServerGet(requestSpecWithTFA, responseSpec, "/fineract-provider/api/v1/twofactor/configure?" + TENANT_IDENTIFIER, "");
+        assertEquals(json.get("otp-token-length"), token.length());
+
+        // Invalidate token for re-login
+        performServerPost(requestSpecWithTFA, responseSpec, "/fineract-provider/api/v1/twofactor/invalidate?" + TENANT_IDENTIFIER,
+                "{ \"token\": \"" + tfaToken + "\" }", "");
+    }
+
+    private static void initializeRestAssured() {
+        RestAssured.baseURI = "https://localhost";
+        RestAssured.port = 8443;
+        RestAssured.keyStore("src/main/resources/keystore.jks", "openmf");
+        RestAssured.useRelaxedHTTPSValidation();
+    }
+
+    private static void awaitSpringBootActuatorHealthyUp() throws InterruptedException {
+        int attempt = 0;
+        final int max_attempts = 10;
+        Response response = null;
+
+        do {
+            try {
+                response = RestAssured.get(HEALTH_URL);
+
+                if (response.statusCode() == 200) {
+                    return;
+                }
+
+                Thread.sleep(3000);
+            } catch (Exception e) {
+                Thread.sleep(3000);
+            }
+        } while (attempt < max_attempts);
+
+        fail(HEALTH_URL + " returned " + response.prettyPrint());
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <T> T performServerGet(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String getURL, final String jsonAttributeToGetBack) {
+        final String json = given().spec(requestSpec).expect().spec(responseSpec).log().ifError().when().get(getURL).andReturn().asString();
+        if (jsonAttributeToGetBack == null) {
+            return (T) json;
+        }
+        return (T) JsonPath.from(json).get(jsonAttributeToGetBack);
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <T> T performServerPost(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String postURL, final String jsonBodyToSend, final String jsonAttributeToGetBack) {
+        final String json = given().spec(requestSpec).body(jsonBodyToSend).expect().spec(responseSpec).log().ifError().when().post(postURL)
+                .andReturn().asString();
+        if (jsonAttributeToGetBack == null) {
+            return (T) json;
+        }
+        return (T) JsonPath.from(json).get(jsonAttributeToGetBack);
+    }
+
+    @SuppressWarnings("unchecked")
+    public static <T> T performServerPut(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String putURL, final String jsonBodyToSend, final String jsonAttributeToGetBack) {
+        final String json = given().spec(requestSpec).body(jsonBodyToSend).expect().spec(responseSpec).log().ifError().when().put(putURL)
+                .andReturn().asString();
+        return (T) JsonPath.from(json).get(jsonAttributeToGetBack);
+    }
+}