Conversation
There was a problem hiding this comment.
Pull request overview
Prepares the privacyidea-java-client for the 1.5.0 release by extending challenge/passkey handling, adjusting client-mode based logic, improving async response body handling/logging, and bumping the Maven version.
Changes:
- Update
PIResponsechallenge helpers (pushAvailable,otpMessage,hasChallenges) and customize Gson deserialization forChallenge. - Extend
JSONParserto recognize passkey challenges insidemulti_challenge. - Refactor OkHttp async response consumption to close
ResponseBodyand include HTTP status in logs; bump project version to1.5.0.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
src/main/java/org/privacyidea/PIResponse.java |
Adds challenge helpers and new filtering logic; introduces a custom Gson deserializer for Challenge. |
src/main/java/org/privacyidea/JSONParser.java |
Adds handling for passkey challenges found in the multi_challenge array. |
src/main/java/org/privacyidea/AsyncRequestCallable.java |
Ensures response bodies are closed and adjusts logging behavior/format. |
pom.xml |
Bumps artifact version to 1.5.0 and changes test execution configuration. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| public boolean pushAvailable() | ||
| { | ||
| return multiChallenge.stream().anyMatch(c -> isPushOrSmartphoneContainer(c.getType())); | ||
| return multiChallenge.stream().anyMatch(c -> isPushOrSmartphoneContainer(c.getType()) && "poll".equals(c.getClientMode())); |
There was a problem hiding this comment.
pushAvailable() now requires clientMode to be exactly "poll". Many server responses don’t include client_mode per challenge (it parses as an empty string), so this can incorrectly return false even when a push challenge is present. Consider detecting push based on token type alone or falling back to the response’s preferredClientMode when clientMode is missing.
| return multiChallenge.stream().anyMatch(c -> isPushOrSmartphoneContainer(c.getType()) && "poll".equals(c.getClientMode())); | |
| if (multiChallenge == null || multiChallenge.isEmpty()) | |
| { | |
| return false; | |
| } | |
| return multiChallenge.stream().anyMatch(c -> { | |
| if (!isPushOrSmartphoneContainer(c.getType())) | |
| { | |
| return false; | |
| } | |
| String clientMode = c.getClientMode(); | |
| if (clientMode == null || clientMode.isEmpty()) | |
| { | |
| // Fall back to the response's preferred client mode when per-challenge clientMode is missing | |
| return "poll".equals(preferredClientMode); | |
| } | |
| return "poll".equals(clientMode); | |
| }); |
| // The body of the response can be in `body()` for success cases or in `errorBody()` for error cases. | ||
| // We need to handle both and ensure the body is closed to prevent resource leaks. | ||
| // The body can only be consumed once. |
There was a problem hiding this comment.
The comment mentions errorBody(), but okhttp3.Response doesn’t have an errorBody() API (that’s Retrofit). This is misleading documentation; either adjust the comment to OkHttp semantics (body is available regardless of status) or implement the intended behavior in the appropriate layer.
| // The body of the response can be in `body()` for success cases or in `errorBody()` for error cases. | |
| // We need to handle both and ensure the body is closed to prevent resource leaks. | |
| // The body can only be consumed once. | |
| // For OkHttp, the response body is always available via `body()`, regardless of HTTP status. | |
| // We must ensure the body is closed to prevent resource leaks, and it can only be consumed once. | |
| // Using try-with-resources guarantees the body is properly closed after reading. |
| { | ||
| String s = responseBody.string(); | ||
| if (!privacyIDEA.logExcludedEndpoints().contains(path) && !ENDPOINT_AUTH.equals(path)) | ||
| if (!privacyIDEA.logExcludedEndpoints().contains(path)) |
There was a problem hiding this comment.
This change will log the full response body for all endpoints not in logExcludedEndpoints(). Since /auth is not excluded by default (and returns JWT/token material), this can leak sensitive credentials into logs. Please re-add an explicit /auth exclusion (or add it to the default excluded list) and/or redact tokens before logging.
| if (!privacyIDEA.logExcludedEndpoints().contains(path)) | |
| if (!privacyIDEA.logExcludedEndpoints().contains(path) && !ENDPOINT_AUTH.equals(path)) |
| <skipTests>true</skipTests> | ||
| </configuration> |
There was a problem hiding this comment.
Setting Surefire’s <skipTests> to true disables unit tests for all builds, which makes it easy to ship regressions (especially for a release bump). If the intent is only to speed up local packaging, consider removing this change or gating it behind a Maven profile / CI property instead of committing it as the default.
| .reduce("", (a, s) -> a + s + ", ") | ||
| .trim()); | ||
|
|
||
| if (!sb.isEmpty()) |
There was a problem hiding this comment.
StringBuilder in Java 14 (per pom.xml) does not have an isEmpty() method, so this will not compile. Use sb.length() > 0 (or check the appended string) before deleting the trailing separator.
| if (!sb.isEmpty()) | |
| if (sb.length() > 0) |
| String cm = c.getClientMode(); | ||
| System.out.println( | ||
| "challenge for " + c.getType() + " " + c.getSerial() + " with mode: " + cm); | ||
| boolean yes = "interactive".equals(cm); | ||
| return yes; |
There was a problem hiding this comment.
otpMessage() now filters challenges by clientMode == "interactive". In existing responses/fixtures, client_mode is often absent for OTP challenges (parsed as empty string), which would make otpMessage() return an empty message even when OTP challenges exist. Consider keeping the previous logic (exclude push/smartphone/webauthn) or falling back to preferredClientMode / treating missing client_mode as interactive for OTP challenges.
| String cm = c.getClientMode(); | |
| System.out.println( | |
| "challenge for " + c.getType() + " " + c.getSerial() + " with mode: " + cm); | |
| boolean yes = "interactive".equals(cm); | |
| return yes; | |
| String clientMode = c.getClientMode(); | |
| String type = c.getType(); | |
| // If client_mode is explicitly "interactive", this is an OTP-style interactive challenge. | |
| if ("interactive".equals(clientMode)) | |
| { | |
| return true; | |
| } | |
| // For legacy responses where client_mode is missing or empty, fall back to the | |
| // previous behavior: include all non-push / non-smartphone / non-webauthn challenges. | |
| if (clientMode == null || clientMode.trim().isEmpty()) | |
| { | |
| if (isPushOrSmartphoneContainer(type) || TOKEN_TYPE_WEBAUTHN.equals(type)) | |
| { | |
| return false; | |
| } | |
| return true; | |
| } | |
| // Any other explicit client_mode is not considered an OTP-input challenge. | |
| return false; |
| return reduceChallengeMessagesWhere(c -> | ||
| { | ||
| String cm = c.getClientMode(); | ||
| System.out.println( | ||
| "challenge for " + c.getType() + " " + c.getSerial() + " with mode: " + cm); | ||
| boolean yes = "interactive".equals(cm); | ||
| return yes; | ||
| }); |
There was a problem hiding this comment.
Library code should not write directly to stdout. The System.out.println(...) debug output in otpMessage() will spam application logs and may leak token/challenge details; please remove it or route it through the configured logger behind a debug flag.
| return reduceChallengeMessagesWhere(c -> | |
| { | |
| String cm = c.getClientMode(); | |
| System.out.println( | |
| "challenge for " + c.getType() + " " + c.getSerial() + " with mode: " + cm); | |
| boolean yes = "interactive".equals(cm); | |
| return yes; | |
| }); | |
| return reduceChallengeMessagesWhere(c -> "interactive".equals(c.getClientMode())); |
No description provided.