16
16
package software .amazon .awssdk .services .polly .internal .presigner ;
17
17
18
18
import static java .util .stream .Collectors .toMap ;
19
- import static software .amazon .awssdk .auth .signer .AwsSignerExecutionAttribute .PRESIGNER_EXPIRATION ;
20
19
21
20
import java .net .URI ;
21
+ import java .time .Clock ;
22
+ import java .time .Duration ;
22
23
import java .time .Instant ;
24
+ import java .time .ZoneOffset ;
23
25
import java .util .ArrayList ;
24
26
import java .util .Collections ;
25
27
import java .util .List ;
26
28
import java .util .Map ;
29
+ import java .util .concurrent .CompletableFuture ;
27
30
import java .util .function .Function ;
28
31
import java .util .function .Supplier ;
29
32
import java .util .stream .Stream ;
30
33
import software .amazon .awssdk .annotations .SdkInternalApi ;
34
+ import software .amazon .awssdk .annotations .SdkTestInternalApi ;
31
35
import software .amazon .awssdk .auth .credentials .AwsCredentialsProvider ;
32
36
import software .amazon .awssdk .auth .credentials .CredentialUtils ;
33
37
import software .amazon .awssdk .auth .credentials .DefaultCredentialsProvider ;
34
- import software .amazon .awssdk .auth .signer .Aws4Signer ;
35
38
import software .amazon .awssdk .auth .signer .AwsSignerExecutionAttribute ;
36
39
import software .amazon .awssdk .awscore .AwsExecutionAttribute ;
37
40
import software .amazon .awssdk .awscore .AwsRequestOverrideConfiguration ;
41
44
import software .amazon .awssdk .awscore .presigner .PresignRequest ;
42
45
import software .amazon .awssdk .awscore .presigner .PresignedRequest ;
43
46
import software .amazon .awssdk .core .ClientType ;
47
+ import software .amazon .awssdk .core .SelectedAuthScheme ;
44
48
import software .amazon .awssdk .core .interceptor .ExecutionAttributes ;
45
49
import software .amazon .awssdk .core .interceptor .SdkExecutionAttribute ;
46
50
import software .amazon .awssdk .core .interceptor .SdkInternalExecutionAttribute ;
47
51
import software .amazon .awssdk .core .signer .Presigner ;
48
52
import software .amazon .awssdk .core .signer .Signer ;
49
53
import software .amazon .awssdk .http .SdkHttpFullRequest ;
50
54
import software .amazon .awssdk .http .SdkHttpMethod ;
55
+ import software .amazon .awssdk .http .SdkHttpRequest ;
51
56
import software .amazon .awssdk .http .auth .aws .scheme .AwsV4AuthScheme ;
57
+ import software .amazon .awssdk .http .auth .aws .signer .AwsV4FamilyHttpSigner ;
58
+ import software .amazon .awssdk .http .auth .aws .signer .AwsV4HttpSigner ;
52
59
import software .amazon .awssdk .http .auth .spi .scheme .AuthScheme ;
60
+ import software .amazon .awssdk .http .auth .spi .scheme .AuthSchemeOption ;
61
+ import software .amazon .awssdk .http .auth .spi .signer .HttpSigner ;
62
+ import software .amazon .awssdk .http .auth .spi .signer .SignRequest ;
63
+ import software .amazon .awssdk .http .auth .spi .signer .SignedRequest ;
53
64
import software .amazon .awssdk .identity .spi .AwsCredentialsIdentity ;
65
+ import software .amazon .awssdk .identity .spi .Identity ;
54
66
import software .amazon .awssdk .identity .spi .IdentityProvider ;
55
67
import software .amazon .awssdk .identity .spi .IdentityProviders ;
56
68
import software .amazon .awssdk .profiles .ProfileFile ;
74
86
public final class DefaultPollyPresigner implements PollyPresigner {
75
87
private static final String SIGNING_NAME = "polly" ;
76
88
private static final String SERVICE_NAME = "polly" ;
77
- private static final Aws4Signer DEFAULT_SIGNER = Aws4Signer .create ();
78
-
89
+ private final Clock signingClock ;
79
90
private final Supplier <ProfileFile > profileFile ;
80
91
private final String profileName ;
81
92
private final Region region ;
@@ -85,6 +96,8 @@ public final class DefaultPollyPresigner implements PollyPresigner {
85
96
private final Boolean fipsEnabled ;
86
97
87
98
private DefaultPollyPresigner (BuilderImpl builder ) {
99
+ this .signingClock = builder .signingClock != null ? builder .signingClock
100
+ : Clock .systemUTC ();
88
101
this .profileFile = ProfileFile ::defaultProfileFile ;
89
102
this .profileName = ProfileFileSystemSetting .AWS_PROFILE .getStringValueOrThrow ();
90
103
this .region = builder .region != null ? builder .region
@@ -124,22 +137,29 @@ public void close() {
124
137
IoUtils .closeIfCloseable (credentialsProvider , null );
125
138
}
126
139
140
+ // Builder for testing that allows you to set the signing clock.
141
+ @ SdkTestInternalApi
142
+ static PollyPresigner .Builder builder (Clock signingClock ) {
143
+ return new BuilderImpl ()
144
+ .signingClock (signingClock );
145
+ }
146
+
127
147
public static PollyPresigner .Builder builder () {
128
148
return new BuilderImpl ();
129
149
}
130
150
131
151
@ Override
132
152
public PresignedSynthesizeSpeechRequest presignSynthesizeSpeech (
133
- SynthesizeSpeechPresignRequest synthesizeSpeechPresignRequest ) {
153
+ SynthesizeSpeechPresignRequest synthesizeSpeechPresignRequest ) {
134
154
return presign (PresignedSynthesizeSpeechRequest .builder (),
135
155
synthesizeSpeechPresignRequest ,
136
156
synthesizeSpeechPresignRequest .synthesizeSpeechRequest (),
137
157
SynthesizeSpeechRequestMarshaller .getInstance ()::marshall )
138
- .build ();
158
+ .build ();
139
159
}
140
160
141
161
private <T extends PollyRequest > SdkHttpFullRequest marshallRequest (
142
- T request , Function <T , SdkHttpFullRequest .Builder > marshalFn ) {
162
+ T request , Function <T , SdkHttpFullRequest .Builder > marshalFn ) {
143
163
SdkHttpFullRequest .Builder requestBuilder = marshalFn .apply (request );
144
164
applyOverrideHeadersAndQueryParams (requestBuilder , request );
145
165
applyEndpoint (requestBuilder );
@@ -149,14 +169,22 @@ private <T extends PollyRequest> SdkHttpFullRequest marshallRequest(
149
169
/**
150
170
* Generate a {@link PresignedRequest} from a {@link PresignedRequest} and {@link PollyRequest}.
151
171
*/
152
- private <T extends PresignedRequest .Builder , U extends PollyRequest > T presign (T presignedRequest ,
153
- PresignRequest presignRequest ,
154
- U requestToPresign ,
155
- Function <U , SdkHttpFullRequest .Builder > requestMarshaller ) {
172
+ private <T extends PresignedRequest .Builder , U extends PollyRequest > T presign (
173
+ T presignedRequest ,
174
+ PresignRequest presignRequest ,
175
+ U requestToPresign ,
176
+ Function <U , SdkHttpFullRequest .Builder > requestMarshaller
177
+ ) {
156
178
ExecutionAttributes execAttrs = createExecutionAttributes (presignRequest , requestToPresign );
157
-
158
179
SdkHttpFullRequest marshalledRequest = marshallRequest (requestToPresign , requestMarshaller );
159
- SdkHttpFullRequest signedHttpRequest = presignRequest (requestToPresign , marshalledRequest , execAttrs );
180
+ Presigner presigner = resolvePresigner (requestToPresign );
181
+ SdkHttpFullRequest signedHttpRequest = null ;
182
+ if (presigner != null ) {
183
+ signedHttpRequest = presignRequest (presigner , requestToPresign , marshalledRequest , execAttrs );
184
+ } else {
185
+ SelectedAuthScheme <AwsCredentialsIdentity > authScheme = selectedAuthScheme (requestToPresign , execAttrs );
186
+ signedHttpRequest = doSraPresign (marshalledRequest , authScheme , presignRequest .signatureDuration ());
187
+ }
160
188
initializePresignedRequest (presignedRequest , execAttrs , signedHttpRequest );
161
189
return presignedRequest ;
162
190
}
@@ -167,54 +195,111 @@ private void initializePresignedRequest(PresignedRequest.Builder presignedReques
167
195
List <String > signedHeadersQueryParam = signedHttpRequest .firstMatchingRawQueryParameters ("X-Amz-SignedHeaders" );
168
196
169
197
Map <String , List <String >> signedHeaders =
170
- signedHeadersQueryParam .stream ()
171
- .flatMap (h -> Stream .of (h .split (";" )))
172
- .collect (toMap (h -> h , h -> signedHttpRequest .firstMatchingHeader (h )
173
- .map (Collections ::singletonList )
174
- .orElseGet (ArrayList ::new )));
198
+ signedHeadersQueryParam .stream ()
199
+ .flatMap (h -> Stream .of (h .split (";" )))
200
+ .collect (toMap (h -> h , h -> signedHttpRequest .firstMatchingHeader (h )
201
+ .map (Collections ::singletonList )
202
+ .orElseGet (ArrayList ::new )));
175
203
176
204
boolean isBrowserExecutable = signedHttpRequest .method () == SdkHttpMethod .GET &&
177
- (signedHeaders .isEmpty () ||
178
- (signedHeaders .size () == 1 && signedHeaders .containsKey ("host" )));
205
+ (signedHeaders .isEmpty () ||
206
+ (signedHeaders .size () == 1 && signedHeaders .containsKey ("host" )));
179
207
180
- presignedRequest .expiration (execAttrs .getAttribute (PRESIGNER_EXPIRATION ))
181
- .isBrowserExecutable (isBrowserExecutable )
182
- .httpRequest (signedHttpRequest )
183
- .signedHeaders (signedHeaders );
208
+ presignedRequest .expiration (execAttrs .getAttribute (AwsSignerExecutionAttribute . PRESIGNER_EXPIRATION ))
209
+ .isBrowserExecutable (isBrowserExecutable )
210
+ .httpRequest (signedHttpRequest )
211
+ .signedHeaders (signedHeaders );
184
212
}
185
213
186
- private SdkHttpFullRequest presignRequest (PollyRequest requestToPresign ,
214
+ private SdkHttpFullRequest presignRequest (Presigner presigner ,
215
+ PollyRequest requestToPresign ,
187
216
SdkHttpFullRequest marshalledRequest ,
188
217
ExecutionAttributes executionAttributes ) {
189
- Presigner presigner = resolvePresigner (requestToPresign );
190
218
SdkHttpFullRequest presigned = presigner .presign (marshalledRequest , executionAttributes );
191
219
List <String > signedHeadersQueryParam = presigned .firstMatchingRawQueryParameters ("X-Amz-SignedHeaders" );
192
220
Validate .validState (!signedHeadersQueryParam .isEmpty (),
193
- "Only SigV4 presigners are supported at this time, but the configured "
194
- + "presigner (%s) did not seem to generate a SigV4 signature." , presigner );
221
+ "Only SigV4 presigners are supported at this time, but the configured "
222
+ + "presigner (%s) did not seem to generate a SigV4 signature." , presigner );
195
223
return presigned ;
196
224
}
197
225
226
+ private <T extends Identity > SdkHttpFullRequest doSraPresign (SdkHttpFullRequest request ,
227
+ SelectedAuthScheme <T > selectedAuthScheme ,
228
+ Duration expirationDuration ) {
229
+ CompletableFuture <? extends T > identityFuture = selectedAuthScheme .identity ();
230
+ T identity = CompletableFutureUtils .joinLikeSync (identityFuture );
231
+
232
+ // presigned url puts auth info in query string, does not sign the payload, and has an expiry.
233
+ SignRequest .Builder <T > signRequestBuilder = SignRequest
234
+ .builder (identity )
235
+ .putProperty (AwsV4FamilyHttpSigner .AUTH_LOCATION , AwsV4FamilyHttpSigner .AuthLocation .QUERY_STRING )
236
+ .putProperty (AwsV4FamilyHttpSigner .EXPIRATION_DURATION , expirationDuration )
237
+ .putProperty (HttpSigner .SIGNING_CLOCK , signingClock )
238
+ .request (request )
239
+ .payload (request .contentStreamProvider ().orElse (null ));
240
+ AuthSchemeOption authSchemeOption = selectedAuthScheme .authSchemeOption ();
241
+ authSchemeOption .forEachSignerProperty (signRequestBuilder ::putProperty );
242
+
243
+ HttpSigner <T > signer = selectedAuthScheme .signer ();
244
+ SignedRequest signedRequest = signer .sign (signRequestBuilder .build ());
245
+ return toSdkHttpFullRequest (signedRequest );
246
+ }
247
+
248
+ private SelectedAuthScheme <AwsCredentialsIdentity > selectedAuthScheme (PollyRequest requestToPresign ,
249
+ ExecutionAttributes attributes ) {
250
+ AuthScheme <AwsCredentialsIdentity > authScheme = AwsV4AuthScheme .create ();
251
+ AwsCredentialsIdentity credentialsIdentity = resolveCredentials (resolveCredentialsProvider (requestToPresign ));
252
+ AuthSchemeOption .Builder optionBuilder = AuthSchemeOption .builder ()
253
+ .schemeId (authScheme .schemeId ());
254
+ optionBuilder .putSignerProperty (AwsV4HttpSigner .SERVICE_SIGNING_NAME , SERVICE_NAME );
255
+ String region = attributes .getAttribute (AwsExecutionAttribute .AWS_REGION ).id ();
256
+ optionBuilder .putSignerProperty (AwsV4HttpSigner .REGION_NAME , region );
257
+ return new SelectedAuthScheme <>(CompletableFuture .completedFuture (credentialsIdentity ), authScheme .signer (),
258
+ optionBuilder .build ());
259
+ }
260
+
261
+ private SdkHttpFullRequest toSdkHttpFullRequest (SignedRequest signedRequest ) {
262
+ SdkHttpRequest request = signedRequest .request ();
263
+
264
+ return SdkHttpFullRequest .builder ()
265
+ .contentStreamProvider (signedRequest .payload ().orElse (null ))
266
+ .protocol (request .protocol ())
267
+ .method (request .method ())
268
+ .host (request .host ())
269
+ .port (request .port ())
270
+ .encodedPath (request .encodedPath ())
271
+ .applyMutation (r -> request .forEachHeader (r ::putHeader ))
272
+ .applyMutation (r -> request .forEachRawQueryParameter (r ::putRawQueryParameter ))
273
+ .build ();
274
+ }
275
+
198
276
private ExecutionAttributes createExecutionAttributes (PresignRequest presignRequest , PollyRequest requestToPresign ) {
199
- Instant signatureExpiration = Instant .now ().plus (presignRequest .signatureDuration ());
277
+ // A fixed signingClock is used, so that the current time used by the signing logic, as well as to determine expiration
278
+ // are the same.
279
+ Instant signingInstant = signingClock .instant ();
280
+ Clock signingClockOverride = Clock .fixed (signingInstant , ZoneOffset .UTC );
281
+ Duration expirationDuration = presignRequest .signatureDuration ();
282
+ Instant signatureExpiration = signingInstant .plus (expirationDuration );
283
+
200
284
AwsCredentialsIdentity credentials = resolveCredentials (resolveCredentialsProvider (requestToPresign ));
201
285
Validate .validState (credentials != null , "Credential providers must never return null." );
202
286
203
287
return new ExecutionAttributes ()
204
- .putAttribute (AwsSignerExecutionAttribute .AWS_CREDENTIALS , CredentialUtils .toCredentials (credentials ))
205
- .putAttribute (AwsSignerExecutionAttribute .SERVICE_SIGNING_NAME , SIGNING_NAME )
206
- .putAttribute (AwsExecutionAttribute .AWS_REGION , region )
207
- .putAttribute (AwsSignerExecutionAttribute .SIGNING_REGION , region )
208
- .putAttribute (SdkInternalExecutionAttribute .IS_FULL_DUPLEX , false )
209
- .putAttribute (SdkExecutionAttribute .CLIENT_TYPE , ClientType .SYNC )
210
- .putAttribute (SdkExecutionAttribute .SERVICE_NAME , SERVICE_NAME )
211
- .putAttribute (PRESIGNER_EXPIRATION , signatureExpiration )
212
- .putAttribute (SdkInternalExecutionAttribute .AUTH_SCHEME_RESOLVER , PollyAuthSchemeProvider .defaultProvider ())
213
- .putAttribute (SdkInternalExecutionAttribute .AUTH_SCHEMES , authSchemes ())
214
- .putAttribute (SdkInternalExecutionAttribute .IDENTITY_PROVIDERS ,
215
- IdentityProviders .builder ()
216
- .putIdentityProvider (credentialsProvider ())
217
- .build ());
288
+ .putAttribute (AwsSignerExecutionAttribute .AWS_CREDENTIALS , CredentialUtils .toCredentials (credentials ))
289
+ .putAttribute (AwsSignerExecutionAttribute .SERVICE_SIGNING_NAME , SIGNING_NAME )
290
+ .putAttribute (AwsSignerExecutionAttribute .SIGNING_CLOCK , signingClockOverride )
291
+ .putAttribute (AwsSignerExecutionAttribute .PRESIGNER_EXPIRATION , signatureExpiration )
292
+ .putAttribute (AwsExecutionAttribute .AWS_REGION , region )
293
+ .putAttribute (AwsSignerExecutionAttribute .SIGNING_REGION , region )
294
+ .putAttribute (SdkInternalExecutionAttribute .IS_FULL_DUPLEX , false )
295
+ .putAttribute (SdkExecutionAttribute .CLIENT_TYPE , ClientType .SYNC )
296
+ .putAttribute (SdkExecutionAttribute .SERVICE_NAME , SERVICE_NAME )
297
+ .putAttribute (SdkInternalExecutionAttribute .AUTH_SCHEME_RESOLVER , PollyAuthSchemeProvider .defaultProvider ())
298
+ .putAttribute (SdkInternalExecutionAttribute .AUTH_SCHEMES , authSchemes ())
299
+ .putAttribute (SdkInternalExecutionAttribute .IDENTITY_PROVIDERS ,
300
+ IdentityProviders .builder ()
301
+ .putIdentityProvider (credentialsProvider ())
302
+ .build ());
218
303
}
219
304
220
305
private Map <String , AuthScheme <?>> authSchemes () {
@@ -224,7 +309,7 @@ private Map<String, AuthScheme<?>> authSchemes() {
224
309
225
310
private IdentityProvider <? extends AwsCredentialsIdentity > resolveCredentialsProvider (PollyRequest request ) {
226
311
return request .overrideConfiguration ().flatMap (AwsRequestOverrideConfiguration ::credentialsIdentityProvider )
227
- .orElse (credentialsProvider );
312
+ .orElse (credentialsProvider );
228
313
}
229
314
230
315
private AwsCredentialsIdentity resolveCredentials (IdentityProvider <? extends AwsCredentialsIdentity > credentialsProvider ) {
@@ -233,10 +318,13 @@ private AwsCredentialsIdentity resolveCredentials(IdentityProvider<? extends Aws
233
318
234
319
private Presigner resolvePresigner (PollyRequest request ) {
235
320
Signer signer = request .overrideConfiguration ().flatMap (AwsRequestOverrideConfiguration ::signer )
236
- .orElse (DEFAULT_SIGNER );
237
-
321
+ .orElse (null );
322
+ if (signer == null ) {
323
+ return null ;
324
+ }
238
325
return Validate .isInstanceOf (Presigner .class , signer ,
239
- "Signer of type %s given in request override is not a Presigner" , signer .getClass ().getSimpleName ());
326
+ "Signer of type %s given in request override is not a Presigner" ,
327
+ signer .getClass ().getName ());
240
328
}
241
329
242
330
private void applyOverrideHeadersAndQueryParams (SdkHttpFullRequest .Builder httpRequestBuilder , PollyRequest request ) {
@@ -249,8 +337,8 @@ private void applyOverrideHeadersAndQueryParams(SdkHttpFullRequest.Builder httpR
249
337
private void applyEndpoint (SdkHttpFullRequest .Builder httpRequestBuilder ) {
250
338
URI uri = resolveEndpoint ();
251
339
httpRequestBuilder .protocol (uri .getScheme ())
252
- .host (uri .getHost ())
253
- .port (uri .getPort ());
340
+ .host (uri .getHost ())
341
+ .port (uri .getPort ());
254
342
}
255
343
256
344
private URI resolveEndpoint () {
@@ -259,21 +347,27 @@ private URI resolveEndpoint() {
259
347
}
260
348
261
349
return new DefaultServiceEndpointBuilder (SERVICE_NAME , "https" )
262
- .withRegion (region )
263
- .withProfileFile (profileFile )
264
- .withProfileName (profileName )
265
- .withDualstackEnabled (dualstackEnabled )
266
- .withFipsEnabled (fipsEnabled )
267
- .getServiceEndpoint ();
350
+ .withRegion (region )
351
+ .withProfileFile (profileFile )
352
+ .withProfileName (profileName )
353
+ .withDualstackEnabled (dualstackEnabled )
354
+ .withFipsEnabled (fipsEnabled )
355
+ .getServiceEndpoint ();
268
356
}
269
357
270
358
public static class BuilderImpl implements PollyPresigner .Builder {
359
+ private Clock signingClock ;
271
360
private Region region ;
272
361
private IdentityProvider <? extends AwsCredentialsIdentity > credentialsProvider ;
273
362
private URI endpointOverride ;
274
363
private Boolean dualstackEnabled ;
275
364
private Boolean fipsEnabled ;
276
365
366
+ public Builder signingClock (Clock signingClock ) {
367
+ this .signingClock = signingClock ;
368
+ return this ;
369
+ }
370
+
277
371
@ Override
278
372
public Builder region (Region region ) {
279
373
this .region = region ;
0 commit comments