From 7f10c716e78151593e3e81365b123b4875f77059 Mon Sep 17 00:00:00 2001 From: Yuriy Movchan Date: Thu, 21 Mar 2024 17:46:04 +0300 Subject: [PATCH] feat(fido2): allow to update device data in SG authentication response #8116 Signed-off-by: Yuriy Movchan --- .../SuperGluuExternalAuthenticator.py | 6 +- .../service/operation/AssertionService.java | 40 +++++++++++ .../AssertionSuperGluuController.java | 5 +- .../fido2/Fido2DeviceNotificationConf.java | 71 +++++++++++++++++++ .../model/fido2/Fido2RegistrationEntry.java | 7 +- 5 files changed, 122 insertions(+), 7 deletions(-) create mode 100644 jans-orm/model/src/main/java/io/jans/orm/model/fido2/Fido2DeviceNotificationConf.java diff --git a/docs/script-catalog/person_authentication/super-gluu-external-authenticator/SuperGluuExternalAuthenticator.py b/docs/script-catalog/person_authentication/super-gluu-external-authenticator/SuperGluuExternalAuthenticator.py index 1a38b83b76c..7d76416ad14 100644 --- a/docs/script-catalog/person_authentication/super-gluu-external-authenticator/SuperGluuExternalAuthenticator.py +++ b/docs/script-catalog/person_authentication/super-gluu-external-authenticator/SuperGluuExternalAuthenticator.py @@ -29,6 +29,7 @@ from java.time.format import DateTimeFormatter from io.jans.as.model.configuration import AppConfiguration from io.jans.as.server.service.custom import CustomScriptService +from io.jans.orm.model.fido2 import Fido2DeviceNotificationConf import sys import json @@ -930,8 +931,7 @@ def getTargetEndpointArn(self, registrationPersistenceService, pushSnsService, p # Return endpoint ARN if it created already notificationConf = u2fDevice.getDeviceNotificationConf() if StringHelper.isNotEmpty(notificationConf): - notificationConfJson = json.loads(notificationConf) - targetEndpointArn = notificationConfJson['sns_endpoint_arn'] + targetEndpointArn = notificationConf.getSnsEndpointArn() if StringHelper.isNotEmpty(targetEndpointArn): print "Super-Gluu. Get target endpoint ARN. There is already created target endpoint ARN" return targetEndpointArn @@ -977,7 +977,7 @@ def getTargetEndpointArn(self, registrationPersistenceService, pushSnsService, p # Store created endpoint ARN in device entry userInum = user.getAttribute("inum") u2fDeviceUpdate = registrationPersistenceService.findRegisteredUserDevice(userInum, u2fDevice.getId()) - u2fDeviceUpdate.setDeviceNotificationConf('{"sns_endpoint_arn" : "%s"}' % targetEndpointArn) + u2fDeviceUpdate.setDeviceNotificationConf(Fido2DeviceNotificationConf(targetEndpointArn, None, None)) registrationPersistenceService.update(u2fDeviceUpdate) print "Super-Gluu. Send push notification. Stored ARN user's '%s' enpoint " % user.getUserId() diff --git a/jans-fido2/server/src/main/java/io/jans/fido2/service/operation/AssertionService.java b/jans-fido2/server/src/main/java/io/jans/fido2/service/operation/AssertionService.java index 2fea45a433f..82ddcb6e799 100644 --- a/jans-fido2/server/src/main/java/io/jans/fido2/service/operation/AssertionService.java +++ b/jans-fido2/server/src/main/java/io/jans/fido2/service/operation/AssertionService.java @@ -42,6 +42,7 @@ import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -359,6 +360,23 @@ public ObjectNode verify(JsonNode params) { authenticationData.setStatus(Fido2AuthenticationStatus.canceled); } else { authenticationData.setStatus(Fido2AuthenticationStatus.authenticated); + + JsonNode responseDeviceData = responseNode.get("deviceData"); + if (responseDeviceData != null && responseDeviceData.isTextual()) { + try { + Fido2DeviceData deviceData = dataMapperService.readValue( + new String(base64Service.urlDecode(responseDeviceData.asText()), StandardCharsets.UTF_8), + Fido2DeviceData.class); + + boolean pushTokenUpdated = !StringHelper.equals(registrationEntry.getDeviceData().getPushToken(), deviceData.getPushToken()); + if (pushTokenUpdated) { + prepareForPushTokenChange(registrationEntry); + } + registrationEntry.setDeviceData(deviceData); + } catch (Exception ex) { + throw errorResponseFactory.invalidRequest(String.format("Device data is invalid: %s", responseDeviceData), ex); + } + } } // Set expiration @@ -397,6 +415,28 @@ public ObjectNode verify(JsonNode params) { return finishResponseNode; } + private void prepareForPushTokenChange(Fido2RegistrationEntry registrationEntry) { + Fido2DeviceNotificationConf deviceNotificationConf = registrationEntry.getDeviceNotificationConf(); + if (deviceNotificationConf == null) { + return; + } + + String snsEndpointArn = deviceNotificationConf.getSnsEndpointArn(); + if (StringHelper.isEmpty(snsEndpointArn)) { + return; + } + + deviceNotificationConf.setSnsEndpointArn(null); + deviceNotificationConf.setSnsEndpointArnRemove(snsEndpointArn); + List snsEndpointArnHistory = deviceNotificationConf.getSnsEndpointArnHistory(); + if (snsEndpointArnHistory == null) { + snsEndpointArnHistory = new ArrayList<>(); + deviceNotificationConf.setSnsEndpointArnHistory(snsEndpointArnHistory); + } + + snsEndpointArnHistory.add(snsEndpointArn); + } + private Pair prepareAllowedCredentials(String documentDomain, String username, String requestedKeyHandle, boolean superGluu) { if (appConfiguration.isOldU2fMigrationEnabled()) { List existingFidoRegistrations = deviceRegistrationService.findAllRegisteredByUsername(username, diff --git a/jans-fido2/server/src/main/java/io/jans/fido2/service/sg/converter/AssertionSuperGluuController.java b/jans-fido2/server/src/main/java/io/jans/fido2/service/sg/converter/AssertionSuperGluuController.java index bdd6c4fb919..7cc731f538a 100644 --- a/jans-fido2/server/src/main/java/io/jans/fido2/service/sg/converter/AssertionSuperGluuController.java +++ b/jans-fido2/server/src/main/java/io/jans/fido2/service/sg/converter/AssertionSuperGluuController.java @@ -166,7 +166,9 @@ public ObjectNode buildFido2AssertionStartResponse(String userName, String keyHa * mYAmH5qk5KMaYATFAryIpoVwARGvEFQTWE2Q","clientData":"eyJ0eXAiOiJuYXZpZ2F0b3IuaWQuZ2V0QXNzZXJ0aW9uIiwi * Y2hhbGxlbmdlIjoiNVFvUnR1ZG1lajV0cmNyTVJnRkJvSTVyWjZweklaaVlQM3UzYlhDdnZBRSIsIm9yaWdpbiI6Imh0dHBzOlwv * XC95dXJlbS1lbWVyZ2luZy1waWcuZ2x1dS5pbmZvXC9pZGVudGl0eVwvYXV0aGNvZGUuaHRtIn0","keyHandle":"YJvWD9n40e - * IurInJvPKUoxpKzrleUMWgu9w3v_NUBu7BiGAclgkH_Zg88_T5y6Rh78imTxTh0djWFYG4jxOixw"} + * IurInJvPKUoxpKzrleUMWgu9w3v_NUBu7BiGAclgkH_Zg88_T5y6Rh78imTxTh0djWFYG4jxOixw","deviceData":"eyJuYW1l + * IjoiU00tRzk5MUIiLCJvc19uYW1lIjoidGlyYW1pc3UiLCJvc192ZXJzaW9uIjoiMTMiLCJwbGF0Zm9ybSI6ImFuZHJvaWQiLCJw + * dXNoX3Rva2VuIjoicHVzaF90b2tlbiIsInR5cGUiOiJub3JtYWwiLCJ1dWlkIjoidXVpZCJ9"} * - response: * {"status":"success","challenge":"5QoRtudmej5trcrMRgFBoI5rZ6pzIZiYP3u3bXCvvAE"} * @@ -215,6 +217,7 @@ public ObjectNode buildFido2AuthenticationVerifyResponse(String userName, String // Add response node ObjectNode response = dataMapperService.createObjectNode(); params.set("response", response); + response.put("deviceData", authenticateResponse.getDeviceData()); // We have to quote URL to conform bug in Super Gluu response.put("clientDataJSON", base64Service.urlEncodeToString(clientData.toString().replaceAll("/", "\\\\/").getBytes(StandardCharsets.UTF_8))); diff --git a/jans-orm/model/src/main/java/io/jans/orm/model/fido2/Fido2DeviceNotificationConf.java b/jans-orm/model/src/main/java/io/jans/orm/model/fido2/Fido2DeviceNotificationConf.java new file mode 100644 index 00000000000..eaf87f083ad --- /dev/null +++ b/jans-orm/model/src/main/java/io/jans/orm/model/fido2/Fido2DeviceNotificationConf.java @@ -0,0 +1,71 @@ +/* + * Janssen Project software is available under the Apache License (2004). See http://www.apache.org/licenses/ for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.orm.model.fido2; + +import java.io.Serializable; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * FIDO2 U2F device notification configuration + * + * @author Yuriy Movchan Date: 03/21/2024 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class Fido2DeviceNotificationConf implements Serializable { + + private static final long serialVersionUID = -8173244116167488365L; + + @JsonProperty(value = "sns_endpoint_arn") + private String snsEndpointArn; + + @JsonProperty(value = "sns_endpoint_arn_remove") + private String snsEndpointArnRemove; + + @JsonProperty(value = "sns_endpoint_arn_history") + private List snsEndpointArnHistory; + + public Fido2DeviceNotificationConf(@JsonProperty(value = "sns_endpoint_arn") String snsEndpointArn, @JsonProperty(value = "sns_endpoint_arn_remove") String snsEndpointArnRemove, + @JsonProperty(value = "sns_endpoint_arn_history") List snsEndpointArnHistory) { + this.snsEndpointArn = snsEndpointArn; + this.snsEndpointArnRemove = snsEndpointArnRemove; + this.snsEndpointArnHistory = snsEndpointArnHistory; + } + + public String getSnsEndpointArn() { + return snsEndpointArn; + } + + public void setSnsEndpointArn(String snsEndpointArn) { + this.snsEndpointArn = snsEndpointArn; + } + + public String getSnsEndpointArnRemove() { + return snsEndpointArnRemove; + } + + public void setSnsEndpointArnRemove(String snsEndpointArnRemove) { + this.snsEndpointArnRemove = snsEndpointArnRemove; + } + + public List getSnsEndpointArnHistory() { + return snsEndpointArnHistory; + } + + public void setSnsEndpointArnHistory(List snsEndpointArnHistory) { + this.snsEndpointArnHistory = snsEndpointArnHistory; + } + + @Override + public String toString() { + return "Fido2DeviceNotificationConf [snsEndpointArn=" + snsEndpointArn + ", snsEndpointArnRemove=" + + snsEndpointArnRemove + ", snsEndpointArnHistory=" + snsEndpointArnHistory + "]"; + } + +} diff --git a/jans-orm/model/src/main/java/io/jans/orm/model/fido2/Fido2RegistrationEntry.java b/jans-orm/model/src/main/java/io/jans/orm/model/fido2/Fido2RegistrationEntry.java index f625315dba9..2ec655871dd 100644 --- a/jans-orm/model/src/main/java/io/jans/orm/model/fido2/Fido2RegistrationEntry.java +++ b/jans-orm/model/src/main/java/io/jans/orm/model/fido2/Fido2RegistrationEntry.java @@ -44,8 +44,9 @@ public class Fido2RegistrationEntry extends Fido2Entry implements Serializable { @AttributeName(name = "jansStatus") private Fido2RegistrationStatus registrationStatus; + @JsonObject @AttributeName(name = "jansDeviceNotificationConf") - private String deviceNotificationConf; + private Fido2DeviceNotificationConf deviceNotificationConf; @JsonObject @AttributeName(name = "jansDeviceData") @@ -92,11 +93,11 @@ public void setRegistrationStatus(Fido2RegistrationStatus registrationStatus) { this.registrationStatus = registrationStatus; } - public String getDeviceNotificationConf() { + public Fido2DeviceNotificationConf getDeviceNotificationConf() { return deviceNotificationConf; } - public void setDeviceNotificationConf(String deviceNotificationConf) { + public void setDeviceNotificationConf(Fido2DeviceNotificationConf deviceNotificationConf) { this.deviceNotificationConf = deviceNotificationConf; }