Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ jobs:
with:
java-version: 11
- name: Cache SonarCloud packages
uses: actions/cache@v1
uses: actions/cache@v4
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- name: Cache Gradle packages
uses: actions/cache@v1
uses: actions/cache@v4
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.cos.cas.osf.authentication.exception;

import lombok.NoArgsConstructor;

import javax.security.auth.login.AccountException;

/**
* Describes an authentication error condition where institution SSO has failed
* due to OSF API not supporting multiple SSO emails.
*
* @author Longze Chen
* @since 25.0.0
*/
@NoArgsConstructor
public class InstitutionSsoMultipleEmailsNotSupportedException extends AccountException {

/**
* Serialization metadata.
*/
private static final long serialVersionUID = -7703550523317297865L;

/**
* Instantiates a new {@link InstitutionSsoMultipleEmailsNotSupportedException}.
*
* @param msg the msg
*/
public InstitutionSsoMultipleEmailsNotSupportedException(final String msg) {
super(msg);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ public enum OsfApiPermissionDenied {

DEFAULT("PermissionDenied"),

INSTITUTION_SSO_ACCOUNT_INACTIVE("InstitutionSsoAccountInactive"),

INSTITUTION_SSO_DUPLICATE_IDENTITY("InstitutionSsoDuplicateIdentity"),

INSTITUTION_SSO_ACCOUNT_INACTIVE("InstitutionSsoAccountInactive"),
INSTITUTION_SSO_MULTIPLE_EMAILS_NOT_SUPPORTED("InstitutionSsoMultipleEmailsNotSupported"),

INSTITUTION_SSO_SELECTIVE_LOGIN_DENIED("InstitutionSsoSelectiveLoginDenied");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import io.cos.cas.osf.authentication.exception.InstitutionSsoAttributeParsingException;
import io.cos.cas.osf.authentication.exception.InstitutionSsoDuplicateIdentityException;
import io.cos.cas.osf.authentication.exception.InstitutionSsoFailedException;
import io.cos.cas.osf.authentication.exception.InstitutionSsoMultipleEmailsNotSupportedException;
import io.cos.cas.osf.authentication.exception.InstitutionSsoOsfApiFailedException;
import io.cos.cas.osf.authentication.exception.InstitutionSsoSelectiveLoginDeniedException;
import io.cos.cas.osf.authentication.exception.InvalidOneTimePasswordException;
Expand Down Expand Up @@ -53,6 +54,7 @@ public Set<Class<? extends Throwable>> handledAuthenticationExceptions() {
errors.add(InstitutionSsoAttributeParsingException.class);
errors.add(InstitutionSsoDuplicateIdentityException.class);
errors.add(InstitutionSsoFailedException.class);
errors.add(InstitutionSsoMultipleEmailsNotSupportedException.class);
errors.add(InstitutionSsoOsfApiFailedException.class);
errors.add(InstitutionSsoSelectiveLoginDeniedException.class);
errors.add(InvalidOneTimePasswordException.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import io.cos.cas.osf.authentication.exception.InstitutionSsoAttributeParsingException;
import io.cos.cas.osf.authentication.exception.InstitutionSsoDuplicateIdentityException;
import io.cos.cas.osf.authentication.exception.InstitutionSsoFailedException;
import io.cos.cas.osf.authentication.exception.InstitutionSsoMultipleEmailsNotSupportedException;
import io.cos.cas.osf.authentication.exception.InstitutionSsoOsfApiFailedException;
import io.cos.cas.osf.authentication.exception.InstitutionSsoSelectiveLoginDeniedException;
import io.cos.cas.osf.authentication.exception.InvalidOneTimePasswordException;
Expand Down Expand Up @@ -270,6 +271,11 @@ protected void createHandleAuthenticationFailureAction(final Flow flow) {
InstitutionSsoSelectiveLoginDeniedException.class.getSimpleName(),
OsfCasWebflowConstants.VIEW_ID_INSTITUTION_SSO_SELECTIVE_LOGIN_DENIED
);
createTransitionForState(
handler,
InstitutionSsoMultipleEmailsNotSupportedException.class.getSimpleName(),
OsfCasWebflowConstants.VIEW_ID_INSTITUTION_SSO_MULTIPLE_EMAILS_NOT_SUPPORTED
);
createTransitionForState(
handler,
InvalidUserStatusException.class.getSimpleName(),
Expand Down Expand Up @@ -470,6 +476,11 @@ private void createOsfCasAuthenticationExceptionViewStates(final Flow flow) {
OsfCasWebflowConstants.VIEW_ID_INSTITUTION_SSO_SELECTIVE_LOGIN_DENIED,
OsfCasWebflowConstants.VIEW_ID_INSTITUTION_SSO_SELECTIVE_LOGIN_DENIED
);
createViewState(
flow,
OsfCasWebflowConstants.VIEW_ID_INSTITUTION_SSO_MULTIPLE_EMAILS_NOT_SUPPORTED,
OsfCasWebflowConstants.VIEW_ID_INSTITUTION_SSO_MULTIPLE_EMAILS_NOT_SUPPORTED
);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import io.cos.cas.osf.authentication.exception.InstitutionSsoAttributeParsingException;
import io.cos.cas.osf.authentication.exception.InstitutionSsoDuplicateIdentityException;
import io.cos.cas.osf.authentication.exception.InstitutionSsoFailedException;
import io.cos.cas.osf.authentication.exception.InstitutionSsoMultipleEmailsNotSupportedException;
import io.cos.cas.osf.authentication.exception.InstitutionSsoSelectiveLoginDeniedException;
import io.cos.cas.osf.authentication.exception.InstitutionSsoOsfApiFailedException;
import io.cos.cas.osf.authentication.support.DelegationProtocol;
Expand Down Expand Up @@ -791,6 +792,10 @@ private OsfApiInstitutionAuthenticationResult notifyOsfApiOfInstnAuthnSuccess(
LOGGER.error("[OSF API] Failure - Inactive Account: {}", ssoUser);
throw new InstitutionSsoAccountInactiveException("OSF API denies inactive account");
}
if (OsfApiPermissionDenied.INSTITUTION_SSO_MULTIPLE_EMAILS_NOT_SUPPORTED.getId().equals(errorDetail)) {
LOGGER.error("[OSF API] Failure - Multiple SSO Emails Error: {}", ssoUser);
throw new InstitutionSsoMultipleEmailsNotSupportedException("OSF API can't process multiple SSO emails");
}
}
// Handle unidentified HTTP 403 FORBIDDEN failures
LOGGER.error("[OSF API] Failure - HTTP 403 FORBIDDEN: {}, statusCode={}", ssoUser, statusCode);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ public interface OsfCasWebflowConstants {

String VIEW_ID_INSTITUTION_SSO_SELECTIVE_LOGIN_DENIED = "casInstitutionSsoSelectiveLoginDeniedView";

String VIEW_ID_INSTITUTION_SSO_MULTIPLE_EMAILS_NOT_SUPPORTED = "casInstitutionSsoMultipleEmailsNotSupportedView";

// Exception Views for OAuth 2.0 Authorization Flow

String VIEW_ID_OAUTH_20_ERROR_VIEW = "casOAuth20ErrorView";
Expand Down
5 changes: 5 additions & 0 deletions src/main/resources/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,11 @@ screen.institutionssoosfapifailed.message=\
Your request cannot be completed at this time due to an unexpected error. \
Please <a style="white-space: nowrap" href="{0}">return to OSF</a> and try again later. \
If the issue persists, contact <a style="white-space: nowrap" href="mailto:support@osf.io">Support</a> for help.
screen.institutionssomultipleemailsnotsupported.message=\
Your request cannot be completed at this time. \
The system received multiple SSO emails from your institution. \
Please <a style="white-space: nowrap" href="{0}">return to OSF</a> and try again later. \
If the issue persists, contact <a style="white-space: nowrap" href="mailto:support@osf.io">Support</a> for help.

#
# OAuth 2.0 Views and Error Views
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layoutosf}">

<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />

<title th:text="#{screen.institutionssofailed.title}"></title>
<link href="../../static/css/cas.css" rel="stylesheet" th:remove="tag" />
</head>

<body class="mdc-typography">
<div layout:fragment="content" class="d-flex justify-content-center">

<div class="d-flex justify-content-center flex-md-row flex-column mdc-card mdc-card-content w-lg-30">
<section class="login-error-card">
<section>
<div th:replace="fragments/osfbannerui :: osfBannerUI">
<a href="fragments/osfbannerui.html"></a>
</div>
</section>
<section class="text-without-mdi text-center text-bold text-large margin-large-vertical title-danger">
<span th:utext="#{screen.authnerror.tips}"></span>
</section>
<hr class="my-4" />
<section class="card-message">
<h1 th:utext="#{screen.institutionssofailed.heading}"></h1>
<p th:utext="#{screen.institutionssomultipleemailsnotsupported.message}"></p>
</section>
<section class="form-button">
<a class="mdc-button mdc-button--raised button-osf-blue" th:href="@{/logout(service=${osfUrl.logout})}">
<span class="mdc-button__label" th:utext="#{screen.authnerror.button.backtoosf}"></span>
</a>
</section>
<hr class="my-4" />
<section class="text-with-mdi" th:with="loginUrl=@{${@casServerLoginUrl}(casRedirectSource=cas)}">
<span><a th:href="@{/logout(service=${loginUrl})}" th:utext="#{screen.error.page.loginagain}"></a></span>
</section>
</section>
</div>

<script type="text/javascript">
disableSignUpButton();
</script>

</div>
</body>

</html>
Loading