|
9 | 9 | import io.cos.cas.osf.authentication.credential.OsfPostgresCredential; |
10 | 10 | import io.cos.cas.osf.authentication.exception.InstitutionSelectiveSsoFailedException; |
11 | 11 | import io.cos.cas.osf.authentication.exception.InstitutionSsoFailedException; |
| 12 | +import io.cos.cas.osf.authentication.exception.InstitutionSsoOsfApiFailureException; |
12 | 13 | import io.cos.cas.osf.authentication.support.DelegationProtocol; |
13 | 14 | import io.cos.cas.osf.authentication.support.OsfApiPermissionDenied; |
14 | 15 | import io.cos.cas.osf.configuration.model.OsfApiProperties; |
|
92 | 93 | import java.util.Date; |
93 | 94 | import java.util.List; |
94 | 95 | import java.util.Map; |
| 96 | +import java.util.concurrent.TimeUnit; |
95 | 97 |
|
96 | 98 | /** |
97 | 99 | * This is {@link OsfPrincipalFromNonInteractiveCredentialsAction}. |
@@ -160,6 +162,17 @@ public class OsfPrincipalFromNonInteractiveCredentialsAction extends AbstractNon |
160 | 162 |
|
161 | 163 | private static final String LDAP_DN_OU_PREFIX = "ou="; |
162 | 164 |
|
| 165 | + private static final int OSF_API_RETRY_LIMIT = 3; |
| 166 | + |
| 167 | + private static final List<Integer> OSF_API_RETRY_STATUS = List.of( |
| 168 | + HttpStatus.SC_INTERNAL_SERVER_ERROR, |
| 169 | + HttpStatus.SC_BAD_GATEWAY, |
| 170 | + HttpStatus.SC_SERVICE_UNAVAILABLE, |
| 171 | + HttpStatus.SC_GATEWAY_TIMEOUT |
| 172 | + ); |
| 173 | + |
| 174 | + private static final int OSF_API_RETRY_DELAY_IN_SECONDS = 1; |
| 175 | + |
163 | 176 | @NotNull |
164 | 177 | private CentralAuthenticationService centralAuthenticationService; |
165 | 178 |
|
@@ -672,28 +685,75 @@ private OsfApiInstitutionAuthenticationResult notifyOsfApiOfInstnAuthnSuccess( |
672 | 685 | throw new InstitutionSsoFailedException("OSF CAS failed to build JWT / JWE payload for OSF API"); |
673 | 686 | } |
674 | 687 | // Send the POST request to OSF API to verify an existing institution user or to create a new one |
675 | | - int statusCode; |
676 | | - HttpResponse httpResponse; |
677 | | - try { |
678 | | - httpResponse = Request.Post(osfApiProperties.getInstnAuthnEndpoint()) |
679 | | - .addHeader(new BasicHeader("Content-Type", "text/plain")) |
680 | | - .bodyString(jweString, ContentType.APPLICATION_JSON) |
681 | | - .execute() |
682 | | - .returnResponse(); |
683 | | - statusCode = httpResponse.getStatusLine().getStatusCode(); |
684 | | - LOGGER.info( |
685 | | - "[OSF API] Notify Remote Principal Authenticated Response: username={}, statusCode={}", |
686 | | - username, |
687 | | - statusCode |
688 | | - ); |
689 | | - } catch (final IOException e) { |
690 | | - LOGGER.error("[OSF API] Notify Remote Principal Authenticated Failed: Communication Error - {}", e.getMessage()); |
691 | | - throw new InstitutionSsoFailedException("Communication Error between OSF CAS and OSF API"); |
| 688 | + int statusCode = -1; |
| 689 | + int retry = 0; |
| 690 | + final String ssoUser = String.format("institution=%s, username=%s", institutionId, username); |
| 691 | + HttpResponse httpResponse = null; |
| 692 | + InstitutionSsoOsfApiFailureException casError = null; |
| 693 | + while (retry < OSF_API_RETRY_LIMIT) { |
| 694 | + retry += 1; |
| 695 | + // Reset exception from previous attempt |
| 696 | + casError = null; |
| 697 | + try { |
| 698 | + httpResponse = Request.Post(osfApiProperties.getInstnAuthnEndpoint()) |
| 699 | + .connectTimeout(SIXTY_SECONDS) |
| 700 | + .socketTimeout(SIXTY_SECONDS) |
| 701 | + .addHeader(new BasicHeader("Content-Type", "text/plain")) |
| 702 | + .bodyString(jweString, ContentType.APPLICATION_JSON) |
| 703 | + .execute() |
| 704 | + .returnResponse(); |
| 705 | + statusCode = httpResponse.getStatusLine().getStatusCode(); |
| 706 | + LOGGER.info( |
| 707 | + "[OSF API] Notify Remote Principal Authenticated Response Received: {}, attempt={}, status={}", |
| 708 | + ssoUser, |
| 709 | + retry, |
| 710 | + statusCode |
| 711 | + ); |
| 712 | + // CAS expects OSF API to return HTTP 204 OK with no content if authentication succeeds |
| 713 | + if (statusCode == HttpStatus.SC_NO_CONTENT) { |
| 714 | + LOGGER.info( |
| 715 | + "[OSF API] Notify Remote Principal Authenticated Passed: {}, attempt={}, status={}", |
| 716 | + ssoUser, |
| 717 | + retry, |
| 718 | + statusCode |
| 719 | + ); |
| 720 | + return new OsfApiInstitutionAuthenticationResult(username, institutionId); |
| 721 | + } |
| 722 | + if (OSF_API_RETRY_STATUS.contains(statusCode)) { |
| 723 | + LOGGER.error( |
| 724 | + "[OSF API] Notify Remote Principal Authenticated Failed - Server Error: {}, attempt={}, status={}", |
| 725 | + ssoUser, |
| 726 | + retry, |
| 727 | + statusCode |
| 728 | + ); |
| 729 | + casError = new InstitutionSsoOsfApiFailureException("Communication Error between OSF CAS and OSF API"); |
| 730 | + } else { |
| 731 | + break; |
| 732 | + } |
| 733 | + } catch (final IOException e) { |
| 734 | + LOGGER.error( |
| 735 | + "[OSF API] Notify Remote Principal Authenticated Failed - Communication Error: {}, attempt={}, error={}", |
| 736 | + ssoUser, |
| 737 | + retry, |
| 738 | + e.getMessage() |
| 739 | + ); |
| 740 | + casError = new InstitutionSsoOsfApiFailureException("Communication Error between OSF CAS and OSF API"); |
| 741 | + } |
| 742 | + try { |
| 743 | + TimeUnit.SECONDS.sleep(OSF_API_RETRY_DELAY_IN_SECONDS * retry); |
| 744 | + } catch (InterruptedException e) { |
| 745 | + LOGGER.error( |
| 746 | + "[OSF API] Notify Remote Principal Authenticated Failed - Retry Interrupted: {}, attempt={}, error={}", |
| 747 | + ssoUser, |
| 748 | + retry, |
| 749 | + e.getMessage() |
| 750 | + ); |
| 751 | + casError = new InstitutionSsoOsfApiFailureException("Communication Error between OSF CAS and OSF API"); |
| 752 | + break; |
| 753 | + } |
692 | 754 | } |
693 | | - // CAS expects OSF API to return HTTP 204 OK with no content if authentication succeeds |
694 | | - if (statusCode == HttpStatus.SC_NO_CONTENT) { |
695 | | - LOGGER.info("[OSF API] Notify Remote Principal Authenticated Passed: institution={}, username={}", institutionId, username); |
696 | | - return new OsfApiInstitutionAuthenticationResult(username, institutionId); |
| 755 | + if (casError != null) { |
| 756 | + throw casError; |
697 | 757 | } |
698 | 758 | // Handler unexpected exceptions (i.e. any status other than 403) |
699 | 759 | if (statusCode != HttpStatus.SC_FORBIDDEN) { |
|
0 commit comments