|
| 1 | +package software.amazon.awssdk.services.json.auth.scheme.internal; |
| 2 | + |
| 3 | +import java.time.Duration; |
| 4 | +import java.util.ArrayList; |
| 5 | +import java.util.List; |
| 6 | +import java.util.Map; |
| 7 | +import java.util.concurrent.CompletableFuture; |
| 8 | +import java.util.function.Supplier; |
| 9 | +import java.util.stream.Collectors; |
| 10 | +import software.amazon.awssdk.annotations.Generated; |
| 11 | +import software.amazon.awssdk.annotations.SdkInternalApi; |
| 12 | +import software.amazon.awssdk.core.SdkRequest; |
| 13 | +import software.amazon.awssdk.core.SelectedAuthScheme; |
| 14 | +import software.amazon.awssdk.core.exception.SdkException; |
| 15 | +import software.amazon.awssdk.core.interceptor.Context; |
| 16 | +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; |
| 17 | +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; |
| 18 | +import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute; |
| 19 | +import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; |
| 20 | +import software.amazon.awssdk.core.internal.util.MetricUtils; |
| 21 | +import software.amazon.awssdk.core.metrics.CoreMetric; |
| 22 | +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; |
| 23 | +import software.amazon.awssdk.http.auth.scheme.BearerAuthScheme; |
| 24 | +import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; |
| 25 | +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; |
| 26 | +import software.amazon.awssdk.http.auth.spi.signer.HttpSigner; |
| 27 | +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; |
| 28 | +import software.amazon.awssdk.identity.spi.Identity; |
| 29 | +import software.amazon.awssdk.identity.spi.IdentityProvider; |
| 30 | +import software.amazon.awssdk.identity.spi.IdentityProviders; |
| 31 | +import software.amazon.awssdk.identity.spi.ResolveIdentityRequest; |
| 32 | +import software.amazon.awssdk.identity.spi.TokenIdentity; |
| 33 | +import software.amazon.awssdk.metrics.MetricCollector; |
| 34 | +import software.amazon.awssdk.metrics.SdkMetric; |
| 35 | +import software.amazon.awssdk.services.json.auth.scheme.JsonAuthSchemeParams; |
| 36 | +import software.amazon.awssdk.services.json.auth.scheme.JsonAuthSchemeProvider; |
| 37 | +import software.amazon.awssdk.utils.Logger; |
| 38 | +import software.amazon.awssdk.utils.Validate; |
| 39 | + |
| 40 | +@Generated("software.amazon.awssdk:codegen") |
| 41 | +@SdkInternalApi |
| 42 | +public final class JsonAuthSchemeInterceptor implements ExecutionInterceptor { |
| 43 | + private static Logger LOG = Logger.loggerFor(JsonAuthSchemeInterceptor.class); |
| 44 | + |
| 45 | + @Override |
| 46 | + public void beforeExecution(Context.BeforeExecution context, ExecutionAttributes executionAttributes) { |
| 47 | + List<AuthSchemeOption> authOptions = resolveAuthOptions(context, executionAttributes); |
| 48 | + SelectedAuthScheme<? extends Identity> selectedAuthScheme = selectAuthScheme(authOptions, executionAttributes); |
| 49 | + putSelectedAuthScheme(executionAttributes, selectedAuthScheme); |
| 50 | + recordEnvironmentTokenBusinessMetric(selectedAuthScheme, executionAttributes); |
| 51 | + } |
| 52 | + |
| 53 | + private List<AuthSchemeOption> resolveAuthOptions(Context.BeforeExecution context, ExecutionAttributes executionAttributes) { |
| 54 | + JsonAuthSchemeProvider authSchemeProvider = Validate.isInstanceOf(JsonAuthSchemeProvider.class, |
| 55 | + executionAttributes.getAttribute(SdkInternalExecutionAttribute.AUTH_SCHEME_RESOLVER), |
| 56 | + "Expected an instance of JsonAuthSchemeProvider"); |
| 57 | + JsonAuthSchemeParams params = authSchemeParams(context.request(), executionAttributes); |
| 58 | + return authSchemeProvider.resolveAuthScheme(params); |
| 59 | + } |
| 60 | + |
| 61 | + private SelectedAuthScheme<? extends Identity> selectAuthScheme(List<AuthSchemeOption> authOptions, |
| 62 | + ExecutionAttributes executionAttributes) { |
| 63 | + MetricCollector metricCollector = executionAttributes.getAttribute(SdkExecutionAttribute.API_CALL_METRIC_COLLECTOR); |
| 64 | + Map<String, AuthScheme<?>> authSchemes = executionAttributes.getAttribute(SdkInternalExecutionAttribute.AUTH_SCHEMES); |
| 65 | + IdentityProviders identityProviders = executionAttributes.getAttribute(SdkInternalExecutionAttribute.IDENTITY_PROVIDERS); |
| 66 | + List<Supplier<String>> discardedReasons = new ArrayList<>(); |
| 67 | + for (AuthSchemeOption authOption : authOptions) { |
| 68 | + AuthScheme<?> authScheme = authSchemes.get(authOption.schemeId()); |
| 69 | + SelectedAuthScheme<? extends Identity> selectedAuthScheme = trySelectAuthScheme(authOption, authScheme, |
| 70 | + identityProviders, discardedReasons, metricCollector, executionAttributes); |
| 71 | + if (selectedAuthScheme != null) { |
| 72 | + if (!discardedReasons.isEmpty()) { |
| 73 | + LOG.debug(() -> String.format("%s auth will be used, discarded: '%s'", authOption.schemeId(), |
| 74 | + discardedReasons.stream().map(Supplier::get).collect(Collectors.joining(", ")))); |
| 75 | + } |
| 76 | + return selectedAuthScheme; |
| 77 | + } |
| 78 | + } |
| 79 | + throw SdkException |
| 80 | + .builder() |
| 81 | + .message( |
| 82 | + "Failed to determine how to authenticate the user: " |
| 83 | + + discardedReasons.stream().map(Supplier::get).collect(Collectors.joining(", "))).build(); |
| 84 | + } |
| 85 | + |
| 86 | + private JsonAuthSchemeParams authSchemeParams(SdkRequest request, ExecutionAttributes executionAttributes) { |
| 87 | + String operation = executionAttributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME); |
| 88 | + JsonAuthSchemeParams.Builder builder = JsonAuthSchemeParams.builder().operation(operation); |
| 89 | + return builder.build(); |
| 90 | + } |
| 91 | + |
| 92 | + private <T extends Identity> SelectedAuthScheme<T> trySelectAuthScheme(AuthSchemeOption authOption, AuthScheme<T> authScheme, |
| 93 | + IdentityProviders identityProviders, List<Supplier<String>> discardedReasons, MetricCollector metricCollector, |
| 94 | + ExecutionAttributes executionAttributes) { |
| 95 | + if (authScheme == null) { |
| 96 | + discardedReasons.add(() -> String.format("'%s' is not enabled for this request.", authOption.schemeId())); |
| 97 | + return null; |
| 98 | + } |
| 99 | + IdentityProvider<T> identityProvider = authScheme.identityProvider(identityProviders); |
| 100 | + if (identityProvider == null) { |
| 101 | + discardedReasons |
| 102 | + .add(() -> String.format("'%s' does not have an identity provider configured.", authOption.schemeId())); |
| 103 | + return null; |
| 104 | + } |
| 105 | + HttpSigner<T> signer; |
| 106 | + try { |
| 107 | + signer = authScheme.signer(); |
| 108 | + } catch (RuntimeException e) { |
| 109 | + discardedReasons.add(() -> String.format("'%s' signer could not be retrieved: %s", authOption.schemeId(), |
| 110 | + e.getMessage())); |
| 111 | + return null; |
| 112 | + } |
| 113 | + ResolveIdentityRequest.Builder identityRequestBuilder = ResolveIdentityRequest.builder(); |
| 114 | + authOption.forEachIdentityProperty(identityRequestBuilder::putProperty); |
| 115 | + CompletableFuture<? extends T> identity; |
| 116 | + SdkMetric<Duration> metric = getIdentityMetric(identityProvider); |
| 117 | + if (metric == null) { |
| 118 | + identity = identityProvider.resolveIdentity(identityRequestBuilder.build()); |
| 119 | + } else { |
| 120 | + identity = MetricUtils.reportDuration(() -> identityProvider.resolveIdentity(identityRequestBuilder.build()), |
| 121 | + metricCollector, metric); |
| 122 | + } |
| 123 | + return new SelectedAuthScheme<>(identity, signer, authOption); |
| 124 | + } |
| 125 | + |
| 126 | + private SdkMetric<Duration> getIdentityMetric(IdentityProvider<?> identityProvider) { |
| 127 | + Class<?> identityType = identityProvider.identityType(); |
| 128 | + if (identityType == AwsCredentialsIdentity.class) { |
| 129 | + return CoreMetric.CREDENTIALS_FETCH_DURATION; |
| 130 | + } |
| 131 | + if (identityType == TokenIdentity.class) { |
| 132 | + return CoreMetric.TOKEN_FETCH_DURATION; |
| 133 | + } |
| 134 | + return null; |
| 135 | + } |
| 136 | + |
| 137 | + private <T extends Identity> void putSelectedAuthScheme(ExecutionAttributes attributes, |
| 138 | + SelectedAuthScheme<T> selectedAuthScheme) { |
| 139 | + SelectedAuthScheme<?> existingAuthScheme = attributes.getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME); |
| 140 | + if (existingAuthScheme != null) { |
| 141 | + AuthSchemeOption.Builder selectedOption = selectedAuthScheme.authSchemeOption().toBuilder(); |
| 142 | + existingAuthScheme.authSchemeOption().forEachIdentityProperty(selectedOption::putIdentityPropertyIfAbsent); |
| 143 | + existingAuthScheme.authSchemeOption().forEachSignerProperty(selectedOption::putSignerPropertyIfAbsent); |
| 144 | + selectedAuthScheme = new SelectedAuthScheme<>(selectedAuthScheme.identity(), selectedAuthScheme.signer(), |
| 145 | + selectedOption.build()); |
| 146 | + } |
| 147 | + attributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, selectedAuthScheme); |
| 148 | + } |
| 149 | + |
| 150 | + private <T extends Identity> void recordEnvironmentTokenBusinessMetric(SelectedAuthScheme<T> selectedAuthScheme, |
| 151 | + ExecutionAttributes executionAttributes) { |
| 152 | + String tokenFromEnv = executionAttributes.getAttribute(SdkInternalExecutionAttribute.TOKEN_CONFIGURED_FROM_ENV); |
| 153 | + if (selectedAuthScheme != null && selectedAuthScheme.authSchemeOption().schemeId().equals(BearerAuthScheme.SCHEME_ID) |
| 154 | + && selectedAuthScheme.identity().isDone()) { |
| 155 | + if (selectedAuthScheme.identity().getNow(null) instanceof TokenIdentity) { |
| 156 | + TokenIdentity configuredToken = (TokenIdentity) selectedAuthScheme.identity().getNow(null); |
| 157 | + if (configuredToken.token().equals(tokenFromEnv)) { |
| 158 | + executionAttributes.getAttribute(SdkInternalExecutionAttribute.BUSINESS_METRICS).addMetric( |
| 159 | + BusinessMetricFeatureId.BEARER_SERVICE_ENV_VARS.value()); |
| 160 | + } |
| 161 | + } |
| 162 | + } |
| 163 | + } |
| 164 | +} |
0 commit comments