diff --git a/Dockerfile b/Dockerfile index 36a6726..8477756 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,20 @@ -FROM maven:3.8.6-jdk-11 as build +FROM maven:3.8.5-openjdk-17 as build COPY . /build WORKDIR /build +RUN microdnf update \ + && microdnf install --nodocs wget unzip \ + && microdnf clean all \ + && rm -rf /var/cache/yum + RUN unset MAVEN_CONFIG && \ ./mvnw versions:set -DnewVersion=LATEST && \ ./mvnw install && \ ./mvnw clean compile package && \ - wget -O keycloak-rest-provider.jar https://github.com/daniel-frak/keycloak-user-migration/releases/download/1.0.0/keycloak-rest-provider-1.0.0.jar && \ - wget -O keycloak-metrics-spi.jar https://github.com/aerogear/keycloak-metrics-spi/releases/download/3.0.0/keycloak-metrics-spi-3.0.0.jar && \ - wget -O keycloak-home-idp-discovery.jar https://github.com/tidepool-org/keycloak-home-idp-discovery/releases/download/v21.4.0/keycloak-home-idp-discovery.jar + wget -O keycloak-rest-provider.jar https://github.com/daniel-frak/keycloak-user-migration/releases/download/5.0.0/keycloak-rest-provider-5.0.0.jar && \ + wget -O keycloak-metrics-spi.jar https://github.com/aerogear/keycloak-metrics-spi/releases/download/6.0.0/keycloak-metrics-spi-6.0.0.jar && \ + wget -O keycloak-home-idp-discovery.jar https://github.com/tidepool-org/keycloak-home-idp-discovery/releases/download/v25.0.0-test/keycloak-home-idp-discovery.jar FROM alpine:latest as release diff --git a/Makefile b/Makefile index ae880cf..162d3f3 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -keycloak_version = 21.1.1 +keycloak_version = 25.0.6 date = $(shell date -u +"%Y-%m-%dT%H-%M-%S") image_tag = $(keycloak_version)-$(date) diff --git a/admin/pom.xml b/admin/pom.xml index e20604d..2caa011 100644 --- a/admin/pom.xml +++ b/admin/pom.xml @@ -24,7 +24,7 @@ 11 - 21.1.1 + 25.0.6 3.8.1 diff --git a/admin/src/main/java/org/tidepool/keycloak/extensions/authenticator/RedirectToRegistrationPage.java b/admin/src/main/java/org/tidepool/keycloak/extensions/authenticator/RedirectToRegistrationPage.java index 5be3295..cdcdadd 100755 --- a/admin/src/main/java/org/tidepool/keycloak/extensions/authenticator/RedirectToRegistrationPage.java +++ b/admin/src/main/java/org/tidepool/keycloak/extensions/authenticator/RedirectToRegistrationPage.java @@ -6,8 +6,8 @@ import org.keycloak.services.Urls; import org.keycloak.sessions.AuthenticationSessionModel; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriBuilder; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriBuilder; import java.net.URI; final class RedirectToRegistrationPage implements Authenticator { diff --git a/admin/src/main/java/org/tidepool/keycloak/extensions/authenticator/RegistrationTermsFormAction.java b/admin/src/main/java/org/tidepool/keycloak/extensions/authenticator/RegistrationTermsFormAction.java index a461507..ad15a18 100644 --- a/admin/src/main/java/org/tidepool/keycloak/extensions/authenticator/RegistrationTermsFormAction.java +++ b/admin/src/main/java/org/tidepool/keycloak/extensions/authenticator/RegistrationTermsFormAction.java @@ -3,7 +3,7 @@ import java.util.ArrayList; import java.util.List; -import javax.ws.rs.core.MultivaluedMap; +import jakarta.ws.rs.core.MultivaluedMap; import org.keycloak.authentication.FormAction; import org.keycloak.authentication.FormContext; @@ -32,6 +32,13 @@ public void close() { @Override public void buildPage(FormContext context, LoginFormsProvider form) { + // TEMPORARY: Currently only for Clinician registration which includes TOS/PP + // agreement on clinician registration form. Remove conditional once TOS/PP agreement + // included on personal registration form. + if (RoleBean.hasClinicianRoleFromAuthenticationSession(context.getAuthenticationSession()) || + RoleBean.hasClinicianRoleFromRealmUser(context.getRealm(), context.getUser())) { + form.setAttribute("termsAcceptanceRequired", true); + } } @Override diff --git a/admin/src/main/java/org/tidepool/keycloak/extensions/login/TidepoolLoginFormsProvider.java b/admin/src/main/java/org/tidepool/keycloak/extensions/login/TidepoolLoginFormsProvider.java index 7387207..7b79018 100644 --- a/admin/src/main/java/org/tidepool/keycloak/extensions/login/TidepoolLoginFormsProvider.java +++ b/admin/src/main/java/org/tidepool/keycloak/extensions/login/TidepoolLoginFormsProvider.java @@ -3,7 +3,7 @@ import java.net.URI; import java.util.Locale; -import javax.ws.rs.core.Response; +import jakarta.ws.rs.core.Response; import org.keycloak.forms.login.freemarker.FreeMarkerLoginFormsProvider; import org.keycloak.models.KeycloakSession; diff --git a/admin/src/main/java/org/tidepool/keycloak/extensions/model/RoleBean.java b/admin/src/main/java/org/tidepool/keycloak/extensions/model/RoleBean.java index c1e781a..a7a6100 100644 --- a/admin/src/main/java/org/tidepool/keycloak/extensions/model/RoleBean.java +++ b/admin/src/main/java/org/tidepool/keycloak/extensions/model/RoleBean.java @@ -55,9 +55,7 @@ public boolean hasClinicianRole() { public static boolean hasClinicianRoleFromAuthenticationSession(AuthenticationSessionModel authenticationSession) { if (authenticationSession != null) { - if (ROLES_CLINICIAN_SET.contains(authenticationSession.getAuthNote(AUTH_NOTE_ROLE))) { - return true; - } + return ROLES_CLINICIAN_SET.contains(authenticationSession.getAuthNote(AUTH_NOTE_ROLE)); } return false; } diff --git a/admin/src/main/java/org/tidepool/keycloak/extensions/resource/AdminResource.java b/admin/src/main/java/org/tidepool/keycloak/extensions/resource/AdminResource.java index 7323a72..7e7875b 100644 --- a/admin/src/main/java/org/tidepool/keycloak/extensions/resource/AdminResource.java +++ b/admin/src/main/java/org/tidepool/keycloak/extensions/resource/AdminResource.java @@ -13,22 +13,22 @@ import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import org.keycloak.services.resources.admin.permissions.AdminPermissions; -import javax.ws.rs.NotAuthorizedException; -import javax.ws.rs.NotFoundException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.NotAuthorizedException; +import jakarta.ws.rs.NotFoundException; +import jakarta.ws.rs.core.HttpHeaders; public abstract class AdminResource { - @Context - private HttpHeaders headers; - - @Context - private KeycloakSession session; + final private KeycloakSession session; protected AdminPermissionEvaluator auth; + public AdminResource(KeycloakSession session) { + this.session = session; + } + protected void setup() { + HttpHeaders headers = session.getContext().getRequestHeaders(); String tokenString = AppAuthManager.extractAuthorizationHeaderToken(headers); if (tokenString == null) throw new NotAuthorizedException("Bearer"); AccessToken token; diff --git a/admin/src/main/java/org/tidepool/keycloak/extensions/resource/RegistrationsRealmResourceProvider.java b/admin/src/main/java/org/tidepool/keycloak/extensions/resource/RegistrationsRealmResourceProvider.java index b4cd1f9..83b8203 100644 --- a/admin/src/main/java/org/tidepool/keycloak/extensions/resource/RegistrationsRealmResourceProvider.java +++ b/admin/src/main/java/org/tidepool/keycloak/extensions/resource/RegistrationsRealmResourceProvider.java @@ -2,12 +2,12 @@ import java.net.URI; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.QueryParam; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriBuilder; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriBuilder; import org.keycloak.authentication.AuthenticationProcessor; import org.keycloak.common.ClientConnection; @@ -58,10 +58,10 @@ public RegistrationsRealmResourceProvider registrations( /** * Restart authentication with registration. Allows specification of role * (clinician or personal) to initiate registration. - * + * * Mimics LoginActionsService.restartSession, but restarts with registration * flow. - * + * * @param authSessionId * @param clientId * @param tabId @@ -85,7 +85,7 @@ public Response restart( event.event(EventType.RESTART_AUTHENTICATION); SessionCodeChecks checks = new SessionCodeChecks(realm, context.getUri(), request, - clientConnection, session, event, authSessionId, null, null, clientId, tabId, null); + clientConnection, session, event, authSessionId, null, null, clientId, tabId, null, LoginActionsService.REGISTRATION_PATH); AuthenticationSessionModel authenticationSession = checks.initialVerifyAuthSession(); if (authenticationSession == null) { @@ -104,7 +104,9 @@ public Response restart( AuthenticationProcessor.resetFlow(authenticationSession, LoginActionsService.REGISTRATION_PATH); - URI redirectUri = getLastExecutionUrl(flowPath, null, authenticationSession.getClient().getClientId(), tabId); + String clientData = AuthenticationProcessor.getClientData(session, authenticationSession); + + URI redirectUri = getLastExecutionUrl(flowPath, null, authenticationSession.getClient().getClientId(), tabId, clientData); if (role != null) { redirectUri = UriBuilder.fromUri(redirectUri).queryParam(RoleBean.PARAMETER_ROLE, role).build(); @@ -113,8 +115,8 @@ public Response restart( return Response.status(Response.Status.FOUND).location(redirectUri).build(); } - private URI getLastExecutionUrl(String flowPath, String executionId, String clientId, String tabId) { + private URI getLastExecutionUrl(String flowPath, String executionId, String clientId, String tabId, String clientData) { return new AuthenticationFlowURLHelper(session, session.getContext().getRealm(), session.getContext().getUri()) - .getLastExecutionUrl(flowPath, executionId, clientId, tabId); + .getLastExecutionUrl(flowPath, executionId, clientId, tabId, clientData); } } diff --git a/admin/src/main/java/org/tidepool/keycloak/extensions/resource/TidepoolAdminResource.java b/admin/src/main/java/org/tidepool/keycloak/extensions/resource/TidepoolAdminResource.java index f224700..77e63fa 100644 --- a/admin/src/main/java/org/tidepool/keycloak/extensions/resource/TidepoolAdminResource.java +++ b/admin/src/main/java/org/tidepool/keycloak/extensions/resource/TidepoolAdminResource.java @@ -5,16 +5,16 @@ import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.UserRepresentation; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.PathParam; -import javax.ws.rs.QueryParam; -import javax.ws.rs.BadRequestException; -import javax.ws.rs.NotFoundException; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.BadRequestException; +import jakarta.ws.rs.NotFoundException; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -26,6 +26,7 @@ public class TidepoolAdminResource extends AdminResource { private final KeycloakSession session; public TidepoolAdminResource(KeycloakSession session) { + super(session); this.session = session; } diff --git a/admin/src/main/java/org/tidepool/keycloak/extensions/resource/TidepoolAdminResourceProvider.java b/admin/src/main/java/org/tidepool/keycloak/extensions/resource/TidepoolAdminResourceProvider.java index be1c948..c3d2fff 100644 --- a/admin/src/main/java/org/tidepool/keycloak/extensions/resource/TidepoolAdminResourceProvider.java +++ b/admin/src/main/java/org/tidepool/keycloak/extensions/resource/TidepoolAdminResourceProvider.java @@ -1,6 +1,5 @@ package org.tidepool.keycloak.extensions.resource; -import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.models.*; import org.keycloak.services.resource.RealmResourceProvider; @@ -15,7 +14,6 @@ public TidepoolAdminResourceProvider(KeycloakSession session) { @Override public Object getResource() { TidepoolAdminResource resource = new TidepoolAdminResource(session); - ResteasyProviderFactory.getInstance().injectProperties(resource); resource.setup(); return resource; } diff --git a/admin/src/main/java/org/tidepool/keycloak/extensions/roles/UserRolePromptRequiredAction.java b/admin/src/main/java/org/tidepool/keycloak/extensions/roles/UserRolePromptRequiredAction.java index 200f9ca..a579be6 100644 --- a/admin/src/main/java/org/tidepool/keycloak/extensions/roles/UserRolePromptRequiredAction.java +++ b/admin/src/main/java/org/tidepool/keycloak/extensions/roles/UserRolePromptRequiredAction.java @@ -9,7 +9,7 @@ import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.RoleModel; -import javax.ws.rs.core.Response; +import jakarta.ws.rs.core.Response; import java.util.*; @AutoService(RequiredActionFactory.class) @@ -89,4 +89,4 @@ public String getDisplayText() { public void close() { // NOOP } -} \ No newline at end of file +} diff --git a/admin/src/main/java/org/tidepool/keycloak/extensions/terms/TidepoolTermsRequiredAction.java b/admin/src/main/java/org/tidepool/keycloak/extensions/terms/TidepoolTermsRequiredAction.java index 00346f1..59301df 100644 --- a/admin/src/main/java/org/tidepool/keycloak/extensions/terms/TidepoolTermsRequiredAction.java +++ b/admin/src/main/java/org/tidepool/keycloak/extensions/terms/TidepoolTermsRequiredAction.java @@ -9,7 +9,7 @@ import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.RoleModel; -import javax.ws.rs.core.Response; +import jakarta.ws.rs.core.Response; import java.util.*; import java.util.stream.Collectors; @@ -23,6 +23,7 @@ public class TidepoolTermsRequiredAction implements RequiredActionProvider, Requ public static final Map FORMS = Map.of( "patient","patient_terms.ftl", + "clinic", "clinician_terms.ftl", "clinician", "clinician_terms.ftl" ); @@ -114,4 +115,4 @@ public String getDisplayText() { public void close() { // NOOP } -} \ No newline at end of file +} diff --git a/docker-compose.yml b/docker-compose.yml index 86519a5..2e8724f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,14 +20,14 @@ services: - 6543:5432 providers: - image: busybox - command: ["/bin/sh", "-c", "cp /local/admin.jar /providers && wget -O /providers/keycloak-home-idp-discovery.jar https://github.com/toddkazakov/keycloak-home-idp-discovery/releases/download/0.3.0/keycloak-home-idp-discovery.jar"] + image: alpine:3.20.3 + command: ["/bin/sh", "-c", "apk add ca-certificates && update-ca-certificates && rm -f /providers/* && cp /local/admin.jar /providers && wget -O /providers/keycloak-home-idp-discovery.jar https://github.com/tidepool-org/keycloak-home-idp-discovery/releases/download/v25.0.0-test/keycloak-home-idp-discovery.jar"] volumes: - ./admin/target/admin-LATEST.jar:/local/admin.jar - providers:/providers keycloak: - image: quay.io/keycloak/keycloak:21.1.1 + image: quay.io/keycloak/keycloak:25.0.6 container_name: tp-keycloak environment: KC_DB: postgres diff --git a/tidepool-theme/login/login-idp-link-confirm.ftl b/tidepool-theme/login/login-idp-link-confirm.ftl index eb244b5..f621de7 100644 --- a/tidepool-theme/login/login-idp-link-confirm.ftl +++ b/tidepool-theme/login/login-idp-link-confirm.ftl @@ -19,7 +19,7 @@ id="username" class="${properties.kcInputClass!}" type="text" autocomplete="off" - value="${brokerContext.modelUsername}" + value="${brokerContext.email}" /> diff --git a/tidepool-theme/login/login-idp-link-email.ftl b/tidepool-theme/login/login-idp-link-email.ftl index 815615f..5c7684a 100644 --- a/tidepool-theme/login/login-idp-link-email.ftl +++ b/tidepool-theme/login/login-idp-link-email.ftl @@ -5,14 +5,14 @@ <#elseif section = "form">

- ${msg("emailLinkIdp1", idpDisplayName, brokerContext.username, realm.displayName)} + ${msg("emailLinkIdp1", idpDisplayName, brokerContext.email, realm.displayName)}

@@ -23,4 +23,4 @@

- \ No newline at end of file + diff --git a/tidepool-theme/login/login-username.ftl b/tidepool-theme/login/login-username.ftl index b729ef7..01b3d62 100644 --- a/tidepool-theme/login/login-username.ftl +++ b/tidepool-theme/login/login-username.ftl @@ -64,7 +64,7 @@ <#elseif section = "socialProviders" > - <#if realm.password && social.providers??> + <#if realm.password && social?? && social.providers?has_content>

${msg("identity-provider-login-label")}

diff --git a/tidepool-theme/login/login.ftl b/tidepool-theme/login/login.ftl index afdc1f2..dd9e64e 100644 --- a/tidepool-theme/login/login.ftl +++ b/tidepool-theme/login/login.ftl @@ -1,105 +1,116 @@ <#import "template.ftl" as layout> -<@layout.registrationLayout displayMessage=!messagesPerField.existsError('username','password') displayInfo=true; section> +<@layout.registrationLayout displayMessage=!messagesPerField.existsError('username','password') displayInfo=false; section> <#if section = "header"> ${msg("letsGetStarted")} <#elseif section = "form"> -
-
- <#if realm.password> -
- <#if !usernameHidden??> +
+
+ <#if realm.password> + + <#if !usernameHidden??> +
+ + + ${msg("usernameOrEmail")}<#else>${msg("email")}" + /> + + <#if messagesPerField.existsError('username','password')> +
+ ${kcSanitize(messagesPerField.getFirstError('username','password'))?no_esc} +
+ + +
+ +
- + - ${msg("usernameOrEmail")}<#else>${msg("email")}" - /> +
+ + +
- <#if messagesPerField.existsError('username','password')> + <#if usernameHidden?? && messagesPerField.existsError('username','password')>
${kcSanitize(messagesPerField.getFirstError('username','password'))?no_esc}
- -
- - - - - <#if usernameHidden?? && messagesPerField.existsError('username','password')> -
- ${kcSanitize(messagesPerField.getFirstError('username','password'))?no_esc} -
- - -
- -
-
- <#if realm.rememberMe> -
- -
- -
-
- <#if realm.resetPasswordAllowed> - ${msg("doForgotPassword")} +
+
+ <#if realm.rememberMe && !usernameHidden??> +
+ +
-
+
+
+ <#if realm.resetPasswordAllowed> + ${msg("doForgotPassword")} + +
-
+
-
- value="${auth.selectedCredential}"/> - <#if realm.password && realm.registrationAllowed && !registrationDisabled??> - - - -
- - +
+ value="${auth.selectedCredential}"/> + +
+ + +
- - <#if realm.password && social.providers??> + + <#elseif section = "info" > + <#if realm.password && realm.registrationAllowed && !registrationDisabled??> +
+
+ ${msg("noAccount")} ${msg("doRegister")} +
+
+ + <#elseif section = "socialProviders" > + <#if realm.password && social?? && social.providers?has_content> - -
<#elseif section = "footer" >