FINERACT-1984: OAuth2.1

This commit is contained in:
CsengeSóti
2025-09-12 14:58:37 +02:00
committed by Arnold Gálovics
parent 0c0d8592bb
commit fa9c7b9364
36 changed files with 802 additions and 599 deletions
+4 -1
View File
@@ -35,6 +35,9 @@ configurations {
}
dependencies {
driver 'com.mysql:mysql-connector-j'
testImplementation 'org.seleniumhq.selenium:selenium-java:4.21.0'
testImplementation 'io.github.bonigarcia:webdrivermanager:5.5.1'
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
}
cargo {
@@ -57,7 +60,7 @@ cargo {
startStopTimeout = 240000
sharedClasspath = configurations.driver
containerProperties {
def jvmArgs = '--add-exports=java.naming/com.sun.jndi.ldap=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.security=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.management/javax.management=ALL-UNNAMED --add-opens=java.naming/javax.naming=ALL-UNNAMED -Dfineract.security.basicauth.enabled=false -Dfineract.security.oauth.enabled=true -Dfineract.security.2fa.enabled=false '
def jvmArgs = '--add-exports=java.naming/com.sun.jndi.ldap=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.security=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.management/javax.management=ALL-UNNAMED --add-opens=java.naming/javax.naming=ALL-UNNAMED -Dfineract.security.basicauth.enabled=false -Dfineract.security.oauth2.enabled=true -Dfineract.security.2fa.enabled=false '
if (project.hasProperty('dbType')) {
if ('postgresql'.equalsIgnoreCase(dbType)) {
jvmArgs += '-Dspring.datasource.hikari.driverClassName=org.postgresql.Driver -Dspring.datasource.hikari.jdbcUrl=jdbc:postgresql://localhost:5432/fineract_tenants -Dspring.datasource.hikari.username=root -Dspring.datasource.hikari.password=postgres -Dfineract.tenant.host=localhost -Dfineract.tenant.port=5432 -Dfineract.tenant.username=root -Dfineract.tenant.password=postgres'
@@ -20,9 +20,11 @@ package org.apache.fineract.oauth2tests;
import static io.restassured.RestAssured.given;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
import com.google.common.base.Splitter;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import io.restassured.RestAssured;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.builder.ResponseSpecBuilder;
@@ -31,10 +33,21 @@ import io.restassured.path.json.JsonPath;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import jakarta.mail.MessagingException;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.springframework.lang.NonNull;
public class OAuth2AuthenticationTest {
@@ -75,49 +88,22 @@ public class OAuth2AuthenticationTest {
@Test
public void testAccessWithoutAuthentication() {
performServerGet(requestSpec, responseSpec401, "/fineract-provider/api/v1/offices/1?" + TENANT_IDENTIFIER, "");
performServerGet(requestSpec, responseSpec401, "/fineract-provider/api/v1/offices/1?" + TENANT_IDENTIFIER, null);
}
@Test
public void testOAuth2Login() throws IOException, MessagingException {
public void testGetOAuth2UserDetails() throws IOException, InterruptedException {
performServerGet(requestSpec, responseSpec401, "/fineract-provider/api/v1/offices/1?" + TENANT_IDENTIFIER, null);
performServerGet(requestSpec, responseSpec401, "/fineract-provider/api/v1/offices/1?" + TENANT_IDENTIFIER, "");
String accessToken = performServerPost(requestFormSpec, responseSpec, "http://localhost:9000/auth/realms/fineract/token",
"grant_type=client_credentials&client_id=community-app&client_secret=123123", "access_token");
assertNotNull(accessToken);
String bearerToken = performServerPost(requestFormSpec, responseSpec, "http://localhost:9000/auth/realms/fineract/token",
"grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=" + accessToken
+ "&client_id=community-app&scope=fineract",
"access_token");
assertNotNull(bearerToken);
String token = loginAndClaimToken(
"https://localhost:8443/fineract-provider/oauth2/authorize" + "?response_type=code&client_id=frontend-client"
+ "&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback&scope=read&state=xyz"
+ "&code_challenge=zudG_xkz8WrPPMq2MwmFP-NRvNapCL0OD-xYWRapTsU" + "&code_challenge_method=S256",
requestFormSpec);
RequestSpecification requestSpecWithToken = new RequestSpecBuilder() //
.setContentType(ContentType.JSON) //
.addHeader("Authorization", "Bearer " + bearerToken) //
.build();
performServerGet(requestSpecWithToken, responseSpec, "/fineract-provider/api/v1/offices/1?" + TENANT_IDENTIFIER, "");
}
@Test
public void testGetOAuth2UserDetails() {
performServerGet(requestSpec, responseSpec401, "/fineract-provider/api/v1/offices/1?" + TENANT_IDENTIFIER, "");
String accessToken = performServerPost(requestFormSpec, responseSpec, "http://localhost:9000/auth/realms/fineract/token",
"grant_type=client_credentials&client_id=community-app&client_secret=123123", "access_token");
assertNotNull(accessToken);
String bearerToken = performServerPost(requestFormSpec, responseSpec, "http://localhost:9000/auth/realms/fineract/token",
"grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=" + accessToken
+ "&client_id=community-app&scope=fineract",
"access_token");
assertNotNull(bearerToken);
RequestSpecification requestSpecWithToken = new RequestSpecBuilder() //
.setContentType(ContentType.JSON) //
.addHeader("Authorization", "Bearer " + bearerToken) //
.addHeader("Authorization", "Bearer " + token) //
.build();
Boolean authenticationCheck = performServerGet(requestSpecWithToken, responseSpec,
@@ -149,11 +135,73 @@ public class OAuth2AuthenticationTest {
} catch (Exception e) {
Thread.sleep(3000);
}
attempt++;
} while (attempt < max_attempts);
fail(HEALTH_URL + " returned " + response.prettyPrint());
}
public String loginAndClaimToken(String url, RequestSpecification requestSpec) throws IOException {
CompletableFuture<String> futureToken = new CompletableFuture<>();
HttpServer server = HttpServer.create(new InetSocketAddress(3000), 0);
server.createContext("/callback", exchange -> {
try {
String token = claimTokenOnCallback(requestSpec, exchange);
futureToken.complete(token); // complete future with value
exchange.sendResponseHeaders(200, 0);
} catch (Exception e) {
futureToken.completeExceptionally(e); // propagate exception
} finally {
exchange.close();
}
});
server.start();
WebDriver driver = getWebDriver();
try {
driver.get(url);
driver.findElement(By.name("username")).sendKeys("mifos");
driver.findElement(By.name("password")).sendKeys("password");
driver.findElement(By.name("tenantId")).sendKeys("default");
driver.findElement(By.cssSelector("button[type='submit'], input[type='submit']")).click();
return futureToken.get(5, TimeUnit.SECONDS);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
driver.quit();
server.stop(0);
}
}
@NonNull
private static WebDriver getWebDriver() {
ChromeOptions options = new ChromeOptions();
options.addArguments("--headless"); // run in headless mode
options.addArguments("--no-sandbox");
options.addArguments("--disable-dev-shm-usage");
options.addArguments("--ignore-certificate-errors");
return new ChromeDriver(options);
}
private String claimTokenOnCallback(RequestSpecification requestSpec, HttpExchange exchange) {
String query = exchange.getRequestURI().getQuery();
String code = null;
if (query != null) {
for (String param : Splitter.on("&").split(query)) {
List<String> keyValue = Splitter.on("=").splitToList(param);
if (keyValue.size() == 2) {
String key = keyValue.getFirst();
if ("code".equals(key)) {
code = URLDecoder.decode(keyValue.getLast(), StandardCharsets.UTF_8);
}
}
}
}
Map<String, String> formParams = Map.of("grant_type", "authorization_code", "code", code, "redirect_uri",
"http://localhost:3000/callback", "client_id", "frontend-client", "code_verifier",
"gyQBFpozcvcosvPt7m9Q1A4SqSf1yJtPIERruioHLjQ");
return performServerPost(requestSpec, responseSpec, "/fineract-provider/oauth2/token", formParams, "access_token");
}
@SuppressWarnings("unchecked")
private static <T> T performServerGet(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final String getURL, final String jsonAttributeToGetBack) {
@@ -164,11 +212,10 @@ public class OAuth2AuthenticationTest {
return (T) JsonPath.from(json).get(jsonAttributeToGetBack);
}
@SuppressWarnings("unchecked")
public static <T> T performServerPost(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final String putURL, final String formBody, final String jsonAttributeToGetBack) {
final String json = given().spec(requestSpec).body(formBody).expect().spec(responseSpec).log().ifError().when().post(putURL)
.andReturn().asString();
return (T) JsonPath.from(json).get(jsonAttributeToGetBack);
final String putURL, final Map<String, String> formBody, final String jsonAttributeToGetBack) {
final String response = given().spec(requestSpec).header("Content-Type", "application/x-www-form-urlencoded").formParams(formBody)
.expect().spec(responseSpec).log().ifError().when().post(putURL).andReturn().asString();
return (T) JsonPath.from(response).get(jsonAttributeToGetBack);
}
}