From 3e72eebaf5998ea6102fbb27c8f3d2fdf024d948 Mon Sep 17 00:00:00 2001 From: Nils Behlen <29949516+nilsbehlen@users.noreply.github.com> Date: Thu, 3 Apr 2025 14:29:22 +0200 Subject: [PATCH 1/6] remove forwardip setting, formatting * forwardip was added to add the clientip parameter. however, it does not make sense to have it per instance of privacyidea, because that one instance can serve multiple clients with different ip. Now there is the option for users of the privacyidea instance, to add any parameter to the request, so it is up to the caller to add that parameter * renaming authtoken to jwt * --- .../org/privacyidea/AsyncRequestCallable.java | 14 +- src/main/java/org/privacyidea/Endpoint.java | 17 ++- src/main/java/org/privacyidea/JSONParser.java | 19 +-- src/main/java/org/privacyidea/PIConfig.java | 1 - .../java/org/privacyidea/PIConstants.java | 4 +- .../java/org/privacyidea/PrivacyIDEA.java | 132 +++++++++--------- .../org/privacyidea/TestTriggerChallenge.java | 42 +++--- 7 files changed, 108 insertions(+), 121 deletions(-) diff --git a/src/main/java/org/privacyidea/AsyncRequestCallable.java b/src/main/java/org/privacyidea/AsyncRequestCallable.java index 7615584..26f00ef 100644 --- a/src/main/java/org/privacyidea/AsyncRequestCallable.java +++ b/src/main/java/org/privacyidea/AsyncRequestCallable.java @@ -38,35 +38,25 @@ public class AsyncRequestCallable implements Callable, Callback private final String method; private final Map headers; private final Map params; - private final boolean authTokenRequired; private final Endpoint endpoint; private final PrivacyIDEA privacyIDEA; final String[] callbackResult = {null}; private CountDownLatch latch; public AsyncRequestCallable(PrivacyIDEA privacyIDEA, Endpoint endpoint, String path, Map params, - Map headers, boolean authTokenRequired, String method) + Map headers, String method) { this.privacyIDEA = privacyIDEA; this.endpoint = endpoint; this.path = path; this.params = params; this.headers = headers; - this.authTokenRequired = authTokenRequired; this.method = method; } @Override public String call() throws Exception { - // If an auth token is required for the request, get that first then do the actual request - if (this.authTokenRequired) - { - // Wait for the auth token to be retrieved and add it to the header - headers.put(PIConstants.HEADER_AUTHORIZATION, privacyIDEA.getJWT()); - } - - // Do the actual request latch = new CountDownLatch(1); endpoint.sendRequestAsync(path, params, headers, method, this); if (!latch.await(30, TimeUnit.SECONDS)) @@ -90,7 +80,7 @@ public void onResponse(@NotNull Call call, @NotNull Response response) throws IO if (response.body() != null) { String s = response.body().string(); - if (!privacyIDEA.logExcludedEndpoints().contains(path) && !ENDPOINT_AUTH.equals(path)) + if (!privacyIDEA.logExcludedEndpoints().contains(path)) { privacyIDEA.log(path + ":\n" + privacyIDEA.parser.formatJson(s)); } diff --git a/src/main/java/org/privacyidea/Endpoint.java b/src/main/java/org/privacyidea/Endpoint.java index 24cbeae..efdcde1 100644 --- a/src/main/java/org/privacyidea/Endpoint.java +++ b/src/main/java/org/privacyidea/Endpoint.java @@ -118,11 +118,6 @@ void sendRequestAsync(String endpoint, Map params, Map { @@ -150,7 +145,17 @@ void sendRequestAsync(String endpoint, Map params, Map + { + if (v == null) + { + privacyIDEA.error("Unable to add header " + k + " because the value is null"); + } + else + { + requestBuilder.addHeader(k, v); + } + }); } if (POST.equals(method)) diff --git a/src/main/java/org/privacyidea/JSONParser.java b/src/main/java/org/privacyidea/JSONParser.java index 70ea860..84b4495 100644 --- a/src/main/java/org/privacyidea/JSONParser.java +++ b/src/main/java/org/privacyidea/JSONParser.java @@ -65,7 +65,7 @@ public String formatJson(String json) * @param serverResponse response of the server * @return the AuthToken obj or null if error */ - LinkedHashMap extractAuthToken(String serverResponse) + LinkedHashMap getJWT(String serverResponse) { if (serverResponse != null && !serverResponse.isEmpty()) { @@ -75,21 +75,22 @@ LinkedHashMap extractAuthToken(String serverResponse) try { JsonObject obj = root.getAsJsonObject(); - String authToken = obj.getAsJsonObject(RESULT).getAsJsonObject(VALUE).getAsJsonPrimitive(TOKEN).getAsString(); - var parts = authToken.split("\\."); + String jwt = obj.getAsJsonObject(RESULT).getAsJsonObject(VALUE).getAsJsonPrimitive(TOKEN).getAsString(); + var parts = jwt.split("\\."); String dec = new String(Base64.getDecoder().decode(parts[1])); // Extract the expiration date from the token - int respDate = obj.getAsJsonPrimitive(TIME).getAsInt(); - int expDate = JsonParser.parseString(dec).getAsJsonObject().getAsJsonPrimitive(EXP).getAsInt(); - int difference = expDate - respDate; - privacyIDEA.log("JWT Validity: " + difference / 60 + " minutes. Token expires at: " + new Date(expDate * 1000L)); + int responseTime = obj.getAsJsonPrimitive(TIME).getAsInt(); + int expirationTime = JsonParser.parseString(dec).getAsJsonObject().getAsJsonPrimitive(EXP).getAsInt(); + int difference = expirationTime - responseTime; + privacyIDEA.log("JWT Validity: " + difference / 60 + " minutes. Token expires at: " + new Date(expirationTime * 1000L)); - return new LinkedHashMap<>(Map.of(AUTH_TOKEN, authToken, AUTH_TOKEN_EXP, String.valueOf(expDate))); + return new LinkedHashMap<>(Map.of(JWT, jwt, JWT_EXPIRATION_TIME, String.valueOf(expirationTime))); } catch (Exception e) { - privacyIDEA.error("Auth token extraction failed: " + e); + privacyIDEA.error("JWT token extraction failed: " + e); + privacyIDEA.error("Server response: " + serverResponse); } } } diff --git a/src/main/java/org/privacyidea/PIConfig.java b/src/main/java/org/privacyidea/PIConfig.java index cf9326d..21c4303 100644 --- a/src/main/java/org/privacyidea/PIConfig.java +++ b/src/main/java/org/privacyidea/PIConfig.java @@ -26,7 +26,6 @@ class PIConfig public String serviceAccountPass = ""; public String serviceAccountRealm = ""; public boolean disableLog = false; - public String forwardClientIP = ""; public int httpTimeoutMs = 30000; protected String proxyHost = ""; protected int proxyPort = 0; diff --git a/src/main/java/org/privacyidea/PIConstants.java b/src/main/java/org/privacyidea/PIConstants.java index efcd378..5735fd3 100644 --- a/src/main/java/org/privacyidea/PIConstants.java +++ b/src/main/java/org/privacyidea/PIConstants.java @@ -51,8 +51,8 @@ public class PIConstants public static final String TIME = "time"; public static final String EXP = "exp"; public static final String CHALLENGE_STATUS = "challenge_status"; - public static final String AUTH_TOKEN = "authToken"; - public static final String AUTH_TOKEN_EXP = "authTokenExp"; + public static final String JWT = "jwt"; + public static final String JWT_EXPIRATION_TIME = "jwt_expiration_time"; public static final String TYPE = "type"; public static final String TRANSACTION_ID = "transaction_id"; public static final String REALM = "realm"; diff --git a/src/main/java/org/privacyidea/PrivacyIDEA.java b/src/main/java/org/privacyidea/PrivacyIDEA.java index b8dd308..daba791 100644 --- a/src/main/java/org/privacyidea/PrivacyIDEA.java +++ b/src/main/java/org/privacyidea/PrivacyIDEA.java @@ -41,8 +41,8 @@ public class PrivacyIDEA implements Closeable private CountDownLatch jwtRetrievalLatch; final JSONParser parser; // Responses from these endpoints will not be logged. The list can be overwritten. - private List logExcludedEndpoints = Arrays.asList(PIConstants.ENDPOINT_AUTH, - PIConstants.ENDPOINT_POLLTRANSACTION); //Collections.emptyList(); + private List logExcludedEndpoints = Arrays.asList( + PIConstants.ENDPOINT_POLLTRANSACTION); //Collections.emptyList();PIConstants.ENDPOINT_AUTH, private PrivacyIDEA(PIConfig configuration, IPILogger logger, IPISimpleLogger simpleLog) { @@ -52,10 +52,15 @@ private PrivacyIDEA(PIConfig configuration, IPILogger logger, IPISimpleLogger si this.endpoint = new Endpoint(this); this.parser = new JSONParser(this); this.threadPool.allowCoreThreadTimeOut(true); + error("privacyidea constructor"); if (serviceAccountAvailable()) { retrieveJWT(); } + else + { + error("No service account configured. No JWT will be retrieved."); + } } /** @@ -95,10 +100,11 @@ public PIResponse validateCheck(String username, String pass, String transaction * Which parameters to send depends on the use case and how privacyIDEA is configured. * (E.g. this can also be used to trigger challenges without a service account) * - * @param username username - * @param pass pass/otp value - * @param transactionID optional, will be appended if set - * @param headers optional headers for the request + * @param username username + * @param pass pass/otp value + * @param transactionID optional, will be appended if set + * @param additionalParams additional parameters for the request + * @param headers optional headers for the request * @return PIResponse object containing the response or null if error */ public PIResponse validateCheck(String username, String pass, String transactionID, Map additionalParams, @@ -108,27 +114,27 @@ public PIResponse validateCheck(String username, String pass, String transaction } /** - * @see PrivacyIDEA#validateCheckSerial(String, String, String, Map) + * @see PrivacyIDEA#validateCheckSerial(String, String, String, Map, Map) */ public PIResponse validateCheckSerial(String serial, String pass) { - return this.validateCheckSerial(serial, pass, null, Collections.emptyMap()); + return this.validateCheckSerial(serial, pass, null, Collections.emptyMap(), Collections.emptyMap()); } /** - * @see PrivacyIDEA#validateCheckSerial(String, String, String, Map) + * @see PrivacyIDEA#validateCheckSerial(String, String, String, Map, Map) */ public PIResponse validateCheckSerial(String serial, String pass, Map headers) { - return this.validateCheckSerial(serial, pass, null, headers); + return this.validateCheckSerial(serial, pass, null, Collections.emptyMap(), headers); } /** - * @see PrivacyIDEA#validateCheckSerial(String, String, String, Map) + * @see PrivacyIDEA#validateCheckSerial(String, String, String, Map, Map) */ public PIResponse validateCheckSerial(String serial, String pass, String transactionID) { - return this.validateCheckSerial(serial, pass, transactionID, Collections.emptyMap()); + return this.validateCheckSerial(serial, pass, transactionID, Collections.emptyMap(), Collections.emptyMap()); } /** @@ -139,49 +145,44 @@ public PIResponse validateCheckSerial(String serial, String pass, String transac * @param transactionID transaction ID * @return PIResponse or null if error */ - public PIResponse validateCheckSerial(String serial, String pass, String transactionID, Map headers) + public PIResponse validateCheckSerial(String serial, String pass, String transactionID, Map additionalParams, + Map headers) { - return getPIResponse(SERIAL, serial, pass, headers, transactionID, Collections.emptyMap()); + return getPIResponse(SERIAL, serial, pass, headers, transactionID, additionalParams); } /** * Used by validateCheck and validateCheckSerial to get the PI Response. * - * @param type distinguish between user and serial to set forwarded input to the right PI-request param - * @param input forwarded username for classic validateCheck or serial to trigger exact token - * @param pass OTP, PIN+OTP or password to use - * @param headers optional headers for the request - * @param transactionID optional, will be appended if set + * @param type distinguish between user and serial to set forwarded input to the right PI-request param + * @param input forwarded username for classic validateCheck or serial to trigger exact token + * @param pass OTP, PIN+OTP or password to use + * @param headers optional headers for the request + * @param transactionID optional, will be appended if set + * @param additionalParams additional parameters for the request * @return PIResponse object containing the response or null if error */ private PIResponse getPIResponse(String type, String input, String pass, Map headers, String transactionID, Map additionalParams) { - Map params = new LinkedHashMap<>(); + Map params = new LinkedHashMap<>(additionalParams); params.put(type, input); params.put(PASS, (pass != null ? pass : "")); - params.putAll(additionalParams); appendRealm(params); if (transactionID != null && !transactionID.isEmpty()) { params.put(TRANSACTION_ID, transactionID); } String response = runRequestAsync(ENDPOINT_VALIDATE_CHECK, params, headers, false, POST); - // Shutdown the scheduler if user successfully authenticated - PIResponse piResponse = this.parser.parsePIResponse(response); - if (piResponse != null && piResponse.value) - { - this.scheduler.shutdownNow(); - } - return piResponse; + return this.parser.parsePIResponse(response); } /** - * @see PrivacyIDEA#validateCheckWebAuthn(String, String, String, String, Map) + * @see PrivacyIDEA#validateCheckWebAuthn(String, String, String, String, Map, Map) */ public PIResponse validateCheckWebAuthn(String user, String transactionID, String signResponse, String origin) { - return this.validateCheckWebAuthn(user, transactionID, signResponse, origin, Collections.emptyMap()); + return this.validateCheckWebAuthn(user, transactionID, signResponse, origin, Collections.emptyMap(), Collections.emptyMap()); } /** @@ -191,13 +192,14 @@ public PIResponse validateCheckWebAuthn(String user, String transactionID, Strin * @param transactionID transaction ID * @param webAuthnSignResponse the WebAuthnSignResponse as returned from the browser * @param origin server name that was used for + * @param additionalParams additional parameters for the request * @param headers optional headers for the request * @return PIResponse or null if error */ public PIResponse validateCheckWebAuthn(String user, String transactionID, String webAuthnSignResponse, String origin, - Map headers) + Map additionalParams, Map headers) { - Map params = new LinkedHashMap<>(); + Map params = new LinkedHashMap<>(additionalParams); // Standard validateCheck data params.put(USER, user); params.put(TRANSACTION_ID, transactionID); @@ -287,21 +289,22 @@ public PIResponse validateCheckCompletePasskeyRegistration(String transactionID, } /** - * @see PrivacyIDEA#triggerChallenges(String, Map) + * @see PrivacyIDEA#triggerChallenges(String, Map, Map) */ public PIResponse triggerChallenges(String username) { - return this.triggerChallenges(username, new LinkedHashMap<>()); + return this.triggerChallenges(username, Collections.emptyMap(), Collections.emptyMap()); } /** * Trigger all challenges for the given username. This requires a service account to be set. * - * @param username username to trigger challenges for - * @param headers optional headers for the request + * @param username username to trigger challenges for + * @param additionalParams additional parameters for the request + * @param headers optional headers for the request * @return the server response or null if error */ - public PIResponse triggerChallenges(String username, Map headers) + public PIResponse triggerChallenges(String username, Map additionalParams, Map headers) { Objects.requireNonNull(username, "Username is required!"); @@ -310,7 +313,7 @@ public PIResponse triggerChallenges(String username, Map headers log("No service account configured. Cannot trigger challenges"); return null; } - Map params = new LinkedHashMap<>(); + Map params = new LinkedHashMap<>(additionalParams); params.put(USER, username); appendRealm(params); @@ -448,30 +451,31 @@ private void appendRealm(Map params) */ private void retrieveJWT() { + log("Getting new JWT with service account..."); this.jwtRetrievalLatch = new CountDownLatch(1); try { String response = runRequestAsync(ENDPOINT_AUTH, serviceAccountParam(), Collections.emptyMap(), false, POST); if (response == null) { - error("Failed to retrieve auth token, response was empty. Retrying in 10 seconds."); + error("Failed to retrieve JWT: Response was empty. Retrying in 10 seconds."); this.scheduler.schedule(this::retrieveJWT, 10, TimeUnit.SECONDS); } else { - LinkedHashMap authTokenMap = parser.extractAuthToken(response); - this.jwt = authTokenMap.get(AUTH_TOKEN); - long authTokenExp = Integer.parseInt(authTokenMap.get(AUTH_TOKEN_EXP)); + LinkedHashMap jwtMap = parser.getJWT(response); + this.jwt = jwtMap.get(JWT); + long jwtExpiration = Integer.parseInt(jwtMap.get(JWT_EXPIRATION_TIME)); // Schedule the next token retrieval to 1 min before expiration - long delay = Math.max(1, authTokenExp - 60 - (System.currentTimeMillis() / 1000L)); + long delay = Math.max(1, jwtExpiration - 60 - (System.currentTimeMillis() / 1000L)); this.scheduler.schedule(this::retrieveJWT, delay, TimeUnit.SECONDS); log("Next JWT retrieval in " + delay + " seconds."); } } catch (Exception e) { - error("Failed to retrieve auth token: " + e.getMessage()); + error("Failed to retrieve JWT: " + e.getMessage()); } this.jwtRetrievalLatch.countDown(); } @@ -483,12 +487,17 @@ private void retrieveJWT() */ public String getJWT() { + if (jwtRetrievalLatch.getCount() == 0 && this.jwt == null) + { + retrieveJWT(); + } try { jwtRetrievalLatch.await(); } catch (InterruptedException e) { + error("Error while waiting for JWT retrieval: " + e.getMessage()); error(e); return null; } @@ -508,21 +517,22 @@ public boolean serviceAccountAvailable() * Run a request in a thread of the thread pool. Then join that thread to the one that was calling this method. * If the server takes longer to answer a request, the other requests do not have to wait. * - * @param path path to the endpoint of the privacyIDEA server - * @param params request parameters - * @param headers request headers - * @param authTokenRequired whether an auth token should be acquired prior to the request - * @param method http request method + * @param path path to the endpoint of the privacyIDEA server + * @param params request parameters + * @param headers request headers + * @param authorizationRequired whether an JWT for Authorization should be acquired prior to the request. Requires a service account. + * @param method http request method * @return response of the server as string or null */ - private String runRequestAsync(String path, Map params, Map headers, boolean authTokenRequired, + private String runRequestAsync(String path, Map params, Map headers, boolean authorizationRequired, String method) { - if (!configuration.forwardClientIP.isEmpty()) + if (authorizationRequired) { - params.put(CLIENT_IP, configuration.forwardClientIP); + // Wait for the JWT to be retrieved and add it to the header + headers.put(PIConstants.HEADER_AUTHORIZATION, getJWT()); } - Callable callable = new AsyncRequestCallable(this, this.endpoint, path, params, headers, authTokenRequired, method); + Callable callable = new AsyncRequestCallable(this, this.endpoint, path, params, headers, method); Future future = this.threadPool.submit(callable); String response = null; try @@ -679,11 +689,10 @@ public static class Builder private String serviceAccountName = ""; private String serviceAccountPass = ""; private String serviceAccountRealm = ""; - private String forwardClientIP = ""; private IPILogger logger = null; private boolean disableLog = false; private IPISimpleLogger simpleLogBridge = null; - private int httpTimeoutMs = 30000; + private int httpTimeoutMs = 10000; private String proxyHost = ""; private int proxyPort = 0; @@ -774,18 +783,6 @@ public Builder serviceRealm(String serviceAccountRealm) return this; } - /** - * Set the client IP to be forwarded to the privacyIDEA server. - * - * @param clientIP client IP or an empty String - * @return Builder - */ - public Builder forwardClientIP(String clientIP) - { - this.forwardClientIP = clientIP; - return this; - } - /** * Disable logging completely regardless of any set loggers. * @@ -837,7 +834,6 @@ public PrivacyIDEA build() configuration.serviceAccountName = serviceAccountName; configuration.serviceAccountPass = serviceAccountPass; configuration.serviceAccountRealm = serviceAccountRealm; - configuration.forwardClientIP = forwardClientIP; configuration.disableLog = disableLog; configuration.httpTimeoutMs = httpTimeoutMs; configuration.setProxy(proxyHost, proxyPort); diff --git a/src/test/java/org/privacyidea/TestTriggerChallenge.java b/src/test/java/org/privacyidea/TestTriggerChallenge.java index af217d7..a473178 100644 --- a/src/test/java/org/privacyidea/TestTriggerChallenge.java +++ b/src/test/java/org/privacyidea/TestTriggerChallenge.java @@ -16,6 +16,7 @@ */ package org.privacyidea; +import java.util.Collections; import java.util.List; import org.junit.After; import org.junit.Before; @@ -47,13 +48,11 @@ public void setup() public void testTriggerChallengeSuccess() { mockServer.when(HttpRequest.request().withPath(PIConstants.ENDPOINT_AUTH).withMethod("POST").withBody("")) - .respond(HttpResponse.response() - .withBody(Utils.postAuthSuccessResponse())); + .respond(HttpResponse.response().withBody(Utils.postAuthSuccessResponse())); privacyIDEA = PrivacyIDEA.newBuilder("https://127.0.0.1:1080", "test") .verifySSL(false) .serviceAccount(serviceAccount, servicePass) - .forwardClientIP(forwardClientIP) .logger(new PILogImplementation()) .realm("realm") .build(); @@ -61,26 +60,27 @@ public void testTriggerChallengeSuccess() mockServer.when(HttpRequest.request() .withPath(PIConstants.ENDPOINT_TRIGGERCHALLENGE) .withMethod("POST") - .withBody("user=testuser&realm=realm&client=127.0.0.1")) + .withBody("user=testuser&realm=realm&clientip=127.0.0.1")) .respond(HttpResponse.response().withBody(Utils.triggerChallengeSuccess())); String username = "testuser"; - PIResponse responseTriggerChallenge = privacyIDEA.triggerChallenges(username); - - assertEquals("otp", responseTriggerChallenge.preferredClientMode); - assertEquals(1, responseTriggerChallenge.id); - assertEquals("BittegebenSieeinenOTP-Wertein:", responseTriggerChallenge.message); - assertEquals("2.0", responseTriggerChallenge.jsonRPCVersion); - assertEquals("3.6.3", responseTriggerChallenge.piVersion); - assertEquals("rsa_sha256_pss:4b0f0e12c2...89409a2e65c87d27b", responseTriggerChallenge.signature); + PIResponse response = privacyIDEA.triggerChallenges(username, Collections.singletonMap("clientip", forwardClientIP), + Collections.emptyMap()); + + assertEquals("otp", response.preferredClientMode); + assertEquals(1, response.id); + assertEquals("BittegebenSieeinenOTP-Wertein:", response.message); + assertEquals("2.0", response.jsonRPCVersion); + assertEquals("3.6.3", response.piVersion); + assertEquals("rsa_sha256_pss:4b0f0e12c2...89409a2e65c87d27b", response.signature); // Trim all whitespaces, newlines - assertEquals(Utils.triggerChallengeSuccess().replaceAll("[\n\r]", ""), responseTriggerChallenge.rawMessage.replaceAll("[\n\r]", "")); - assertEquals(Utils.triggerChallengeSuccess().replaceAll("[\n\r]", ""), responseTriggerChallenge.toString().replaceAll("[\n\r]", "")); + assertEquals(Utils.triggerChallengeSuccess().replaceAll("[\n\r]", ""), response.rawMessage.replaceAll("[\n\r]", "")); + assertEquals(Utils.triggerChallengeSuccess().replaceAll("[\n\r]", ""), response.toString().replaceAll("[\n\r]", "")); // result - assertTrue(responseTriggerChallenge.status); - assertFalse(responseTriggerChallenge.value); + assertTrue(response.status); + assertFalse(response.value); - List challenges = responseTriggerChallenge.multiChallenge; + List challenges = response.multiChallenge; String imageTOTP = ""; for (Challenge c : challenges) { @@ -98,10 +98,7 @@ public void testTriggerChallengeSuccess() @Test public void testNoServiceAccount() { - privacyIDEA = PrivacyIDEA.newBuilder("https://127.0.0.1:1080", "test") - .verifySSL(false) - .logger(new PILogImplementation()) - .build(); + privacyIDEA = PrivacyIDEA.newBuilder("https://127.0.0.1:1080", "test").verifySSL(false).logger(new PILogImplementation()).build(); PIResponse responseTriggerChallenge = privacyIDEA.triggerChallenges("Test"); @@ -112,8 +109,7 @@ public void testNoServiceAccount() public void testNoUsername() { mockServer.when(HttpRequest.request().withPath(PIConstants.ENDPOINT_AUTH).withMethod("POST").withBody("")) - .respond(HttpResponse.response() - .withBody(Utils.postAuthSuccessResponse())); + .respond(HttpResponse.response().withBody(Utils.postAuthSuccessResponse())); privacyIDEA = PrivacyIDEA.newBuilder("https://127.0.0.1:1080", "test") .verifySSL(false) From 4155d40b9e5bf1902b1cc3de5e6320c382e61811 Mon Sep 17 00:00:00 2001 From: Nils Behlen <29949516+nilsbehlen@users.noreply.github.com> Date: Thu, 3 Apr 2025 14:35:17 +0200 Subject: [PATCH 2/6] set version 1.3.0 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 54179b0..228e389 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.privacyidea privacyidea-java-client - 1.2.2 + 1.3.0 jar UTF-8 @@ -18,7 +18,7 @@ maven-surefire-plugin 2.12.4 - false + true From 29e0de7728768dd01f8a62adf25c35589246ddac Mon Sep 17 00:00:00 2001 From: Nils Behlen <29949516+nilsbehlen@users.noreply.github.com> Date: Thu, 3 Apr 2025 14:35:51 +0200 Subject: [PATCH 3/6] dont skip tests --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 228e389..e80b89c 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ maven-surefire-plugin 2.12.4 - true + false @@ -121,4 +121,4 @@ 4.4.0 - \ No newline at end of file + From fee90e516749fa48c339ee0d2d1b95365e8948e3 Mon Sep 17 00:00:00 2001 From: Nils Behlen <29949516+nilsbehlen@users.noreply.github.com> Date: Thu, 3 Apr 2025 15:03:46 +0200 Subject: [PATCH 4/6] copy headers --- pom.xml | 8 ++++---- src/main/java/org/privacyidea/PrivacyIDEA.java | 5 ++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 228e389..af3b767 100644 --- a/pom.xml +++ b/pom.xml @@ -16,9 +16,9 @@ org.apache.maven.plugins maven-surefire-plugin - 2.12.4 + 3.5.3 - true + false @@ -50,8 +50,8 @@ maven-compiler-plugin 3.13.0 - 11 - 11 + 14 + 14 diff --git a/src/main/java/org/privacyidea/PrivacyIDEA.java b/src/main/java/org/privacyidea/PrivacyIDEA.java index daba791..607d6a3 100644 --- a/src/main/java/org/privacyidea/PrivacyIDEA.java +++ b/src/main/java/org/privacyidea/PrivacyIDEA.java @@ -52,7 +52,6 @@ private PrivacyIDEA(PIConfig configuration, IPILogger logger, IPISimpleLogger si this.endpoint = new Endpoint(this); this.parser = new JSONParser(this); this.threadPool.allowCoreThreadTimeOut(true); - error("privacyidea constructor"); if (serviceAccountAvailable()) { retrieveJWT(); @@ -307,17 +306,17 @@ public PIResponse triggerChallenges(String username) public PIResponse triggerChallenges(String username, Map additionalParams, Map headers) { Objects.requireNonNull(username, "Username is required!"); - if (!serviceAccountAvailable()) { log("No service account configured. Cannot trigger challenges"); return null; } + Map headersCopy = new LinkedHashMap<>(headers); Map params = new LinkedHashMap<>(additionalParams); params.put(USER, username); appendRealm(params); - String response = runRequestAsync(ENDPOINT_TRIGGERCHALLENGE, params, headers, true, POST); + String response = runRequestAsync(ENDPOINT_TRIGGERCHALLENGE, params, headersCopy, true, POST); return this.parser.parsePIResponse(response); } From 66b6d1152be8704ebfbb29a9d0aa207ec8d0659c Mon Sep 17 00:00:00 2001 From: Nils Behlen <29949516+nilsbehlen@users.noreply.github.com> Date: Thu, 3 Apr 2025 15:22:08 +0200 Subject: [PATCH 5/6] update test --- src/test/java/org/privacyidea/TestTriggerChallenge.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/privacyidea/TestTriggerChallenge.java b/src/test/java/org/privacyidea/TestTriggerChallenge.java index a473178..0be106b 100644 --- a/src/test/java/org/privacyidea/TestTriggerChallenge.java +++ b/src/test/java/org/privacyidea/TestTriggerChallenge.java @@ -36,7 +36,7 @@ public class TestTriggerChallenge private PrivacyIDEA privacyIDEA; String serviceAccount = "service"; String servicePass = "pass"; - String forwardClientIP = "127.0.0.1"; + String clientIP = "127.0.0.1"; @Before public void setup() @@ -60,11 +60,11 @@ public void testTriggerChallengeSuccess() mockServer.when(HttpRequest.request() .withPath(PIConstants.ENDPOINT_TRIGGERCHALLENGE) .withMethod("POST") - .withBody("user=testuser&realm=realm&clientip=127.0.0.1")) + .withBody("clientip=127.0.0.1&user=testuser&realm=realm")) .respond(HttpResponse.response().withBody(Utils.triggerChallengeSuccess())); String username = "testuser"; - PIResponse response = privacyIDEA.triggerChallenges(username, Collections.singletonMap("clientip", forwardClientIP), + PIResponse response = privacyIDEA.triggerChallenges(username, Collections.singletonMap("clientip", clientIP), Collections.emptyMap()); assertEquals("otp", response.preferredClientMode); From e85a998fe623380e803877a1fa6fb90b4c859afc Mon Sep 17 00:00:00 2001 From: Nils Behlen <29949516+nilsbehlen@users.noreply.github.com> Date: Thu, 3 Apr 2025 15:27:30 +0200 Subject: [PATCH 6/6] Update Changelog.md --- Changelog.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Changelog.md b/Changelog.md index e7c849c..156180e 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,8 +1,14 @@ # Changelog +### v1.3.0 - 0 Apr 2025 + +* Passkey functions +* JWT will be reused and renewed automatically +* Added option to add arbitrary parameters to the requests + ### v1.2.2 - 5 Mar 2024 -* Fixed a problem with the threadpool where thread would not time out and accumulate over time +* Fixed a problem with the thread pool where thread would not time out and accumulate over time * Added the option to set http timeouts ### v1.2.1 - 9 Aug 2023 @@ -42,4 +48,4 @@ ### v0.1 - 18 Sep 2020 * First version -* Supports basic OTP token +* Supports basic OTP token \ No newline at end of file