Skip to content

Commit 277217e

Browse files
authored
v1.4.0 (#76)
* v1.4.0 * PIResponse class can return the transaction based on the mode/type, which currently are Push, WebAuthn, Passkey and OTP. * HTTP request headers are logged * WebAuthn class as derived class of Challenge has been removed to allow simple serialization of PIResponse * allowCredentials for WebAuthnSignRequests are merged when the PIResponse object is created and the combined SignRequest is set to PIResponse.webAuthnSignRequest. WebAuthn challenges are not in the multi_challenge list anymore! * Update TestWebAuthn.java the tests are bad currently
1 parent 9667dff commit 277217e

File tree

8 files changed

+115
-147
lines changed

8 files changed

+115
-147
lines changed

Changelog.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
### 1.4.0 - 21 May 2025
4+
5+
* PIResponse class can return the transaction based on the mode/type, which currently are Push, WebAuthn, Passkey and OTP.
6+
* HTTP request headers are logged
7+
* WebAuthn class as derived class of Challenge has been removed to allow simple serialization of PIResponse
8+
* allowCredentials for WebAuthnSignRequests are merged when the PIResponse object is created and the combined SignRequest
9+
is set to PIResponse.webAuthnSignRequest. WebAuthn challenges are not in the multi_challenge list anymore!
10+
311
### v1.3.1 - 14 May 2025
412

513
* PIResponse::isAuthenticationSuccessful will also consider if multi_challenge is present, not just the authentication field

src/main/java/org/privacyidea/Challenge.java

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717
package org.privacyidea;
1818

1919
import java.util.ArrayList;
20+
import java.util.HashMap;
2021
import java.util.List;
22+
import java.util.Map;
2123

2224
public class Challenge
2325
{
24-
protected final List<String> attributes = new ArrayList<>();
26+
protected final Map<String, String> attributes = new HashMap<>();
2527
protected final String serial;
2628
protected final String clientMode;
2729
protected final String message;
@@ -39,17 +41,38 @@ public Challenge(String serial, String message, String clientMode, String image,
3941
this.type = type;
4042
}
4143

42-
public List<String> getAttributes() {return attributes;}
44+
public Map<String, String> getAttributes()
45+
{
46+
return attributes;
47+
}
4348

44-
public String getSerial() {return serial;}
49+
public String getSerial()
50+
{
51+
return serial;
52+
}
4553

46-
public String getMessage() {return message;}
54+
public String getMessage()
55+
{
56+
return message;
57+
}
4758

48-
public String getClientMode() {return clientMode;}
59+
public String getClientMode()
60+
{
61+
return clientMode;
62+
}
4963

50-
public String getImage() {return image.replaceAll("\"", "");}
64+
public String getImage()
65+
{
66+
return image.replaceAll("\"", "");
67+
}
5168

52-
public String getTransactionID() {return transactionID;}
69+
public String getTransactionID()
70+
{
71+
return transactionID;
72+
}
5373

54-
public String getType() {return type;}
55-
}
74+
public String getType()
75+
{
76+
return type;
77+
}
78+
}

src/main/java/org/privacyidea/Endpoint.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ void sendRequestAsync(String endpoint, Map<String, String> params, Map<String, S
186186
}
187187

188188
Request request = requestBuilder.build();
189-
//privacyIDEA.log("HEADERS:\n" + request.headers());
189+
privacyIDEA.log("Header: " + request.headers().toString().replace("\n", " | "));
190190
client.newCall(request).enqueue(callback);
191191
}
192192
}

src/main/java/org/privacyidea/JSONParser.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ else if ("interactive".equals(modeFromResponse))
276276
JsonArray arrChallenges = detail.getAsJsonArray(MULTI_CHALLENGE);
277277
if (arrChallenges != null)
278278
{
279+
List<String> webauthnSignRequests = new ArrayList<>();
279280
for (int i = 0; i < arrChallenges.size(); i++)
280281
{
281282
JsonObject challenge = arrChallenges.get(i).getAsJsonObject();
@@ -300,28 +301,39 @@ else if ("interactive".equals(modeFromResponse))
300301
if (TOKEN_TYPE_WEBAUTHN.equals(type))
301302
{
302303
String webauthnSignRequest = getItemFromAttributes(challenge);
303-
response.multiChallenge.add(new WebAuthn(serial, message, clientMode, image, transactionID, webauthnSignRequest));
304+
response.webAuthnTransactionId = transactionID;
305+
if (webauthnSignRequest != null && !webauthnSignRequest.isEmpty())
306+
{
307+
webauthnSignRequests.add(webauthnSignRequest);
308+
}
304309
}
305310
else
306311
{
307312
response.multiChallenge.add(new Challenge(serial, message, clientMode, image, transactionID, type));
308313
}
309314
}
315+
if (!webauthnSignRequests.isEmpty())
316+
{
317+
response.webAuthnSignRequest = mergeWebAuthnSignRequest(webauthnSignRequests);
318+
}
310319
}
311320
}
312321
return response;
313322
}
314323

315-
static String mergeWebAuthnSignRequest(WebAuthn webauthn, List<String> arr) throws JsonSyntaxException
324+
String mergeWebAuthnSignRequest(List<String> webAuthnSignRequests) throws JsonSyntaxException
316325
{
326+
String first = webAuthnSignRequests.get(0);
327+
//webAuthnSignRequests.remove(0);
328+
317329
List<JsonArray> extracted = new ArrayList<>();
318-
for (String signRequest : arr)
330+
for (String signRequest : webAuthnSignRequests)
319331
{
320332
JsonObject obj = JsonParser.parseString(signRequest).getAsJsonObject();
321333
extracted.add(obj.getAsJsonArray("allowCredentials"));
322334
}
323335

324-
JsonObject signRequest = JsonParser.parseString(webauthn.signRequest()).getAsJsonObject();
336+
JsonObject signRequest = JsonParser.parseString(first).getAsJsonObject();
325337
JsonArray allowCredentials = new JsonArray();
326338
extracted.forEach(allowCredentials::addAll);
327339

src/main/java/org/privacyidea/PIConstants.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public class PIConstants
9797
public static final String SIGNATUREDATA = "signaturedata";
9898
public static final String AUTHENTICATORDATA = "authenticatordata";
9999
public static final String AUTHENTICATOR_DATA = "authenticatorData";
100-
public static final String USERHANDLE = "userhandle";
100+
public static final String USERHANDLE = "userHandle";
101101
public static final String ASSERTIONCLIENTEXTENSIONS = "assertionclientextensions";
102102
public static final String PASSKEY = "passkey";
103103
public static final String RAW_ID = "rawId";

src/main/java/org/privacyidea/PIResponse.java

Lines changed: 47 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
*/
1717
package org.privacyidea;
1818

19+
import com.google.gson.Gson;
20+
import com.google.gson.GsonBuilder;
1921
import com.google.gson.JsonSyntaxException;
2022
import java.util.ArrayList;
2123
import java.util.List;
@@ -55,6 +57,9 @@ public class PIResponse
5557
public String username = "";
5658
public String enrollmentLink = "";
5759

60+
public String webAuthnSignRequest = "";
61+
public String webAuthnTransactionId = "";
62+
5863
public boolean authenticationSuccessful()
5964
{
6065
if (authentication == AuthenticationStatus.ACCEPT && (multiChallenge == null || multiChallenge.isEmpty()))
@@ -87,6 +92,29 @@ public String pushMessage()
8792
return reduceChallengeMessagesWhere(c -> TOKEN_TYPE_PUSH.equals(c.getType()));
8893
}
8994

95+
public String otpTransactionId()
96+
{
97+
for (Challenge challenge : multiChallenge)
98+
{
99+
if (!TOKEN_TYPE_PUSH.equals(challenge.getType()) && !TOKEN_TYPE_WEBAUTHN.equals(challenge.getType()))
100+
{
101+
return challenge.transactionID;
102+
}
103+
}
104+
return null;
105+
}
106+
107+
public String pushTransactionId() {
108+
for (Challenge challenge : multiChallenge)
109+
{
110+
if (TOKEN_TYPE_PUSH.equals(challenge.getType()))
111+
{
112+
return challenge.transactionID;
113+
}
114+
}
115+
return null;
116+
}
117+
90118
/**
91119
* Get the messages of all token that require an input field (HOTP, TOTP, SMS, Email...) reduced to a single string.
92120
*
@@ -115,25 +143,12 @@ private String reduceChallengeMessagesWhere(Predicate<Challenge> predicate)
115143
*/
116144
public List<String> triggeredTokenTypes()
117145
{
118-
return multiChallenge.stream().map(Challenge::getType).distinct().collect(Collectors.toList());
119-
}
120-
121-
/**
122-
* Get all WebAuthn challenges from the multi_challenge.
123-
*
124-
* @return List of WebAuthn objects or empty list
125-
*/
126-
public List<WebAuthn> webAuthnSignRequests()
127-
{
128-
List<WebAuthn> ret = new ArrayList<>();
129-
multiChallenge.stream().filter(c -> TOKEN_TYPE_WEBAUTHN.equals(c.getType())).collect(Collectors.toList()).forEach(c ->
130-
{
131-
if (c instanceof WebAuthn)
132-
{
133-
ret.add((WebAuthn) c);
134-
}
135-
});
136-
return ret;
146+
List<String> types = multiChallenge.stream().map(Challenge::getType).distinct().collect(Collectors.toList());
147+
if (this.webAuthnSignRequest != null && !this.webAuthnSignRequest.isEmpty())
148+
{
149+
types.add(TOKEN_TYPE_WEBAUTHN);
150+
}
151+
return types;
137152
}
138153

139154
/**
@@ -146,27 +161,24 @@ public List<WebAuthn> webAuthnSignRequests()
146161
*/
147162
public String mergedSignRequest()
148163
{
149-
List<WebAuthn> webauthnSignRequests = webAuthnSignRequests();
150-
if (webauthnSignRequests.isEmpty())
164+
if (this.webAuthnSignRequest == null || this.webAuthnSignRequest.isEmpty())
151165
{
152166
return "";
153167
}
154-
if (webauthnSignRequests.size() == 1)
155-
{
156-
return webauthnSignRequests.get(0).signRequest();
157-
}
168+
return this.webAuthnSignRequest;
169+
}
158170

159-
WebAuthn webauthn = webauthnSignRequests.get(0);
160-
List<String> stringSignRequests = webauthnSignRequests.stream().map(WebAuthn::signRequest).collect(Collectors.toList());
171+
public String toJSON()
172+
{
173+
GsonBuilder builder = new GsonBuilder();
174+
builder.setPrettyPrinting();
175+
Gson gson = builder.create();
176+
return gson.toJson(this);
177+
}
161178

162-
try
163-
{
164-
return JSONParser.mergeWebAuthnSignRequest(webauthn, stringSignRequests);
165-
}
166-
catch (JsonSyntaxException e)
167-
{
168-
return "";
169-
}
179+
public static PIResponse fromJSON(String json)
180+
{
181+
return new Gson().fromJson(json, PIResponse.class);
170182
}
171183

172184
@Override

src/main/java/org/privacyidea/WebAuthn.java

Lines changed: 0 additions & 39 deletions
This file was deleted.

0 commit comments

Comments
 (0)