Skip to content

Commit 9005913

Browse files
Snow 1925254 authentication logic refactor (#2073)
1 parent a2af702 commit 9005913

File tree

3 files changed

+250
-150
lines changed

3 files changed

+250
-150
lines changed

src/main/java/net/snowflake/client/core/HttpUtil.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ public static boolean isSocksProxyDisabled() {
580580
}
581581

582582
/**
583-
* Executes a HTTP request with the cookie spec set to IGNORE_COOKIES
583+
* Executes an HTTP request with the cookie spec set to IGNORE_COOKIES
584584
*
585585
* @param httpRequest HttpRequestBase
586586
* @param retryTimeout retry timeout
@@ -622,7 +622,7 @@ static String executeRequestWithoutCookies(
622622
}
623623

624624
/**
625-
* Executes a HTTP request for Snowflake.
625+
* Executes an HTTP request for Snowflake.
626626
*
627627
* @param httpRequest HttpRequestBase
628628
* @param retryTimeout retry timeout
@@ -658,7 +658,7 @@ public static String executeGeneralRequest(
658658
}
659659

660660
/**
661-
* Executes a HTTP request for Snowflake
661+
* Executes an HTTP request for Snowflake
662662
*
663663
* @param httpRequest HttpRequestBase
664664
* @param retryTimeout retry timeout
@@ -696,7 +696,7 @@ public static String executeGeneralRequest(
696696
}
697697

698698
/**
699-
* Executes a HTTP request for Snowflake.
699+
* Executes an HTTP request for Snowflake.
700700
*
701701
* @param httpRequest HttpRequestBase
702702
* @param retryTimeout retry timeout

src/main/java/net/snowflake/client/core/SessionUtil.java

+151-86
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import static net.snowflake.client.core.SFTrustManager.resetOCSPResponseCacherServerURL;
88
import static net.snowflake.client.jdbc.SnowflakeUtil.systemGetProperty;
99

10+
import com.fasterxml.jackson.core.JsonProcessingException;
1011
import com.fasterxml.jackson.databind.JsonNode;
1112
import com.fasterxml.jackson.databind.ObjectMapper;
1213
import com.google.common.base.Strings;
@@ -271,7 +272,7 @@ static SFLoginOutput openSession(
271272
AssertUtil.assertTrue(
272273
loginInput.getUserName() != null, "missing user name for opening session");
273274
} else {
274-
// OAUTH needs either token or passord
275+
// OAUTH needs either token or password
275276
AssertUtil.assertTrue(
276277
loginInput.getToken() != null || loginInput.getPassword() != null,
277278
"missing token or password for opening session");
@@ -707,7 +708,7 @@ private static SFLoginOutput newSession(
707708
// In RestRequest.execute(), socket timeout is replaced with auth timeout
708709
// so we can renew the request within auth timeout.
709710
// auth timeout within socket timeout is thrown without backoff,
710-
// and we need to update time remained in socket timeout here to control the
711+
// and we need to update time remained in socket timeout here to control
711712
// the actual socket timeout from customer setting.
712713
if (loginInput.getSocketTimeoutInMillis() > 0) {
713714
if (ex.issocketTimeoutNoBackoff()) {
@@ -737,29 +738,7 @@ private static SFLoginOutput newSession(
737738
break;
738739
}
739740

740-
if (theString == null) {
741-
if (lastRestException != null) {
742-
logger.error(
743-
"Failed to open new session for user: {}, host: {}. Error: {}",
744-
loginInput.getUserName(),
745-
loginInput.getHostFromServerUrl(),
746-
lastRestException);
747-
throw lastRestException;
748-
} else {
749-
SnowflakeSQLException exception =
750-
new SnowflakeSQLException(
751-
NO_QUERY_ID,
752-
"empty authentication response",
753-
SqlState.CONNECTION_EXCEPTION,
754-
ErrorCode.CONNECTION_ERROR.getMessageCode());
755-
logger.error(
756-
"Failed to open new session for user: {}, host: {}. Error: {}",
757-
loginInput.getUserName(),
758-
loginInput.getHostFromServerUrl(),
759-
exception);
760-
throw exception;
761-
}
762-
}
741+
handleEmptyAuthResponse(theString, loginInput, lastRestException);
763742

764743
// general method, same as with data binding
765744
JsonNode jsonNode = mapper.readTree(theString);
@@ -1201,22 +1180,11 @@ static void closeSession(SFLoginInput loginInput) throws SFException, SnowflakeS
12011180
private static String federatedFlowStep4(
12021181
SFLoginInput loginInput, String ssoUrl, String oneTimeToken) throws SnowflakeSQLException {
12031182
String responseHtml = "";
1204-
try {
12051183

1206-
final URL url = new URL(ssoUrl);
1207-
URI oktaGetUri =
1208-
new URIBuilder()
1209-
.setScheme(url.getProtocol())
1210-
.setHost(url.getHost())
1211-
.setPath(url.getPath())
1212-
.setParameter("RelayState", "%2Fsome%2Fdeep%2Flink")
1213-
.setParameter("onetimetoken", oneTimeToken)
1214-
.build();
1215-
HttpGet httpGet = new HttpGet(oktaGetUri);
1184+
try {
12161185

1217-
HeaderGroup headers = new HeaderGroup();
1218-
headers.addHeader(new BasicHeader(HttpHeaders.ACCEPT, "*/*"));
1219-
httpGet.setHeaders(headers.getAllHeaders());
1186+
HttpGet httpGet = new HttpGet();
1187+
prepareFederatedFlowStep4Request(httpGet, ssoUrl, oneTimeToken);
12201188

12211189
responseHtml =
12221190
HttpUtil.executeGeneralRequest(
@@ -1276,26 +1244,7 @@ private static String federatedFlowStep3(SFLoginInput loginInput, String tokenUr
12761244
URL url = new URL(tokenUrl);
12771245
URI tokenUri = url.toURI();
12781246
final HttpPost postRequest = new HttpPost(tokenUri);
1279-
1280-
String userName;
1281-
if (Strings.isNullOrEmpty(loginInput.getOKTAUserName())) {
1282-
userName = loginInput.getUserName();
1283-
} else {
1284-
userName = loginInput.getOKTAUserName();
1285-
}
1286-
StringEntity params =
1287-
new StringEntity(
1288-
"{\"username\":\""
1289-
+ userName
1290-
+ "\",\"password\":\""
1291-
+ loginInput.getPassword()
1292-
+ "\"}");
1293-
postRequest.setEntity(params);
1294-
1295-
HeaderGroup headers = new HeaderGroup();
1296-
headers.addHeader(new BasicHeader(HttpHeaders.ACCEPT, "application/json"));
1297-
headers.addHeader(new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json"));
1298-
postRequest.setHeaders(headers.getAllHeaders());
1247+
setFederatedFlowStep3PostRequestAuthData(postRequest, loginInput);
12991248

13001249
final String idpResponse =
13011250
HttpUtil.executeRequestWithoutCookies(
@@ -1363,29 +1312,8 @@ private static void federatedFlowStep2(SFLoginInput loginInput, String tokenUrl,
13631312
private static JsonNode federatedFlowStep1(SFLoginInput loginInput) throws SnowflakeSQLException {
13641313
JsonNode dataNode = null;
13651314
try {
1366-
URIBuilder fedUriBuilder = new URIBuilder(loginInput.getServerUrl());
1367-
fedUriBuilder.setPath(SF_PATH_AUTHENTICATOR_REQUEST);
1368-
URI fedUrlUri = fedUriBuilder.build();
1369-
1370-
Map<String, Object> data = new HashMap<>();
1371-
data.put(ClientAuthnParameter.ACCOUNT_NAME.name(), loginInput.getAccountName());
1372-
data.put(ClientAuthnParameter.AUTHENTICATOR.name(), loginInput.getAuthenticator());
1373-
data.put(ClientAuthnParameter.CLIENT_APP_ID.name(), loginInput.getAppId());
1374-
data.put(ClientAuthnParameter.CLIENT_APP_VERSION.name(), loginInput.getAppVersion());
1375-
1376-
ClientAuthnDTO authnData = new ClientAuthnDTO(data, null);
1377-
String json = mapper.writeValueAsString(authnData);
1378-
1379-
// attach the login info json body to the post request
1380-
StringEntity input = new StringEntity(json, StandardCharsets.UTF_8);
1381-
input.setContentType("application/json");
1382-
HttpPost postRequest = new HttpPost(fedUrlUri);
1383-
postRequest.setEntity(input);
1384-
postRequest.addHeader("accept", "application/json");
1385-
1386-
// Add headers for driver name and version
1387-
postRequest.addHeader(SF_HEADER_CLIENT_APP_ID, loginInput.getAppId());
1388-
postRequest.addHeader(SF_HEADER_CLIENT_APP_VERSION, loginInput.getAppVersion());
1315+
StringEntity requestInput = prepareFederatedFlowStep1RequestInput(loginInput);
1316+
HttpPost postRequest = prepareFederatedFlowStep1PostRequest(loginInput, requestInput);
13891317

13901318
final String gsResponse =
13911319
HttpUtil.executeGeneralRequest(
@@ -1395,6 +1323,7 @@ private static JsonNode federatedFlowStep1(SFLoginInput loginInput) throws Snowf
13951323
loginInput.getSocketTimeoutInMillis(),
13961324
0,
13971325
loginInput.getHttpClientSettingsKey());
1326+
13981327
logger.debug("Authenticator-request response: {}", gsResponse);
13991328
JsonNode jsonNode = mapper.readTree(gsResponse);
14001329

@@ -1790,12 +1719,148 @@ public static boolean isNewRetryStrategyRequest(HttpRequestBase request) {
17901719
URI requestURI = request.getURI();
17911720
String requestPath = requestURI.getPath();
17921721
if (requestPath != null) {
1793-
if (requestPath.equals(SF_PATH_LOGIN_REQUEST)
1722+
return requestPath.equals(SF_PATH_LOGIN_REQUEST)
17941723
|| requestPath.equals(SF_PATH_AUTHENTICATOR_REQUEST)
1795-
|| requestPath.equals(SF_PATH_TOKEN_REQUEST)) {
1796-
return true;
1797-
}
1724+
|| requestPath.equals(SF_PATH_TOKEN_REQUEST);
17981725
}
17991726
return false;
18001727
}
1728+
1729+
/**
1730+
* Prepares an HTTP POST request for the first step of the federated authentication flow.
1731+
*
1732+
* @param loginInput The login information for the request.
1733+
* @param inputData The JSON input data to include in the request.
1734+
* @return An {@link HttpPost} object ready to execute the federated flow request.
1735+
* @throws URISyntaxException If the constructed URI is invalid.
1736+
*/
1737+
private static HttpPost prepareFederatedFlowStep1PostRequest(
1738+
SFLoginInput loginInput, StringEntity inputData) throws URISyntaxException {
1739+
URIBuilder fedUriBuilder = new URIBuilder(loginInput.getServerUrl());
1740+
// TODO: if loginInput.serverUrl contains port or additional segments - it will be ignored and
1741+
// overwritten here - to be fixed in SNOW-1922872
1742+
fedUriBuilder.setPath(SF_PATH_AUTHENTICATOR_REQUEST);
1743+
URI fedUrlUri = fedUriBuilder.build();
1744+
1745+
HttpPost postRequest = new HttpPost(fedUrlUri);
1746+
postRequest.setEntity(inputData);
1747+
postRequest.addHeader("accept", "application/json");
1748+
1749+
postRequest.addHeader(SF_HEADER_CLIENT_APP_ID, loginInput.getAppId());
1750+
postRequest.addHeader(SF_HEADER_CLIENT_APP_VERSION, loginInput.getAppVersion());
1751+
1752+
return postRequest;
1753+
}
1754+
1755+
/**
1756+
* Prepares the JSON input for the first step of the federated authentication flow.
1757+
*
1758+
* @param loginInput The login information for the request.
1759+
* @return A {@link StringEntity} containing the JSON input for the request.
1760+
* @throws JsonProcessingException If there is an error generating the JSON input.
1761+
*/
1762+
private static StringEntity prepareFederatedFlowStep1RequestInput(SFLoginInput loginInput)
1763+
throws JsonProcessingException {
1764+
Map<String, Object> data = new HashMap<>();
1765+
data.put(ClientAuthnParameter.ACCOUNT_NAME.name(), loginInput.getAccountName());
1766+
data.put(ClientAuthnParameter.AUTHENTICATOR.name(), loginInput.getAuthenticator());
1767+
data.put(ClientAuthnParameter.CLIENT_APP_ID.name(), loginInput.getAppId());
1768+
data.put(ClientAuthnParameter.CLIENT_APP_VERSION.name(), loginInput.getAppVersion());
1769+
1770+
ClientAuthnDTO authnData = new ClientAuthnDTO(data, null);
1771+
String json = mapper.writeValueAsString(authnData);
1772+
1773+
StringEntity input = new StringEntity(json, StandardCharsets.UTF_8);
1774+
input.setContentType("application/json");
1775+
return input;
1776+
}
1777+
1778+
/**
1779+
* Sets the authentication data for the third step of the federated authentication flow.
1780+
*
1781+
* @param postRequest The {@link HttpPost} request to update with authentication data.
1782+
* @param loginInput The login information for the request.
1783+
* @throws SnowflakeSQLException If an error occurs while preparing the request.
1784+
*/
1785+
private static void setFederatedFlowStep3PostRequestAuthData(
1786+
HttpPost postRequest, SFLoginInput loginInput) throws SnowflakeSQLException {
1787+
String userName =
1788+
Strings.isNullOrEmpty(loginInput.getOKTAUserName())
1789+
? loginInput.getUserName()
1790+
: loginInput.getOKTAUserName();
1791+
try {
1792+
StringEntity params =
1793+
new StringEntity(
1794+
"{\"username\":\""
1795+
+ userName
1796+
+ "\",\"password\":\""
1797+
+ loginInput.getPassword()
1798+
+ "\"}");
1799+
postRequest.setEntity(params);
1800+
1801+
HeaderGroup headers = new HeaderGroup();
1802+
headers.addHeader(new BasicHeader(HttpHeaders.ACCEPT, "application/json"));
1803+
headers.addHeader(new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json"));
1804+
postRequest.setHeaders(headers.getAllHeaders());
1805+
} catch (IOException ex) {
1806+
handleFederatedFlowError(loginInput, ex);
1807+
}
1808+
}
1809+
1810+
/**
1811+
* Prepares an HTTP GET request for the fourth step of the federated authentication flow.
1812+
*
1813+
* @param retrieveSamlRequest The {@link HttpRequestBase} to update with the SAML request details.
1814+
* @param ssoUrl The SSO URL to use for the request.
1815+
* @param oneTimeToken The one-time token to include in the request.
1816+
* @throws MalformedURLException If the SSO URL is malformed.
1817+
* @throws URISyntaxException If the URI for the request cannot be built.
1818+
*/
1819+
private static void prepareFederatedFlowStep4Request(
1820+
HttpRequestBase retrieveSamlRequest, String ssoUrl, String oneTimeToken)
1821+
throws MalformedURLException, URISyntaxException {
1822+
final URL url = new URL(ssoUrl);
1823+
URI oktaGetUri =
1824+
new URIBuilder()
1825+
.setScheme(url.getProtocol())
1826+
.setHost(url.getHost())
1827+
.setPort(url.getPort())
1828+
.setPath(url.getPath())
1829+
.setParameter("RelayState", "%2Fsome%2Fdeep%2Flink")
1830+
.setParameter("onetimetoken", oneTimeToken)
1831+
.build();
1832+
retrieveSamlRequest.setURI(oktaGetUri);
1833+
1834+
HeaderGroup headers = new HeaderGroup();
1835+
headers.addHeader(new BasicHeader(HttpHeaders.ACCEPT, "*/*"));
1836+
retrieveSamlRequest.setHeaders(headers.getAllHeaders());
1837+
}
1838+
1839+
private static void handleEmptyAuthResponse(
1840+
String theString, SFLoginInput loginInput, Exception lastRestException)
1841+
throws Exception, SFException {
1842+
if (theString == null) {
1843+
if (lastRestException != null) {
1844+
logger.error(
1845+
"Failed to open new session for user: {}, host: {}. Error: {}",
1846+
loginInput.getUserName(),
1847+
loginInput.getHostFromServerUrl(),
1848+
lastRestException);
1849+
throw lastRestException;
1850+
} else {
1851+
SnowflakeSQLException exception =
1852+
new SnowflakeSQLException(
1853+
NO_QUERY_ID,
1854+
"empty authentication response",
1855+
SqlState.CONNECTION_EXCEPTION,
1856+
ErrorCode.CONNECTION_ERROR.getMessageCode());
1857+
logger.error(
1858+
"Failed to open new session for user: {}, host: {}. Error: {}",
1859+
loginInput.getUserName(),
1860+
loginInput.getHostFromServerUrl(),
1861+
exception);
1862+
throw exception;
1863+
}
1864+
}
1865+
}
18011866
}

0 commit comments

Comments
 (0)