From 1a38ef379861731682a7411cfdf429123e697eae Mon Sep 17 00:00:00 2001 From: IgorHorta Date: Mon, 16 Feb 2026 21:06:24 -0300 Subject: [PATCH 1/5] ENG-4561 Add ProjectsClient with get-by-slug support --- docker-compose.yaml | 8 ++ pom.xml | 51 ++++++++++++ .../java/com/infisical/sdk/InfisicalSdk.java | 7 ++ .../com/infisical/sdk/models/Project.java | 23 ++++++ .../sdk/resources/ProjectsClient.java | 45 +++++++++++ .../sdk/resources/ProjectsClientTest.java | 79 +++++++++++++++++++ 6 files changed, 213 insertions(+) create mode 100644 src/main/java/com/infisical/sdk/models/Project.java create mode 100644 src/main/java/com/infisical/sdk/resources/ProjectsClient.java create mode 100644 src/test/java/com/infisical/sdk/resources/ProjectsClientTest.java diff --git a/docker-compose.yaml b/docker-compose.yaml index 7884376..31a083c 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,6 +1,14 @@ version: '3.8' services: + test: + image: maven:3.9-eclipse-temurin-11 + volumes: + - .:/app + working_dir: /app + # Run only unit tests (no real credentials). Full suite: mvn test with env set. + command: mvn test -Dtest=ProjectsClientTest,AwsAuthProviderTest + format: image: cimg/openjdk:21.0 volumes: diff --git a/pom.xml b/pom.xml index 9ad79c7..b1c8c9b 100644 --- a/pom.xml +++ b/pom.xml @@ -151,6 +151,18 @@ 5.9.1 test + + org.mockito + mockito-core + 5.5.0 + test + + + org.mockito + mockito-junit-jupiter + 5.5.0 + test + @@ -175,6 +187,45 @@ + + + manual + + false + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.4.0 + + + add-manual-test-source + generate-test-sources + + add-test-source + + + + java-test + + + + + + + org.codehaus.mojo + exec-maven-plugin + 3.1.0 + + javatest.ManualProjectsTest + test + + + + + release diff --git a/src/main/java/com/infisical/sdk/InfisicalSdk.java b/src/main/java/com/infisical/sdk/InfisicalSdk.java index b01228f..cac4701 100644 --- a/src/main/java/com/infisical/sdk/InfisicalSdk.java +++ b/src/main/java/com/infisical/sdk/InfisicalSdk.java @@ -4,11 +4,13 @@ import com.infisical.sdk.config.SdkConfig; import com.infisical.sdk.resources.AuthClient; import com.infisical.sdk.resources.FoldersClient; +import com.infisical.sdk.resources.ProjectsClient; import com.infisical.sdk.resources.SecretsClient; public class InfisicalSdk { private SecretsClient secretsClient; private FoldersClient foldersClient; + private ProjectsClient projectsClient; private AuthClient authClient; private ApiClient apiClient; @@ -26,6 +28,7 @@ private void onAuthenticate(String accessToken) { this.secretsClient = new SecretsClient(apiClient); this.foldersClient = new FoldersClient(apiClient); + this.projectsClient = new ProjectsClient(apiClient); this.authClient = new AuthClient(apiClient, this::onAuthenticate); } @@ -40,4 +43,8 @@ public SecretsClient Secrets() { public FoldersClient Folders() { return this.foldersClient; } + + public ProjectsClient Projects() { + return this.projectsClient; + } } diff --git a/src/main/java/com/infisical/sdk/models/Project.java b/src/main/java/com/infisical/sdk/models/Project.java new file mode 100644 index 0000000..3b15abc --- /dev/null +++ b/src/main/java/com/infisical/sdk/models/Project.java @@ -0,0 +1,23 @@ +package com.infisical.sdk.models; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; + +@Data +public class Project { + + @SerializedName("id") + private String id; + + @SerializedName("name") + private String name; + + @SerializedName("slug") + private String slug; + + @SerializedName("orgId") + private String orgId; + + @SerializedName("description") + private String description; +} diff --git a/src/main/java/com/infisical/sdk/resources/ProjectsClient.java b/src/main/java/com/infisical/sdk/resources/ProjectsClient.java new file mode 100644 index 0000000..57c2514 --- /dev/null +++ b/src/main/java/com/infisical/sdk/resources/ProjectsClient.java @@ -0,0 +1,45 @@ +package com.infisical.sdk.resources; + +import com.infisical.sdk.api.ApiClient; +import com.infisical.sdk.models.Project; +import com.infisical.sdk.util.Helper; +import com.infisical.sdk.util.InfisicalException; + +public class ProjectsClient { + private final ApiClient apiClient; + + public ProjectsClient(ApiClient apiClient) { + this.apiClient = apiClient; + } + + /** + * Fetches project details by slug. Requires authentication. + * + * @param slug the project slug (e.g. from the project URL) + * @return the project including id, name, slug, orgId, etc. + * @throws InfisicalException when slug is invalid or the API request fails + */ + public Project GetBySlug(String slug) throws InfisicalException { + if (Helper.isNullOrEmpty(slug)) { + throw new InfisicalException("Project slug is required"); + } + + String url = + String.format( + "%s/api/v1/projects/slug/%s", + this.apiClient.GetBaseUrl(), slug.trim()); + return this.apiClient.get(url, null, Project.class); + } + + /** + * Returns the project ID for the given project slug. Convenience method that + * calls GetBySlug and returns {@link Project#getId()}. + * + * @param slug the project slug + * @return the project id + * @throws InfisicalException when slug is invalid or the API request fails + */ + public String GetProjectIdBySlug(String slug) throws InfisicalException { + return GetBySlug(slug).getId(); + } +} diff --git a/src/test/java/com/infisical/sdk/resources/ProjectsClientTest.java b/src/test/java/com/infisical/sdk/resources/ProjectsClientTest.java new file mode 100644 index 0000000..4b8f582 --- /dev/null +++ b/src/test/java/com/infisical/sdk/resources/ProjectsClientTest.java @@ -0,0 +1,79 @@ +package com.infisical.sdk.resources; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.infisical.sdk.api.ApiClient; +import com.infisical.sdk.models.Project; +import com.infisical.sdk.util.InfisicalException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class ProjectsClientTest { + + @Mock + private ApiClient apiClient; + + @Test + public void GetBySlug_throwsWhenSlugIsNull() { + ProjectsClient client = new ProjectsClient(apiClient); + + InfisicalException ex = assertThrows(InfisicalException.class, () -> client.GetBySlug(null)); + assertEquals("Project slug is required", ex.getMessage()); + } + + @Test + public void GetBySlug_throwsWhenSlugIsEmpty() { + ProjectsClient client = new ProjectsClient(apiClient); + + InfisicalException ex = assertThrows(InfisicalException.class, () -> client.GetBySlug("")); + assertEquals("Project slug is required", ex.getMessage()); + } + + @Test + public void GetBySlug_callsGetWithCorrectUrl() throws InfisicalException { + when(apiClient.GetBaseUrl()).thenReturn("https://app.infisical.com"); + Project project = new Project(); + project.setId("proj-123"); + project.setSlug("my-project"); + when(apiClient.get( + eq("https://app.infisical.com/api/v1/projects/slug/my-project"), + eq(null), + eq(Project.class))) + .thenReturn(project); + + ProjectsClient client = new ProjectsClient(apiClient); + Project result = client.GetBySlug("my-project"); + + assertEquals("proj-123", result.getId()); + assertEquals("my-project", result.getSlug()); + verify(apiClient) + .get( + eq("https://app.infisical.com/api/v1/projects/slug/my-project"), + eq(null), + eq(Project.class)); + } + + @Test + public void GetProjectIdBySlug_returnsIdFromGetBySlug() throws InfisicalException { + when(apiClient.GetBaseUrl()).thenReturn("https://app.infisical.com"); + Project project = new Project(); + project.setId("proj-456"); + when(apiClient.get( + eq("https://app.infisical.com/api/v1/projects/slug/acme"), + eq(null), + eq(Project.class))) + .thenReturn(project); + + ProjectsClient client = new ProjectsClient(apiClient); + String projectId = client.GetProjectIdBySlug("acme"); + + assertEquals("proj-456", projectId); + } +} From 844ff7140577c9d63293884a6fb56b2f40dacc37 Mon Sep 17 00:00:00 2001 From: IgorHorta Date: Mon, 16 Feb 2026 21:11:15 -0300 Subject: [PATCH 2/5] fix: remove manual referece --- pom.xml | 39 --------------------------------------- 1 file changed, 39 deletions(-) diff --git a/pom.xml b/pom.xml index b1c8c9b..024aa82 100644 --- a/pom.xml +++ b/pom.xml @@ -187,45 +187,6 @@ - - - manual - - false - - - - - org.codehaus.mojo - build-helper-maven-plugin - 3.4.0 - - - add-manual-test-source - generate-test-sources - - add-test-source - - - - java-test - - - - - - - org.codehaus.mojo - exec-maven-plugin - 3.1.0 - - javatest.ManualProjectsTest - test - - - - - release From 7b3a886cad80ec2092771660dca8e6caaf2427af Mon Sep 17 00:00:00 2001 From: IgorHorta Date: Mon, 16 Feb 2026 21:14:38 -0300 Subject: [PATCH 3/5] Validate project slug to prevent path traversal (ENG-4561) Co-authored-by: Cursor --- .../sdk/resources/ProjectsClient.java | 11 ++++++- .../sdk/resources/ProjectsClientTest.java | 33 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/infisical/sdk/resources/ProjectsClient.java b/src/main/java/com/infisical/sdk/resources/ProjectsClient.java index 57c2514..05b23b0 100644 --- a/src/main/java/com/infisical/sdk/resources/ProjectsClient.java +++ b/src/main/java/com/infisical/sdk/resources/ProjectsClient.java @@ -4,10 +4,14 @@ import com.infisical.sdk.models.Project; import com.infisical.sdk.util.Helper; import com.infisical.sdk.util.InfisicalException; +import java.util.regex.Pattern; public class ProjectsClient { private final ApiClient apiClient; + /** Allowed slug characters: alphanumeric, hyphen, underscore. Prevents path traversal. */ + private static final Pattern SLUG_PATTERN = Pattern.compile("^[a-zA-Z0-9_-]{1,64}$"); + public ProjectsClient(ApiClient apiClient) { this.apiClient = apiClient; } @@ -23,11 +27,16 @@ public Project GetBySlug(String slug) throws InfisicalException { if (Helper.isNullOrEmpty(slug)) { throw new InfisicalException("Project slug is required"); } + String trimmed = slug.trim(); + if (!SLUG_PATTERN.matcher(trimmed).matches()) { + throw new InfisicalException( + "Project slug must be 1–64 characters and contain only letters, numbers, hyphens, and underscores"); + } String url = String.format( "%s/api/v1/projects/slug/%s", - this.apiClient.GetBaseUrl(), slug.trim()); + this.apiClient.GetBaseUrl(), trimmed); return this.apiClient.get(url, null, Project.class); } diff --git a/src/test/java/com/infisical/sdk/resources/ProjectsClientTest.java b/src/test/java/com/infisical/sdk/resources/ProjectsClientTest.java index 4b8f582..d52b1cc 100644 --- a/src/test/java/com/infisical/sdk/resources/ProjectsClientTest.java +++ b/src/test/java/com/infisical/sdk/resources/ProjectsClientTest.java @@ -36,6 +36,39 @@ public void GetBySlug_throwsWhenSlugIsEmpty() { assertEquals("Project slug is required", ex.getMessage()); } + @Test + public void GetBySlug_throwsWhenSlugContainsPathTraversal() { + ProjectsClient client = new ProjectsClient(apiClient); + + InfisicalException ex = + assertThrows(InfisicalException.class, () -> client.GetBySlug("../../../admin")); + assertEquals( + "Project slug must be 1–64 characters and contain only letters, numbers, hyphens, and underscores", + ex.getMessage()); + } + + @Test + public void GetBySlug_throwsWhenSlugContainsSlash() { + ProjectsClient client = new ProjectsClient(apiClient); + + InfisicalException ex = + assertThrows(InfisicalException.class, () -> client.GetBySlug("foo/bar")); + assertEquals( + "Project slug must be 1–64 characters and contain only letters, numbers, hyphens, and underscores", + ex.getMessage()); + } + + @Test + public void GetBySlug_throwsWhenSlugContainsInvalidCharacters() { + ProjectsClient client = new ProjectsClient(apiClient); + + InfisicalException ex = + assertThrows(InfisicalException.class, () -> client.GetBySlug("my project")); + assertEquals( + "Project slug must be 1–64 characters and contain only letters, numbers, hyphens, and underscores", + ex.getMessage()); + } + @Test public void GetBySlug_callsGetWithCorrectUrl() throws InfisicalException { when(apiClient.GetBaseUrl()).thenReturn("https://app.infisical.com"); From 1cf2c6896cd7428e7da0a4b0e14a8ebbd5b8e3de Mon Sep 17 00:00:00 2001 From: IgorHorta Date: Wed, 18 Feb 2026 13:38:31 -0300 Subject: [PATCH 4/5] Removes GetProjectIdBySlug method. --- .../infisical/sdk/resources/ProjectsClient.java | 12 ------------ .../sdk/resources/ProjectsClientTest.java | 17 ----------------- 2 files changed, 29 deletions(-) diff --git a/src/main/java/com/infisical/sdk/resources/ProjectsClient.java b/src/main/java/com/infisical/sdk/resources/ProjectsClient.java index 05b23b0..fb5be9a 100644 --- a/src/main/java/com/infisical/sdk/resources/ProjectsClient.java +++ b/src/main/java/com/infisical/sdk/resources/ProjectsClient.java @@ -39,16 +39,4 @@ public Project GetBySlug(String slug) throws InfisicalException { this.apiClient.GetBaseUrl(), trimmed); return this.apiClient.get(url, null, Project.class); } - - /** - * Returns the project ID for the given project slug. Convenience method that - * calls GetBySlug and returns {@link Project#getId()}. - * - * @param slug the project slug - * @return the project id - * @throws InfisicalException when slug is invalid or the API request fails - */ - public String GetProjectIdBySlug(String slug) throws InfisicalException { - return GetBySlug(slug).getId(); - } } diff --git a/src/test/java/com/infisical/sdk/resources/ProjectsClientTest.java b/src/test/java/com/infisical/sdk/resources/ProjectsClientTest.java index d52b1cc..c88eb7a 100644 --- a/src/test/java/com/infisical/sdk/resources/ProjectsClientTest.java +++ b/src/test/java/com/infisical/sdk/resources/ProjectsClientTest.java @@ -92,21 +92,4 @@ public void GetBySlug_callsGetWithCorrectUrl() throws InfisicalException { eq(null), eq(Project.class)); } - - @Test - public void GetProjectIdBySlug_returnsIdFromGetBySlug() throws InfisicalException { - when(apiClient.GetBaseUrl()).thenReturn("https://app.infisical.com"); - Project project = new Project(); - project.setId("proj-456"); - when(apiClient.get( - eq("https://app.infisical.com/api/v1/projects/slug/acme"), - eq(null), - eq(Project.class))) - .thenReturn(project); - - ProjectsClient client = new ProjectsClient(apiClient); - String projectId = client.GetProjectIdBySlug("acme"); - - assertEquals("proj-456", projectId); - } } From 6fa62cdfcadad85021d6a0c3db1f1763e2b5ef0a Mon Sep 17 00:00:00 2001 From: IgorHorta Date: Wed, 18 Feb 2026 19:52:25 -0300 Subject: [PATCH 5/5] fix unecessary validation --- .../sdk/resources/ProjectsClient.java | 8 ----- .../sdk/resources/ProjectsClientTest.java | 33 ------------------- 2 files changed, 41 deletions(-) diff --git a/src/main/java/com/infisical/sdk/resources/ProjectsClient.java b/src/main/java/com/infisical/sdk/resources/ProjectsClient.java index fb5be9a..e8b5ba7 100644 --- a/src/main/java/com/infisical/sdk/resources/ProjectsClient.java +++ b/src/main/java/com/infisical/sdk/resources/ProjectsClient.java @@ -4,14 +4,10 @@ import com.infisical.sdk.models.Project; import com.infisical.sdk.util.Helper; import com.infisical.sdk.util.InfisicalException; -import java.util.regex.Pattern; public class ProjectsClient { private final ApiClient apiClient; - /** Allowed slug characters: alphanumeric, hyphen, underscore. Prevents path traversal. */ - private static final Pattern SLUG_PATTERN = Pattern.compile("^[a-zA-Z0-9_-]{1,64}$"); - public ProjectsClient(ApiClient apiClient) { this.apiClient = apiClient; } @@ -28,10 +24,6 @@ public Project GetBySlug(String slug) throws InfisicalException { throw new InfisicalException("Project slug is required"); } String trimmed = slug.trim(); - if (!SLUG_PATTERN.matcher(trimmed).matches()) { - throw new InfisicalException( - "Project slug must be 1–64 characters and contain only letters, numbers, hyphens, and underscores"); - } String url = String.format( diff --git a/src/test/java/com/infisical/sdk/resources/ProjectsClientTest.java b/src/test/java/com/infisical/sdk/resources/ProjectsClientTest.java index c88eb7a..ab033bf 100644 --- a/src/test/java/com/infisical/sdk/resources/ProjectsClientTest.java +++ b/src/test/java/com/infisical/sdk/resources/ProjectsClientTest.java @@ -36,39 +36,6 @@ public void GetBySlug_throwsWhenSlugIsEmpty() { assertEquals("Project slug is required", ex.getMessage()); } - @Test - public void GetBySlug_throwsWhenSlugContainsPathTraversal() { - ProjectsClient client = new ProjectsClient(apiClient); - - InfisicalException ex = - assertThrows(InfisicalException.class, () -> client.GetBySlug("../../../admin")); - assertEquals( - "Project slug must be 1–64 characters and contain only letters, numbers, hyphens, and underscores", - ex.getMessage()); - } - - @Test - public void GetBySlug_throwsWhenSlugContainsSlash() { - ProjectsClient client = new ProjectsClient(apiClient); - - InfisicalException ex = - assertThrows(InfisicalException.class, () -> client.GetBySlug("foo/bar")); - assertEquals( - "Project slug must be 1–64 characters and contain only letters, numbers, hyphens, and underscores", - ex.getMessage()); - } - - @Test - public void GetBySlug_throwsWhenSlugContainsInvalidCharacters() { - ProjectsClient client = new ProjectsClient(apiClient); - - InfisicalException ex = - assertThrows(InfisicalException.class, () -> client.GetBySlug("my project")); - assertEquals( - "Project slug must be 1–64 characters and contain only letters, numbers, hyphens, and underscores", - ex.getMessage()); - } - @Test public void GetBySlug_callsGetWithCorrectUrl() throws InfisicalException { when(apiClient.GetBaseUrl()).thenReturn("https://app.infisical.com");