Skip to content

Commit deff952

Browse files
committed
ProfileTokenProviderLoader can use Supplier<Profilefile> internally
1 parent 82f045e commit deff952

File tree

4 files changed

+140
-65
lines changed

4 files changed

+140
-65
lines changed

core/auth/src/main/java/software/amazon/awssdk/auth/token/credentials/ProfileTokenProvider.java

Lines changed: 27 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
package software.amazon.awssdk.auth.token.credentials;
1717

18-
import java.util.Objects;
1918
import java.util.Optional;
2019
import java.util.function.Supplier;
2120
import software.amazon.awssdk.annotations.SdkPublicApi;
@@ -38,35 +37,45 @@
3837
*/
3938
@SdkPublicApi
4039
public final class ProfileTokenProvider implements SdkTokenProvider, SdkAutoCloseable {
41-
private SdkTokenProvider tokenProvider;
40+
private final SdkTokenProvider tokenProvider;
4241
private final RuntimeException loadException;
43-
private final Supplier<ProfileFile> profileFile;
44-
private volatile ProfileFile currentProfileFile;
42+
4543
private final String profileName;
4644

4745
/**
4846
* @see #builder()
4947
*/
5048
private ProfileTokenProvider(BuilderImpl builder) {
49+
SdkTokenProvider sdkTokenProvider = null;
5150
RuntimeException thrownException = null;
52-
Supplier<ProfileFile> selectedProfileFileSupplier = null;
51+
Supplier<ProfileFile> selectedProfileFile = null;
5352
String selectedProfileName = null;
5453

5554
try {
5655
selectedProfileName = Optional.ofNullable(builder.profileName)
5756
.orElseGet(ProfileFileSystemSetting.AWS_PROFILE::getStringValueOrThrow);
5857

59-
selectedProfileFileSupplier = Optional.ofNullable(builder.profileFile)
60-
.orElse(builder.defaultProfileFileLoader);
58+
// Load the profiles file
59+
selectedProfileFile = Optional.ofNullable(builder.profileFile)
60+
.orElse(builder.defaultProfileFileLoader);
61+
62+
// Load the profile and token provider
63+
sdkTokenProvider = createTokenProvider(selectedProfileFile, selectedProfileName);
6164

6265
} catch (RuntimeException e) {
6366
// If we couldn't load the provider for some reason, save an exception describing why.
6467
thrownException = e;
6568
}
6669

67-
this.loadException = thrownException;
68-
this.profileFile = selectedProfileFileSupplier;
69-
this.profileName = selectedProfileName;
70+
if (thrownException != null) {
71+
this.loadException = thrownException;
72+
this.tokenProvider = null;
73+
this.profileName = null;
74+
} else {
75+
this.loadException = null;
76+
this.tokenProvider = sdkTokenProvider;
77+
this.profileName = selectedProfileName;
78+
}
7079
}
7180

7281
/**
@@ -99,21 +108,13 @@ public SdkToken resolveToken() {
99108
if (loadException != null) {
100109
throw loadException;
101110
}
102-
103-
ProfileFile cachedOrRefreshedProfileFile = refreshProfileFile();
104-
if (isNewProfileFile(cachedOrRefreshedProfileFile)) {
105-
currentProfileFile = cachedOrRefreshedProfileFile;
106-
handleProfileFileReload(cachedOrRefreshedProfileFile);
107-
}
108-
109111
return tokenProvider.resolveToken();
110112
}
111113

112114
@Override
113115
public String toString() {
114116
return ToString.builder("ProfileTokenProvider")
115117
.add("profileName", profileName)
116-
.add("profileFile", currentProfileFile)
117118
.build();
118119
}
119120

@@ -123,27 +124,14 @@ public void close() {
123124
IoUtils.closeIfCloseable(tokenProvider, null);
124125
}
125126

126-
private SdkTokenProvider createTokenProvider(ProfileFile profileFile, String profileName) {
127-
// Load the profile and token provider
128-
return profileFile.profile(profileName)
129-
.flatMap(p -> new ProfileTokenProviderLoader(profileFile, p).tokenProvider())
130-
.orElseThrow(() -> {
131-
String errorMessage = String.format("Profile file contained no information for " +
132-
"profile '%s': %s", profileName, profileFile);
133-
return SdkClientException.builder().message(errorMessage).build();
134-
});
135-
}
136-
137-
private void handleProfileFileReload(ProfileFile profileFile) {
138-
tokenProvider = createTokenProvider(profileFile, profileName);
139-
}
140-
141-
private ProfileFile refreshProfileFile() {
142-
return profileFile.get();
143-
}
144-
145-
private boolean isNewProfileFile(ProfileFile profileFile) {
146-
return !Objects.equals(currentProfileFile, profileFile);
127+
private SdkTokenProvider createTokenProvider(Supplier<ProfileFile> profileFile, String profileName) {
128+
return new ProfileTokenProviderLoader(profileFile, profileName)
129+
.tokenProvider()
130+
.orElseThrow(() -> {
131+
String errorMessage = String.format("Profile file contained no information for " +
132+
"profile '%s'", profileName);
133+
return SdkClientException.builder().message(errorMessage).build();
134+
});
147135
}
148136

149137
/**

core/auth/src/main/java/software/amazon/awssdk/auth/token/internal/ProfileTokenProviderLoader.java

Lines changed: 75 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,19 @@
1717

1818
import java.lang.reflect.InvocationTargetException;
1919
import java.util.Arrays;
20+
import java.util.Objects;
2021
import java.util.Optional;
22+
import java.util.function.Supplier;
2123
import software.amazon.awssdk.annotations.SdkInternalApi;
2224
import software.amazon.awssdk.auth.token.credentials.ChildProfileTokenProviderFactory;
2325
import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider;
26+
import software.amazon.awssdk.core.exception.SdkClientException;
2427
import software.amazon.awssdk.core.internal.util.ClassLoaderHelper;
2528
import software.amazon.awssdk.profiles.Profile;
2629
import software.amazon.awssdk.profiles.ProfileFile;
2730
import software.amazon.awssdk.profiles.ProfileProperty;
2831
import software.amazon.awssdk.profiles.internal.ProfileSection;
32+
import software.amazon.awssdk.utils.Lazy;
2933
import software.amazon.awssdk.utils.Validate;
3034

3135
/**
@@ -36,12 +40,33 @@ public final class ProfileTokenProviderLoader {
3640
private static final String SSO_OIDC_TOKEN_PROVIDER_FACTORY =
3741
"software.amazon.awssdk.services.ssooidc.SsoOidcProfileTokenProviderFactory";
3842

43+
private final boolean createProviderFromSupplier;
44+
3945
private final Profile profile;
4046
private final ProfileFile profileFile;
47+
private final Supplier<ProfileFile> profileFileSupplier;
48+
private final String profileName;
49+
private volatile ProfileFile currentProfileFile;
50+
private volatile SdkTokenProvider currentTokenProvider;
51+
52+
private final Lazy<ChildProfileTokenProviderFactory> factory;
4153

42-
public ProfileTokenProviderLoader(ProfileFile profileFile, Profile profile) {
43-
this.profile = Validate.paramNotNull(profile, "profile");
54+
public ProfileTokenProviderLoader(ProfileFile profileFile, String profileName) {
55+
this.createProviderFromSupplier = false;
4456
this.profileFile = Validate.paramNotNull(profileFile, "profileFile");
57+
this.profileFileSupplier = null;
58+
this.profileName = Validate.paramNotNull(profileName, "profileName");
59+
this.profile = resolveProfile(profileFile, profileName);
60+
this.factory = new Lazy<>(this::ssoTokenProviderFactory);
61+
}
62+
63+
public ProfileTokenProviderLoader(Supplier<ProfileFile> profileFile, String profileName) {
64+
createProviderFromSupplier = true;
65+
this.profileFile = null;
66+
this.profileFileSupplier = Validate.paramNotNull(profileFile, "profileFile");
67+
this.profileName = Validate.paramNotNull(profileName, "profileName");
68+
this.profile = null;
69+
this.factory = new Lazy<>(this::ssoTokenProviderFactory);
4570
}
4671

4772
/**
@@ -55,19 +80,53 @@ public Optional<SdkTokenProvider> tokenProvider() {
5580
* Create the SSO credentials provider based on the related profile properties.
5681
*/
5782
private SdkTokenProvider ssoProfileCredentialsProvider() {
83+
if (createProviderFromSupplier) {
84+
return () -> ssoProfileCredentialsProvider(profileFileSupplier, profileName).resolveToken();
85+
}
5886

59-
String profileSsoSectionName = profile.property(ProfileSection.SSO_SESSION.getPropertyKeyName())
60-
.orElseThrow(() -> new IllegalArgumentException(
61-
"Profile " + profile.name() + " does not have sso_session property"));
87+
return ssoProfileCredentialsProvider(profileFile, profile);
88+
}
89+
90+
private SdkTokenProvider ssoProfileCredentialsProvider(ProfileFile profileFile, Profile profile) {
91+
String profileSsoSectionName = profileSsoSectionName(profile);
92+
Profile ssoProfile = ssoProfile(profileFile, profileSsoSectionName);
6293

63-
Profile ssoProfile = profileFile.getSection(ProfileSection.SSO_SESSION.getSectionTitle(), profileSsoSectionName)
64-
.orElseThrow(() -> new IllegalArgumentException(
65-
"Sso-session section not found with sso-session title " + profileSsoSectionName));
94+
validateRequiredProperties(ssoProfile, ProfileProperty.SSO_REGION, ProfileProperty.SSO_START_URL);
95+
96+
return factory.getValue().create(profileFile, profile);
97+
}
98+
99+
private synchronized SdkTokenProvider ssoProfileCredentialsProvider(Supplier<ProfileFile> profileFile, String profileName) {
100+
ProfileFile profileFileInstance = profileFile.get();
101+
Profile profileInstance = resolveProfile(profileFileInstance, profileName);
102+
if (!Objects.equals(profileFileInstance, currentProfileFile)) {
103+
currentProfileFile = profileFileInstance;
104+
currentTokenProvider = ssoProfileCredentialsProvider(profileFileInstance, profileInstance);
105+
}
106+
107+
return currentTokenProvider;
108+
}
109+
110+
private Profile resolveProfile(ProfileFile profileFile, String profileName) {
111+
return profileFile.profile(profileName)
112+
.orElseThrow(() -> {
113+
String errorMessage = String.format("Profile file contained no information for profile '%s': %s",
114+
profileName, profileFile);
115+
return SdkClientException.builder().message(errorMessage).build();
116+
});
117+
}
118+
119+
private String profileSsoSectionName(Profile profile) {
120+
return Optional.ofNullable(profile)
121+
.flatMap(p -> p.property(ProfileSection.SSO_SESSION.getPropertyKeyName()))
122+
.orElseThrow(() -> new IllegalArgumentException(
123+
"Profile " + profileName + " does not have sso_session property"));
124+
}
66125

67-
validateRequiredProperties(ssoProfile,
68-
ProfileProperty.SSO_REGION,
69-
ProfileProperty.SSO_START_URL);
70-
return ssoTokenProviderFactory().create(profileFile, profile);
126+
private Profile ssoProfile(ProfileFile profileFile, String profileSsoSectionName) {
127+
return profileFile.getSection(ProfileSection.SSO_SESSION.getSectionTitle(), profileSsoSectionName)
128+
.orElseThrow(() -> new IllegalArgumentException(
129+
"Sso-session section not found with sso-session title " + profileSsoSectionName));
71130
}
72131

73132
/**
@@ -76,7 +135,8 @@ private SdkTokenProvider ssoProfileCredentialsProvider() {
76135
private void validateRequiredProperties(Profile ssoProfile, String... requiredProperties) {
77136
Arrays.stream(requiredProperties)
78137
.forEach(p -> Validate.isTrue(ssoProfile.properties().containsKey(p),
79-
"Property '%s' was not configured for profile '%s'.", p, this.profile.name()));
138+
"Property '%s' was not configured for profile '%s'.",
139+
p, profileName));
80140
}
81141

82142
/**
@@ -88,10 +148,10 @@ private ChildProfileTokenProviderFactory ssoTokenProviderFactory() {
88148
getClass());
89149
return (ChildProfileTokenProviderFactory) ssoOidcTokenProviderFactory.getConstructor().newInstance();
90150
} catch (ClassNotFoundException e) {
91-
throw new IllegalStateException("To use SSO OIDC related properties in the '" + profile.name() + "' profile, "
151+
throw new IllegalStateException("To use SSO OIDC related properties in the '" + profileName + "' profile, "
92152
+ "the 'ssooidc' service module must be on the class path.", e);
93153
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
94-
throw new IllegalStateException("Failed to create the '" + profile.name() + "' token provider factory.", e);
154+
throw new IllegalStateException("Failed to create the '%s" + profileName + "' token provider factory.", e);
95155
}
96156
}
97157
}

services/ssooidc/src/test/java/software/amazon/awssdk/services/ssooidc/internal/common/SsoOidcTokenRefreshTestBase.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,7 @@ protected void initializeProfileProperties() {
102102
.content(new StringInputStream(profileContent))
103103
.type(ProfileFile.Type.CONFIGURATION)
104104
.build();
105-
Optional<Profile> profile = profiles.profile("sso-refresh");
106-
profileTokenProviderLoader = new ProfileTokenProviderLoader(profiles, profile.get());
105+
profileTokenProviderLoader = new ProfileTokenProviderLoader(profiles, "sso-refresh");
107106
}
108107

109108
@Test

test/auth-tests/src/it/java/software/amazon/awssdk/auth/ssooidc/ProfileTokenProviderLoaderTest.java

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@
1717

1818
import static org.assertj.core.api.Assertions.assertThat;
1919
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
20+
import static org.assertj.core.api.Assertions.assertThatNoException;
2021
import static org.assertj.core.api.Assertions.assertThatThrownBy;
2122

22-
import java.util.HashMap;
2323
import java.util.Optional;
24+
import java.util.function.Supplier;
2425
import java.util.stream.Stream;
2526
import org.junit.jupiter.api.Test;
2627
import org.junit.jupiter.params.ParameterizedTest;
@@ -29,7 +30,6 @@
2930
import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider;
3031
import software.amazon.awssdk.auth.token.internal.ProfileTokenProviderLoader;
3132
import software.amazon.awssdk.core.exception.SdkClientException;
32-
import software.amazon.awssdk.profiles.Profile;
3333
import software.amazon.awssdk.profiles.ProfileFile;
3434
import software.amazon.awssdk.utils.StringInputStream;
3535

@@ -38,23 +38,52 @@ public class ProfileTokenProviderLoaderTest {
3838
@Test
3939
public void noProfile_throwsException() {
4040
assertThatThrownBy(() -> new ProfileTokenProviderLoader(ProfileFile.defaultProfileFile(), null))
41-
.hasMessageContaining("profile must not be null");
41+
.hasMessageContaining("profileName must not be null");
4242
}
4343

4444
@Test
4545
public void noProfileFile_throwsException() {
46-
assertThatThrownBy(() -> new ProfileTokenProviderLoader(null, Profile.builder().name("sso").properties(new HashMap<>()).build()))
46+
assertThatThrownBy(() -> new ProfileTokenProviderLoader((ProfileFile) null, "sso"))
4747
.hasMessageContaining("profileFile must not be null");
4848
}
4949

50+
@Test
51+
public void profileTokenProviderLoader_noProfileFileSupplier_throwsException() {
52+
assertThatThrownBy(() -> new ProfileTokenProviderLoader((ProfileFile) null, "sso'"))
53+
.hasMessageContaining("profileFile must not be null");
54+
}
55+
56+
@Test
57+
public void profileTokenProviderLoader_noProfileName_throwsException() {
58+
assertThatThrownBy(() -> new ProfileTokenProviderLoader(ProfileFile::defaultProfileFile, null))
59+
.hasMessageContaining("profileName must not be null");
60+
}
61+
5062
@ParameterizedTest
5163
@MethodSource("ssoErrorValues")
5264
public void incorrectSsoProperties_throwsException(String profileContent, String msg) {
5365
ProfileFile profileFile = configFile(profileContent);
5466

55-
assertThat(profileFile.profile("sso")).hasValueSatisfying(profile -> {
56-
ProfileTokenProviderLoader providerLoader = new ProfileTokenProviderLoader(profileFile, profile);
57-
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(providerLoader::tokenProvider).withMessageContaining(msg);
67+
ProfileTokenProviderLoader providerLoader = new ProfileTokenProviderLoader(profileFile, "sso");
68+
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(providerLoader::tokenProvider)
69+
.withMessageContaining(msg);
70+
}
71+
72+
@ParameterizedTest
73+
@MethodSource("ssoErrorValues")
74+
public void incorrectSsoProperties_supplier_delaysThrowingExceptionUntilResolvingToken(String profileContent, String msg) {
75+
ProfileFile profileFile = configFile(profileContent);
76+
Supplier<ProfileFile> supplier = () -> profileFile;
77+
78+
ProfileTokenProviderLoader providerLoader = new ProfileTokenProviderLoader(supplier, "sso");
79+
80+
assertThatNoException().isThrownBy(providerLoader::tokenProvider);
81+
82+
assertThat(providerLoader.tokenProvider()).satisfies(tokenProviderOptional -> {
83+
assertThat(tokenProviderOptional).isPresent().get().satisfies(tokenProvider-> {
84+
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(tokenProvider::resolveToken)
85+
.withMessageContaining(msg);
86+
});
5887
});
5988
}
6089

@@ -66,8 +95,7 @@ public void correctSsoProperties_createsTokenProvider() {
6695
"sso_region=us-east-1\n" +
6796
"sso_start_url=https://d-abc123.awsapps.com/start\n";
6897

69-
Optional<Profile> ssoProfile = configFile(profileContent).profile("sso");
70-
ProfileTokenProviderLoader providerLoader = new ProfileTokenProviderLoader(configFile(profileContent), ssoProfile.get());
98+
ProfileTokenProviderLoader providerLoader = new ProfileTokenProviderLoader(configFile(profileContent), "sso");
7199
Optional<SdkTokenProvider> tokenProvider = providerLoader.tokenProvider();
72100
assertThat(tokenProvider).isPresent();
73101
assertThatThrownBy(() -> tokenProvider.get().resolveToken())

0 commit comments

Comments
 (0)