77import com .google .gson .JsonParser ;
88
99import 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 ;
1113import 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 ;
1316import io .cos .cas .osf .authentication .support .DelegationProtocol ;
1417import io .cos .cas .osf .authentication .support .OsfApiPermissionDenied ;
1518import 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