|
20 | 20 | import java.io.OutputStream; |
21 | 21 | import java.io.UncheckedIOException; |
22 | 22 | import java.nio.charset.StandardCharsets; |
| 23 | +import java.nio.file.AtomicMoveNotSupportedException; |
23 | 24 | import java.nio.file.Files; |
24 | 25 | import java.nio.file.Path; |
25 | 26 | import java.nio.file.StandardCopyOption; |
| 27 | +import java.nio.file.attribute.FileAttribute; |
| 28 | +import java.nio.file.attribute.PosixFilePermission; |
| 29 | +import java.nio.file.attribute.PosixFilePermissions; |
26 | 30 | import java.security.MessageDigest; |
27 | 31 | import java.security.NoSuchAlgorithmException; |
28 | 32 | import java.time.Instant; |
29 | 33 | import java.time.format.DateTimeFormatter; |
| 34 | +import java.util.EnumSet; |
30 | 35 | import java.util.Locale; |
31 | 36 | import java.util.Optional; |
| 37 | +import java.util.Set; |
32 | 38 | import software.amazon.awssdk.annotations.SdkInternalApi; |
33 | 39 | import software.amazon.awssdk.auth.credentials.AwsSessionCredentials; |
34 | 40 | import software.amazon.awssdk.core.exception.SdkClientException; |
|
41 | 47 |
|
42 | 48 | @SdkInternalApi |
43 | 49 | public final class OnDiskTokenManager implements AccessTokenManager { |
| 50 | + private static final Set<PosixFilePermission> OWNER_ONLY_PERMISSIONS = |
| 51 | + EnumSet.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE); |
| 52 | + |
44 | 53 | private final JsonNodeParser jsonParser = JsonNodeParser.builder().removeErrorLocations(true).build(); |
45 | 54 |
|
46 | 55 | private final Path tokenLocation; |
@@ -82,18 +91,45 @@ public Optional<LoginAccessToken> loadToken() { |
82 | 91 |
|
83 | 92 | @Override |
84 | 93 | public void storeToken(LoginAccessToken token) { |
85 | | - // atomic write (write to a temp file and then move/replace the destination location). |
| 94 | + // Write to a temp file first, then move to the destination to avoid partial reads. |
86 | 95 | try { |
87 | | - Path temp = Files.createTempFile(tokenLocation.getParent(), "token-", ".tmp"); |
| 96 | + Path temp = createOwnerOnlyTempFile(tokenLocation.getParent(), "token-", ".tmp"); |
88 | 97 | try (OutputStream os = Files.newOutputStream(temp)) { |
89 | 98 | os.write(marshalToken(token)); |
90 | 99 | } |
91 | | - Files.move(temp, tokenLocation, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); |
| 100 | + atomicOrFallbackMove(temp, tokenLocation); |
92 | 101 | } catch (IOException | UncheckedIOException e) { |
93 | 102 | throw SdkClientException.create("Unable to write token to location " + tokenLocation, e); |
94 | 103 | } |
95 | 104 | } |
96 | 105 |
|
| 106 | + /** |
| 107 | + * Creates a temp file with owner-only read/write permissions (0600) on POSIX-compatible file systems. |
| 108 | + * On non-POSIX file systems (e.g., Windows), falls back to default permissions. |
| 109 | + */ |
| 110 | + private static Path createOwnerOnlyTempFile(Path dir, String prefix, String suffix) throws IOException { |
| 111 | + try { |
| 112 | + FileAttribute<Set<PosixFilePermission>> attr = |
| 113 | + PosixFilePermissions.asFileAttribute(OWNER_ONLY_PERMISSIONS); |
| 114 | + return Files.createTempFile(dir, prefix, suffix, attr); |
| 115 | + } catch (UnsupportedOperationException | IllegalArgumentException e) { |
| 116 | + // File system does not support POSIX permissions (e.g., Windows, or in-memory file systems); |
| 117 | + // fall back to default permissions. |
| 118 | + return Files.createTempFile(dir, prefix, suffix); |
| 119 | + } |
| 120 | + } |
| 121 | + |
| 122 | + /** |
| 123 | + * Attempts an atomic move, falling back to a non-atomic replace if the file system does not support it. |
| 124 | + */ |
| 125 | + private static void atomicOrFallbackMove(Path source, Path target) throws IOException { |
| 126 | + try { |
| 127 | + Files.move(source, target, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); |
| 128 | + } catch (AtomicMoveNotSupportedException e) { |
| 129 | + Files.move(source, target, StandardCopyOption.REPLACE_EXISTING); |
| 130 | + } |
| 131 | + } |
| 132 | + |
97 | 133 | @Override |
98 | 134 | public void close() { |
99 | 135 |
|
|
0 commit comments