Skip to content

Commit efb0e3c

Browse files
committed
Refactor institution SSO exceptior/error/failure handling
* Attribute exceptions: missing or invalid attributes, parsing or normalizationfailures, etc. * API exceptions: payload encryption/encoding, network/communication, expected and unexpected response, etc. * Special exceptions: selective SSO denied, invalid account status, duplicate identity, etc. * Generic exception: the rest
1 parent cebe0fa commit efb0e3c

2 files changed

Lines changed: 34 additions & 19 deletions

File tree

src/main/java/io/cos/cas/osf/authentication/support/OsfApiPermissionDenied.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ public enum OsfApiPermissionDenied {
1010

1111
DEFAULT("PermissionDenied"),
1212

13-
INSTITUTION_SELECTIVE_SSO_FAILURE("InstitutionSsoSelectiveNotAllowed");
13+
INSTITUTION_SSO_DUPLICATE_IDENTITY("InstitutionSsoDuplicateIdentity"),
14+
15+
INSTITUTION_SSO_ACCOUNT_INACTIVE("InstitutionSsoAccountInactive"),
16+
17+
INSTITUTION_SSO_SELECTIVE_LOGIN_DENIED("InstitutionSsoSelectiveLoginDenied");
1418

1519
private final String id;
1620

src/main/java/io/cos/cas/osf/web/flow/login/OsfPrincipalFromNonInteractiveCredentialsAction.java

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@
77
import com.google.gson.JsonParser;
88

99
import io.cos.cas.osf.authentication.credential.OsfPostgresCredential;
10-
import io.cos.cas.osf.authentication.exception.InstitutionSelectiveSsoFailedException;
10+
import io.cos.cas.osf.authentication.exception.InstitutionSsoAttributeMissingException;
11+
import io.cos.cas.osf.authentication.exception.InstitutionSsoAttributeParsingException;
12+
import io.cos.cas.osf.authentication.exception.InstitutionSsoDuplicateIdentityException;
1113
import io.cos.cas.osf.authentication.exception.InstitutionSsoFailedException;
12-
import io.cos.cas.osf.authentication.exception.InstitutionSsoOsfApiFailureException;
14+
import io.cos.cas.osf.authentication.exception.InstitutionSsoSelectiveLoginDeniedException;
15+
import io.cos.cas.osf.authentication.exception.InstitutionSsoOsfApiFailedException;
1316
import io.cos.cas.osf.authentication.support.DelegationProtocol;
1417
import io.cos.cas.osf.authentication.support.OsfApiPermissionDenied;
1518
import io.cos.cas.osf.configuration.model.OsfApiProperties;
@@ -574,23 +577,23 @@ private OsfApiInstitutionAuthenticationResult notifyOsfApiOfInstnAuthnSuccess(
574577
normalizedPayload = extractInstnAuthnDataFromCredential(credential);
575578
} catch (final ParserConfigurationException | TransformerException e) {
576579
LOGGER.error("[CAS XSLT] Exception - Failed to normalize attributes in the credential: {}", e.getMessage());
577-
throw new InstitutionSsoFailedException("Attribute normalization failure");
580+
throw new InstitutionSsoAttributeParsingException("Attribute normalization failure");
578581
}
579582
// Verify required and optional attributes
580583
final JSONObject provider = normalizedPayload.optJSONObject("provider");
581584
if (provider == null) {
582585
LOGGER.error("[CAS XSLT] Error - Missing identity provider.");
583-
throw new InstitutionSsoFailedException("Missing identity provider");
586+
throw new InstitutionSsoAttributeMissingException("Missing identity provider");
584587
}
585588
final String institutionId = provider.optString("id").trim();
586589
if (institutionId.isEmpty()) {
587590
LOGGER.error("[CAS XSLT] Error - Empty identity provider");
588-
throw new InstitutionSsoFailedException("Empty identity provider");
591+
throw new InstitutionSsoAttributeMissingException("Empty identity provider");
589592
}
590593
final JSONObject user = provider.optJSONObject("user");
591594
if (user == null) {
592595
LOGGER.error("[CAS XSLT] Error - Missing institutional user");
593-
throw new InstitutionSsoFailedException("Missing institutional user");
596+
throw new InstitutionSsoAttributeMissingException("Missing institutional user");
594597
}
595598
// Note: SSO Identity didn't come from the normalized attribute set but came from a dedicated Shibboleth header. It was parsed,
596599
// trimmed and stored in the credential object. Thus, it must be explicitly inserted into the payload.
@@ -609,11 +612,11 @@ private OsfApiInstitutionAuthenticationResult notifyOsfApiOfInstnAuthnSuccess(
609612
final String userRoles = user.optString("userRoles").trim();
610613
if (ssoEmail.isEmpty()) {
611614
LOGGER.error("[CAS XSLT] Error - Missing SSO Email for user: {}", ssoUser);
612-
throw new InstitutionSsoFailedException("Missing email (username)");
615+
throw new InstitutionSsoAttributeMissingException("Missing SSO Email)");
613616
}
614617
if (fullname.isEmpty() && (givenName.isEmpty() || familyName.isEmpty())) {
615618
LOGGER.error("[CAS XSLT] Error - Missing names: {}", ssoUser);
616-
throw new InstitutionSsoFailedException("Missing user's names");
619+
throw new InstitutionSsoAttributeMissingException("Missing user's names");
617620
}
618621
if (!isMemberOf.isEmpty()) {
619622
LOGGER.info("[CAS XSLT] Shared SSO \"isMemberOf\" detected: {}, isMemberOf={}", ssoUser, isMemberOf);
@@ -678,13 +681,13 @@ private OsfApiInstitutionAuthenticationResult notifyOsfApiOfInstnAuthnSuccess(
678681
jweString = jweObject.serialize();
679682
} catch (final JOSEException e) {
680683
LOGGER.error("[OSF API] Exception - Failed to construct API Payload: {}, error={}", ssoUser, e.getMessage());
681-
throw new InstitutionSsoFailedException("OSF CAS failed to build JWT / JWE payload for OSF API");
684+
throw new InstitutionSsoOsfApiFailedException("OSF CAS failed to build JWT / JWE payload for OSF API");
682685
}
683686
// Send the POST request to OSF API to verify an existing institution user or to create a new one
684687
int statusCode = -1;
685688
int retry = 0;
686689
HttpResponse httpResponse = null;
687-
InstitutionSsoOsfApiFailureException casError = null;
690+
InstitutionSsoOsfApiFailedException casError = null;
688691
while (retry < OSF_API_RETRY_LIMIT) {
689692
retry += 1;
690693
// Reset exception from previous attempt
@@ -706,19 +709,19 @@ private OsfApiInstitutionAuthenticationResult notifyOsfApiOfInstnAuthnSuccess(
706709
}
707710
if (OSF_API_RETRY_STATUS.contains(statusCode)) {
708711
LOGGER.error("[OSF API] Failure - Server Error: {}, attempt={}, status={}", ssoUser, retry, statusCode);
709-
casError = new InstitutionSsoOsfApiFailureException("Communication Error between OSF CAS and OSF API");
712+
casError = new InstitutionSsoOsfApiFailedException("Communication Error between OSF CAS and OSF API");
710713
} else {
711714
break;
712715
}
713716
} catch (final IOException e) {
714717
LOGGER.error("[OSF API] Exception - IO Exception: {}, attempt={}, error={}", ssoUser, retry, e.getMessage());
715-
casError = new InstitutionSsoOsfApiFailureException("Communication Error between OSF CAS and OSF API");
718+
casError = new InstitutionSsoOsfApiFailedException("Communication Error between OSF CAS and OSF API");
716719
}
717720
try {
718721
TimeUnit.SECONDS.sleep(OSF_API_RETRY_DELAY_IN_SECONDS * retry);
719722
} catch (InterruptedException e) {
720723
LOGGER.error("[OSF API] Exception - Retry Interrupted: {}, attempt={}, error={}", ssoUser, retry, e.getMessage());
721-
casError = new InstitutionSsoOsfApiFailureException("Communication Error between OSF CAS and OSF API");
724+
casError = new InstitutionSsoOsfApiFailedException("Communication Error between OSF CAS and OSF API");
722725
break;
723726
}
724727
}
@@ -728,7 +731,7 @@ private OsfApiInstitutionAuthenticationResult notifyOsfApiOfInstnAuthnSuccess(
728731
// Handler unexpected exceptions (i.e. any status other than 403)
729732
if (statusCode != HttpStatus.SC_FORBIDDEN) {
730733
LOGGER.error("[OSF API] Failure - Unexpected HTTP response code: {}, statusCode={}", ssoUser, statusCode);
731-
throw new InstitutionSsoFailedException("OSF API failed to process CAS request");
734+
throw new InstitutionSsoOsfApiFailedException("OSF API failed to process CAS request");
732735
}
733736
// CAS expects OSF API to return HTTP 403 FORBIDDEN with error details if authentication fails.
734737
String responseRaw;
@@ -753,17 +756,25 @@ private OsfApiInstitutionAuthenticationResult notifyOsfApiOfInstnAuthnSuccess(
753756
continue;
754757
}
755758
final String errorDetail = ((JsonObject) error).get("detail").getAsString();
756-
if (OsfApiPermissionDenied.INSTITUTION_SELECTIVE_SSO_FAILURE.getId().equals(errorDetail)) {
759+
if (OsfApiPermissionDenied.INSTITUTION_SSO_SELECTIVE_LOGIN_DENIED.getId().equals(errorDetail)) {
757760
LOGGER.error("[OSF API] Failure - Institution Selective SSO Not Allowed: {}, filter={}", ssoUser, selectiveSsoFilter);
758-
throw new InstitutionSelectiveSsoFailedException("OSF API denies selective SSO login");
761+
throw new InstitutionSsoSelectiveLoginDeniedException("OSF API denies selective SSO login");
762+
}
763+
if (OsfApiPermissionDenied.INSTITUTION_SSO_DUPLICATE_IDENTITY.getId().equals(errorDetail)) {
764+
LOGGER.error("[OSF API] Failure - Duplicate SSO Identity: {}", ssoUser);
765+
throw new InstitutionSsoDuplicateIdentityException("OSF API can't handle duplicate SSO identity");
766+
}
767+
if (OsfApiPermissionDenied.INSTITUTION_SSO_ACCOUNT_INACTIVE.getId().equals(errorDetail)) {
768+
LOGGER.error("[OSF API] Failure - Inactive Account: {}", ssoUser);
769+
throw new InstitutionSsoDuplicateIdentityException("OSF API denies inactive account");
759770
}
760771
}
761772
// Handle unidentified HTTP 403 FORBIDDEN failures
762773
LOGGER.error("[OSF API] Failure - HTTP 403 FORBIDDEN: {}, statusCode={}", ssoUser, statusCode);
763-
throw new InstitutionSsoFailedException("OSF API failed to process CAS request");
774+
throw new InstitutionSsoOsfApiFailedException("OSF API failed to process CAS request");
764775
} catch (final JsonParseException | IllegalStateException e) {
765776
LOGGER.error("[OSF API] Exception - Invalid Response: {}, error={}", ssoUser, e.getMessage());
766-
throw new InstitutionSsoFailedException("CAS failed to parse OSF API error response");
777+
throw new InstitutionSsoOsfApiFailedException("CAS failed to parse OSF API error response");
767778
}
768779
}
769780

0 commit comments

Comments
 (0)