Skip to content

Commit 68ff756

Browse files
authored
ProfileTokenProvider Reload ProfileFile (#3608)
* Updated ProfileTokenProvider * Updated tests, do not explicitly swallow exceptions * ProfileTokenProviderLoader can use Supplier<Profilefile> internally * Simplified ProfileTokenProviderLoader API; implemented synchronized block
1 parent 2f8f819 commit 68ff756

File tree

5 files changed

+141
-74
lines changed

5 files changed

+141
-74
lines changed

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

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -40,53 +40,41 @@ public final class ProfileTokenProvider implements SdkTokenProvider, SdkAutoClos
4040
private final SdkTokenProvider tokenProvider;
4141
private final RuntimeException loadException;
4242

43-
private final ProfileFile profileFile;
4443
private final String profileName;
4544

4645
/**
4746
* @see #builder()
4847
*/
4948
private ProfileTokenProvider(BuilderImpl builder) {
50-
SdkTokenProvider tokenProvider = null;
51-
RuntimeException loadException = null;
52-
ProfileFile profileFile = null;
53-
String profileName = null;
49+
SdkTokenProvider sdkTokenProvider = null;
50+
RuntimeException thrownException = null;
51+
Supplier<ProfileFile> selectedProfileFile = null;
52+
String selectedProfileName = null;
5453

5554
try {
56-
profileName = builder.profileName != null ? builder.profileName
57-
: ProfileFileSystemSetting.AWS_PROFILE.getStringValueOrThrow();
55+
selectedProfileName = Optional.ofNullable(builder.profileName)
56+
.orElseGet(ProfileFileSystemSetting.AWS_PROFILE::getStringValueOrThrow);
5857

5958
// Load the profiles file
60-
profileFile = Optional.ofNullable(builder.profileFile)
61-
.orElse(builder.defaultProfileFileLoader)
62-
.get();
59+
selectedProfileFile = Optional.ofNullable(builder.profileFile)
60+
.orElse(builder.defaultProfileFileLoader);
6361

6462
// Load the profile and token provider
65-
String finalProfileName = profileName;
66-
ProfileFile finalProfileFile = profileFile;
67-
tokenProvider =
68-
profileFile.profile(profileName)
69-
.flatMap(p -> new ProfileTokenProviderLoader(finalProfileFile, p).tokenProvider())
70-
.orElseThrow(() -> {
71-
String errorMessage = String.format("Profile file contained no information for " +
72-
"profile '%s': %s", finalProfileName, finalProfileFile);
73-
return SdkClientException.builder().message(errorMessage).build();
74-
});
63+
sdkTokenProvider = createTokenProvider(selectedProfileFile, selectedProfileName);
64+
7565
} catch (RuntimeException e) {
7666
// If we couldn't load the provider for some reason, save an exception describing why.
77-
loadException = e;
67+
thrownException = e;
7868
}
7969

80-
if (loadException != null) {
81-
this.loadException = loadException;
70+
if (thrownException != null) {
71+
this.loadException = thrownException;
8272
this.tokenProvider = null;
83-
this.profileFile = null;
8473
this.profileName = null;
8574
} else {
8675
this.loadException = null;
87-
this.tokenProvider = tokenProvider;
88-
this.profileFile = profileFile;
89-
this.profileName = profileName;
76+
this.tokenProvider = sdkTokenProvider;
77+
this.profileName = selectedProfileName;
9078
}
9179
}
9280

@@ -127,7 +115,6 @@ public SdkToken resolveToken() {
127115
public String toString() {
128116
return ToString.builder("ProfileTokenProvider")
129117
.add("profileName", profileName)
130-
.add("profileFile", profileFile)
131118
.build();
132119
}
133120

@@ -137,6 +124,16 @@ public void close() {
137124
IoUtils.closeIfCloseable(tokenProvider, null);
138125
}
139126

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+
});
135+
}
136+
140137
/**
141138
* A builder for creating a custom {@link ProfileTokenProvider}.
142139
*/

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

Lines changed: 62 additions & 18 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,17 @@ public final class ProfileTokenProviderLoader {
3640
private static final String SSO_OIDC_TOKEN_PROVIDER_FACTORY =
3741
"software.amazon.awssdk.services.ssooidc.SsoOidcProfileTokenProviderFactory";
3842

39-
private final Profile profile;
40-
private final ProfileFile profileFile;
43+
private final Supplier<ProfileFile> profileFileSupplier;
44+
private final String profileName;
45+
private volatile ProfileFile currentProfileFile;
46+
private volatile SdkTokenProvider currentTokenProvider;
4147

42-
public ProfileTokenProviderLoader(ProfileFile profileFile, Profile profile) {
43-
this.profile = Validate.paramNotNull(profile, "profile");
44-
this.profileFile = Validate.paramNotNull(profileFile, "profileFile");
48+
private final Lazy<ChildProfileTokenProviderFactory> factory;
49+
50+
public ProfileTokenProviderLoader(Supplier<ProfileFile> profileFile, String profileName) {
51+
this.profileFileSupplier = Validate.paramNotNull(profileFile, "profileFile");
52+
this.profileName = Validate.paramNotNull(profileName, "profileName");
53+
this.factory = new Lazy<>(this::ssoTokenProviderFactory);
4554
}
4655

4756
/**
@@ -55,19 +64,53 @@ public Optional<SdkTokenProvider> tokenProvider() {
5564
* Create the SSO credentials provider based on the related profile properties.
5665
*/
5766
private SdkTokenProvider ssoProfileCredentialsProvider() {
67+
return () -> ssoProfileCredentialsProvider(profileFileSupplier, profileName).resolveToken();
68+
}
69+
70+
private SdkTokenProvider ssoProfileCredentialsProvider(ProfileFile profileFile, Profile profile) {
71+
String profileSsoSectionName = profileSsoSectionName(profile);
72+
Profile ssoProfile = ssoProfile(profileFile, profileSsoSectionName);
73+
74+
validateRequiredProperties(ssoProfile, ProfileProperty.SSO_REGION, ProfileProperty.SSO_START_URL);
75+
76+
return factory.getValue().create(profileFile, profile);
77+
}
5878

59-
String profileSsoSectionName = profile.property(ProfileSection.SSO_SESSION.getPropertyKeyName())
60-
.orElseThrow(() -> new IllegalArgumentException(
61-
"Profile " + profile.name() + " does not have sso_session property"));
79+
private SdkTokenProvider ssoProfileCredentialsProvider(Supplier<ProfileFile> profileFile, String profileName) {
80+
ProfileFile profileFileInstance = profileFile.get();
81+
if (!Objects.equals(profileFileInstance, currentProfileFile)) {
82+
synchronized (this) {
83+
if (!Objects.equals(profileFileInstance, currentProfileFile)) {
84+
Profile profileInstance = resolveProfile(profileFileInstance, profileName);
85+
currentProfileFile = profileFileInstance;
86+
currentTokenProvider = ssoProfileCredentialsProvider(profileFileInstance, profileInstance);
87+
}
88+
}
89+
}
90+
91+
return currentTokenProvider;
92+
}
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+
private Profile resolveProfile(ProfileFile profileFile, String profileName) {
95+
return profileFile.profile(profileName)
96+
.orElseThrow(() -> {
97+
String errorMessage = String.format("Profile file contained no information for profile '%s': %s",
98+
profileName, profileFile);
99+
return SdkClientException.builder().message(errorMessage).build();
100+
});
101+
}
102+
103+
private String profileSsoSectionName(Profile profile) {
104+
return Optional.ofNullable(profile)
105+
.flatMap(p -> p.property(ProfileSection.SSO_SESSION.getPropertyKeyName()))
106+
.orElseThrow(() -> new IllegalArgumentException(
107+
"Profile " + profileName + " does not have sso_session property"));
108+
}
66109

67-
validateRequiredProperties(ssoProfile,
68-
ProfileProperty.SSO_REGION,
69-
ProfileProperty.SSO_START_URL);
70-
return ssoTokenProviderFactory().create(profileFile, profile);
110+
private Profile ssoProfile(ProfileFile profileFile, String profileSsoSectionName) {
111+
return profileFile.getSection(ProfileSection.SSO_SESSION.getSectionTitle(), profileSsoSectionName)
112+
.orElseThrow(() -> new IllegalArgumentException(
113+
"Sso-session section not found with sso-session title " + profileSsoSectionName));
71114
}
72115

73116
/**
@@ -76,7 +119,8 @@ private SdkTokenProvider ssoProfileCredentialsProvider() {
76119
private void validateRequiredProperties(Profile ssoProfile, String... requiredProperties) {
77120
Arrays.stream(requiredProperties)
78121
.forEach(p -> Validate.isTrue(ssoProfile.properties().containsKey(p),
79-
"Property '%s' was not configured for profile '%s'.", p, this.profile.name()));
122+
"Property '%s' was not configured for profile '%s'.",
123+
p, profileName));
80124
}
81125

82126
/**
@@ -88,10 +132,10 @@ private ChildProfileTokenProviderFactory ssoTokenProviderFactory() {
88132
getClass());
89133
return (ChildProfileTokenProviderFactory) ssoOidcTokenProviderFactory.getConstructor().newInstance();
90134
} catch (ClassNotFoundException e) {
91-
throw new IllegalStateException("To use SSO OIDC related properties in the '" + profile.name() + "' profile, "
135+
throw new IllegalStateException("To use SSO OIDC related properties in the '" + profileName + "' profile, "
92136
+ "the 'ssooidc' service module must be on the class path.", e);
93137
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
94-
throw new IllegalStateException("Failed to create the '" + profile.name() + "' token provider factory.", e);
138+
throw new IllegalStateException("Failed to create the '%s" + profileName + "' token provider factory.", e);
95139
}
96140
}
97141
}

core/auth/src/test/java/software/amazon/awssdk/auth/token/credentials/ProfileTokenProviderTest.java

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,17 @@
1717

1818
import static org.assertj.core.api.Assertions.assertThatThrownBy;
1919

20+
import java.util.function.Supplier;
2021
import org.junit.jupiter.api.Test;
21-
import software.amazon.awssdk.auth.token.credentials.ProfileTokenProvider;
22+
import org.mockito.Mockito;
2223
import software.amazon.awssdk.core.exception.SdkClientException;
2324
import software.amazon.awssdk.profiles.ProfileFile;
2425
import software.amazon.awssdk.utils.StringInputStream;
2526

26-
public class ProfileTokenProviderTest {
27+
class ProfileTokenProviderTest {
2728

2829
@Test
29-
public void missingProfileFile_throwsException() {
30+
void missingProfileFile_throwsException() {
3031
ProfileTokenProvider provider =
3132
new ProfileTokenProvider.BuilderImpl()
3233
.defaultProfileFileLoader(() -> ProfileFile.builder()
@@ -39,7 +40,7 @@ public void missingProfileFile_throwsException() {
3940
}
4041

4142
@Test
42-
public void emptyProfileFile_throwsException() {
43+
void emptyProfileFile_throwsException() {
4344
ProfileTokenProvider provider =
4445
new ProfileTokenProvider.BuilderImpl()
4546
.defaultProfileFileLoader(() -> ProfileFile.builder()
@@ -52,7 +53,7 @@ public void emptyProfileFile_throwsException() {
5253
}
5354

5455
@Test
55-
public void missingProfile_throwsException() {
56+
void missingProfile_throwsException() {
5657
ProfileFile file = profileFile("[default]\n"
5758
+ "aws_access_key_id = defaultAccessKey\n"
5859
+ "aws_secret_access_key = defaultSecretAccessKey");
@@ -64,7 +65,7 @@ public void missingProfile_throwsException() {
6465
}
6566

6667
@Test
67-
public void compatibleProfileSettings_callsLoader() {
68+
void compatibleProfileSettings_callsLoader() {
6869
ProfileFile file = profileFile("[default]");
6970

7071
ProfileTokenProvider provider =
@@ -73,6 +74,28 @@ public void compatibleProfileSettings_callsLoader() {
7374
assertThatThrownBy(provider::resolveToken).hasMessageContaining("does not have sso_session property");
7475
}
7576

77+
@Test
78+
void resolveToken_profileFileSupplier_suppliesObjectPerCall() {
79+
ProfileFile file1 = profileFile("[profile sso]\n"
80+
+ "aws_access_key_id = defaultAccessKey\n"
81+
+ "aws_secret_access_key = defaultSecretAccessKey\n"
82+
+ "sso_session = xyz");
83+
ProfileFile file2 = profileFile("[profile sso]\n"
84+
+ "aws_access_key_id = modifiedAccessKey\n"
85+
+ "aws_secret_access_key = modifiedSecretAccessKey\n"
86+
+ "sso_session = xyz");
87+
Supplier<ProfileFile> supplier = Mockito.mock(Supplier.class);
88+
89+
ProfileTokenProvider provider =
90+
ProfileTokenProvider.builder().profileFile(supplier).profileName("sso").build();
91+
92+
Mockito.when(supplier.get()).thenReturn(file1, file2);
93+
assertThatThrownBy(provider::resolveToken).isInstanceOf(IllegalArgumentException.class);
94+
assertThatThrownBy(provider::resolveToken).isInstanceOf(IllegalArgumentException.class);
95+
96+
Mockito.verify(supplier, Mockito.times(2)).get();
97+
}
98+
7699
private ProfileFile profileFile(String string) {
77100
return ProfileFile.builder()
78101
.content(new StringInputStream(string))

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

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import static org.mockito.ArgumentMatchers.any;
2121
import static org.mockito.Mockito.mock;
2222
import static org.mockito.Mockito.when;
23-
import static software.amazon.awssdk.services.ssooidc.internal.SsoOidcTokenProviderTest.START_URL;
2423
import static software.amazon.awssdk.services.ssooidc.internal.SsoOidcTokenProviderTest.deriveCacheKey;
2524
import static software.amazon.awssdk.utils.UserHomeDirectoryUtils.userHomeDirectory;
2625

@@ -41,7 +40,6 @@
4140
import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider;
4241
import software.amazon.awssdk.auth.token.internal.ProfileTokenProviderLoader;
4342
import software.amazon.awssdk.core.exception.SdkClientException;
44-
import software.amazon.awssdk.profiles.Profile;
4543
import software.amazon.awssdk.profiles.ProfileFile;
4644
import software.amazon.awssdk.protocols.jsoncore.JsonNode;
4745
import software.amazon.awssdk.services.ssooidc.SsoOidcClient;
@@ -59,7 +57,7 @@ public abstract class SsoOidcTokenRefreshTestBase {
5957
protected boolean shouldMockServiceClient;
6058
protected SsoOidcClient ssoOidcClient;
6159
protected String testStartUrl;
62-
protected String testSessionName = "sso-prod";;
60+
protected String testSessionName = "sso-prod";
6361
protected String baseTokenResourceFile;
6462
protected ProfileTokenProviderLoader profileTokenProviderLoader;
6563

@@ -98,12 +96,11 @@ protected void initializeProfileProperties() {
9896
"sso_region=us-east-1\n" +
9997
"sso_start_url=" + testStartUrl + "\n";
10098

101-
ProfileFile profiles = ProfileFile.builder()
99+
ProfileFile profile = ProfileFile.builder()
102100
.content(new StringInputStream(profileContent))
103101
.type(ProfileFile.Type.CONFIGURATION)
104102
.build();
105-
Optional<Profile> profile = profiles.profile("sso-refresh");
106-
profileTokenProviderLoader = new ProfileTokenProviderLoader(profiles, profile.get());
103+
profileTokenProviderLoader = new ProfileTokenProviderLoader(() -> profile, "sso-refresh");
107104
}
108105

109106
@Test

0 commit comments

Comments
 (0)