From b16960ca3f20b937f9090197380dc0fd5357c671 Mon Sep 17 00:00:00 2001 From: John Sullivan Date: Wed, 6 Mar 2024 12:34:12 -0500 Subject: [PATCH 1/3] Adding in extra exception handling so error messages from retrieving auth code are surfaced to the API consumer --- .../data/api/helper/RestApiSession.java | 24 ++- .../exception/AuthorizationCodeException.java | 32 +++ .../data/exception/RestApiException.java | 29 ++- .../TestAuthorizationCodeException.java | 194 ++++++++++++++++++ 4 files changed, 258 insertions(+), 21 deletions(-) create mode 100644 src/main/java/com/bullhornsdk/data/exception/AuthorizationCodeException.java create mode 100644 src/test/java/com/bullhornsdk/data/exception/TestAuthorizationCodeException.java diff --git a/src/main/java/com/bullhornsdk/data/api/helper/RestApiSession.java b/src/main/java/com/bullhornsdk/data/api/helper/RestApiSession.java index bb8a4116..7afbb059 100644 --- a/src/main/java/com/bullhornsdk/data/api/helper/RestApiSession.java +++ b/src/main/java/com/bullhornsdk/data/api/helper/RestApiSession.java @@ -1,6 +1,7 @@ package com.bullhornsdk.data.api.helper; import com.bullhornsdk.data.api.BullhornRestCredentials; +import com.bullhornsdk.data.exception.AuthorizationCodeException; import com.bullhornsdk.data.exception.RestApiException; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -18,6 +19,7 @@ import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; @@ -25,14 +27,6 @@ import java.util.LinkedHashMap; import java.util.Map; -/** - * Wraps rest api session management. - * - * @author Yaniv Or-Shahar - * @author Magnus Fiore Palm - * - */ - @JsonIgnoreProperties({"sessionExpired"}) public class RestApiSession { @@ -134,6 +128,8 @@ private void createSession() { AccessTokenInfo accessTokenInfo = getAccessToken(authCode); login(accessTokenInfo); break; + } catch (AuthorizationCodeException e) { + throw e; } catch (Exception e) { if (tryNumber < SESSION_RETRY) { String message = "Error creating REST session. Try number: " + tryNumber + " out of " + SESSION_RETRY + " trying again."; @@ -168,7 +164,7 @@ private String getAuthorizationCode() throws RestApiException { private String getAuthorizationCode(String url, Map parameters, Boolean followRedirect) { try { - ResponseEntity response = restTemplate.exchange(url, HttpMethod.POST, HttpEntity.EMPTY, Void.class, parameters); + ResponseEntity response = restTemplate.exchange(url, HttpMethod.POST, HttpEntity.EMPTY, String.class, parameters); URI location = response.getHeaders().getLocation(); @@ -180,11 +176,17 @@ private String getAuthorizationCode(String url, Map parameters, return getAuthCode(location); } - throw new RestApiException("Failed to get authorization code. Response had no Location header and code was: " + response.getStatusCodeValue()); + throw new AuthorizationCodeException("Failed to get authorization code. Response had no Location header and code was: " + response.getStatusCodeValue(), response.getBody()); + } catch (AuthorizationCodeException e) { + throw e; + } catch (HttpStatusCodeException e ) { + LOG.error("Failed to get authorization code.", e); + + throw new AuthorizationCodeException("Failed to get authorization code.", e.getResponseBodyAsString(), e); } catch (Exception e) { LOG.error("Failed to get authorization code.", e); - throw new RestApiException("Failed to get authorization code.", e); + throw new AuthorizationCodeException("Failed to get authorization code.", e); } } diff --git a/src/main/java/com/bullhornsdk/data/exception/AuthorizationCodeException.java b/src/main/java/com/bullhornsdk/data/exception/AuthorizationCodeException.java new file mode 100644 index 00000000..1fc289db --- /dev/null +++ b/src/main/java/com/bullhornsdk/data/exception/AuthorizationCodeException.java @@ -0,0 +1,32 @@ +package com.bullhornsdk.data.exception; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class AuthorizationCodeException extends RestApiException { + + private static final Pattern GET_DETAIL_ERROR = Pattern.compile(".*?

(.*?)

", Pattern.DOTALL); + + public AuthorizationCodeException(String message, String responseBody) { + super(message, parseErrorMessageFromHtml(responseBody)); + } + + public AuthorizationCodeException(String message, String responseBody, Throwable cause) { + super(message, parseErrorMessageFromHtml(responseBody), cause); + } + + public AuthorizationCodeException(String message, Throwable cause) { + super(message, cause); + } + + private static String parseErrorMessageFromHtml(String responseBody) { + Matcher matcher = GET_DETAIL_ERROR.matcher(responseBody); + + if (matcher.find()) { + return matcher.group(1).trim(); + } + + return ""; + } + +} diff --git a/src/main/java/com/bullhornsdk/data/exception/RestApiException.java b/src/main/java/com/bullhornsdk/data/exception/RestApiException.java index c256ff88..2dfd9328 100644 --- a/src/main/java/com/bullhornsdk/data/exception/RestApiException.java +++ b/src/main/java/com/bullhornsdk/data/exception/RestApiException.java @@ -1,16 +1,10 @@ -/** - * - */ package com.bullhornsdk.data.exception; -/** - * @author Yaniv Or-Shahar - * - */ + public class RestApiException extends RuntimeException { - /** - * - */ + + private String detailMessage; + private static final long serialVersionUID = 1L; public RestApiException() { @@ -29,4 +23,19 @@ public RestApiException(String message, Throwable cause) { super(message, cause); } + public RestApiException(String message, String detailMessage) { + super(message); + this.detailMessage = detailMessage; + } + + + public RestApiException(String message, String detailMessage, Throwable cause) { + super(message, cause); + this.detailMessage = detailMessage; + } + + public String getDetailMessage() { + return detailMessage; + } + } diff --git a/src/test/java/com/bullhornsdk/data/exception/TestAuthorizationCodeException.java b/src/test/java/com/bullhornsdk/data/exception/TestAuthorizationCodeException.java new file mode 100644 index 00000000..9b72044e --- /dev/null +++ b/src/test/java/com/bullhornsdk/data/exception/TestAuthorizationCodeException.java @@ -0,0 +1,194 @@ +package com.bullhornsdk.data.exception; + +import org.junit.Assert; +import org.junit.jupiter.api.Test; + +import com.bullhornsdk.data.BaseTest; +import com.bullhornsdk.data.api.BullhornRestCredentials; +import com.bullhornsdk.data.api.StandardBullhornData; + +public class TestAuthorizationCodeException extends BaseTest { + + @Test + public void testAccountLockedOut() { + RestApiException exception = new AuthorizationCodeException("", ACCOUNT_LOCKED_OUT_HTML); + + Assert.assertEquals("Account is locked out.", exception.getDetailMessage()); + } + + @Test + public void testInvalidCredentials() { + RestApiException exception = new AuthorizationCodeException("", INVALID_CREDENTIALS_HTML); + + Assert.assertEquals("Invalid credentials.", exception.getDetailMessage()); + } + + @Test + public void testBullhornDataInvalidCredentials() { + BullhornRestCredentials restCredentials = new BullhornRestCredentials(); + restCredentials.setUsername("octopus.api.user"); + restCredentials.setPassword("invalidPassword"); + restCredentials.setRestClientId("b627302e-e971-4a8b-b122-91c622f6bf39"); + + try { + new StandardBullhornData(restCredentials); + } catch (RestApiException e) { + Assert.assertEquals("Invalid credentials.", e.getDetailMessage()); + } + } + + private static final String ACCOUNT_LOCKED_OUT_HTML = "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " Bullhorn | Log in\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + " \n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + " \t\n" + + " \t\n" + + " \t
Hello, octopus.api.user.3!
\n" + + " \n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "\n" + + "
\n" + + " \n" + + "

\n" + + " Account is locked out.\n" + + "

\n" + + " \n" + + "
\n" + + "\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + " \n" + + "\n"; + + private static final String INVALID_CREDENTIALS_HTML = "\n" + + "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " Bullhorn | Log in\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n" + + "\n" + + "\n" + + "
\n" + + "\n" + + " \n" + + "\n" + + "
\n" + + "
\n" + + "
\n" + + "\n" + + " \n" + + "
Hello, octopus.api.user.3!
\n" + + "\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "\n" + + "
\n" + + "\n" + + "

\n" + + " Invalid credentials.\n" + + "

\n" + + "\n" + + "
\n" + + "\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "\n" + + "\n" + + ""; + +} From 7bbf6a1d629d62fb9d7bccc53804ecabdc7dab52 Mon Sep 17 00:00:00 2001 From: John Sullivan Date: Wed, 6 Mar 2024 12:58:09 -0500 Subject: [PATCH 2/3] Adding in extra exception handling so error messages from retrieving auth code are surfaced to the API consumer --- .../TestAuthorizationCodeException.java | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/test/java/com/bullhornsdk/data/exception/TestAuthorizationCodeException.java b/src/test/java/com/bullhornsdk/data/exception/TestAuthorizationCodeException.java index 9b72044e..393252e2 100644 --- a/src/test/java/com/bullhornsdk/data/exception/TestAuthorizationCodeException.java +++ b/src/test/java/com/bullhornsdk/data/exception/TestAuthorizationCodeException.java @@ -4,8 +4,6 @@ import org.junit.jupiter.api.Test; import com.bullhornsdk.data.BaseTest; -import com.bullhornsdk.data.api.BullhornRestCredentials; -import com.bullhornsdk.data.api.StandardBullhornData; public class TestAuthorizationCodeException extends BaseTest { @@ -23,20 +21,6 @@ public void testInvalidCredentials() { Assert.assertEquals("Invalid credentials.", exception.getDetailMessage()); } - @Test - public void testBullhornDataInvalidCredentials() { - BullhornRestCredentials restCredentials = new BullhornRestCredentials(); - restCredentials.setUsername("octopus.api.user"); - restCredentials.setPassword("invalidPassword"); - restCredentials.setRestClientId("b627302e-e971-4a8b-b122-91c622f6bf39"); - - try { - new StandardBullhornData(restCredentials); - } catch (RestApiException e) { - Assert.assertEquals("Invalid credentials.", e.getDetailMessage()); - } - } - private static final String ACCOUNT_LOCKED_OUT_HTML = "\n" + "\n" + "\n" + From 3ff69f48b8c468b753a974c758f443e4062b9017 Mon Sep 17 00:00:00 2001 From: John Sullivan Date: Wed, 6 Mar 2024 12:59:23 -0500 Subject: [PATCH 3/3] Adding in a defensive check --- .../data/exception/AuthorizationCodeException.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/com/bullhornsdk/data/exception/AuthorizationCodeException.java b/src/main/java/com/bullhornsdk/data/exception/AuthorizationCodeException.java index 1fc289db..b611c6d5 100644 --- a/src/main/java/com/bullhornsdk/data/exception/AuthorizationCodeException.java +++ b/src/main/java/com/bullhornsdk/data/exception/AuthorizationCodeException.java @@ -3,6 +3,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.apache.commons.lang3.StringUtils; + public class AuthorizationCodeException extends RestApiException { private static final Pattern GET_DETAIL_ERROR = Pattern.compile(".*?

(.*?)

", Pattern.DOTALL); @@ -20,6 +22,10 @@ public AuthorizationCodeException(String message, Throwable cause) { } private static String parseErrorMessageFromHtml(String responseBody) { + if (StringUtils.isBlank(responseBody)) { + return ""; + } + Matcher matcher = GET_DETAIL_ERROR.matcher(responseBody); if (matcher.find()) {