Skip to content

Commit 44a5fae

Browse files
Merge branch 'master' into 69-reuse-saved-jwt-auth-token-until-it-expire-1
2 parents 437700b + bb42f5d commit 44a5fae

File tree

9 files changed

+246
-59
lines changed

9 files changed

+246
-59
lines changed

.github/badges/branches.svg

Lines changed: 1 addition & 1 deletion
Loading
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"branches": 68.42105263157895, "coverage": 90.43595809395066}
1+
{"branches": 62.698412698412696, "coverage": 81.90562067929065}

.github/badges/jacoco.svg

Lines changed: 1 addition & 1 deletion
Loading

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
<plugin>
4949
<groupId>org.apache.maven.plugins</groupId>
5050
<artifactId>maven-compiler-plugin</artifactId>
51-
<version>3.8.1</version>
51+
<version>3.13.0</version>
5252
<configuration>
5353
<source>11</source>
5454
<target>11</target>

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -125,21 +125,21 @@ void sendRequestAsync(String endpoint, Map<String, String> params, Map<String, S
125125
}
126126
privacyIDEA.log(method + " " + endpoint);
127127
params.forEach((k, v) ->
128-
{
128+
{
129129
if (k.equals("pass") || k.equals("password"))
130130
{
131131
v = "*".repeat(v.length());
132132
}
133133
privacyIDEA.log(k + "=" + v);
134-
});
134+
});
135135

136136
if (GET.equals(method))
137137
{
138138
params.forEach((key, value) ->
139-
{
139+
{
140140
String encValue = URLEncoder.encode(value, StandardCharsets.UTF_8);
141141
urlBuilder.addQueryParameter(key, encValue);
142-
});
142+
});
143143
}
144144

145145
String url = urlBuilder.build().toString();
@@ -157,7 +157,7 @@ void sendRequestAsync(String endpoint, Map<String, String> params, Map<String, S
157157
{
158158
FormBody.Builder formBodyBuilder = new FormBody.Builder();
159159
params.forEach((key, value) ->
160-
{
160+
{
161161
if (key != null && value != null)
162162
{
163163
String encValue = value;
@@ -169,13 +169,13 @@ void sendRequestAsync(String endpoint, Map<String, String> params, Map<String, S
169169
}
170170
formBodyBuilder.add(key, encValue);
171171
}
172-
});
172+
});
173173
// This switches okhttp to make a post request
174174
requestBuilder.post(formBodyBuilder.build());
175175
}
176176

177177
Request request = requestBuilder.build();
178-
//privacyIDEA.log("HEADERS:\n" + request.headers().toString());
178+
//privacyIDEA.log("HEADERS:\n" + request.headers());
179179
client.newCall(request).enqueue(callback);
180180
}
181181
}

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

Lines changed: 86 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,23 @@ else if ("interactive".equals(modeFromResponse))
175175
response.preferredClientMode = modeFromResponse;
176176
}
177177
response.message = getString(detail, MESSAGE);
178+
response.username = getString(detail, USERNAME);
178179
response.image = getString(detail, IMAGE);
179180
response.serial = getString(detail, SERIAL);
180181
response.transactionID = getString(detail, TRANSACTION_ID);
181182
response.type = getString(detail, TYPE);
182183
response.otpLength = getInt(detail, OTPLEN);
183-
184+
JsonObject passkeyChallenge = detail.getAsJsonObject(PASSKEY);
185+
if (passkeyChallenge != null && !passkeyChallenge.isJsonNull())
186+
{
187+
response.passkeyChallenge = passkeyChallenge.toString();
188+
// The passkey challenge can contain a transaction id, use that if none was set prior
189+
// This will happen if the passkey challenge was requested via /validate/initialize
190+
if (response.transactionID == null || response.transactionID.isEmpty())
191+
{
192+
response.transactionID = getString(passkeyChallenge, TRANSACTION_ID);
193+
}
194+
}
184195
String r = getString(detail, CHALLENGE_STATUS);
185196
for (ChallengeStatus cs : ChallengeStatus.values())
186197
{
@@ -194,12 +205,12 @@ else if ("interactive".equals(modeFromResponse))
194205
if (arrMessages != null)
195206
{
196207
arrMessages.forEach(val ->
197-
{
208+
{
198209
if (val != null)
199210
{
200211
response.messages.add(val.getAsString());
201212
}
202-
});
213+
});
203214
}
204215

205216
JsonArray arrChallenges = detail.getAsJsonArray(MULTI_CHALLENGE);
@@ -215,6 +226,17 @@ else if ("interactive".equals(modeFromResponse))
215226
String transactionID = getString(challenge, TRANSACTION_ID);
216227
String type = getString(challenge, TYPE);
217228

229+
if (challenge.has(PASSKEY_REGISTRATION))
230+
{
231+
response.passkeyRegistration = challenge.get(PASSKEY_REGISTRATION).toString();
232+
// TODO for passkey registration with enroll_via_multichallenge, the txid is probably in the wrong place
233+
// as of 3.11.0
234+
if (response.transactionID == null || response.transactionID.isEmpty())
235+
{
236+
response.transactionID = transactionID;
237+
}
238+
}
239+
218240
if (TOKEN_TYPE_WEBAUTHN.equals(type))
219241
{
220242
String webauthnSignRequest = getItemFromAttributes(challenge);
@@ -359,24 +381,24 @@ private TokenInfo parseSingleTokenInfo(String json)
359381
if (joInfo != null)
360382
{
361383
joInfo.entrySet().forEach(entry ->
362-
{
384+
{
363385
if (entry.getKey() != null && entry.getValue() != null)
364386
{
365387
info.info.put(entry.getKey(), entry.getValue().getAsString());
366388
}
367-
});
389+
});
368390
}
369391

370392
JsonArray arrRealms = obj.getAsJsonArray(REALMS);
371393
if (arrRealms != null)
372394
{
373395
arrRealms.forEach(val ->
374-
{
396+
{
375397
if (val != null)
376398
{
377399
info.realms.add(val.getAsString());
378400
}
379-
});
401+
});
380402
}
381403
return info;
382404
}
@@ -472,7 +494,7 @@ Map<String, String> parseWebAuthnSignResponse(String json)
472494
}
473495
catch (JsonSyntaxException e)
474496
{
475-
privacyIDEA.error("WebAuthn sign response has the wrong format: " + e.getLocalizedMessage());
497+
privacyIDEA.error("FIDO2 sign response has the wrong format: " + e.getLocalizedMessage());
476498
return null;
477499
}
478500

@@ -495,6 +517,40 @@ Map<String, String> parseWebAuthnSignResponse(String json)
495517
return params;
496518
}
497519

520+
Map<String, String> parseFIDO2AuthenticationResponse(String json)
521+
{
522+
Map<String, String> params = new LinkedHashMap<>();
523+
JsonObject obj;
524+
try
525+
{
526+
obj = JsonParser.parseString(json).getAsJsonObject();
527+
}
528+
catch (JsonSyntaxException e)
529+
{
530+
privacyIDEA.error("FIDO2 sign response has the wrong format: " + e.getLocalizedMessage());
531+
return null;
532+
}
533+
534+
params.put(CREDENTIAL_ID, getString(obj, CREDENTIAL_ID));
535+
params.put(CLIENTDATAJSON, getString(obj, CLIENTDATAJSON));
536+
params.put(SIGNATURE, getString(obj, SIGNATURE));
537+
params.put(AUTHENTICATOR_DATA, getString(obj, AUTHENTICATOR_DATA));
538+
539+
// The userhandle and assertionclientextension fields are optional
540+
String userhandle = getString(obj, USERHANDLE);
541+
if (!userhandle.isEmpty())
542+
{
543+
params.put(USERHANDLE, userhandle);
544+
}
545+
String extensions = getString(obj, ASSERTIONCLIENTEXTENSIONS);
546+
if (!extensions.isEmpty())
547+
{
548+
params.put(ASSERTIONCLIENTEXTENSIONS, extensions);
549+
}
550+
return params;
551+
}
552+
553+
498554
private boolean getBoolean(JsonObject obj, String name)
499555
{
500556
JsonPrimitive primitive = getPrimitiveOrNull(obj, name);
@@ -528,4 +584,26 @@ private JsonPrimitive getPrimitiveOrNull(JsonObject obj, String name)
528584
}
529585
return primitive;
530586
}
587+
588+
public Map<String, String> parseFIDO2RegistrationResponse(String registrationResponse)
589+
{
590+
Map<String, String> params = new LinkedHashMap<>();
591+
JsonObject obj;
592+
try
593+
{
594+
obj = JsonParser.parseString(registrationResponse).getAsJsonObject();
595+
}
596+
catch (JsonSyntaxException e)
597+
{
598+
privacyIDEA.error("Passkey registration response is not JSON: " + e.getLocalizedMessage());
599+
return null;
600+
}
601+
602+
params.put(CREDENTIAL_ID, getString(obj, CREDENTIAL_ID));
603+
params.put(CLIENTDATAJSON, getString(obj, CLIENTDATAJSON));
604+
params.put(ATTESTATION_OBJECT, getString(obj, ATTESTATION_OBJECT));
605+
params.put(AUTHENTICATOR_ATTACHMENT, getString(obj, AUTHENTICATOR_ATTACHMENT));
606+
params.put(RAW_ID, getString(obj, RAW_ID));
607+
return params;
608+
}
531609
}

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

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,6 @@
2121

2222
public class PIConstants
2323
{
24-
private PIConstants()
25-
{
26-
}
27-
2824
public static final String GET = "GET";
2925
public static final String POST = "POST";
3026

@@ -34,6 +30,7 @@ private PIConstants()
3430
public static final String ENDPOINT_TRIGGERCHALLENGE = "/validate/triggerchallenge";
3531
public static final String ENDPOINT_POLLTRANSACTION = "/validate/polltransaction";
3632
public static final String ENDPOINT_VALIDATE_CHECK = "/validate/check";
33+
public static final String ENDPOINT_VALIDATE_INITIALIZE = "/validate/initialize";
3734
public static final String ENDPOINT_TOKEN = "/token/";
3835

3936
public static final String HEADER_ORIGIN = "Origin";
@@ -43,6 +40,7 @@ private PIConstants()
4340
// TOKEN TYPES
4441
public static final String TOKEN_TYPE_PUSH = "push";
4542
public static final String TOKEN_TYPE_WEBAUTHN = "webauthn";
43+
public static final String TOKEN_TYPE_PASSKEY = "passkey";
4644

4745
// JSON KEYS
4846
public static final String USERNAME = "username";
@@ -85,20 +83,28 @@ private PIConstants()
8583
public static final String ID = "id";
8684
public static final String MAXFAIL = "maxfail";
8785
public static final String INFO = "info";
86+
public static final String PASSKEY_REGISTRATION = "passkey_registration";
87+
public static final String AUTH_FORM = "authenticationForm";
88+
public static final String AUTH_FORM_RESULT = "authenticationFormResult";
8889

89-
// WebAuthn params
90+
// WebAuthn/Passkey params
9091
public static final String WEBAUTHN_SIGN_REQUEST = "webAuthnSignRequest";
9192
public static final String CREDENTIALID = "credentialid";
93+
public static final String CREDENTIAL_ID = "credential_id";
9294
public static final String CLIENTDATA = "clientdata";
95+
public static final String CLIENTDATAJSON = "clientDataJSON";
9396
public static final String SIGNATUREDATA = "signaturedata";
9497
public static final String AUTHENTICATORDATA = "authenticatordata";
98+
public static final String AUTHENTICATOR_DATA = "authenticatorData";
9599
public static final String USERHANDLE = "userhandle";
96100
public static final String ASSERTIONCLIENTEXTENSIONS = "assertionclientextensions";
97-
101+
public static final String PASSKEY = "passkey";
102+
public static final String RAW_ID = "rawId";
103+
public static final String AUTHENTICATOR_ATTACHMENT = "authenticatorAttachment";
104+
public static final String ATTESTATION_OBJECT = "attestationObject";
98105

99106
// These will be excluded from url encoding
100-
public static final List<String>
101-
WEBAUTHN_PARAMETERS =
102-
Arrays.asList(CREDENTIALID, CLIENTDATA, SIGNATUREDATA, AUTHENTICATORDATA, USERHANDLE,
103-
ASSERTIONCLIENTEXTENSIONS);
104-
}
107+
public static final List<String> WEBAUTHN_PARAMETERS = Arrays.asList(CREDENTIALID, CLIENTDATA, SIGNATUREDATA, AUTHENTICATORDATA,
108+
USERHANDLE, ASSERTIONCLIENTEXTENSIONS, CREDENTIAL_ID, RAW_ID,
109+
AUTHENTICATOR_ATTACHMENT, ATTESTATION_OBJECT);
110+
}

0 commit comments

Comments
 (0)