Skip to content

Commit e949acb

Browse files
Jithendar12aimethedburhan94
authored
Add Support for OAuth in athena-saphana Connector (#2894)
Co-authored-by: Aimery Methena <[email protected]> Co-authored-by: burhan94 <[email protected]>
1 parent 2a34dbb commit e949acb

File tree

9 files changed

+353
-6
lines changed

9 files changed

+353
-6
lines changed

athena-saphana/athena-saphana-connection.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ Transform: 'AWS::Serverless-2016-10-31'
22
Metadata:
33
'AWS::ServerlessRepo::Application':
44
Name: AthenaSaphanaConnectorWithGlueConnection
5-
Description: 'This connector enables Amazon Athena to communicate with your Teradata instance(s) using JDBC driver.'
5+
Description: 'This connector enables Amazon Athena to communicate with your SAP HANA instance(s) using JDBC driver.'
66
Author: 'default author'
77
SpdxLicenseId: Apache-2.0
88
LicenseUrl: LICENSE.txt
@@ -66,7 +66,7 @@ Resources:
6666
- Account: !If [IsRegionBAH, 084828588479, !If [IsRegionHKG, 183295418215, 292517598671]]
6767
ImageConfig:
6868
Command: [ "com.amazonaws.athena.connectors.saphana.SaphanaCompositeHandler" ]
69-
Description: "Enables Amazon Athena to communicate with Teradata using JDBC"
69+
Description: "Enables Amazon Athena to communicate with SAP HANA using JDBC"
7070
Timeout: 900
7171
MemorySize: 3008
7272
Role: !If [ NotHasLambdaRole, !GetAtt FunctionRole.Arn, !Ref LambdaRoleArn ]
@@ -102,6 +102,7 @@ Resources:
102102
Statement:
103103
- Action:
104104
- secretsmanager:GetSecretValue
105+
- secretsmanager:PutSecretValue
105106
Effect: Allow
106107
Resource: !Sub 'arn:${AWS::Partition}:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:${SecretName}*'
107108
- Action:

athena-saphana/athena-saphana.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ Transform: 'AWS::Serverless-2016-10-31'
22
Metadata:
33
'AWS::ServerlessRepo::Application':
44
Name: AthenaSaphanaConnector
5-
Description: 'This connector enables Amazon Athena to communicate with your Teradata instance(s) using JDBC driver.'
5+
Description: 'This connector enables Amazon Athena to communicate with your SAP HANA instance(s) using JDBC driver.'
66
Author: 'default author'
77
SpdxLicenseId: Apache-2.0
88
LicenseUrl: LICENSE.txt
@@ -77,14 +77,15 @@ Resources:
7777
- Account: !If [IsRegionBAH, 084828588479, !If [IsRegionHKG, 183295418215, 292517598671]]
7878
ImageConfig:
7979
Command: [ "com.amazonaws.athena.connectors.saphana.SaphanaMuxCompositeHandler" ]
80-
Description: "Enables Amazon Athena to communicate with Teradata using JDBC"
80+
Description: "Enables Amazon Athena to communicate with SAP HANA using JDBC"
8181
Timeout: !Ref LambdaTimeout
8282
MemorySize: !Ref LambdaMemory
8383
PermissionsBoundary: !If [ HasPermissionsBoundary, !Ref PermissionsBoundaryARN, !Ref "AWS::NoValue" ]
8484
Policies:
8585
- Statement:
8686
- Action:
8787
- secretsmanager:GetSecretValue
88+
- secretsmanager:PutSecretValue
8889
Effect: Allow
8990
Resource: !Sub 'arn:${AWS::Partition}:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:${SecretNamePrefix}*'
9091
Version: '2012-10-17'

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
*/
2121
package com.amazonaws.athena.connectors.saphana;
2222

23+
import com.amazonaws.athena.connector.credentials.CredentialsProvider;
24+
import com.amazonaws.athena.connector.credentials.CredentialsProviderFactory;
2325
import com.amazonaws.athena.connector.lambda.QueryStatusChecker;
2426
import com.amazonaws.athena.connector.lambda.data.Block;
2527
import com.amazonaws.athena.connector.lambda.data.BlockAllocator;
@@ -448,4 +450,14 @@ private String quoteColumnName(String columnName)
448450
columnName = columnName.replace(SAPHANA_QUOTE_CHARACTER, SAPHANA_QUOTE_CHARACTER + SAPHANA_QUOTE_CHARACTER);
449451
return SAPHANA_QUOTE_CHARACTER + columnName + SAPHANA_QUOTE_CHARACTER;
450452
}
453+
454+
@Override
455+
protected CredentialsProvider getCredentialProvider()
456+
{
457+
return CredentialsProviderFactory.createCredentialProvider(
458+
getDatabaseConnectionConfig().getSecret(),
459+
getCachableSecretsManager(),
460+
new SaphanaOAuthCredentialsProvider()
461+
);
462+
}
451463
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*-
2+
* #%L
3+
* athena-saphana
4+
* %%
5+
* Copyright (C) 2019 - 2025 Amazon Web Services
6+
* %%
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* #L%
19+
*/
20+
package com.amazonaws.athena.connectors.saphana;
21+
22+
import com.amazonaws.athena.connector.credentials.Credentials;
23+
import com.amazonaws.athena.connector.credentials.CredentialsConstants;
24+
import org.apache.commons.lang3.Validate;
25+
26+
import java.util.Map;
27+
28+
/**
29+
* Saphana-specific OAuth credentials implementation.
30+
* Unlike the standard OAuthAccessTokenCredentials which uses "accessToken" as the property key,
31+
* Saphana requires the OAuth access token to be provided as the "password" property.
32+
*/
33+
public class SaphanaOAuthAccessTokenCredentials implements Credentials
34+
{
35+
private final String accessToken;
36+
37+
public SaphanaOAuthAccessTokenCredentials(String accessToken)
38+
{
39+
this.accessToken = Validate.notBlank(accessToken, "Access token must not be blank");
40+
}
41+
42+
public String getAccessToken()
43+
{
44+
return accessToken;
45+
}
46+
47+
@Override
48+
public Map<String, String> getProperties()
49+
{
50+
// saphana OAuth mapping: user = empty, password = access token
51+
return Map.of(
52+
CredentialsConstants.USER, "",
53+
CredentialsConstants.PASSWORD, accessToken
54+
);
55+
}
56+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*-
2+
* #%L
3+
* athena-saphana
4+
* %%
5+
* Copyright (C) 2019 - 2025 Amazon Web Services
6+
* %%
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* #L%
19+
*/
20+
package com.amazonaws.athena.connectors.saphana;
21+
22+
import com.amazonaws.athena.connector.credentials.Credentials;
23+
import com.amazonaws.athena.connector.credentials.CredentialsConstants;
24+
import com.amazonaws.athena.connector.credentials.OAuthAccessTokenCredentials;
25+
import com.amazonaws.athena.connector.credentials.OAuthCredentialsProvider;
26+
import com.google.common.annotations.VisibleForTesting;
27+
28+
import java.net.URI;
29+
import java.net.http.HttpClient;
30+
import java.net.http.HttpRequest;
31+
import java.nio.charset.StandardCharsets;
32+
import java.util.Base64;
33+
import java.util.Map;
34+
35+
/**
36+
* OAuth credentials provider for Saphana.
37+
*/
38+
public class SaphanaOAuthCredentialsProvider extends OAuthCredentialsProvider
39+
{
40+
protected static final String TOKEN_URL = "token_url";
41+
42+
public SaphanaOAuthCredentialsProvider()
43+
{
44+
super();
45+
}
46+
47+
@VisibleForTesting
48+
protected SaphanaOAuthCredentialsProvider(HttpClient httpClient)
49+
{
50+
super(httpClient);
51+
}
52+
53+
@Override
54+
protected boolean isOAuthConfigured(Map<String, String> oauthConfig)
55+
{
56+
return oauthConfig.containsKey(CredentialsConstants.CLIENT_ID) &&
57+
!oauthConfig.get(CredentialsConstants.CLIENT_ID).isEmpty() &&
58+
oauthConfig.containsKey(CredentialsConstants.CLIENT_SECRET) &&
59+
!oauthConfig.get(CredentialsConstants.CLIENT_SECRET).isEmpty() &&
60+
oauthConfig.containsKey(TOKEN_URL) &&
61+
!oauthConfig.get(TOKEN_URL).isEmpty();
62+
}
63+
64+
@Override
65+
protected HttpRequest buildTokenRequest(Map<String, String> secretMap)
66+
{
67+
String clientId = secretMap.get(CredentialsConstants.CLIENT_ID);
68+
String clientSecret = secretMap.get(CredentialsConstants.CLIENT_SECRET);
69+
String tokenEndpoint = secretMap.get(TOKEN_URL);
70+
71+
String auth = clientId + ":" + clientSecret;
72+
String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes(StandardCharsets.UTF_8));
73+
74+
return HttpRequest.newBuilder()
75+
.uri(URI.create(tokenEndpoint))
76+
.header("Authorization", "Basic " + encodedAuth)
77+
.header("Content-Type", "application/x-www-form-urlencoded")
78+
.POST(HttpRequest.BodyPublishers.ofString("grant_type=client_credentials"))
79+
.build();
80+
}
81+
82+
@Override
83+
public Credentials getCredential()
84+
{
85+
// Parent always returns OAuthAccessTokenCredentials (or throws exception)
86+
OAuthAccessTokenCredentials parentCredentials = (OAuthAccessTokenCredentials) super.getCredential();
87+
88+
// Convert to Saphana-specific credentials that use "password" property
89+
return new SaphanaOAuthAccessTokenCredentials(parentCredentials.getAccessToken());
90+
}
91+
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
*/
2222
package com.amazonaws.athena.connectors.saphana;
2323

24+
import com.amazonaws.athena.connector.credentials.CredentialsProvider;
25+
import com.amazonaws.athena.connector.credentials.CredentialsProviderFactory;
2426
import com.amazonaws.athena.connector.lambda.domain.Split;
2527
import com.amazonaws.athena.connector.lambda.domain.TableName;
2628
import com.amazonaws.athena.connector.lambda.domain.predicate.Constraints;
@@ -112,4 +114,14 @@ private void clearChildren(Schema schema)
112114
LOGGER.debug("SaphanaRecordHandler:clearChildren exception raised", exception);
113115
}
114116
}
117+
118+
@Override
119+
protected CredentialsProvider getCredentialProvider()
120+
{
121+
return CredentialsProviderFactory.createCredentialProvider(
122+
getDatabaseConnectionConfig().getSecret(),
123+
getCachableSecretsManager(),
124+
new SaphanaOAuthCredentialsProvider()
125+
);
126+
}
115127
}

athena-saphana/src/test/java/com/amazonaws/athena/connectors/saphana/SaphanaMuxJdbcMetadataHandlerTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*-
22
* #%L
3-
* athena-Teradata
3+
* athena-saphana
44
* %%
55
* Copyright (C) 2019 Amazon Web Services
66
* %%

0 commit comments

Comments
 (0)