Skip to content

Commit fdffbac

Browse files
committed
Address CodeGuru Review Comments
1 parent 6cf86a6 commit fdffbac

File tree

3 files changed

+250
-152
lines changed

3 files changed

+250
-152
lines changed

athena-saphana/src/main/java/com/amazonaws/athena/connectors/saphana/SaphanaConstants.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,5 @@ public final class SaphanaConstants
6161

6262
static final String TO_WELL_KNOWN_TEXT_FUNCTION = ".ST_AsWKT()";
6363

64-
// OAuth constants
65-
public static final String CLIENT_ID = "client_id";
66-
public static final String CLIENT_SECRET = "client_secret";
67-
public static final String TOKEN_URL = "token_url";
68-
6964
private SaphanaConstants() {}
7065
}

athena-saphana/src/main/java/com/amazonaws/athena/connectors/saphana/SaphanaCredentialsProvider.java

Lines changed: 106 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.amazonaws.athena.connector.credentials.DefaultCredentials;
2424
import com.amazonaws.athena.connector.lambda.exceptions.AthenaConnectorException;
2525
import com.amazonaws.athena.connector.lambda.security.CachableSecretsManager;
26+
import com.fasterxml.jackson.core.JsonProcessingException;
2627
import com.fasterxml.jackson.databind.JsonNode;
2728
import com.fasterxml.jackson.databind.ObjectMapper;
2829
import com.fasterxml.jackson.databind.node.ObjectNode;
@@ -32,20 +33,24 @@
3233
import software.amazon.awssdk.services.glue.model.ErrorDetails;
3334
import software.amazon.awssdk.services.glue.model.FederationSourceErrorCode;
3435
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
36+
import software.amazon.awssdk.services.secretsmanager.model.EncryptionFailureException;
37+
import software.amazon.awssdk.services.secretsmanager.model.InternalServiceErrorException;
38+
import software.amazon.awssdk.services.secretsmanager.model.InvalidParameterException;
39+
import software.amazon.awssdk.services.secretsmanager.model.InvalidRequestException;
40+
import software.amazon.awssdk.services.secretsmanager.model.LimitExceededException;
41+
import software.amazon.awssdk.services.secretsmanager.model.ResourceNotFoundException;
42+
import software.amazon.awssdk.services.secretsmanager.model.SecretsManagerException;
3543
import software.amazon.awssdk.utils.Validate;
3644

37-
import java.io.BufferedReader;
3845
import java.io.IOException;
39-
import java.io.InputStream;
40-
import java.io.InputStreamReader;
41-
import java.io.OutputStream;
42-
import java.net.HttpURLConnection;
43-
import java.net.URL;
46+
import java.net.URI;
47+
import java.net.http.HttpClient;
48+
import java.net.http.HttpRequest;
49+
import java.net.http.HttpResponse;
4450
import java.nio.charset.StandardCharsets;
4551
import java.util.Base64;
4652
import java.util.HashMap;
4753
import java.util.Map;
48-
import java.util.stream.Collectors;
4954

5055
/**
5156
* SAP HANA OAuth credentials provider that manages OAuth token lifecycle.
@@ -60,28 +65,38 @@ public class SaphanaCredentialsProvider implements CredentialsProvider
6065
{
6166
private static final Logger LOGGER = LoggerFactory.getLogger(SaphanaCredentialsProvider.class);
6267

68+
// Constants for OAuth configuration fields
69+
private static final String CLIENT_ID = "client_id";
70+
private static final String CLIENT_SECRET = "client_secret";
71+
private static final String TOKEN_URL = "token_url";
72+
73+
// Constants for OAuth token response fields
6374
private static final String ACCESS_TOKEN = "access_token";
6475
private static final String FETCHED_AT = "fetched_at";
6576
private static final String EXPIRES_IN = "expires_in";
77+
78+
// Constants for basic authentication fields
6679
private static final String USERNAME = "username";
6780
private static final String PASSWORD = "password";
6881
private static final String USER = "user";
6982

7083
private final String oauthSecretName;
7184
private final CachableSecretsManager secretsManager;
7285
private final ObjectMapper objectMapper;
86+
private final HttpClient httpClient;
7387

7488
public SaphanaCredentialsProvider(String oauthSecretName)
7589
{
76-
this(oauthSecretName, SecretsManagerClient.create());
90+
this(oauthSecretName, SecretsManagerClient.create(), HttpClient.newHttpClient());
7791
}
7892

7993
@VisibleForTesting
80-
public SaphanaCredentialsProvider(String oauthSecretName, SecretsManagerClient secretsClient)
94+
public SaphanaCredentialsProvider(String oauthSecretName, SecretsManagerClient secretsClient, HttpClient httpClient)
8195
{
8296
this.oauthSecretName = Validate.notNull(oauthSecretName, "oauthSecretName must not be null");
8397
this.secretsManager = new CachableSecretsManager(secretsClient);
8498
this.objectMapper = new ObjectMapper();
99+
this.httpClient = httpClient;
85100
}
86101

87102
@Override
@@ -132,12 +147,12 @@ public Map<String, String> getCredentialMap()
132147

133148
private boolean isOAuthConfigured(Map<String, String> oauthConfig)
134149
{
135-
return oauthConfig.containsKey(SaphanaConstants.CLIENT_ID) &&
136-
!oauthConfig.get(SaphanaConstants.CLIENT_ID).isEmpty() &&
137-
oauthConfig.containsKey(SaphanaConstants.CLIENT_SECRET) &&
138-
!oauthConfig.get(SaphanaConstants.CLIENT_SECRET).isEmpty() &&
139-
oauthConfig.containsKey(SaphanaConstants.TOKEN_URL) &&
140-
!oauthConfig.get(SaphanaConstants.TOKEN_URL).isEmpty();
150+
return oauthConfig.containsKey(CLIENT_ID) &&
151+
!oauthConfig.get(CLIENT_ID).isEmpty() &&
152+
oauthConfig.containsKey(CLIENT_SECRET) &&
153+
!oauthConfig.get(CLIENT_SECRET).isEmpty() &&
154+
oauthConfig.containsKey(TOKEN_URL) &&
155+
!oauthConfig.get(TOKEN_URL).isEmpty();
141156
}
142157

143158
private String loadTokenFromSecretsManager(Map<String, String> oauthConfig)
@@ -148,31 +163,32 @@ private String loadTokenFromSecretsManager(Map<String, String> oauthConfig)
148163
return null;
149164
}
150165

151-
private String fetchAndStoreNewToken(Map<String, String> oauthConfig) throws Exception
166+
private String fetchAndStoreNewToken(Map<String, String> oauthConfig) throws IOException, InterruptedException
152167
{
153-
String clientId = oauthConfig.get(SaphanaConstants.CLIENT_ID);
154-
String clientSecret = oauthConfig.get(SaphanaConstants.CLIENT_SECRET);
155-
String tokenEndpoint = oauthConfig.get(SaphanaConstants.TOKEN_URL);
168+
String clientId = oauthConfig.get(CLIENT_ID);
169+
String clientSecret = oauthConfig.get(CLIENT_SECRET);
170+
String tokenEndpoint = oauthConfig.get(TOKEN_URL);
156171

157-
HttpURLConnection conn = getHttpURLConnection(tokenEndpoint, clientId, clientSecret);
158-
try (OutputStream os = conn.getOutputStream()) {
159-
byte[] input = "grant_type=client_credentials".getBytes(StandardCharsets.UTF_8);
160-
os.write(input, 0, input.length);
161-
}
172+
String auth = clientId + ":" + clientSecret;
173+
String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes(StandardCharsets.UTF_8));
162174

163-
int responseCode = conn.getResponseCode();
164-
InputStream responseStream = (responseCode >= 200 && responseCode < 300) ?
165-
conn.getInputStream() : conn.getErrorStream();
175+
HttpRequest request = HttpRequest.newBuilder()
176+
.uri(URI.create(tokenEndpoint))
177+
.header("Authorization", "Basic " + encodedAuth)
178+
.header("Content-Type", "application/x-www-form-urlencoded")
179+
.POST(HttpRequest.BodyPublishers.ofString("grant_type=client_credentials"))
180+
.build();
166181

167-
String responseBody = new BufferedReader(new InputStreamReader(responseStream))
168-
.lines()
169-
.collect(Collectors.joining("\n"));
182+
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
183+
int responseCode = response.statusCode();
184+
String responseBody = response.body();
170185

171186
if (responseCode != 200) {
172187
throw new AthenaConnectorException(
173-
"Failed to obtain access token: " + responseCode + " - " + responseBody,
188+
"Failed to obtain access token",
189+
"HTTP " + responseCode + " - " + responseBody,
174190
ErrorDetails.builder()
175-
.errorCode(FederationSourceErrorCode.INTERNAL_SERVICE_EXCEPTION.toString())
191+
.errorCode(FederationSourceErrorCode.INVALID_RESPONSE_EXCEPTION.toString())
176192
.build()
177193
);
178194
}
@@ -181,14 +197,15 @@ private String fetchAndStoreNewToken(Map<String, String> oauthConfig) throws Exc
181197
try {
182198
tokenResponse = objectMapper.readTree(responseBody);
183199
}
184-
catch (Exception e) {
200+
catch (JsonProcessingException e) {
185201
throw new AthenaConnectorException(
186-
"Failed to parse token response JSON",
202+
"Failed to parse OAuth token response",
187203
ErrorDetails.builder()
188-
.errorCode(FederationSourceErrorCode.INTERNAL_SERVICE_EXCEPTION.toString())
204+
.errorCode(FederationSourceErrorCode.INVALID_RESPONSE_EXCEPTION.toString())
189205
.build()
190206
);
191207
}
208+
192209
String accessToken = tokenResponse.get(ACCESS_TOKEN).asText();
193210
long expiresIn = tokenResponse.get(EXPIRES_IN).asLong();
194211
long fetchedAt = System.currentTimeMillis() / 1000;
@@ -206,25 +223,70 @@ private String fetchAndStoreNewToken(Map<String, String> oauthConfig) throws Exc
206223
try {
207224
secretString = objectMapper.writeValueAsString(updatedSecretJson);
208225
}
209-
catch (Exception e) {
226+
catch (JsonProcessingException e) {
210227
throw new AthenaConnectorException(
211-
"Failed to serialize updated secret JSON",
228+
"Failed to parse OAuth token response",
212229
ErrorDetails.builder()
213-
.errorCode(FederationSourceErrorCode.INTERNAL_SERVICE_EXCEPTION.toString())
230+
.errorCode(FederationSourceErrorCode.INVALID_RESPONSE_EXCEPTION.toString())
214231
.build()
215232
);
216233
}
217234

218-
secretsManager.getSecretsManager().putSecretValue(builder -> builder
219-
.secretId(this.oauthSecretName)
220-
.secretString(secretString)
221-
.build());
235+
try {
236+
secretsManager.getSecretsManager().putSecretValue(builder -> builder
237+
.secretId(this.oauthSecretName)
238+
.secretString(secretString)
239+
.build());
240+
}
241+
catch (ResourceNotFoundException e) {
242+
throw new AthenaConnectorException(
243+
"Secret not found: " + oauthSecretName,
244+
ErrorDetails.builder()
245+
.errorCode(FederationSourceErrorCode.ENTITY_NOT_FOUND_EXCEPTION.toString())
246+
.build()
247+
);
248+
}
249+
catch (InvalidParameterException | InvalidRequestException e) {
250+
throw new AthenaConnectorException(
251+
"Invalid request to Secrets Manager",
252+
e.getMessage(),
253+
ErrorDetails.builder()
254+
.errorCode(FederationSourceErrorCode.INVALID_INPUT_EXCEPTION.toString())
255+
.build()
256+
);
257+
}
258+
catch (LimitExceededException e) {
259+
throw new AthenaConnectorException(
260+
"Rate limit exceeded for Secrets Manager",
261+
e.getMessage(),
262+
ErrorDetails.builder()
263+
.errorCode(FederationSourceErrorCode.THROTTLING_EXCEPTION.toString())
264+
.build()
265+
);
266+
}
267+
catch (EncryptionFailureException | InternalServiceErrorException e) {
268+
throw new AthenaConnectorException(
269+
"AWS Secrets Manager internal error",
270+
e.getMessage(),
271+
ErrorDetails.builder()
272+
.errorCode(FederationSourceErrorCode.INTERNAL_SERVICE_EXCEPTION.toString())
273+
.build()
274+
);
275+
}
276+
catch (SecretsManagerException e) {
277+
throw new AthenaConnectorException(
278+
"Failed to update OAuth token in Secrets Manager",
279+
e.getMessage(),
280+
ErrorDetails.builder()
281+
.errorCode(FederationSourceErrorCode.INTERNAL_SERVICE_EXCEPTION.toString())
282+
.build()
283+
);
284+
}
222285

223286
return accessToken;
224287
}
225288

226-
// Replace fetchAccessTokenFromSecret to use fetchAndStoreNewToken
227-
private String fetchAccessTokenFromSecret(Map<String, String> oauthConfig) throws Exception
289+
private String fetchAccessTokenFromSecret(Map<String, String> oauthConfig) throws IOException, InterruptedException
228290
{
229291
String accessToken = loadTokenFromSecretsManager(oauthConfig);
230292
if (accessToken == null) {
@@ -245,20 +307,4 @@ private String fetchAccessTokenFromSecret(Map<String, String> oauthConfig) throw
245307
}
246308
}
247309
}
248-
249-
@VisibleForTesting
250-
static HttpURLConnection getHttpURLConnection(String tokenEndpoint, String clientId, String clientSecret) throws IOException
251-
{
252-
URL url = new URL(tokenEndpoint);
253-
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
254-
255-
String auth = clientId + ":" + clientSecret;
256-
String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes(StandardCharsets.UTF_8));
257-
258-
conn.setRequestMethod("POST");
259-
conn.setRequestProperty("Authorization", "Basic " + encodedAuth);
260-
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
261-
conn.setDoOutput(true);
262-
return conn;
263-
}
264310
}

0 commit comments

Comments
 (0)