From 3b53bde6afac05b963fa355acb5c727b75249d25 Mon Sep 17 00:00:00 2001 From: Okke Harsta Date: Wed, 30 Oct 2024 21:40:05 +0100 Subject: [PATCH 1/3] WIP for #535 --- .../java/myconext/api/UserController.java | 6 ++++++ .../GuestIdpAuthenticationRequestFilter.java | 19 +++++++------------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/myconext-server/src/main/java/myconext/api/UserController.java b/myconext-server/src/main/java/myconext/api/UserController.java index e5692589..efa4ba5a 100644 --- a/myconext-server/src/main/java/myconext/api/UserController.java +++ b/myconext-server/src/main/java/myconext/api/UserController.java @@ -100,6 +100,7 @@ public class UserController implements UserAuthentication { private final List issuers; //For now, hardcode the not known issuers from test private final List unknownIssuers = List.of("CURRNL2A"); + private final int nudgeAppDays; public UserController(UserRepository userRepository, UserCredentialRepository userCredentialRepository, @@ -123,6 +124,7 @@ public UserController(UserRepository userRepository, @Value("${rp_id}") String rpId, @Value("${feature.default_remember_me}") boolean featureDefaultRememberMe, @Value("${verify.issuers_path}") Resource issuersResource, + @Value("${nudge_eduid_app_days}") int nudgeAppDays, ServicesConfiguration servicesConfiguration) throws IOException { this.userRepository = userRepository; this.userCredentialRepository = userCredentialRepository; @@ -146,6 +148,7 @@ public UserController(UserRepository userRepository, this.relyingParty = relyingParty(rpId, rpOrigin); this.emailGuessingPreventor = new EmailGuessingPrevention(emailGuessingSleepMillis); this.featureDefaultRememberMe = featureDefaultRememberMe; + this.nudgeAppDays = nudgeAppDays; List idinIssuers = objectMapper.readValue(issuersResource.getInputStream(), new TypeReference<>() { }); @@ -369,6 +372,9 @@ public ResponseEntity createEduIDAccount(@Valid @RequestBody Cre createAccount.getRelyingPartClientId(), manage); user.setCreateFromInstitutionKey(institution.getHash()); + //Don't bother with the nudge app the next 24 hours + long nudgeAppMillis = System.currentTimeMillis() - (1000L * 60 * 60 * 24 * (nudgeAppDays - 1)); + user.setLastSeenAppNudge(nudgeAppMillis); user.validate(); userRepository.save(user); diff --git a/myconext-server/src/main/java/myconext/security/GuestIdpAuthenticationRequestFilter.java b/myconext-server/src/main/java/myconext/security/GuestIdpAuthenticationRequestFilter.java index 45b3c5c2..3ffd50db 100644 --- a/myconext-server/src/main/java/myconext/security/GuestIdpAuthenticationRequestFilter.java +++ b/myconext-server/src/main/java/myconext/security/GuestIdpAuthenticationRequestFilter.java @@ -456,10 +456,12 @@ private boolean checkStepUp(HttpServletResponse response, HttpServletRequest req !hasStudentAffiliation; boolean missingValidName = !CollectionUtils.isEmpty(authenticationContextClassReferences) && authenticationContextClassReferences.contains(ACR.VALIDATE_NAMES) && !hasValidatedName(user); - + //TODO move this to the last if/ else clause as there is no direct redirect to the aap nudge page if (user.isNewUser()) { user.setNewUser(false); - user.setLastSeenAppNudge(System.currentTimeMillis()); + //ensure the user is the coming 24 hours is not nudged to the app + long nudgeAppMillis = System.currentTimeMillis() - (1000L * 60 * 60 * 24 * (nudgeAppDays - 1)); + user.setLastSeenAppNudge(nudgeAppMillis); userRepository.save(user); logWithContext(user, "add", "account", LOG, "Saving user after new registration and magic link"); @@ -474,15 +476,8 @@ private boolean checkStepUp(HttpServletResponse response, HttpServletRequest req if (samlAuthenticationRequest.isTiqrFlow()) { return true; } - String url = this.redirectUrl + "/confirm?h=" + hash + - "&redirect=" + URLEncoder.encode(this.magicLinkUrl, charSet) + - "&email=" + URLEncoder.encode(user.getEmail(), charSet) + - "&new=true"; - if (!StepUpStatus.NONE.equals(samlAuthenticationRequest.getSteppedUp())) { - url += "&explanation=" + explanation; - } - response.sendRedirect(url); - return false; + //we don't redirect the user to the nudge app page anymore + return true; } else if (inStepUpFlow) { finishStepUp(samlAuthenticationRequest); if (missingStudentAffiliation || missingValidName) { @@ -499,7 +494,7 @@ private boolean checkStepUp(HttpServletResponse response, HttpServletRequest req return false; } else if (!samlAuthenticationRequest.isPasswordOrWebAuthnFlow() && !samlAuthenticationRequest.isTiqrFlow() && !user.loginOptions().contains(LoginOptions.APP.getValue()) && - user.getLastSeenAppNudge() < (System.currentTimeMillis() - 1000L * 60 * 60 * 24 * nudgeAppDays)) { + user.getLastSeenAppNudge() < (System.currentTimeMillis() - (1000L * 60 * 60 * 24 * nudgeAppDays))) { //Nudge user to use the app user.setLastSeenAppNudge(System.currentTimeMillis()); userRepository.save(user); From 0d41780da0a4cb825b3873a5699984497b08c9eb Mon Sep 17 00:00:00 2001 From: Okke Harsta Date: Thu, 31 Oct 2024 07:25:59 +0100 Subject: [PATCH 2/3] WIP for #535 --- README.md | 5 +++ idp_metadata.xml | 32 +++++++++++++++++++ .../GuestIdpAuthenticationRequestFilter.java | 5 +-- 3 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 idp_metadata.xml diff --git a/README.md b/README.md index c58c8ab9..87560a3a 100644 --- a/README.md +++ b/README.md @@ -129,3 +129,8 @@ The redirect URI's for local development have to start with https. You can use t ``` ngrok http --domain okke.harsta.eu.ngrok.io 8081 ``` + +### Running the IdP and testing localhost + +The [idp_metadata.xml](idp_metadata.xml) file contains the IdP metadata for localhost development. Import an IdP in Manage and +whitelist this for the SP's you want to test with. The OIDC-Playground is capable of testing the different ACR options. \ No newline at end of file diff --git a/idp_metadata.xml b/idp_metadata.xml new file mode 100644 index 00000000..486a3e4f --- /dev/null +++ b/idp_metadata.xml @@ -0,0 +1,32 @@ + + + + + + + MIICvDCCAaQCCQCOtlEewzza9zANBgkqhkiG9w0BAQsFADAgMR4wHAYDVQQKDBVPcmdhbml6YXRpb24sIENOPU9JREMwHhcNMjIwMTI3MTA0OTQ2WhcNMjMwMTI3MTA0OTQ2WjAgMR4wHAYDVQQKDBVPcmdhbml6YXRpb24sIENOPU9JREMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMf3M9YNm1B+Db2W+04fvY9nOOM4UU8A1mQkY3m6JGwyq2Hul3LGqEJQgXwtpJhA/TOnlMQ4je9h0/vDfyT7v0exEWhykOqOPcuZ4OVmSACtUiao3VE2HiZz7kjSsSvix0+UeaouqxDcGpi4+qIrPFHFqQ8rNdl+2S4ZEzGQxqgK6n92a9Ng+mnSx21FEIu3o8rNqlTrN9mhtfoTSiVUDIWEM/1fAwt7NOzpw7GMbnV8nbSFUtEnn0oNm8H0Qqy8BAhMUHL8AZXO5Uwm+cVmVh9JQtWANfzhPD7l+DD8h7W5fi1VeLiAgo0Go0EF6z1Jk2aGTKWBQzODClF01j5NizAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAA8ttJeHPAMHduvwmvzkMjw2p0w7AptQXcqTlgBP3MAZKncn81SkO0jd0gvBTmUzt4oMiRJYnTm1ghUXNW5qK+2fjXwt5HXXz5OJGrN6A0MybOd/OXK/3Q5PHWvfLE/+zLWT67c34n0q6KDBBZheoF2SKw10s/mDJ1eHRHZIlsFmF6Z3Ll6h3fkL/0MsBOkmQ3CEtpTFpNk9lma3cnGQJ7p7t4T9iVNDSL1/fqDZPoaVzUJx39sXjb/ouCIDrXN3+ayElehQa+aXpE4KdG22j4Dm+k83OO3hujjugEAze+CeHDyy7X9c6c79bCXBU4HQK0ff0PiSxv0xpjsP3tMMr1U= + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + + + + Support + OpenConext + mailto:support@openconext.org + + + Support + OpenConext + mailto:technical@openconext.org + + + Support + OpenConext + mailto:administrative@openconext.org + + \ No newline at end of file diff --git a/myconext-server/src/main/java/myconext/security/GuestIdpAuthenticationRequestFilter.java b/myconext-server/src/main/java/myconext/security/GuestIdpAuthenticationRequestFilter.java index 3ffd50db..af7c035c 100644 --- a/myconext-server/src/main/java/myconext/security/GuestIdpAuthenticationRequestFilter.java +++ b/myconext-server/src/main/java/myconext/security/GuestIdpAuthenticationRequestFilter.java @@ -456,7 +456,6 @@ private boolean checkStepUp(HttpServletResponse response, HttpServletRequest req !hasStudentAffiliation; boolean missingValidName = !CollectionUtils.isEmpty(authenticationContextClassReferences) && authenticationContextClassReferences.contains(ACR.VALIDATE_NAMES) && !hasValidatedName(user); - //TODO move this to the last if/ else clause as there is no direct redirect to the aap nudge page if (user.isNewUser()) { user.setNewUser(false); //ensure the user is the coming 24 hours is not nudged to the app @@ -477,7 +476,9 @@ private boolean checkStepUp(HttpServletResponse response, HttpServletRequest req return true; } //we don't redirect the user to the nudge app page anymore - return true; + String url = String.format("%s?h=%s&force=true", this.magicLinkUrl, hash); + response.sendRedirect(url); + return false; } else if (inStepUpFlow) { finishStepUp(samlAuthenticationRequest); if (missingStudentAffiliation || missingValidName) { From 31b282ad8068f3cd63ee2af31ca245f9da6f82e1 Mon Sep 17 00:00:00 2001 From: Okke Harsta Date: Thu, 31 Oct 2024 10:34:26 +0100 Subject: [PATCH 3/3] Fow now, fixes #535 --- myconext-server/src/main/java/myconext/model/User.java | 4 ++-- .../src/test/java/myconext/api/UserControllerTest.java | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/myconext-server/src/main/java/myconext/model/User.java b/myconext-server/src/main/java/myconext/model/User.java index c055c389..99b1f665 100644 --- a/myconext-server/src/main/java/myconext/model/User.java +++ b/myconext-server/src/main/java/myconext/model/User.java @@ -83,7 +83,7 @@ public class User implements Serializable, UserDetails { private List eduIDS = new ArrayList<>(); private long created; - private long updatedAt = System.currentTimeMillis() / 1000L; + private long updatedAt; @Setter @Indexed private String trackingUuid; @@ -116,7 +116,6 @@ public User(String uid, String email, String chosenName, String givenName, Strin } this.newUser = true; this.created = System.currentTimeMillis() / 1000L; - this.updatedAt = created; } public User(String uid, String email, String chosenName, String givenName, String familyName, @@ -186,6 +185,7 @@ public String computeEduIdForIdentityProviderProviderIfAbsent(RemoteProvider rem } private String doComputeEduIDIfAbsent(ServiceProvider serviceProvider, Manage manage) { + this.updatedAt = System.currentTimeMillis(); serviceProvider.setLastLogin(new Date()); String institutionGuid = serviceProvider.getInstitutionGuid(); String entityId = serviceProvider.getEntityId(); diff --git a/myconext-server/src/test/java/myconext/api/UserControllerTest.java b/myconext-server/src/test/java/myconext/api/UserControllerTest.java index 28d29ea7..c0615886 100644 --- a/myconext-server/src/test/java/myconext/api/UserControllerTest.java +++ b/myconext-server/src/test/java/myconext/api/UserControllerTest.java @@ -98,11 +98,18 @@ public void newUserProvisioned() throws IOException { User user = user("new@example.com", "Mary", "Doe", "en"); MagicLinkResponse magicLinkResponse = magicLinkRequest(user, HttpMethod.POST); - assertEquals(user.getGivenName(), userRepository.findUserByEmail(user.getEmail()).get().getGivenName()); + User userFromDB = userRepository.findUserByEmail(user.getEmail()).get(); + assertEquals(0L, userFromDB.getLastSeenAppNudge()); + assertEquals(user.getGivenName(), userFromDB.getGivenName()); String samlResponse = samlResponse(magicLinkResponse); assertTrue(samlResponse.contains("new@example.com")); + User userFromAfter = userRepository.findUserByEmail(user.getEmail()).get(); + long appNudgeDiff = System.currentTimeMillis() - userFromAfter.getLastSeenAppNudge(); + int days = (int) Math.floor((double) appNudgeDiff / (1000 * 60 * 60 * 24)); + assertEquals(6, days); + when() .get("/myconext/api/idp/resend_magic_link_request?id=" + magicLinkResponse.authenticationRequestId) .then()