diff --git a/README.md b/README.md index 56bb2da2..7ed04db4 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ OSF CAS is the centralized authentication and authorization service for the [OSF # Implementations -The implementation of OSF CAS is based on [Apereo CAS 6.2.8](https://github.com/apereo/cas/tree/v6.2.8) via [CAS Overlay Template 6.2.x](https://github.com/apereo/cas-overlay-template/tree/6.2). Refer to [CAS Documentation 6.2.x](https://apereo.github.io/cas/6.2.x/) for more details. +The implementation of OSF CAS is based on [Apereo CAS 6.2.8](https://github.com/apereo/cas/tree/v6.2.8) via [CAS Overlay Template 6.2.x](https://github.com/apereo/cas-overlay-template/tree/6.2). Refer to [CAS Documentation 6.2.x](https://github.com/apereo/cas/blob/6.2.x/docs/cas-server-documentation/) for more details. ## Legacy Implementations @@ -40,6 +40,33 @@ A legacy version can be found at [CAS Overlay](https://github.com/CenterForOpenS - PostgreSQL `9.6` - JDK `11` + +# Local Development + +For local development, replace the default Dockerfile with Dockerfile-local. +This is required to ensure proper loading and usage of cas-local.properties. +```bash +cp Dockerfile-local Dockerfile +``` + +# Special Instructions for Apple Silicon (M1, M2, M3) and Other ARM64 Architectures + +If you are running Docker on ARM64 architecture (Apple Silicon or similar), you must explicitly set the platform to linux/amd64 when using OpenJDK 11 images. +Without this, the CAS container may fail to build or run correctly. + +Update the Dockerfile as follows: +```dockerfile +# Dockerfile + +FROM --platform=linux/amd64 adoptopenjdk/openjdk11:alpine-slim AS overlay +... +... + +FROM --platform=linux/amd64 adoptopenjdk/openjdk11:alpine-jre AS cas +... +``` +This forces Docker to use an amd64 image via emulation and ensures compatibility with CAS and OpenJDK 11 on ARM-based machines. + # Configure, Build and Run OSF CAS It is recommended to use the provided scripts to [build](https://github.com/CenterForOpenScience/osf-cas/blob/develop/docker-build.sh) and [run](https://github.com/CenterForOpenScience/osf-cas/blob/develop/docker-run.sh) CAS. Refer to Apereo [README.md](https://github.com/apereo/cas-overlay-template/tree/6.2#cas-overlay-template-) for more options. @@ -68,27 +95,20 @@ cas.authn.osf-postgres.jpa.dialect=io.cos.cas.osf.hibernate.dialect.OsfPostgresD ## CAS DB -The implementation of OSF CAS uses the [JPA Ticket Registry](https://apereo.github.io/cas/6.2.x/ticketing/Configuring-Ticketing-Components.html#ticket-registry) for durable ticket storage and thus requires a relational database. Set up a `PostgreSQL@9.6` server and review [JPA Ticket Registry settings](https://github.com/CenterForOpenScience/osf-cas/blob/d0a03b51c9b1ce7795a210223c1ce38d5b2742de/etc/cas/config/cas.properties#L127-L173). In most cases, only [Database connections](https://github.com/CenterForOpenScience/osf-cas/blob/d0a03b51c9b1ce7795a210223c1ce38d5b2742de/etc/cas/config/cas.properties#L139-L143) need to be updated. Other JDBC and JPA settings can be adjusted if necessary. +The implementation of OSF CAS uses the [JPA Ticket Registry](https://github.com/apereo/cas/blob/6.2.x/docs/cas-server-documentation/ticketing/Configuring-Ticketing-Components.md#ticket-registry) for durable ticket storage and thus requires a relational database. Set up a `PostgreSQL@9.6` server and review [JPA Ticket Registry settings](https://github.com/CenterForOpenScience/osf-cas/blob/d0a03b51c9b1ce7795a210223c1ce38d5b2742de/etc/cas/config/cas.properties#L127-L173). In most cases, only [Database connections](https://github.com/CenterForOpenScience/osf-cas/blob/d0a03b51c9b1ce7795a210223c1ce38d5b2742de/etc/cas/config/cas.properties#L139-L143) need to be updated. Other JDBC and JPA settings can be adjusted if necessary. -Here is an example for local development. Use `192.168.168.167` to access host outside the docker container. Use the port `54321` since the default `5432` one has been used by OSF DB. Update `pg_hba.conf` to grant proper access permission depending on the setup. +Here is an example for local development. Use `192.168.168.167` to access host outside the docker container. ```yaml # In `cas.properties` or `cas-local.properties` -cas.ticket.registry.jpa.user=longzechen +cas.ticket.registry.jpa.user=postgres cas.ticket.registry.jpa.password= cas.ticket.registry.jpa.driver-class=org.postgresql.Driver -cas.ticket.registry.jpa.url=jdbc:postgresql://192.168.168.167:54321/osf-cas?targetServerType=master +cas.ticket.registry.jpa.url=jdbc:postgresql://192.168.168.167:5432/osf-cas?targetServerType=master cas.ticket.registry.jpa.dialect=org.hibernate.dialect.PostgreSQL95Dialect ``` -```yaml -# In `pg_hba.conf` - -# TYPE DATABASE USER ADDRESS METHOD -host osf-cas longzechen 192.168.168.167/24 trust -``` - ## Signing and Encryption Keys ### CAS Server diff --git a/etc/cas/config/cas.properties b/etc/cas/config/cas.properties index 1c8bad4f..7296e826 100644 --- a/etc/cas/config/cas.properties +++ b/etc/cas/config/cas.properties @@ -25,8 +25,8 @@ cas.server.dev-mode.allow-force-http-error=${ALLOW_FORCE_HTTP_ERROR:false} ######################################################################################################################## # Throttling -# Configuration guide: https://apereo.github.io/cas/6.2.x/installation/Configuring-Authentication-Throttling.html -# Properties: https://apereo.github.io/cas/6.2.x/configuration/Configuration-Properties.html#authentication-throttling +# Configuration guide: https://github.com/apereo/cas/blob/6.2.x/docs/cas-server-documentation/installation/Configuring-Authentication-Throttling.md +# Properties: https://github.com/apereo/cas/blob/6.2.x/docs/cas-server-documentation/configuration/Configuration-Properties.md#authentication-throttling ######################################################################################################################## # # Authentication Failure Throttling @@ -40,7 +40,7 @@ cas.authn.throttle.failure.range-seconds=1 ######################################################################################################################## # CAS Monitoring & Statistics Endpoints -# See: https://apereo.github.io/cas/6.2.x/monitoring/Monitoring-Statistics.html +# See: https://github.com/apereo/cas/blob/6.2.x/docs/cas-server-documentation/monitoring/Monitoring-Statistics.md ######################################################################################################################## management.endpoints.web.exposure.include=health management.endpoint.health.enabled=true @@ -72,14 +72,14 @@ cas.authn.accept.users= ######################################################################################################################## # JSON Service Registry -# See: https://apereo.github.io/cas/6.2.x/services/JSON-Service-Management.html +# See: https://github.com/apereo/cas/blob/6.2.x/docs/cas-server-documentation/services/JSON-Service-Management.md ######################################################################################################################## cas.serviceRegistry.json.location=file:/etc/cas/services ######################################################################################################################## ######################################################################################################################## # CAS Logout and Single Logout (SLO) -# https://apereo.github.io/cas/6.2.x/installation/Logout-Single-Signout.html +# https://github.com/apereo/cas/blob/6.2.x/docs/cas-server-documentation/installation/Logout-Single-Signout.md ######################################################################################################################## # CAS Logout # @@ -130,7 +130,7 @@ cas.authn.osf-api.instn-authn-xsl-location=file:/etc/cas/institutions-auth.xsl ######################################################################################################################## # OSF PostgreSQL Authentication -# See: https://apereo.github.io/cas/6.2.x/installation/Configuring-Custom-Authentication.html +# See: https://github.com/apereo/cas/blob/6.2.x/docs/cas-server-documentation/installation/Configuring-Custom-Authentication.md ######################################################################################################################## # Authentication settings # @@ -148,7 +148,7 @@ cas.authn.osf-postgres.jpa.dialect=${OSF_DB_HIBERNATE_DIALECT:io.cos.cas.osf.hib ######################################################################################################################## # JPA Ticket Registry -# See: https://apereo.github.io/cas/6.2.x/ticketing/JPA-Ticket-Registry.html +# See: https://github.com/apereo/cas/blob/6.2.x/docs/cas-server-documentation/ticketing/JPA-Ticket-Registry.md ######################################################################################################################## # Global JDBC Settings # @@ -196,16 +196,16 @@ cas.ticket.registry.jpa.jpa-locking-timeout=PT1H ######################################################################################################################## # Signing and Encryption -# See: https://apereo.github.io/cas/6.2.x/configuration/Configuration-Properties-Common.html#signing--encryption +# See: https://github.com/apereo/cas/blob/6.2.x/docs/cas-server-documentation/configuration/Configuration-Properties-Common.md#signing--encryption ######################################################################################################################## # Spring Client Session -# See: https://apereo.github.io/cas/6.2.x/configuration/Configuration-Properties.html#spring-webflow-client-side-session +# See: https://github.com/apereo/cas/blob/6.2.x/docs/cas-server-documentation/configuration/Configuration-Properties.md#spring-webflow-client-side-session # cas.webflow.crypto.signing.key=${WEB_FLOW_SIGNING_KEY} cas.webflow.crypto.encryption.key=${WEB_FLOW_ENCRYPTION_KEY} # # Ticket Granting Cookie (TGC) -# See: https://apereo.github.io/cas/6.2.x/configuration/Configuration-Properties.html#signing--encryption-4 +# See: https://github.com/apereo/cas/blob/6.2.x/docs/cas-server-documentation/configuration/Configuration-Properties.md#signing--encryption-4 # cas.tgc.crypto.signing.key=${TGC_SIGNING_KEY} cas.tgc.crypto.encryption.key=${TGC_ENCRYPTION_KEY} @@ -213,7 +213,7 @@ cas.tgc.crypto.encryption.key=${TGC_ENCRYPTION_KEY} ######################################################################################################################## # Long-term Authentication: Ticket Granting Cookie (TGC) and Ticket Granting Ticket (TGT) -# See https://apereo.github.io/cas/6.2.x/installation/Configuring-LongTerm-Authentication.html +# See https://github.com/apereo/cas/blob/6.2.x/docs/cas-server-documentation/installation/Configuring-LongTerm-Authentication.md ######################################################################################################################## # General Cookie Setting for Ticket Granting Cookie # @@ -242,7 +242,7 @@ cas.ticket.tgt.remember-me.time-to-kill-in-seconds=7776000 ######################################################################################################################## # Pac4j Delegated Authentication -# https://apereo.github.io/cas/6.2.x/integration/Delegate-Authentication.html +# https://github.com/apereo/cas/blob/6.2.x/docs/cas-server-documentation/integration/Delegate-Authentication.md ######################################################################################################################## # General settings # @@ -275,8 +275,8 @@ cas.authn.pac4j.cas[0].callback-url-type=QUERY_PARAMETER ######################################################################################################################## # OAuth 2.0 Server -# Configuration guide: https://apereo.github.io/cas/6.2.x/installation/OAuth-OpenId-Authentication.html -# Properties: https://apereo.github.io/cas/6.2.x/configuration/Configuration-Properties.html#oauth2 +# Configuration guide: https://github.com/apereo/cas/blob/6.2.x/docs/cas-server-documentation/installation/OAuth-OpenId-Authentication.md +# Properties: https://github.com/apereo/cas/blob/6.2.x/docs/cas-server-documentation/configuration/Configuration-Properties.md#oauth2 ######################################################################################################################## # Authorization Code # diff --git a/etc/cas/config/local/cas-local.properties b/etc/cas/config/local/cas-local.properties index 6626573c..9347da1c 100644 --- a/etc/cas/config/local/cas-local.properties +++ b/etc/cas/config/local/cas-local.properties @@ -30,8 +30,8 @@ cas.server.dev-mode.allow-force-http-error=true ######################################################################################################################## # Throttling -# Configuration guide: https://apereo.github.io/cas/6.2.x/installation/Configuring-Authentication-Throttling.html -# Properties: https://apereo.github.io/cas/6.2.x/configuration/Configuration-Properties.html#authentication-throttling +# Configuration guide: https://github.com/apereo/cas/blob/6.2.x/docs/cas-server-documentation/installation/Configuring-Authentication-Throttling.md +# Properties: https://github.com/apereo/cas/blob/6.2.x/docs/cas-server-documentation/configuration/Configuration-Properties.md#authentication-throttling ######################################################################################################################## # # Authentication Failure Throttling @@ -45,7 +45,7 @@ cas.authn.throttle.failure.range-seconds=1 ######################################################################################################################## # CAS Monitoring & Statistics Endpoints -# See: https://apereo.github.io/cas/6.2.x/monitoring/Monitoring-Statistics.html +# See: https://github.com/apereo/cas/blob/6.2.x/docs/cas-server-documentation/monitoring/Monitoring-Statistics.md ######################################################################################################################## management.endpoints.web.exposure.include=health management.endpoint.health.enabled=true @@ -78,14 +78,14 @@ cas.authn.accept.users= ######################################################################################################################## # JSON Service Registry -# See: https://apereo.github.io/cas/6.2.x/services/JSON-Service-Management.html +# See: https://github.com/apereo/cas/blob/6.2.x/docs/cas-server-documentation/services/JSON-Service-Management.md ######################################################################################################################## cas.serviceRegistry.json.location=file:/etc/cas/services ######################################################################################################################## ######################################################################################################################## # CAS Logout and Single Logout (SLO) -# https://apereo.github.io/cas/6.2.x/installation/Logout-Single-Signout.html +# https://github.com/apereo/cas/blob/6.2.x/docs/cas-server-documentation/installation/Logout-Single-Signout.md ######################################################################################################################## # CAS Logout # @@ -136,7 +136,7 @@ cas.authn.osf-api.instn-authn-xsl-location=file:/etc/cas/config/instn-authn.xsl ######################################################################################################################## # OSF PostgreSQL Authentication -# See: https://apereo.github.io/cas/6.2.x/installation/Configuring-Custom-Authentication.html +# See: https://github.com/apereo/cas/blob/6.2.x/docs/cas-server-documentation/installation/Configuring-Custom-Authentication.md ######################################################################################################################## # Authentication settings # @@ -154,7 +154,7 @@ cas.authn.osf-postgres.jpa.dialect=io.cos.cas.osf.hibernate.dialect.OsfPostgresD ######################################################################################################################## # JPA Ticket Registry -# See: https://apereo.github.io/cas/6.2.x/ticketing/JPA-Ticket-Registry.html +# See: https://github.com/apereo/cas/blob/6.2.x/docs/cas-server-documentation/ticketing/JPA-Ticket-Registry.md ######################################################################################################################## # Global JDBC Settings # @@ -204,16 +204,16 @@ cas.ticket.registry.jpa.jpa-locking-timeout=PT1H ######################################################################################################################## # Signing and Encryption -# See: https://apereo.github.io/cas/6.2.x/configuration/Configuration-Properties-Common.html#signing--encryption +# See: https://github.com/apereo/cas/blob/6.2.x/docs/cas-server-documentation/configuration/Configuration-Properties-Common.md#signing--encryption ######################################################################################################################## # Spring Client Session -# See: https://apereo.github.io/cas/6.2.x/configuration/Configuration-Properties.html#spring-webflow-client-side-session +# See: https://github.com/apereo/cas/blob/6.2.x/docs/cas-server-documentation/configuration/Configuration-Properties.md#spring-webflow-client-side-session # cas.webflow.crypto.signing.key=changeme cas.webflow.crypto.encryption.key=changeme # # Ticket Granting Cookie (TGC) -# See: https://apereo.github.io/cas/6.2.x/configuration/Configuration-Properties.html#signing--encryption-4 +# See: https://github.com/apereo/cas/blob/6.2.x/docs/cas-server-documentation/configuration/Configuration-Properties.md#signing--encryption-4 # cas.tgc.crypto.signing.key=changeme cas.tgc.crypto.encryption.key=changeme @@ -221,7 +221,7 @@ cas.tgc.crypto.encryption.key=changeme ######################################################################################################################## # Long-term Authentication: Ticket Granting Cookie (TGC) and Ticket Granting Ticket (TGT) -# See https://apereo.github.io/cas/6.2.x/installation/Configuring-LongTerm-Authentication.html +# See https://github.com/apereo/cas/blob/6.2.x/docs/cas-server-documentation/installation/Configuring-LongTerm-Authentication.md ######################################################################################################################## # General Cookie Setting for Ticket Granting Cookie # @@ -248,7 +248,7 @@ cas.ticket.tgt.remember-me.time-to-kill-in-seconds=7200 ######################################################################################################################## # Pac4j Delegated Authentication -# https://apereo.github.io/cas/6.2.x/integration/Delegate-Authentication.html +# https://github.com/apereo/cas/blob/6.2.x/docs/cas-server-documentation/integration/Delegate-Authentication.md ######################################################################################################################## # General settings # @@ -287,8 +287,8 @@ cas.authn.pac4j.cas[1].callback-url-type=QUERY_PARAMETER ######################################################################################################################## # OAuth 2.0 Server -# Configuration guide: https://apereo.github.io/cas/6.2.x/installation/OAuth-OpenId-Authentication.html -# Properties: https://apereo.github.io/cas/6.2.x/configuration/Configuration-Properties.html#oauth2 +# Configuration guide: https://github.com/apereo/cas/blob/6.2.x/docs/cas-server-documentation/installation/OAuth-OpenId-Authentication.md +# Properties: https://github.com/apereo/cas/blob/6.2.x/docs/cas-server-documentation/configuration/Configuration-Properties.md#oauth2 ######################################################################################################################## # Authorization Code # diff --git a/src/main/java/io/cos/cas/osf/authentication/support/OsfInstitutionUtils.java b/src/main/java/io/cos/cas/osf/authentication/support/OsfInstitutionUtils.java index 89b28147..0edc7547 100644 --- a/src/main/java/io/cos/cas/osf/authentication/support/OsfInstitutionUtils.java +++ b/src/main/java/io/cos/cas/osf/authentication/support/OsfInstitutionUtils.java @@ -14,7 +14,7 @@ import java.util.Map; /** - * This is {@link OsfInstitutionUtils}. + * This is {@link OsfInstitutionUtils}, which provides helper methods supporting the institution SSO login flow. * * @author Longze Chen * @since 21.0.0 @@ -24,34 +24,79 @@ public final class OsfInstitutionUtils { public final static String ORCID_SUFFIX = " (via ORCiD SSO)"; - public static boolean validateInstitutionForLogin(final JpaOsfDao jpaOsfDao, final String id) { - final OsfInstitution institution = jpaOsfDao.findOneInstitutionById(id); - return institution != null && institution.getDelegationProtocol() != null; + /** + * @param institution the OSF institution to verify + * @return whether the given institution is eligible for institution SSO. + */ + public static boolean validateInstitutionForLogin(final OsfInstitution institution) { + return institution != null + && institution.getDelegationProtocol() != null + && institution.getSsoAvailability() != SsoAvailability.UNAVAILABLE; } - public static String getInstitutionSupportEmail(final JpaOsfDao jpaOsfDao, final String id) { - final OsfInstitution institution = jpaOsfDao.findOneInstitutionById(id); + /** + * @param jpaOsfDao the data access object for OSF DB + * @param institutionId the institution ID + * @return the institution's support email if exists + */ + public static String getInstitutionSupportEmail(final JpaOsfDao jpaOsfDao, final String institutionId) { + final OsfInstitution institution = jpaOsfDao.findOneInstitutionById(institutionId); return institution != null ? institution.getSupportEmail() : null; } + /** + * @param jpaOsfDao the data access object for OSF DB + * @param target the target query param in shibboleth URL + * @param institutionId the institution ID used in shortcut SSO mode + * @return a map of institution name and login URL + */ public static Map getInstitutionLoginUrlMap( final JpaOsfDao jpaOsfDao, final String target, - final String id + final String institutionId ) { List institutionList = new LinkedList<>(); - if (id == null || id.isEmpty()) { + boolean isShortcutSso = false; + if (institutionId == null || institutionId.isEmpty()) { institutionList = jpaOsfDao.findAllInstitutions(); } else { - final OsfInstitution institution = jpaOsfDao.findOneInstitutionById(id); + final OsfInstitution institution = jpaOsfDao.findOneInstitutionById(institutionId); if (institution != null) { + // Must be a valid institution to trigger the shortcut SSO mode institutionList.add(institution); + isShortcutSso = true; } else { institutionList = jpaOsfDao.findAllInstitutions(); } } final Map institutionLoginUrlMap = new HashMap<>(); for (final OsfInstitution institution: institutionList) { + final SsoAvailability ssoAvailability = institution.getSsoAvailability(); + if (ssoAvailability == null) { + // Catch a rare exception case where OSF DB has changed the choices of the field + // `sso_availability` in table `osf_institution` without syncing with CAS. + LOGGER.error( + "Skip instn with invalid SSO avail: [instnId={}]", + institution.getInstitutionId() + ); + continue; + } + if (isShortcutSso && ssoAvailability.isHidden()) { + // Show institutions of hidden SSO Availability in shortcut mode + LOGGER.debug( + "Show instn with hidden SSO avail in shortcut mode: [instnId={}, avail={}]", + institution.getInstitutionId(), + ssoAvailability.getId() + ); + } else if (!ssoAvailability.isPublic()) { + // Hide institutions of non-public SSO Availability + LOGGER.debug( + "Skip instn with non-public SSO avail: [instnId={}, avail={}]", + institution.getInstitutionId(), + ssoAvailability.getId() + ); + continue; + } final DelegationProtocol delegationProtocol = institution.getDelegationProtocol(); if (DelegationProtocol.SAML_SHIB.equals(delegationProtocol)) { institutionLoginUrlMap.put( @@ -70,6 +115,12 @@ public static Map getInstitutionLoginUrlMap( return institutionLoginUrlMap; } + /** + * A helper method that sort a map by value instead of key. + * + * @param map the map to sort by value + * @return the sorted map + */ public static > Map sortByValue(final Map map) { final List> list = new LinkedList<>(map.entrySet()); Collections.sort(list, new Comparator>() { diff --git a/src/main/java/io/cos/cas/osf/authentication/support/SsoAvailability.java b/src/main/java/io/cos/cas/osf/authentication/support/SsoAvailability.java new file mode 100644 index 00000000..fbcfd982 --- /dev/null +++ b/src/main/java/io/cos/cas/osf/authentication/support/SsoAvailability.java @@ -0,0 +1,64 @@ +package io.cos.cas.osf.authentication.support; + +/** + * This is {@link SsoAvailability}, which is used in {@link io.cos.cas.osf.model.OsfInstitution} + * to map to the types/choices of its counterpart in the OSF model. + * + * @author Longze Chen + * @since 26.1.0 + */ +public enum SsoAvailability { + + /** + * The institution is active, has a delegation protocol, and its SSO setup has been verified. + * */ + PUBLIC("Public"), + /** + * The institution is either: 1) inactive and has a delegation protocol, + * or 2) active, has a delegation protocol but its SSO setup is in-progress. + */ + HIDDEN("Hidden"), + /** + * The institution does not have a delegation protocol (i.e. not eligible for SSO). + */ + UNAVAILABLE("Unavailable"); + + private final String id; + + SsoAvailability(final String id) { + this.id = id; + } + + public static SsoAvailability getType(final String id) throws IllegalArgumentException { + if (id == null) { + return null; + } + for (final SsoAvailability type : SsoAvailability.values()) { + if (id.equals(type.getId())) { + return type; + } + } + throw new IllegalArgumentException("No matching type for id " + id); + } + + /** + * @return whether the enum type is {@link SsoAvailability#PUBLIC}. + */ + public boolean isPublic () { + return SsoAvailability.PUBLIC.equals(this); + } + + /** + * @return whether the enum type is {@link SsoAvailability#HIDDEN}. + */ + public boolean isHidden () { + return SsoAvailability.HIDDEN.equals(this); + } + + /** + * @return the enum type string + */ + public final String getId() { + return id; + } +} diff --git a/src/main/java/io/cos/cas/osf/model/OsfInstitution.java b/src/main/java/io/cos/cas/osf/model/OsfInstitution.java index 15ad821d..bf44ef95 100644 --- a/src/main/java/io/cos/cas/osf/model/OsfInstitution.java +++ b/src/main/java/io/cos/cas/osf/model/OsfInstitution.java @@ -1,6 +1,7 @@ package io.cos.cas.osf.model; import io.cos.cas.osf.authentication.support.DelegationProtocol; +import io.cos.cas.osf.authentication.support.SsoAvailability; import lombok.Getter; import lombok.NoArgsConstructor; @@ -14,7 +15,8 @@ import java.util.Date; /** - * This is {@link OsfInstitution}. + * This is {@link OsfInstitution}. It maps to a subset of columns in the OSF DB table {@code osf_instittuion}. + * This subset is required to support institution SSO for CAS and OSF. * * @author Longze Chen * @since 21.0.0 @@ -40,9 +42,18 @@ public class OsfInstitution extends AbstractOsfModel { @Column(name = "logout_url") private String logoutUrl; + /** + * Maps to column {@code delegation_protocol} of table {@code osf_instittuion} in OSF database. + */ @Column(name = "delegation_protocol") private String delegationProtocol; + /** + * Maps to column {@code sso_availability} of table {@code osf_instittuion} in OSF database. + */ + @Column(name = "sso_availability") + private String ssoAvailability; + @Column(name = "is_deleted") private Boolean deleted; @@ -53,6 +64,9 @@ public class OsfInstitution extends AbstractOsfModel { @Column(name = "support_email", nullable = false) private String supportEmail; + /** + * @return the institution's delegation protocol. + */ public DelegationProtocol getDelegationProtocol() { try { return DelegationProtocol.getType(delegationProtocol); @@ -60,4 +74,15 @@ public DelegationProtocol getDelegationProtocol() { return null; } } + + /** + * @return the institution's SSO Availability. + */ + public SsoAvailability getSsoAvailability() { + try { + return SsoAvailability.getType(ssoAvailability); + } catch (final IllegalArgumentException e) { + return null; + } + } } diff --git a/src/main/java/io/cos/cas/osf/web/flow/login/OsfAbstractLoginPreparationAction.java b/src/main/java/io/cos/cas/osf/web/flow/login/OsfAbstractLoginPreparationAction.java index 9751e49f..bc72df02 100644 --- a/src/main/java/io/cos/cas/osf/web/flow/login/OsfAbstractLoginPreparationAction.java +++ b/src/main/java/io/cos/cas/osf/web/flow/login/OsfAbstractLoginPreparationAction.java @@ -27,8 +27,6 @@ public abstract class OsfAbstractLoginPreparationAction extends AbstractAuthenti protected static final String PARAMETER_LOGIN_CONTEXT = "osfCasLoginContext"; - protected static final String PARAMETER_SERVICE = "service"; - protected static final String PARAMETER_CAMPAIGN = "campaign"; protected static final String PARAMETER_CAMPAIGN_VALUE = "institution"; diff --git a/src/main/java/io/cos/cas/osf/web/flow/login/OsfDefaultLoginPreparationAction.java b/src/main/java/io/cos/cas/osf/web/flow/login/OsfDefaultLoginPreparationAction.java index 280718a4..f5c608b8 100644 --- a/src/main/java/io/cos/cas/osf/web/flow/login/OsfDefaultLoginPreparationAction.java +++ b/src/main/java/io/cos/cas/osf/web/flow/login/OsfDefaultLoginPreparationAction.java @@ -70,6 +70,7 @@ protected Event doExecute(RequestContext context) { loginContext = new OsfCasLoginContext( institutionLogin, institutionId, + Boolean.FALSE, StringUtils.EMPTY, unsupportedInstitutionLogin, orcidRedirect, @@ -80,6 +81,7 @@ protected Event doExecute(RequestContext context) { } else { loginContext.setInstitutionLogin(institutionLogin); loginContext.setInstitutionId(institutionId); + loginContext.setHiddenSsoAvailability(false); loginContext.setInstitutionSupportEmail(StringUtils.EMPTY); loginContext.setUnsupportedInstitutionLogin(unsupportedInstitutionLogin); loginContext.setOrcidLoginUrl(orcidLoginUrl); diff --git a/src/main/java/io/cos/cas/osf/web/flow/login/OsfInstitutionLoginPreparationAction.java b/src/main/java/io/cos/cas/osf/web/flow/login/OsfInstitutionLoginPreparationAction.java index 142b1785..3624881f 100644 --- a/src/main/java/io/cos/cas/osf/web/flow/login/OsfInstitutionLoginPreparationAction.java +++ b/src/main/java/io/cos/cas/osf/web/flow/login/OsfInstitutionLoginPreparationAction.java @@ -2,6 +2,7 @@ import io.cos.cas.osf.authentication.support.OsfInstitutionUtils; import io.cos.cas.osf.dao.JpaOsfDao; +import io.cos.cas.osf.model.OsfInstitution; import io.cos.cas.osf.web.support.OsfCasLoginContext; import lombok.extern.slf4j.Slf4j; @@ -76,16 +77,20 @@ protected Event doExecute(RequestContext context) { -> (OsfCasLoginContext) requestContext.getFlowScope().get(PARAMETER_LOGIN_CONTEXT)).orElse(null); if (loginContext != null) { institutionId = loginContext.getInstitutionId(); - if (!OsfInstitutionUtils.validateInstitutionForLogin(jpaOsfDao, institutionId)) { + final OsfInstitution institution = jpaOsfDao.findOneInstitutionById(institutionId); + if (!OsfInstitutionUtils.validateInstitutionForLogin(institution)) { loginContext.setInstitutionId(null); - context.getFlowScope().put(PARAMETER_LOGIN_CONTEXT, loginContext); institutionId = null; } else { - final String institutionSupportEmail = OsfInstitutionUtils.getInstitutionSupportEmail(jpaOsfDao, institutionId); + final String institutionSupportEmail = institution.getSupportEmail(); if (institutionSupportEmail != null) { loginContext.setInstitutionSupportEmail(institutionSupportEmail); } + if (institution.getSsoAvailability().isHidden()) { + loginContext.setHiddenSsoAvailability(true); + } } + context.getFlowScope().put(PARAMETER_LOGIN_CONTEXT, loginContext); } final Map institutionLoginUrlMap diff --git a/src/main/java/io/cos/cas/osf/web/support/OsfCasLoginContext.java b/src/main/java/io/cos/cas/osf/web/support/OsfCasLoginContext.java index d0679497..525f762b 100644 --- a/src/main/java/io/cos/cas/osf/web/support/OsfCasLoginContext.java +++ b/src/main/java/io/cos/cas/osf/web/support/OsfCasLoginContext.java @@ -9,10 +9,8 @@ import java.io.Serializable; /** - * This is {@link OsfCasLoginContext}. - * - * Stores OSF-specific information about the current web flow. Extends {@link Serializable} so that it can be put into - * and retrieved from the flow context conveniently. + * This is {@link OsfCasLoginContext}. Stores OSF-specific information about the current web flow. + * Extends {@link Serializable} so that it can be put into and retrieved from the flow context conveniently. * * @author Longze Chen * @since 20.1.0 @@ -28,8 +26,17 @@ public class OsfCasLoginContext implements Serializable { private boolean institutionLogin; + /** + * A verified institution ID. Its being present allows web flow to handle shortcut SSO mode. + */ private String institutionId; + /** + * Indicates whether the institution with {@link this#institutionId} has hidden SSO availability, + * which allows web flow to handle such institutions properly in shortcut SSO mode. + */ + private boolean hiddenSsoAvailability; + private String institutionSupportEmail; private boolean unsupportedInstitutionLogin; @@ -42,7 +49,6 @@ public class OsfCasLoginContext implements Serializable { /** * The default service URL that uses OSF login endpoint with OSF home as destination. - * * e.g. http(s)://[OSF Domain]/login?next=[encoded version of http(s)://[OSF Domain]/] */ private String defaultServiceUrl; diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 25aa8bb5..a6a68470 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -611,6 +611,7 @@ screen.institutionlogin.message.auto=Your institution has partnered with OSF. Pl screen.institutionlogin.heading.select=Your institution screen.institutionlogin.heading.auto=Your institution screen.institutionlogin.link.select=Not your institution? +screen.institutionlogin.link.hidden=Note: Your institution is currently being set up and may not be available. Please check back at a later date. screen.institutionlogin.link.unsupported=I can't find my institution screen.institutionlogin.button.submit=Sign in screen.institutionlogin.osf=Sign in with email @@ -672,7 +673,6 @@ screen.error.page.loginagain=Log in again screen.authnerror.title=Login Error screen.authnerror.tips=Oops! Something went wrong ... screen.authnerror.tips.devmode=Developer mode only !!! -screen.authnerror.button.resendosfconfirmation=Resend confirmation email screen.authnerror.button.backtoosf=Exit login screen.authnerror.button.logout=Log out screen.blocked.message=You are being throttled for attempting to login too frequently in a short amount of time. \ @@ -688,7 +688,8 @@ screen.accountnotconfirmedidp.message=The OSF account associated with the email "mailto:support@osf.io">OSF Support. screen.accountnotconfirmedosf.heading=Account not confirmed screen.accountnotconfirmedosf.message=The OSF account associated with the email has been registered but not confirmed. \ - Please check your email (and spam folder) or click the button below to resend your confirmation email. + Please check your email (and spam folder) or contact OSF \ + Support. screen.servererror.title=Server Error screen.unavailable.heading=CAS unavailable screen.unavailable.message=Your request cannot be completed at this time. Please return to OSF and try again later. If \ diff --git a/src/main/resources/templates/casAccountNotConfirmedIdPView.html b/src/main/resources/templates/casAccountNotConfirmedIdPView.html index c9156b8b..a021a176 100644 --- a/src/main/resources/templates/casAccountNotConfirmedIdPView.html +++ b/src/main/resources/templates/casAccountNotConfirmedIdPView.html @@ -18,11 +18,6 @@

-
- - - -

diff --git a/src/main/resources/templates/casAccountNotConfirmedOsfView.html b/src/main/resources/templates/casAccountNotConfirmedOsfView.html index f9564799..9beeb0c2 100644 --- a/src/main/resources/templates/casAccountNotConfirmedOsfView.html +++ b/src/main/resources/templates/casAccountNotConfirmedOsfView.html @@ -18,11 +18,6 @@

-
- - - -

diff --git a/src/main/resources/templates/casInstitutionLoginView.html b/src/main/resources/templates/casInstitutionLoginView.html index bdf9f08e..f482072b 100644 --- a/src/main/resources/templates/casInstitutionLoginView.html +++ b/src/main/resources/templates/casInstitutionLoginView.html @@ -31,6 +31,10 @@

+
+

+
+