Skip to content

Commit

Permalink
Merge branch 'release/20.2.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
cslzchen committed Jun 4, 2020
2 parents cdcd0d7 + c8b5736 commit 656e169
Show file tree
Hide file tree
Showing 14 changed files with 190 additions and 49 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

We follow the CalVer (https://calver.org/) versioning scheme: YY.MINOR.MICRO.

20.2.0 (2020-06-04)
===================

- Added automatic institution selection
- Fixed branded sign-in for frenxiv
- Fixed heading for authorization failure page
- Improved generic login success page

20.1.0 (2020-04-14)
===================

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
*
* @author Michael Haselton
* @author Longze Chen
* @since 19.0.0
* @since 19.3.0
*/
public class OpenScienceFrameworkAuthenticationHandler extends AbstractPreAndPostProcessingAuthenticationHandler
implements InitializingBean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,18 @@
import io.cos.cas.adaptors.postgres.models.OpenScienceFrameworkInstitution;
import io.cos.cas.adaptors.postgres.types.DelegationProtocol;

import javax.validation.constraints.NotNull;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.validation.constraints.NotNull;

/**
* The Open Science Framework Institution Handler.
*
* @author Longze Chen
* @since 4.1.5
* @since 19.3.0
*/
public class OpenScienceFrameworkInstitutionHandler {

Expand All @@ -45,10 +47,23 @@ public void setOpenScienceFrameworkDao(final OpenScienceFrameworkDaoImpl openSci
this.openScienceFrameworkDao = openScienceFrameworkDao;
}

/**
* Check if an institution of a given ID exists and supports SSO.
*
* @param id the OSF institution ID that identifies the institution
* @return <code>true</code> if exists and <code>false </code> otherwise
*/
public boolean validateInstitutionForLogin(final String id) {

final OpenScienceFrameworkInstitution institution
= openScienceFrameworkDao.findOneInstitutionById(id);
return institution != null && institution.getDelegationProtocol() != null;
}

/**
* Find the logout url for given institution identified by institution _id.
*
* @param id the institution _id
* @param id the OSF institution ID that identifies the institution
* @return String or null
*/
public String findInstitutionLogoutUrlById(final String id) {
Expand All @@ -57,27 +72,41 @@ public String findInstitutionLogoutUrlById(final String id) {
}

/**
* Return a map of institution login url and institution name.
* 1. The "name" is the value instead of the key.
* 2. For institutions authenticated through "cas-pac4j", the institution id replaces the login url,
* whose actual login url is generated during flow "client action".
* <p>Return a map of institution login url or institution ID as key and institution name as value.</p>
*
* <p>For saml-shib institutions, the login url is the key and the institution display name is the value.</p>
*
* @param target The osf service target after successful institution login (only for "saml-shib" institutions)
* @return Map.
* For "saml-shib", full login url as key and full institution display name as value;
* For "cas-pac4j", institution id as key and full institution display name as value;
* <p>For cas-pac4j institutions, the institution ID is the key and the institution display name is the value. </p>
*
* @param target the osf service target after successful institution login, only used for "saml-shib" institutions
* @param id the OSF institution ID if auto-selection is enabled
* @return a single-entry {@link Map} if <code>id</code> presents and if the institution this <code>id</code>
* identifies exists and supports institution SSO; otherwise return a multi-entry {@link Map} of all.
*/
public Map<String, String> getInstitutionLoginUrls(final String target) {
final List<OpenScienceFrameworkInstitution> institutionList = openScienceFrameworkDao.findAllInstitutions();
final Map<String, String> institutionLogin = new HashMap<>();
public Map<String, String> getInstitutionLoginUrlMap(final String target, final String id) {

List<OpenScienceFrameworkInstitution> institutionList = new LinkedList<>();
if (id == null || id.isEmpty()) {
institutionList = openScienceFrameworkDao.findAllInstitutions();
} else {
final OpenScienceFrameworkInstitution institution
= openScienceFrameworkDao.findOneInstitutionById(id);
if (institution != null) {
institutionList.add(institution);
} else {
institutionList = openScienceFrameworkDao.findAllInstitutions();
}
}

final Map<String, String> institutionLoginUrlMap = new HashMap<>();
for (final OpenScienceFrameworkInstitution institution: institutionList) {
final DelegationProtocol delegationProtocol = institution.getDelegationProtocol();
if (DelegationProtocol.SAML_SHIB.equals(delegationProtocol)) {
institutionLogin.put(institution.getLoginUrl() + "&target=" + target, institution.getName());
institutionLoginUrlMap.put(institution.getLoginUrl() + "&target=" + target, institution.getName());
} else if (DelegationProtocol.CAS_PAC4J.equals(delegationProtocol)) {
institutionLogin.put(institution.getId(), institution.getName());
institutionLoginUrlMap.put(institution.getId(), institution.getName());
}
}
return institutionLogin;
return institutionLoginUrlMap;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
*
* @author Michael Haselton
* @author Longze Chen
* @since 19.0.0
* @since 19.3.0
*/
@Entity
@Table(name = "osf_osfuser")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
*
* @author Michael Haselton
* @author Longze Chen
* @since 19.0.0
* @since 19.3.0
*/
public class OpenScienceFrameworkAuthenticationExceptionHandler extends AuthenticationExceptionHandler {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package io.cos.cas.web.flow;

import io.cos.cas.adaptors.postgres.handlers.OpenScienceFrameworkInstitutionHandler;

import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;

Expand All @@ -32,7 +33,7 @@
* Open Science Framework Institution Login Handler.
*
* @author Longze Chen
* @since 4.1.5
* @since 19.3.0
*/
public class OpenScienceFrameworkInstitutionLoginHandler {

Expand Down Expand Up @@ -68,10 +69,33 @@ public Event getInstitutions(final RequestContext context) {
throw new AssertionError("UTF-8 is unknown");
}

final Map<String, String> institutions = this.institutionHandler.getInstitutionLoginUrls(target);
institutions.put("", " -- select an institution -- ");
final Map<String, String> sortedInstitutions = sortByValue(institutions);
context.getFlowScope().put("institutions", sortedInstitutions);
// Retrieve institution ID from flow context instead of URL params
String institutionId = null;
final String loginContext = (String) context.getFlowScope().get("jsonLoginContext");
final OpenScienceFrameworkLoginHandler.OpenScienceFrameworkLoginContext osfLoginContext
= OpenScienceFrameworkLoginHandler.OpenScienceFrameworkLoginContext.fromJson(loginContext);
if (osfLoginContext != null) {
institutionId = osfLoginContext.getInstitutionId();
// Set institution ID to null if not found
if (!this.institutionHandler.validateInstitutionForLogin(institutionId)) {
osfLoginContext.setInstitutionId(null);
context.getFlowScope().put("jsonLoginContext", osfLoginContext.toJson());
institutionId = null;
}
}

final Map<String, String> institutionLoginUrlMap
= this.institutionHandler.getInstitutionLoginUrlMap(target, institutionId);
final Map<String, String> institutionLoginUrlMapSorted;
if (institutionId != null) {
// One institution (a.k.a. auto selecting the preferred institution)
institutionLoginUrlMapSorted = institutionLoginUrlMap;
} else {
// All institutions with pending selection if auto selection is disabled or turns out to be invalid
institutionLoginUrlMap.put("", " -- select an institution -- ");
institutionLoginUrlMapSorted = sortByValue(institutionLoginUrlMap);
}
context.getFlowScope().put("institutions", institutionLoginUrlMapSorted);
return new Event(this, "success");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
* Open Science Framework Login Handler.
*
* @author Longze Chen
* @since 4.1.5
* @since 19.3.0
*/
public class OpenScienceFrameworkLoginHandler {

Expand All @@ -48,6 +48,9 @@ public static final class OpenScienceFrameworkLoginContext {
/** The flag for institution login instead of normal OSF login. */
private boolean institutionLogin;

/** The OSF institution ID for an auto-selected institution. */
private String institutionId;

/** The flag for redirect to ORCiD login instead of normal OSF login. */
private boolean orcidRedirect;

Expand All @@ -56,16 +59,19 @@ public static final class OpenScienceFrameworkLoginContext {
*
* @param serviceUrl the service URL
* @param institutionLogin the flag for institution login
* @param institutionId the auto-selected institution ID
* @param orcidRedirect the flag for ORCiD redirect
*/
private OpenScienceFrameworkLoginContext(
final String serviceUrl,
final boolean institutionLogin,
final String institutionId,
final boolean orcidRedirect
) {
this.serviceUrl = serviceUrl;
this.handleErrorName = null;
this.institutionLogin = institutionLogin;
this.institutionId = institutionId;
this.orcidRedirect = orcidRedirect;
}

Expand Down Expand Up @@ -95,6 +101,15 @@ void setInstitutionLogin(final boolean institutionLogin) {
this.institutionLogin = institutionLogin;
}

// Must be public to be accessible in the JSP page
public String getInstitutionId() {
return institutionId;
}

void setInstitutionId(final String institutionId) {
this.institutionId = institutionId;
}

boolean isOrcidRedirect() {
return orcidRedirect;
}
Expand Down Expand Up @@ -143,20 +158,27 @@ public static OpenScienceFrameworkLoginContext fromJson(final String jsonString)
public Event beforeLogin(final RequestContext context) {

final OpenScienceFrameworkLoginContext osfLoginContext;
final String serviceUrl = getEncodedServiceUrl(context);
final String serviceUrl = getEncodedServiceUrlFromRequestContext(context);
final boolean institutionLogin = isInstitutionLogin(context);
final boolean orcidRedirect = isOrcidRedirect(context);
final String institutionId = getInstitutionIdFromRequestContext(context);
final boolean orcidRedirect = checkOrcidRedirectFromRequestContext(context);

String jsonLoginContext = (String) context.getFlowScope().get("jsonLoginContext");
if (jsonLoginContext == null) {
// Create a new login context with service URL, institution login and ORCiD redirect flags
osfLoginContext = new OpenScienceFrameworkLoginContext(serviceUrl, institutionLogin, orcidRedirect);
osfLoginContext = new OpenScienceFrameworkLoginContext(
serviceUrl,
institutionLogin,
institutionId,
orcidRedirect
);
} else {
// If the login context already exists, update the service URL and the institution login flag while keeping
// the errors and disabling ORCiD login redirect
osfLoginContext = OpenScienceFrameworkLoginContext.fromJson(jsonLoginContext);
osfLoginContext.setServiceUrl(serviceUrl);
osfLoginContext.setInstitutionLogin(institutionLogin);
osfLoginContext.setInstitutionId(institutionId);
// Only allow ORCiD login redirect from a brand new login flow
osfLoginContext.setOrcidRedirect(false);
}
Expand Down Expand Up @@ -187,13 +209,24 @@ private boolean isInstitutionLogin(final RequestContext context) {
return campaign != null && "institution".equals(campaign.toLowerCase());
}

/**
* Obtain the institution ID in the request parameters for auto institution selection.
*
* @param context the request context
* @return the institution ID
*/
private String getInstitutionIdFromRequestContext(final RequestContext context) {
final String institutionId = context.getRequestParameters().get("institutionId");
return (institutionId == null || institutionId.isEmpty()) ? null : institutionId;
}

/**
* Check if the request is ORCiD login redirect.
*
* @param context the request context
* @return true if `redirectOrcid=true` is present in the request parameters
*/
private boolean isOrcidRedirect(final RequestContext context) {
private boolean checkOrcidRedirectFromRequestContext(final RequestContext context) {
final String orcidRedirect = context.getRequestParameters().get("redirectOrcid");
return orcidRedirect != null && "true".equals(orcidRedirect.toLowerCase());
}
Expand All @@ -208,7 +241,7 @@ private boolean isOrcidRedirect(final RequestContext context) {
* @return the encoded service url
* @throws AssertionError if encoding fails
*/
private String getEncodedServiceUrl(final RequestContext context) throws AssertionError {
private String getEncodedServiceUrlFromRequestContext(final RequestContext context) throws AssertionError {
final String serviceUrl = context.getRequestParameters().get("service");
if (serviceUrl == null || serviceUrl.isEmpty()) {
return null;
Expand Down
7 changes: 5 additions & 2 deletions cas-server-webapp/src/main/resources/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,12 @@ screen.confirmation.message=Click <a href="{0}">here</a> to go to the applicatio

#Generic Success Screen Messages
screen.success.header=Log in successful
screen.success.success=You, {0}, have successfully logged into the OSF central authentication and authorization service.
screen.success.success=You, {0}, have successfully logged into the OSF CAS, the central authentication and authorization service for OSF and its services.
screen.success.security=When you are finished, for security reasons, please log out and exit your web browser.

#Logout Screen Messages
screen.logout.header=Logout successful
screen.logout.success=You have successfully logged out of the OSF central authentication and authorization service.
screen.logout.success=You have successfully logged out of the OSF CAS, the central authentication and authorization service for OSF and its services.
screen.logout.security=For security reasons, exit your web browser.
screen.logout.redirect=The service from which you arrived has supplied a <a href="{0}">link you may follow by clicking here</a>.

Expand Down Expand Up @@ -179,6 +179,8 @@ screen.unavailable.message=There was an error trying to complete your request.
# Institution
screen.institution.login.heading=Sign in through institution
screen.institution.login.select=Select your institution
screen.institution.login.select.auto=Your institution
screen.institution.login.select.all=Not your institution?
screen.institution.login.message=If your institution has partnered with OSF, please select its name below and sign in with your institutional credentials.<br/><br/>If you do not currently have an OSF account, this will create one for you.
screen.institution.login.osf=Log in with OSF
screen.institution.login.consent.checkbox=I have read and agree to the <a style="white-space: nowrap" href=https://github.com/CenterForOpenScience/centerforopenscience.org/blob/master/TERMS_OF_USE.md>Terms of Use</a> and <a style="white-space: nowrap" href=https://github.com/CenterForOpenScience/centerforopenscience.org/blob/master/PRIVACY_POLICY.md>Privacy Policy</a>.
Expand All @@ -187,6 +189,7 @@ screen.institution.login.select.error.message=You must select an institution.

# Sign in through OSF
screen.osf.login.message=Sign in with your OSF account to continue
screen.osf.login.message.continue=<a style=\\\"white-space: nowrap\\\" href=\\\"{0}\\\">Continue to OSF</a>
screen.osf.login.message.error=Oops! Something went wrong ...

# Two-factor Authentication
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,17 @@
<p><spring:message code="screen.success.security"/></p>
</div>

<spring:message code="screen.osf.login.message.continue" arguments="${osfUrl}" var="successDescription"/>
<script>
description = document.getElementById("description");
if (description != null) {
description.innerHTML = "<br><br>${successDescription}";
}
</script>

<c:set var="linkSignIn" value="false"/>
<c:set var="linkSignOut" value="true"/>
<c:set var="linkCreateAccount" value="false"/>
<c:set var="linkBackToOsf" value="true"/>
<c:set var="linkBackToOsf" value="false"/>

<jsp:directive.include file="includes/bottom.jsp"/>
Loading

0 comments on commit 656e169

Please sign in to comment.