Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9fc1ace
Add new method to fetch the credentials from the fileStore
AayushSaini101 Jan 26, 2025
607716b
Changed
AayushSaini101 Feb 12, 2025
4d4c8e5
Remove dependency of jackson and use jsonutil
AayushSaini101 Jan 27, 2025
553cb3f
Format the code
AayushSaini101 Jan 27, 2025
f5c7807
Fixed the improvements
AayushSaini101 Jan 27, 2025
783d186
Added the implementation of the file store
AayushSaini101 Jan 28, 2025
5a439e6
Remove the extra code use UserNamePasswordProvider class
AayushSaini101 Jan 28, 2025
89ec8bb
Optimize tests duration by using Junit 5 concurrency (#72)
jonesbusy Jan 30, 2025
bb259cc
Add stream API methods (#60)
vaidikcode Feb 12, 2025
7bc68ce
Resolve conflicts
AayushSaini101 Feb 12, 2025
20d89d4
Resolve build error for code generation
AayushSaini101 Feb 12, 2025
4169fec
Remove ConfigOrasException
AayushSaini101 Feb 12, 2025
ccf460e
Update the logger file
AayushSaini101 Feb 12, 2025
79b0a8d
Use @tempdir
AayushSaini101 Feb 12, 2025
77c22c1
Bump org.wiremock:wiremock-standalone from 3.11.0 to 3.12.0 (#86)
dependabot[bot] Feb 13, 2025
800e2d0
Remove unwanted file
AayushSaini101 Feb 15, 2025
5ec0901
Update with suggestions:
AayushSaini101 Feb 16, 2025
b3657bb
Add requirement for central publishing (#87)
jonesbusy Feb 16, 2025
308fd7b
Sign snaphosts (#88)
jonesbusy Feb 16, 2025
fd82d3d
Pass sign-only profile (#89)
jonesbusy Feb 16, 2025
3f8f515
Add central settings (#90)
jonesbusy Feb 16, 2025
70bd660
Uncomment code
AayushSaini101 Feb 17, 2025
3fd4c90
Sign snaphosts (#88)
jonesbusy Feb 16, 2025
1034001
Merge branch 'main' into 15
AayushSaini101 Feb 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/main/java/land/oras/ContainerRef.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import land.oras.exception.OrasException;
import land.oras.utils.Const;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
Expand Down
1 change: 1 addition & 0 deletions src/main/java/land/oras/Layer.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import land.oras.exception.OrasException;
import land.oras.utils.Const;
import land.oras.utils.DigestUtils;
import land.oras.utils.JsonUtils;
Expand Down
1 change: 1 addition & 0 deletions src/main/java/land/oras/Registry.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.util.List;
import java.util.Map;
import land.oras.auth.*;
import land.oras.exception.OrasException;
import land.oras.utils.ArchiveUtils;
import land.oras.utils.Const;
import land.oras.utils.DigestUtils;
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/land/oras/auth/BearerTokenProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import land.oras.OrasException;
import land.oras.exception.OrasException;
import land.oras.utils.Const;
import land.oras.utils.JsonUtils;
import land.oras.utils.OrasHttpClient;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package land.oras.auth;

import land.oras.ContainerRef;
import land.oras.credentials.FileStore;
import land.oras.credentials.FileStore.Credential;
import land.oras.exception.OrasException;

/**
* FileStoreAuthenticationProvider is an implementation of the AuthProvider interface.
* It retrieves credentials from a FileStore and generates a Basic Authentication header.
*/
public class FileStoreAuthenticationProvider implements AuthProvider {
Comment thread
jonesbusy marked this conversation as resolved.

private final FileStore fileStore;
private final ContainerRef containerRef;
private final UsernamePasswordProvider usernamePasswordAuthProvider;

/**
* Constructor for FileStoreAuthenticationProvider.
*
* @param fileStore The FileStore instance to retrieve credentials from.
* @param containerRef The server address for which to retrieve credentials.
* @throws Exception If an error occurs during authentication initialization.
*/
public FileStoreAuthenticationProvider(FileStore fileStore, ContainerRef containerRef) throws Exception {
this.fileStore = fileStore;
this.containerRef = containerRef;
Credential credential = fileStore.get(containerRef);
if (credential == null) {
throw new OrasException("No credentials found for containerRef");
}
this.usernamePasswordAuthProvider =
new UsernamePasswordProvider(credential.getUsername(), credential.getPassword());
}

@Override
public String getAuthHeader() {
return usernamePasswordAuthProvider.getAuthHeader();
}
}
220 changes: 220 additions & 0 deletions src/main/java/land/oras/credentials/FileStore.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
package land.oras.credentials;

import java.io.FileReader;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import land.oras.ContainerRef;
import land.oras.exception.OrasException;
import land.oras.utils.JsonUtils;

/**
* FileStore implements a credentials store using a configuration file
* to keep the credentials in plain-text.
*
* Reference: https://docs.docker.com/engine/reference/commandline/cli/#docker-cli-configuration-file-configjson-properties
*/
public class FileStore {
Comment thread
AayushSaini101 marked this conversation as resolved.

private final boolean disablePut;
private final Config config;

/**
* Error message indicating that putting plaintext credentials is disabled.
* This is used to enforce security policies against storing sensitive credentials in plaintext format.
*/
public static final String ERR_PLAINTEXT_PUT_DISABLED = "Putting plaintext credentials is disabled";

/**
* Error message indicating that the format of the provided credential is invalid.
* This is typically used when credentials do not match the expected structure or format.
*/
public static final String ERR_BAD_CREDENTIAL_FORMAT = "Bad credential format";

/**
* Constructor for FileStore.
*
* @param disablePut boolean flag to disable putting credentials in plaintext.
* @param config configuration instance.
*/
public FileStore(boolean disablePut, Config config) {
Comment thread
AayushSaini101 marked this conversation as resolved.
this.disablePut = disablePut;
this.config = Objects.requireNonNull(config, "Config cannot be null");
}

/**
* Creates a new FileStore based on the given configuration file path.
*
* @param configPath Path to the configuration file.
* @return FileStore instance.
* @throws OrasException if loading the configuration fails.
*/
public static FileStore newFileStore(String configPath) throws OrasException {
Config cfg = Config.load(configPath);
return new FileStore(false, cfg);
}

/**
* Retrieves credentials for the given containerRef.
*
* @param containerRef ContainerRef.
* @return Credential object.
* @throws OrasException if retrieval fails.
*/
public Credential get(ContainerRef containerRef) throws OrasException {
return config.getCredential(containerRef);
}

/**
* Saves credentials for the given ContainerRef.
*
* @param containerRef ContainerRef.
* @param credential Credential object.
* @throws Exception if saving fails.
*/
public void put(ContainerRef containerRef, Credential credential) throws Exception {
if (disablePut) {
throw new UnsupportedOperationException(ERR_PLAINTEXT_PUT_DISABLED);
}
validateCredentialFormat(credential);
config.putCredential(containerRef, credential);
}

/**
* Deletes credentials for the given container.
*
* @param containerRef .
* @throws OrasException if deletion fails.
*/
public void delete(ContainerRef containerRef) throws OrasException {
config.deleteCredential(containerRef);
}

/**
* Validates the format of the credential.
*
* @param credential Credential object.
* @throws Exception if the credential format is invalid.
*/
private void validateCredentialFormat(Credential credential) throws Exception {
if (credential.getUsername().contains(":")) {
throw new IllegalArgumentException(ERR_BAD_CREDENTIAL_FORMAT + ": colons(:) are not allowed in username");
}
}

/**
* Nested Config class for configuration management.
*/
public static class Config {
private final ConcurrentHashMap<String, Credential> credentialStore = new ConcurrentHashMap<>();

/**
* Loads the configuration from a JSON file at the specified path and populates the credential store.
*
* @param configPath The path to the JSON configuration file.
* @return A {@code Config} object populated with the credentials from the JSON file.
* @throws OrasException If an error occurs while reading or parsing the JSON file.
*/
public static Config load(String configPath) throws OrasException {
Config config = new Config();
try (FileReader reader = new FileReader(configPath)) {
// Deserialize the JSON file into a map of ContainerRef to Credential
Map<String, Map<String, String>> credentials = JsonUtils.fromJson(reader, Map.class);

// Populate the credential store with the parsed credentials
for (Map.Entry<String, Map<String, String>> entry : credentials.entrySet()) {
Map<String, String> values = entry.getValue();
if (values != null) {
String username = values.get("username");
String password = values.get("password");
if (username != null && password != null) {
config.credentialStore.put(entry.getKey(), new Credential(username, password));
} else {
throw new OrasException(
"Invalid credential entry: missing username or password for " + entry.getKey());
}
}
}
} catch (IOException e) {
throw new OrasException("Failed to load configuration from path: " + configPath, e);
} catch (ClassCastException e) {
throw new OrasException("Invalid JSON structure in configuration file: " + configPath, e);
}
return config;
}

/**
* Retrieves the {@code Credential} associated with the specified containerRef.
*
* @param containerRef The containerRef whose credential is to be retrieved.
* @return The {@code Credential} associated with the containerRef, or {@code null} if no credential is found.
*/
public Credential getCredential(ContainerRef containerRef) throws OrasException {
if (credentialStore.containsKey(containerRef)) {
return credentialStore.get(containerRef);
} else {
throw new OrasException("No credentials found for server address");
}
}

/**
* Associates the specified {@code Credential} with the given containerRef.
* If a credential already exists for the containerRef, it will be replaced.
*
* @param containerRef The containerRef to associate with the credential.
* @param credential The {@code Credential} to store. Must not be {@code null}.
* @throws NullPointerException If the provided credential is {@code null}.
*/
public void putCredential(ContainerRef containerRef, Credential credential) {
credentialStore.put(containerRef.toString(), credential);
}

/**
* Removes the {@code Credential} associated with the specified containerRef.
* If no credential is associated with the containerRef, this method does nothing.
*
* @param containerRef The containerRef whose credential is to be removed.
*/
public void deleteCredential(ContainerRef containerRef) {
credentialStore.remove(containerRef.toString());
}
}

/**
* Nested Credential class to represent username and password pairs.
*/
public static class Credential {
private String username;
private String password;

/**
* Constructs a new {@code Credential} object with the specified username and password.
*
* @param username The username for the credential. Must not be {@code null}.
* @param password The password for the credential. Must not be {@code null}.
*/
public Credential(String username, String password) {
this.username = Objects.requireNonNull(username, "Username cannot be null");
this.password = Objects.requireNonNull(password, "Password cannot be null");
}

/**
* Returns the username associated with this credential.
*
* @return The username as a {@code String}.
*/
public String getUsername() {
return username;
}

/**
* Returns the password associated with this credential.
*
* @return The password as a {@code String}.
*/
public String getPassword() {
return password;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package land.oras;
package land.oras.exception;

import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package land.oras;
package land.oras.exception;

import land.oras.utils.JsonUtils;
import land.oras.utils.OrasHttpClient;
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/land/oras/utils/ArchiveUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import java.util.EnumSet;
import java.util.Set;
import java.util.stream.Stream;
import land.oras.OrasException;
import land.oras.exception.OrasException;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/land/oras/utils/DigestUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.security.MessageDigest;
import land.oras.OrasException;
import land.oras.exception.OrasException;
import org.jspecify.annotations.NullMarked;

/**
Expand Down
34 changes: 33 additions & 1 deletion src/main/java/land/oras/utils/JsonUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.ZonedDateTime;
import land.oras.OrasException;
import land.oras.exception.OrasException;
import org.jspecify.annotations.NullMarked;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -91,4 +93,34 @@ public static <T> T fromJson(Path path, Class<T> clazz) {
throw new OrasException("Unable to read JSON file due to IO error", e);
}
}

/**
* Converts the contents of a JSON file to an object of the specified type.
*
* @param path The {@code Path} to the JSON file to be read.
* @param type The {@code Type} representing the class of the object to be deserialized.
* @param <T> The type of the object to be returned.
* @return An object of type {@code T} deserialized from the JSON file.
* @throws OrasException If an I/O error occurs while reading the file or the JSON is invalid.
*/
public static <T> T fromJson(Path path, Type type) {
try {
return gson.fromJson(Files.readString(path, StandardCharsets.UTF_8), type);
} catch (IOException e) {
throw new OrasException("Unable to read JSON file due to IO error", e);
}
}

/**
* Deserializes the contents of a JSON input to an object of the specified type.
*
* @param reader The {@code Reader} from which the JSON content is read.
* @param type The {@code Type} representing the target object type to be deserialized.
* @param <T> The type of the object to be returned.
* @return An object of type {@code T} deserialized from the JSON content.
* @throws OrasException If an error occurs while reading the input or the JSON format is invalid.
*/
public static <T> T fromJson(Reader reader, Type type) {
return gson.fromJson(reader, type);
}
}
2 changes: 1 addition & 1 deletion src/main/java/land/oras/utils/OrasHttpClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509ExtendedTrustManager;
import land.oras.OrasException;
import land.oras.auth.AuthProvider;
import land.oras.auth.NoAuthProvider;
import land.oras.exception.OrasException;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
Expand Down
1 change: 1 addition & 0 deletions src/test/java/land/oras/RegistryContainerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import land.oras.exception.OrasException;
import land.oras.utils.Const;
import land.oras.utils.JsonUtils;
import land.oras.utils.RegistryContainer;
Expand Down
Loading