From 27dd26f617078fbcff2b74fc9a66b96c0b7d9f5d Mon Sep 17 00:00:00 2001 From: DaisyModi Date: Thu, 23 Oct 2025 01:50:55 +0530 Subject: [PATCH 01/15] Created client layer for IAM --- iam/iam-client/pom.xml | 97 ++++++ .../multicloudj/iam/client/IamClient.java | 212 +++++++++++++ .../multicloudj/iam/model/CreateOptions.java | 146 +++++++++ .../multicloudj/iam/model/PolicyDocument.java | 272 ++++++++++++++++ .../multicloudj/iam/model/Statement.java | 291 ++++++++++++++++++ .../iam/model/TrustConfiguration.java | 146 +++++++++ .../multicloudj/iam/client/IamClientTest.java | 148 +++++++++ .../iam/model/CreateOptionsTest.java | 199 ++++++++++++ .../iam/model/PolicyDocumentTest.java | 107 +++++++ .../multicloudj/iam/model/StatementTest.java | 174 +++++++++++ .../iam/model/TrustConfigurationTest.java | 276 +++++++++++++++++ iam/pom.xml | 19 ++ pom.xml | 1 + 13 files changed, 2088 insertions(+) create mode 100644 iam/iam-client/pom.xml create mode 100644 iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/client/IamClient.java create mode 100644 iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/CreateOptions.java create mode 100644 iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/PolicyDocument.java create mode 100644 iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/Statement.java create mode 100644 iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/TrustConfiguration.java create mode 100644 iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/client/IamClientTest.java create mode 100644 iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/CreateOptionsTest.java create mode 100644 iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java create mode 100644 iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/StatementTest.java create mode 100644 iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/TrustConfigurationTest.java create mode 100644 iam/pom.xml diff --git a/iam/iam-client/pom.xml b/iam/iam-client/pom.xml new file mode 100644 index 00000000..e371cf45 --- /dev/null +++ b/iam/iam-client/pom.xml @@ -0,0 +1,97 @@ + + + 4.0.0 + + iam-client + jar + MultiCloudJ - IAM Client + + + com.salesforce.multicloudj + iam + ${revision} + ../pom.xml + + + + + + com.salesforce.multicloudj + multicloudj-common + + + com.salesforce.multicloudj + sts-client + + + + + org.junit.jupiter + junit-jupiter-api + 5.12.1 + test + + + org.mockito + mockito-core + 5.16.1 + test + + + org.mockito + mockito-junit-jupiter + 5.16.1 + test + + + com.salesforce.multicloudj + multicloudj-common + test-jar + test + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.4.2 + + + + test-jar + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.4.0 + + + org.apache.maven.plugins + maven-failsafe-plugin + 3.4.0 + + + run-integration-tests + integration-test + + integration-test + + + + verify-integration-results + verify + + verify + + + + + + + \ No newline at end of file diff --git a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/client/IamClient.java b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/client/IamClient.java new file mode 100644 index 00000000..8189e09a --- /dev/null +++ b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/client/IamClient.java @@ -0,0 +1,212 @@ +package com.salesforce.multicloudj.iam.client; + +import com.salesforce.multicloudj.iam.model.CreateOptions; +import com.salesforce.multicloudj.iam.model.PolicyDocument; +import com.salesforce.multicloudj.iam.model.TrustConfiguration; +import com.salesforce.multicloudj.sts.model.CredentialsOverrider; + +import java.net.URI; +import java.util.List; +import java.util.Optional; + +/** + * Entry point for client code to interact with Identity and Access Management (IAM) services + * in a substrate-agnostic way. + * + *

This client provides unified IAM operations across multiple cloud providers including + * AWS IAM, GCP IAM, and AliCloud RAM. It handles the complexity of different cloud IAM models + * and provides a consistent API for identity lifecycle management and policy operations. + * + *

Usage example: + *

+ * IamClient client = IamClient.builder("aws")
+ *     .withRegion("us-west-2")
+ *     .build();
+ *
+ * // Create identity
+ * String identityId = client.createIdentity("MyRole", "Example role", "123456789012", "us-west-2",
+ *     Optional.empty(), Optional.empty());
+ *
+ * // Create policy
+ * PolicyDocument policy = PolicyDocument.builder()
+ *     .version("2024-01-01")
+ *     .statement("StorageAccess")
+ *         .effect("Allow")
+ *         .addAction("storage:GetObject")
+ *         .addResource("storage://my-bucket/*")
+ *     .endStatement()
+ *     .build();
+ *
+ * // Attach policy
+ * client.attachInlinePolicy(policy, "123456789012", "us-west-2", "my-bucket");
+ * 
+ * + * @since 0.3.0 + */ +public class IamClient { + + /** + * Protected constructor for IamClient. + * Use the builder pattern to create instances. + */ + protected IamClient() { + // Implementation will be added later when AbstractIamService is available + } + + /** + * Creates a new IamClientBuilder for the specified provider. + * + * @param providerId the ID of the provider such as "aws", "gcp", or "ali" + * @return a new IamClientBuilder instance + */ + public static IamClientBuilder builder(String providerId) { + return new IamClientBuilder(providerId); + } + + /** + * Creates a new identity (role/service account) in the cloud provider. + * + * @param identityName the name of the identity to create + * @param description optional description for the identity (can be null) + * @param tenantId the tenant ID (AWS Account ID, GCP Project ID, or AliCloud Account ID) + * @param region the region for IAM operations + * @param trustConfig optional trust configuration + * @param options optional creation options + * @return the unique identifier of the created identity + */ + public String createIdentity(String identityName, String description, String tenantId, String region, + Optional trustConfig, Optional options) { + // Implementation will be added when driver layer is available + throw new UnsupportedOperationException("Implementation will be added when driver layer is available"); + } + + /** + * Attaches an inline policy to a resource. + * + * @param policyDocument the policy document in substrate-neutral format + * @param tenantId the tenant ID + * @param region the region + * @param resource the resource to attach the policy to + */ + public void attachInlinePolicy(PolicyDocument policyDocument, String tenantId, String region, String resource) { + // Implementation will be added when driver layer is available + throw new UnsupportedOperationException("Implementation will be added when driver layer is available"); + } + + /** + * Retrieves the details of a specific inline policy attached to an identity. + * + * @param identityName the name of the identity + * @param policyName the name of the policy + * @param tenantId the tenant ID + * @param region the region + * @return the policy document details as a string + */ + public String getInlinePolicyDetails(String identityName, String policyName, String tenantId, String region) { + // Implementation will be added when driver layer is available + throw new UnsupportedOperationException("Implementation will be added when driver layer is available"); + } + + /** + * Lists all inline policies attached to an identity. + * + * @param identityName the name of the identity + * @param tenantId the tenant ID + * @param region the region + * @return a list of policy names + */ + public List getAttachedPolicies(String identityName, String tenantId, String region) { + // Implementation will be added when driver layer is available + throw new UnsupportedOperationException("Implementation will be added when driver layer is available"); + } + + /** + * Removes an inline policy from an identity. + * + * @param identityName the name of the identity + * @param policyName the name of the policy to remove + * @param tenantId the tenant ID + * @param region the region + */ + public void removePolicy(String identityName, String policyName, String tenantId, String region) { + // Implementation will be added when driver layer is available + throw new UnsupportedOperationException("Implementation will be added when driver layer is available"); + } + + /** + * Deletes an identity from the cloud provider. + * + * @param identityName the name of the identity to delete + * @param tenantId the tenant ID + * @param region the region + */ + public void deleteIdentity(String identityName, String tenantId, String region) { + // Implementation will be added when driver layer is available + throw new UnsupportedOperationException("Implementation will be added when driver layer is available"); + } + + /** + * Retrieves metadata about an identity. + * + * @param identityName the name of the identity + * @param tenantId the tenant ID + * @param region the region + * @return the unique identity identifier (ARN, email, or roleId) + */ + public String getIdentity(String identityName, String tenantId, String region) { + // Implementation will be added when driver layer is available + throw new UnsupportedOperationException("Implementation will be added when driver layer is available"); + } + + /** + * Builder class for IamClient. + */ + public static class IamClientBuilder { + protected String region; + protected URI endpoint; + + /** + * Constructor for IamClientBuilder. + * + * @param providerId the ID of the provider such as "aws", "gcp", or "ali" + */ + public IamClientBuilder(String providerId) { + // Implementation will be added when ServiceLoader and AbstractIamService are available + // Will find and initialize the provider builder here + } + + /** + * Sets the region for the IAM client. + * + * @param region the region to set + * @return this IamClientBuilder instance + */ + public IamClientBuilder withRegion(String region) { + this.region = region; + // Implementation will be added later to delegate to underlying provider builder + return this; + } + + /** + * Sets the endpoint to override for the IAM client. + * + * @param endpoint the endpoint to set + * @return this IamClientBuilder instance + */ + public IamClientBuilder withEndpoint(URI endpoint) { + this.endpoint = endpoint; + // Implementation will be added later to delegate to underlying provider builder + return this; + } + + /** + * Builds and returns an IamClient instance. + * + * @return a new IamClient instance + */ + public IamClient build() { + // Implementation will be added when ServiceLoader and AbstractIamService are available + return new IamClient(); + } + } +} \ No newline at end of file diff --git a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/CreateOptions.java b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/CreateOptions.java new file mode 100644 index 00000000..3da2946d --- /dev/null +++ b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/CreateOptions.java @@ -0,0 +1,146 @@ +package com.salesforce.multicloudj.iam.model; + +import java.util.Objects; + +/** + * Optional configuration for identity creation operations. + * + *

This class provides additional options that can be set during identity creation, + * such as path specifications, session duration limits, and permission boundaries. + * + *

Usage example: + *

+ * CreateOptions options = CreateOptions.builder()
+ *     .path("/orgstore/")
+ *     .maxSessionDuration(43200) // 12 hours
+ *     .permissionBoundary("arn:aws:iam::123456789012:policy/PowerUserBoundary")
+ *     .build();
+ * 
+ * + * @since 0.3.0 + */ +public class CreateOptions { + private final String path; + private final Integer maxSessionDuration; + private final String permissionBoundary; + + private CreateOptions(Builder builder) { + this.path = builder.path; + this.maxSessionDuration = builder.maxSessionDuration; + this.permissionBoundary = builder.permissionBoundary; + } + + /** + * Creates a new builder for CreateOptions. + * + * @return a new Builder instance + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Gets the path for the identity. + * + * @return the path, or null if not set + */ + public String getPath() { + return path; + } + + /** + * Gets the maximum session duration in seconds. + * + * @return the maximum session duration, or null if not set + */ + public Integer getMaxSessionDuration() { + return maxSessionDuration; + } + + /** + * Gets the permission boundary ARN. + * + * @return the permission boundary ARN, or null if not set + */ + public String getPermissionBoundary() { + return permissionBoundary; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CreateOptions that = (CreateOptions) o; + return Objects.equals(path, that.path) && + Objects.equals(maxSessionDuration, that.maxSessionDuration) && + Objects.equals(permissionBoundary, that.permissionBoundary); + } + + @Override + public int hashCode() { + return Objects.hash(path, maxSessionDuration, permissionBoundary); + } + + @Override + public String toString() { + return "CreateOptions{" + + "path='" + path + '\'' + + ", maxSessionDuration=" + maxSessionDuration + + ", permissionBoundary='" + permissionBoundary + '\'' + + '}'; + } + + /** + * Builder class for CreateOptions. + */ + public static class Builder { + private String path; + private Integer maxSessionDuration; + private String permissionBoundary; + + private Builder() { + } + + /** + * Sets the path for the identity. + * + * @param path the path (e.g., "/orgstore/") for organizing identities + * @return this Builder instance + */ + public Builder path(String path) { + this.path = path; + return this; + } + + /** + * Sets the maximum session duration in seconds. + * + * @param maxSessionDuration the maximum session duration (typically up to 12 hours = 43200 seconds) + * @return this Builder instance + */ + public Builder maxSessionDuration(Integer maxSessionDuration) { + this.maxSessionDuration = maxSessionDuration; + return this; + } + + /** + * Sets the permission boundary ARN. + * + * @param permissionBoundary the ARN of the policy that acts as a permissions boundary + * @return this Builder instance + */ + public Builder permissionBoundary(String permissionBoundary) { + this.permissionBoundary = permissionBoundary; + return this; + } + + /** + * Builds and returns a CreateOptions instance. + * + * @return a new CreateOptions instance + */ + public CreateOptions build() { + return new CreateOptions(this); + } + } +} \ No newline at end of file diff --git a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/PolicyDocument.java b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/PolicyDocument.java new file mode 100644 index 00000000..c6acae2e --- /dev/null +++ b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/PolicyDocument.java @@ -0,0 +1,272 @@ +package com.salesforce.multicloudj.iam.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Represents a substrate-neutral policy document containing multiple statements. + * + *

This class provides a cloud-agnostic way to define IAM policies that can be + * translated to AWS, GCP, or AliCloud native formats. The policy uses a builder + * pattern to prevent JSON parsing errors and provides type safety. + * + *

Usage example: + *

+ * PolicyDocument policy = PolicyDocument.builder()
+ *     .version("2024-01-01")
+ *     .statement("StorageAccess")
+ *         .effect("Allow")
+ *         .addAction("storage:GetObject")
+ *         .addAction("storage:PutObject")
+ *         .addPrincipal("arn:aws:iam::123456789012:user/ExampleUser")
+ *         .addResource("storage://my-bucket/*")
+ *         .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2")
+ *     .endStatement()
+ *     .build();
+ * 
+ * + * @since 0.3.0 + */ +public class PolicyDocument { + private final String version; + private final List statements; + + private PolicyDocument(Builder builder) { + this.version = builder.version; + this.statements = new ArrayList<>(builder.statements); + } + + /** + * Creates a new builder for PolicyDocument. + * + * @return a new Builder instance + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Gets the policy version. + * + * @return the policy version + */ + public String getVersion() { + return version; + } + + /** + * Gets the list of statements. + * + * @return an immutable copy of the statements list + */ + public List getStatements() { + return new ArrayList<>(statements); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PolicyDocument that = (PolicyDocument) o; + return Objects.equals(version, that.version) && + Objects.equals(statements, that.statements); + } + + @Override + public int hashCode() { + return Objects.hash(version, statements); + } + + @Override + public String toString() { + return "PolicyDocument{" + + "version='" + version + '\'' + + ", statements=" + statements + + '}'; + } + + /** + * Builder class for PolicyDocument. + */ + public static class Builder { + private String version = "2024-01-01"; + private final List statements = new ArrayList<>(); + private Statement.Builder currentStatementBuilder; + + private Builder() { + } + + /** + * Sets the policy version. + * + * @param version the policy version (default: "2024-01-01") + * @return this Builder instance + */ + public Builder version(String version) { + this.version = version; + return this; + } + + /** + * Starts building a new statement with the given SID. + * + * @param sid the statement ID + * @return this Builder instance configured for statement building + */ + public Builder statement(String sid) { + finalizeCurrentStatement(); + this.currentStatementBuilder = Statement.builder().sid(sid); + return this; + } + + /** + * Sets the effect for the current statement. + * + * @param effect "Allow" or "Deny" + * @return this Builder instance + */ + public Builder effect(String effect) { + validateCurrentStatement(); + this.currentStatementBuilder.effect(effect); + return this; + } + + /** + * Adds a principal to the current statement. + * + * @param principal the principal (fully qualified principal required) + * @return this Builder instance + */ + public Builder addPrincipal(String principal) { + validateCurrentStatement(); + this.currentStatementBuilder.addPrincipal(principal); + return this; + } + + /** + * Adds multiple principals to the current statement. + * + * @param principals the list of principals + * @return this Builder instance + */ + public Builder addPrincipals(List principals) { + validateCurrentStatement(); + this.currentStatementBuilder.addPrincipals(principals); + return this; + } + + /** + * Adds an action to the current statement. + * + * @param action the action in substrate-neutral format + * @return this Builder instance + */ + public Builder addAction(String action) { + validateCurrentStatement(); + this.currentStatementBuilder.addAction(action); + return this; + } + + /** + * Adds multiple actions to the current statement. + * + * @param actions the list of actions + * @return this Builder instance + */ + public Builder addActions(List actions) { + validateCurrentStatement(); + this.currentStatementBuilder.addActions(actions); + return this; + } + + /** + * Adds a resource to the current statement. + * + * @param resource the resource in URI format + * @return this Builder instance + */ + public Builder addResource(String resource) { + validateCurrentStatement(); + this.currentStatementBuilder.addResource(resource); + return this; + } + + /** + * Adds multiple resources to the current statement. + * + * @param resources the list of resources + * @return this Builder instance + */ + public Builder addResources(List resources) { + validateCurrentStatement(); + this.currentStatementBuilder.addResources(resources); + return this; + } + + /** + * Adds a condition to the current statement. + * + * @param operator the condition operator + * @param key the condition key + * @param value the condition value + * @return this Builder instance + */ + public Builder addCondition(String operator, String key, Object value) { + validateCurrentStatement(); + this.currentStatementBuilder.addCondition(operator, key, value); + return this; + } + + /** + * Ends the current statement and adds it to the policy. + * + * @return this Builder instance + */ + public Builder endStatement() { + finalizeCurrentStatement(); + return this; + } + + /** + * Adds a complete statement to the policy document. + * + * @param statement the statement to add + * @return this Builder instance + */ + public Builder addStatement(Statement statement) { + finalizeCurrentStatement(); + if (statement != null) { + this.statements.add(statement); + } + return this; + } + + /** + * Builds and returns a PolicyDocument instance. + * + * @return a new PolicyDocument instance + * @throws IllegalArgumentException if no statements are defined + */ + public PolicyDocument build() { + finalizeCurrentStatement(); + if (statements.isEmpty()) { + throw new IllegalArgumentException("at least one statement is required"); + } + return new PolicyDocument(this); + } + + private void validateCurrentStatement() { + if (currentStatementBuilder == null) { + throw new IllegalStateException("No statement is currently being built. Call statement(sid) first."); + } + } + + private void finalizeCurrentStatement() { + if (currentStatementBuilder != null) { + statements.add(currentStatementBuilder.build()); + currentStatementBuilder = null; + } + } + } +} \ No newline at end of file diff --git a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/Statement.java b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/Statement.java new file mode 100644 index 00000000..d14ea250 --- /dev/null +++ b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/Statement.java @@ -0,0 +1,291 @@ +package com.salesforce.multicloudj.iam.model; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * Represents a single statement within a policy document. + * + *

A statement defines the permissions, principals, resources, and conditions + * for a specific set of actions in a substrate-neutral format. + * + *

Usage example: + *

+ * Statement statement = Statement.builder()
+ *     .sid("StorageAccess")
+ *     .effect("Allow")
+ *     .addAction("storage:GetObject")
+ *     .addAction("storage:PutObject")
+ *     .addPrincipal("arn:aws:iam::123456789012:user/ExampleUser")
+ *     .addResource("storage://my-bucket/*")
+ *     .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2")
+ *     .build();
+ * 
+ * + * @since 0.3.0 + */ +public class Statement { + private final String sid; + private final String effect; + private final List principals; + private final List actions; + private final List resources; + private final Map> conditions; + + private Statement(Builder builder) { + this.sid = builder.sid; + this.effect = builder.effect; + this.principals = new ArrayList<>(builder.principals); + this.actions = new ArrayList<>(builder.actions); + this.resources = new ArrayList<>(builder.resources); + this.conditions = new HashMap<>(builder.conditions); + } + + /** + * Creates a new builder for Statement. + * + * @return a new Builder instance + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Gets the statement ID. + * + * @return the statement ID, or null if not set + */ + public String getSid() { + return sid; + } + + /** + * Gets the effect (Allow or Deny). + * + * @return the effect + */ + public String getEffect() { + return effect; + } + + /** + * Gets the list of principals. + * + * @return an immutable copy of the principals list + */ + public List getPrincipals() { + return new ArrayList<>(principals); + } + + /** + * Gets the list of actions. + * + * @return an immutable copy of the actions list + */ + public List getActions() { + return new ArrayList<>(actions); + } + + /** + * Gets the list of resources. + * + * @return an immutable copy of the resources list + */ + public List getResources() { + return new ArrayList<>(resources); + } + + /** + * Gets the conditions map. + * + * @return an immutable copy of the conditions map + */ + public Map> getConditions() { + return new HashMap<>(conditions); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Statement statement = (Statement) o; + return Objects.equals(sid, statement.sid) && + Objects.equals(effect, statement.effect) && + Objects.equals(principals, statement.principals) && + Objects.equals(actions, statement.actions) && + Objects.equals(resources, statement.resources) && + Objects.equals(conditions, statement.conditions); + } + + @Override + public int hashCode() { + return Objects.hash(sid, effect, principals, actions, resources, conditions); + } + + @Override + public String toString() { + return "Statement{" + + "sid='" + sid + '\'' + + ", effect='" + effect + '\'' + + ", principals=" + principals + + ", actions=" + actions + + ", resources=" + resources + + ", conditions=" + conditions + + '}'; + } + + /** + * Builder class for Statement. + */ + public static class Builder { + private String sid; + private String effect; + private final List principals = new ArrayList<>(); + private final List actions = new ArrayList<>(); + private final List resources = new ArrayList<>(); + private final Map> conditions = new HashMap<>(); + + private Builder() { + } + + /** + * Sets the statement ID. + * + * @param sid the unique identifier for this statement within the policy + * @return this Builder instance + */ + public Builder sid(String sid) { + this.sid = sid; + return this; + } + + /** + * Sets the effect. + * + * @param effect "Allow" or "Deny" + * @return this Builder instance + */ + public Builder effect(String effect) { + this.effect = effect; + return this; + } + + /** + * Adds a principal to the statement. + * + * @param principal the principal (fully qualified principal required) + * @return this Builder instance + */ + public Builder addPrincipal(String principal) { + if (principal != null && !principal.trim().isEmpty()) { + this.principals.add(principal); + } + return this; + } + + /** + * Adds multiple principals to the statement. + * + * @param principals the list of principals + * @return this Builder instance + */ + public Builder addPrincipals(List principals) { + if (principals != null) { + principals.stream() + .filter(p -> p != null && !p.trim().isEmpty()) + .forEach(this.principals::add); + } + return this; + } + + /** + * Adds an action to the statement. + * + * @param action the action in substrate-neutral format (e.g., "storage:GetObject") + * @return this Builder instance + */ + public Builder addAction(String action) { + if (action != null && !action.trim().isEmpty()) { + this.actions.add(action); + } + return this; + } + + /** + * Adds multiple actions to the statement. + * + * @param actions the list of actions + * @return this Builder instance + */ + public Builder addActions(List actions) { + if (actions != null) { + actions.stream() + .filter(a -> a != null && !a.trim().isEmpty()) + .forEach(this.actions::add); + } + return this; + } + + /** + * Adds a resource to the statement. + * + * @param resource the resource in URI format (e.g., "storage://my-bucket/*") + * @return this Builder instance + */ + public Builder addResource(String resource) { + if (resource != null && !resource.trim().isEmpty()) { + this.resources.add(resource); + } + return this; + } + + /** + * Adds multiple resources to the statement. + * + * @param resources the list of resources + * @return this Builder instance + */ + public Builder addResources(List resources) { + if (resources != null) { + resources.stream() + .filter(r -> r != null && !r.trim().isEmpty()) + .forEach(this.resources::add); + } + return this; + } + + /** + * Adds a condition to the statement. + * + * @param operator the condition operator (e.g., "StringEquals", "IpAddress") + * @param key the condition key (e.g., "aws:RequestedRegion") + * @param value the condition value + * @return this Builder instance + */ + public Builder addCondition(String operator, String key, Object value) { + if (operator != null && key != null && value != null) { + conditions.computeIfAbsent(operator, k -> new HashMap<>()).put(key, value); + } + return this; + } + + /** + * Builds and returns a Statement instance. + * + * @return a new Statement instance + * @throws IllegalArgumentException if required fields are missing + */ + public Statement build() { + if (effect == null || effect.trim().isEmpty()) { + throw new IllegalArgumentException("effect is required"); + } + if (actions.isEmpty()) { + throw new IllegalArgumentException("at least one action is required"); + } + return new Statement(this); + } + } +} \ No newline at end of file diff --git a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/TrustConfiguration.java b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/TrustConfiguration.java new file mode 100644 index 00000000..20f9f114 --- /dev/null +++ b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/TrustConfiguration.java @@ -0,0 +1,146 @@ +package com.salesforce.multicloudj.iam.model; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * Configuration for trust relationships in identity creation. + * + *

This class defines which principals can assume or impersonate the identity being created, + * along with any conditions that must be met for the trust relationship to be valid. + * + *

Usage example: + *

+ * TrustConfiguration trust = TrustConfiguration.builder()
+ *     .addTrustedPrincipal("arn:aws:iam::111122223333:root")
+ *     .addTrustedPrincipal("arn:aws:iam::444455556666:user/ExampleUser")
+ *     .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2")
+ *     .build();
+ * 
+ * + * @since 0.3.0 + */ +public class TrustConfiguration { + private final List trustedPrincipals; + private final Map> conditions; + + private TrustConfiguration(Builder builder) { + this.trustedPrincipals = new ArrayList<>(builder.trustedPrincipals); + this.conditions = new HashMap<>(builder.conditions); + } + + /** + * Creates a new builder for TrustConfiguration. + * + * @return a new Builder instance + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Gets the list of trusted principals. + * + * @return an immutable copy of the trusted principals list + */ + public List getTrustedPrincipals() { + return new ArrayList<>(trustedPrincipals); + } + + /** + * Gets the trust conditions. + * + * @return an immutable copy of the conditions map + */ + public Map> getConditions() { + return new HashMap<>(conditions); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TrustConfiguration that = (TrustConfiguration) o; + return Objects.equals(trustedPrincipals, that.trustedPrincipals) && + Objects.equals(conditions, that.conditions); + } + + @Override + public int hashCode() { + return Objects.hash(trustedPrincipals, conditions); + } + + @Override + public String toString() { + return "TrustConfiguration{" + + "trustedPrincipals=" + trustedPrincipals + + ", conditions=" + conditions + + '}'; + } + + /** + * Builder class for TrustConfiguration. + */ + public static class Builder { + private final List trustedPrincipals = new ArrayList<>(); + private final Map> conditions = new HashMap<>(); + + private Builder() { + } + + /** + * Adds a trusted principal to the trust configuration. + * + * @param principal the principal ARN or identifier that can assume this identity + * @return this Builder instance + */ + public Builder addTrustedPrincipal(String principal) { + if (principal != null && !principal.trim().isEmpty()) { + this.trustedPrincipals.add(principal); + } + return this; + } + + /** + * Adds multiple trusted principals to the trust configuration. + * + * @param principals the list of principal ARNs or identifiers + * @return this Builder instance + */ + public Builder addTrustedPrincipals(List principals) { + if (principals != null) { + principals.stream() + .filter(p -> p != null && !p.trim().isEmpty()) + .forEach(this.trustedPrincipals::add); + } + return this; + } + + /** + * Adds a condition to the trust configuration. + * + * @param operator the condition operator (e.g., "StringEquals", "IpAddress") + * @param key the condition key (e.g., "aws:RequestedRegion", "aws:SourceIp") + * @param value the condition value + * @return this Builder instance + */ + public Builder addCondition(String operator, String key, Object value) { + if (operator != null && key != null && value != null) { + conditions.computeIfAbsent(operator, k -> new HashMap<>()).put(key, value); + } + return this; + } + + /** + * Builds and returns a TrustConfiguration instance. + * + * @return a new TrustConfiguration instance + */ + public TrustConfiguration build() { + return new TrustConfiguration(this); + } + } +} \ No newline at end of file diff --git a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/client/IamClientTest.java b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/client/IamClientTest.java new file mode 100644 index 00000000..d802fea8 --- /dev/null +++ b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/client/IamClientTest.java @@ -0,0 +1,148 @@ +package com.salesforce.multicloudj.iam.client; + +import org.junit.jupiter.api.Test; + +import java.net.URI; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for IamClient builder pattern and basic functionality. + */ +public class IamClientTest { + + @Test + public void testIamClientBuilder() { + // Test builder creation with different provider IDs + IamClient.IamClientBuilder builder = IamClient.builder("aws"); + assertNotNull(builder); + + // Test method chaining + IamClient.IamClientBuilder result = builder + .withRegion("us-west-2") + .withEndpoint(URI.create("https://iam.amazonaws.com")); + + assertSame(builder, result, "Builder methods should return the same instance for chaining"); + } + + @Test + public void testIamClientBuilderWithDifferentProviders() { + // Test with AWS + IamClient.IamClientBuilder awsBuilder = IamClient.builder("aws"); + assertNotNull(awsBuilder); + + // Test with GCP + IamClient.IamClientBuilder gcpBuilder = IamClient.builder("gcp"); + assertNotNull(gcpBuilder); + + // Test with AliCloud + IamClient.IamClientBuilder aliBuilder = IamClient.builder("ali"); + assertNotNull(aliBuilder); + } + + @Test + public void testIamClientBuild() { + IamClient client = IamClient.builder("aws") + .withRegion("us-east-1") + .build(); + + assertNotNull(client); + } + + @Test + public void testIamClientBuildWithEndpoint() { + URI customEndpoint = URI.create("https://custom-iam-endpoint.com"); + + IamClient client = IamClient.builder("gcp") + .withRegion("us-central1") + .withEndpoint(customEndpoint) + .build(); + + assertNotNull(client); + } + + // TODO: Modify this test to verify actual createIdentity functionality + // Expected behavior: Should return the unique identifier of the created identity + @Test + public void testCreateIdentity() { + IamClient client = IamClient.builder("aws").build(); + + // Currently throws UnsupportedOperationException, will be implemented later + assertThrows(UnsupportedOperationException.class, () -> { + client.createIdentity("TestRole", "Test description", "123456789012", "us-west-2", + java.util.Optional.empty(), java.util.Optional.empty()); + }); + } + + // TODO: Modify this test to verify actual attachInlinePolicy functionality + // Expected behavior: Should successfully attach policy without throwing exceptions + @Test + public void testAttachInlinePolicy() { + IamClient client = IamClient.builder("aws").build(); + + // Currently throws UnsupportedOperationException, will be implemented later + assertThrows(UnsupportedOperationException.class, () -> { + client.attachInlinePolicy(null, "123456789012", "us-west-2", "test-resource"); + }); + } + + // TODO: Modify this test to verify actual getInlinePolicyDetails functionality + // Expected behavior: Should return the policy document details as a string + @Test + public void testGetInlinePolicyDetails() { + IamClient client = IamClient.builder("aws").build(); + + // Currently throws UnsupportedOperationException, will be implemented later + assertThrows(UnsupportedOperationException.class, () -> { + client.getInlinePolicyDetails("TestRole", "TestPolicy", "123456789012", "us-west-2"); + }); + } + + // TODO: Modify this test to verify actual getAttachedPolicies functionality + // Expected behavior: Should return a list of policy names attached to the identity + @Test + public void testGetAttachedPolicies() { + IamClient client = IamClient.builder("aws").build(); + + // Currently throws UnsupportedOperationException, will be implemented later + assertThrows(UnsupportedOperationException.class, () -> { + client.getAttachedPolicies("TestRole", "123456789012", "us-west-2"); + }); + } + + // TODO: Modify this test to verify actual removePolicy functionality + // Expected behavior: Should successfully remove policy without throwing exceptions + @Test + public void testRemovePolicy() { + IamClient client = IamClient.builder("aws").build(); + + // Currently throws UnsupportedOperationException, will be implemented later + assertThrows(UnsupportedOperationException.class, () -> { + client.removePolicy("TestRole", "TestPolicy", "123456789012", "us-west-2"); + }); + } + + // TODO: Modify this test to verify actual deleteIdentity functionality + // Expected behavior: Should successfully delete identity without throwing exceptions + @Test + public void testDeleteIdentity() { + IamClient client = IamClient.builder("aws").build(); + + // Currently throws UnsupportedOperationException, will be implemented later + assertThrows(UnsupportedOperationException.class, () -> { + client.deleteIdentity("TestRole", "123456789012", "us-west-2"); + }); + } + + // TODO: Modify this test to verify actual getIdentity functionality + // Expected behavior: Should return the unique identity identifier (ARN, email, or roleId) + @Test + public void testGetIdentity() { + IamClient client = IamClient.builder("aws").build(); + + // Currently throws UnsupportedOperationException, will be implemented later + assertThrows(UnsupportedOperationException.class, () -> { + client.getIdentity("TestRole", "123456789012", "us-west-2"); + }); + } +} \ No newline at end of file diff --git a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/CreateOptionsTest.java b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/CreateOptionsTest.java new file mode 100644 index 00000000..51d83ff3 --- /dev/null +++ b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/CreateOptionsTest.java @@ -0,0 +1,199 @@ +package com.salesforce.multicloudj.iam.model; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for CreateOptions builder pattern and functionality. + */ +public class CreateOptionsTest { + + @Test + public void testCreateOptionsBuilder() { + CreateOptions options = CreateOptions.builder() + .path("/service-roles/") + .maxSessionDuration(3600) + .permissionBoundary("arn:aws:iam::123456789012:policy/PowerUserBoundary") + .build(); + + assertEquals("/service-roles/", options.getPath()); + assertEquals(Integer.valueOf(3600), options.getMaxSessionDuration()); + assertEquals("arn:aws:iam::123456789012:policy/PowerUserBoundary", options.getPermissionBoundary()); + } + + @Test + public void testCreateOptionsBuilderMinimal() { + CreateOptions options = CreateOptions.builder() + .build(); + + assertNull(options.getPath()); + assertNull(options.getMaxSessionDuration()); + assertNull(options.getPermissionBoundary()); + } + + @Test + public void testCreateOptionsBuilderWithPath() { + CreateOptions options = CreateOptions.builder() + .path("/application/backend/") + .build(); + + assertEquals("/application/backend/", options.getPath()); + assertNull(options.getMaxSessionDuration()); + assertNull(options.getPermissionBoundary()); + } + + @Test + public void testCreateOptionsBuilderWithMaxSessionDuration() { + CreateOptions options = CreateOptions.builder() + .maxSessionDuration(7200) + .build(); + + assertNull(options.getPath()); + assertEquals(Integer.valueOf(7200), options.getMaxSessionDuration()); + assertNull(options.getPermissionBoundary()); + } + + @Test + public void testCreateOptionsBuilderWithPermissionBoundary() { + CreateOptions options = CreateOptions.builder() + .permissionBoundary("arn:aws:iam::123456789012:policy/DeveloperBoundary") + .build(); + + assertNull(options.getPath()); + assertNull(options.getMaxSessionDuration()); + assertEquals("arn:aws:iam::123456789012:policy/DeveloperBoundary", options.getPermissionBoundary()); + } + + @Test + public void testCreateOptionsBuilderWithCustomSessionDurations() { + // Test minimum duration (900 seconds = 15 minutes) + CreateOptions minOptions = CreateOptions.builder() + .maxSessionDuration(900) + .build(); + assertEquals(Integer.valueOf(900), minOptions.getMaxSessionDuration()); + + // Test maximum duration (43200 seconds = 12 hours) + CreateOptions maxOptions = CreateOptions.builder() + .maxSessionDuration(43200) + .build(); + assertEquals(Integer.valueOf(43200), maxOptions.getMaxSessionDuration()); + + // Test common duration (7200 seconds = 2 hours) + CreateOptions commonOptions = CreateOptions.builder() + .maxSessionDuration(7200) + .build(); + assertEquals(Integer.valueOf(7200), commonOptions.getMaxSessionDuration()); + } + + @Test + public void testCreateOptionsBuilderWithDifferentPaths() { + // Test root path + CreateOptions rootOptions = CreateOptions.builder() + .path("/") + .build(); + assertEquals("/", rootOptions.getPath()); + + // Test nested path + CreateOptions nestedOptions = CreateOptions.builder() + .path("/division/team/service/") + .build(); + assertEquals("/division/team/service/", nestedOptions.getPath()); + + // Test simple path + CreateOptions simpleOptions = CreateOptions.builder() + .path("/service-roles/") + .build(); + assertEquals("/service-roles/", simpleOptions.getPath()); + } + + @Test + public void testCreateOptionsBuilderWithDifferentPermissionBoundaries() { + // Test AWS IAM policy ARN + CreateOptions awsOptions = CreateOptions.builder() + .permissionBoundary("arn:aws:iam::123456789012:policy/PowerUserBoundary") + .build(); + assertEquals("arn:aws:iam::123456789012:policy/PowerUserBoundary", awsOptions.getPermissionBoundary()); + + // Test different policy name + CreateOptions devOptions = CreateOptions.builder() + .permissionBoundary("arn:aws:iam::987654321098:policy/DeveloperBoundary") + .build(); + assertEquals("arn:aws:iam::987654321098:policy/DeveloperBoundary", devOptions.getPermissionBoundary()); + } + + @Test + public void testCreateOptionsBuilderComplexScenario() { + CreateOptions options = CreateOptions.builder() + .path("/microservices/user-service/") + .maxSessionDuration(14400) // 4 hours + .permissionBoundary("arn:aws:iam::123456789012:policy/MicroserviceBoundary") + .build(); + + assertEquals("/microservices/user-service/", options.getPath()); + assertEquals(Integer.valueOf(14400), options.getMaxSessionDuration()); + assertEquals("arn:aws:iam::123456789012:policy/MicroserviceBoundary", options.getPermissionBoundary()); + } + + @Test + public void testCreateOptionsEquality() { + CreateOptions options1 = CreateOptions.builder() + .path("/test/") + .maxSessionDuration(3600) + .permissionBoundary("arn:aws:iam::123456789012:policy/TestBoundary") + .build(); + + CreateOptions options2 = CreateOptions.builder() + .path("/test/") + .maxSessionDuration(3600) + .permissionBoundary("arn:aws:iam::123456789012:policy/TestBoundary") + .build(); + + CreateOptions options3 = CreateOptions.builder() + .path("/different/") + .maxSessionDuration(3600) + .permissionBoundary("arn:aws:iam::123456789012:policy/TestBoundary") + .build(); + + assertEquals(options1, options2); + assertNotEquals(options1, options3); + assertEquals(options1.hashCode(), options2.hashCode()); + } + + @Test + public void testCreateOptionsToString() { + CreateOptions options = CreateOptions.builder() + .path("/test/") + .maxSessionDuration(7200) + .permissionBoundary("arn:aws:iam::123456789012:policy/TestBoundary") + .build(); + + String toString = options.toString(); + assertTrue(toString.contains("path='/test/'")); + assertTrue(toString.contains("maxSessionDuration=7200")); + assertTrue(toString.contains("permissionBoundary='arn:aws:iam::123456789012:policy/TestBoundary'")); + } + + @Test + public void testCreateOptionsBuilderMethodChaining() { + CreateOptions.Builder builder = CreateOptions.builder(); + + // Test that each method returns the same builder instance + assertSame(builder, builder.path("/test/")); + assertSame(builder, builder.maxSessionDuration(3600)); + assertSame(builder, builder.permissionBoundary("arn:aws:iam::123456789012:policy/TestBoundary")); + } + + @Test + public void testCreateOptionsBuilderNullValues() { + CreateOptions options = CreateOptions.builder() + .path(null) + .maxSessionDuration(null) + .permissionBoundary(null) + .build(); + + assertNull(options.getPath()); + assertNull(options.getMaxSessionDuration()); + assertNull(options.getPermissionBoundary()); + } +} \ No newline at end of file diff --git a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java new file mode 100644 index 00000000..ce92a989 --- /dev/null +++ b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java @@ -0,0 +1,107 @@ +package com.salesforce.multicloudj.iam.model; + +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for PolicyDocument builder pattern. + */ +public class PolicyDocumentTest { + + @Test + public void testPolicyDocumentBuilder() { + PolicyDocument policy = PolicyDocument.builder() + .version("2024-01-01") + .statement("StorageAccess") + .effect("Allow") + .addAction("storage:GetObject") + .addAction("storage:PutObject") + .addPrincipal("arn:aws:iam::123456789012:user/ExampleUser") + .addResource("storage://my-bucket/*") + .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") + .endStatement() + .build(); + + assertEquals("2024-01-01", policy.getVersion()); + assertEquals(1, policy.getStatements().size()); + + Statement statement = policy.getStatements().get(0); + assertEquals("StorageAccess", statement.getSid()); + assertEquals("Allow", statement.getEffect()); + assertEquals(Arrays.asList("storage:GetObject", "storage:PutObject"), statement.getActions()); + assertEquals(Arrays.asList("arn:aws:iam::123456789012:user/ExampleUser"), statement.getPrincipals()); + assertEquals(Arrays.asList("storage://my-bucket/*"), statement.getResources()); + + assertTrue(statement.getConditions().containsKey("StringEquals")); + assertEquals("us-west-2", statement.getConditions().get("StringEquals").get("aws:RequestedRegion")); + } + + @Test + public void testMultipleStatements() { + PolicyDocument policy = PolicyDocument.builder() + .statement("ReadAccess") + .effect("Allow") + .addAction("storage:GetObject") + .addResource("storage://my-bucket/*") + .endStatement() + .statement("WriteAccess") + .effect("Allow") + .addAction("storage:PutObject") + .addResource("storage://my-bucket/*") + .endStatement() + .build(); + + assertEquals(2, policy.getStatements().size()); + assertEquals("ReadAccess", policy.getStatements().get(0).getSid()); + assertEquals("WriteAccess", policy.getStatements().get(1).getSid()); + } + + @Test + public void testAddCompleteStatement() { + Statement statement = Statement.builder() + .sid("TestStatement") + .effect("Allow") + .addAction("storage:GetObject") + .addResource("storage://my-bucket/*") + .build(); + + PolicyDocument policy = PolicyDocument.builder() + .addStatement(statement) + .build(); + + assertEquals(1, policy.getStatements().size()); + assertEquals("TestStatement", policy.getStatements().get(0).getSid()); + } + + @Test + public void testEmptyPolicyThrowsException() { + assertThrows(IllegalArgumentException.class, () -> { + PolicyDocument.builder().build(); + }); + } + + @Test + public void testStatementWithoutEffectThrowsException() { + assertThrows(IllegalArgumentException.class, () -> { + PolicyDocument.builder() + .statement("TestStatement") + .addAction("storage:GetObject") + .endStatement() + .build(); + }); + } + + @Test + public void testStatementWithoutActionsThrowsException() { + assertThrows(IllegalArgumentException.class, () -> { + PolicyDocument.builder() + .statement("TestStatement") + .effect("Allow") + .endStatement() + .build(); + }); + } +} \ No newline at end of file diff --git a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/StatementTest.java b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/StatementTest.java new file mode 100644 index 00000000..ef1fef0d --- /dev/null +++ b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/StatementTest.java @@ -0,0 +1,174 @@ +package com.salesforce.multicloudj.iam.model; + +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for Statement builder pattern and functionality. + */ +public class StatementTest { + + @Test + public void testStatementBuilder() { + Statement statement = Statement.builder() + .sid("TestStatement") + .effect("Allow") + .addAction("storage:GetObject") + .addAction("storage:PutObject") + .addResource("storage://my-bucket/*") + .addPrincipal("arn:aws:iam::123456789012:user/TestUser") + .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") + .build(); + + assertEquals("TestStatement", statement.getSid()); + assertEquals("Allow", statement.getEffect()); + assertEquals(Arrays.asList("storage:GetObject", "storage:PutObject"), statement.getActions()); + assertEquals(Arrays.asList("storage://my-bucket/*"), statement.getResources()); + assertEquals(Arrays.asList("arn:aws:iam::123456789012:user/TestUser"), statement.getPrincipals()); + + assertTrue(statement.getConditions().containsKey("StringEquals")); + assertEquals("us-west-2", statement.getConditions().get("StringEquals").get("aws:RequestedRegion")); + } + + @Test + public void testStatementBuilderMinimal() { + Statement statement = Statement.builder() + .sid("MinimalStatement") + .effect("Deny") + .addAction("storage:DeleteObject") + .addResource("storage://sensitive-bucket/*") + .build(); + + assertEquals("MinimalStatement", statement.getSid()); + assertEquals("Deny", statement.getEffect()); + assertEquals(Arrays.asList("storage:DeleteObject"), statement.getActions()); + assertEquals(Arrays.asList("storage://sensitive-bucket/*"), statement.getResources()); + assertTrue(statement.getPrincipals().isEmpty()); + assertTrue(statement.getConditions().isEmpty()); + } + + @Test + public void testStatementBuilderMultipleActions() { + List expectedActions = Arrays.asList("storage:GetObject", "storage:PutObject", "storage:ListObjects"); + + Statement statement = Statement.builder() + .sid("MultiActionStatement") + .effect("Allow") + .addAction("storage:GetObject") + .addAction("storage:PutObject") + .addAction("storage:ListObjects") + .addResource("storage://multi-action-bucket/*") + .build(); + + assertEquals(expectedActions, statement.getActions()); + } + + @Test + public void testStatementBuilderMultipleResources() { + List expectedResources = Arrays.asList("storage://bucket1/*", "storage://bucket2/*"); + + Statement statement = Statement.builder() + .sid("MultiResourceStatement") + .effect("Allow") + .addAction("storage:GetObject") + .addResource("storage://bucket1/*") + .addResource("storage://bucket2/*") + .build(); + + assertEquals(expectedResources, statement.getResources()); + } + + @Test + public void testStatementBuilderMultiplePrincipals() { + List expectedPrincipals = Arrays.asList( + "arn:aws:iam::123456789012:user/User1", + "arn:aws:iam::123456789012:user/User2" + ); + + Statement statement = Statement.builder() + .sid("MultiPrincipalStatement") + .effect("Allow") + .addAction("storage:GetObject") + .addResource("storage://shared-bucket/*") + .addPrincipal("arn:aws:iam::123456789012:user/User1") + .addPrincipal("arn:aws:iam::123456789012:user/User2") + .build(); + + assertEquals(expectedPrincipals, statement.getPrincipals()); + } + + @Test + public void testStatementBuilderMultipleConditions() { + Statement statement = Statement.builder() + .sid("MultiConditionStatement") + .effect("Allow") + .addAction("storage:GetObject") + .addResource("storage://conditional-bucket/*") + .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") + .addCondition("DateGreaterThan", "aws:CurrentTime", "2024-01-01T00:00:00Z") + .build(); + + assertTrue(statement.getConditions().containsKey("StringEquals")); + assertTrue(statement.getConditions().containsKey("DateGreaterThan")); + assertEquals("us-west-2", statement.getConditions().get("StringEquals").get("aws:RequestedRegion")); + assertEquals("2024-01-01T00:00:00Z", statement.getConditions().get("DateGreaterThan").get("aws:CurrentTime")); + } + + @Test + public void testStatementBuilderDenyEffect() { + Statement statement = Statement.builder() + .sid("DenyStatement") + .effect("Deny") + .addAction("*") + .addResource("storage://restricted-bucket/*") + .build(); + + assertEquals("Deny", statement.getEffect()); + assertEquals(Arrays.asList("*"), statement.getActions()); + } + + @Test + public void testStatementWithoutSid() { + Statement statement = Statement.builder() + .effect("Allow") + .addAction("storage:GetObject") + .addResource("storage://no-sid-bucket/*") + .build(); + + assertNull(statement.getSid()); + assertEquals("Allow", statement.getEffect()); + } + + @Test + public void testEmptyStatementThrowsException() { + assertThrows(IllegalArgumentException.class, () -> { + Statement.builder().build(); + }); + } + + @Test + public void testStatementWithoutEffectThrowsException() { + assertThrows(IllegalArgumentException.class, () -> { + Statement.builder() + .sid("NoEffectStatement") + .addAction("storage:GetObject") + .addResource("storage://test-bucket/*") + .build(); + }); + } + + @Test + public void testStatementWithoutActionsThrowsException() { + assertThrows(IllegalArgumentException.class, () -> { + Statement.builder() + .sid("NoActionsStatement") + .effect("Allow") + .addResource("storage://test-bucket/*") + .build(); + }); + } +} \ No newline at end of file diff --git a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/TrustConfigurationTest.java b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/TrustConfigurationTest.java new file mode 100644 index 00000000..2d37a529 --- /dev/null +++ b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/TrustConfigurationTest.java @@ -0,0 +1,276 @@ +package com.salesforce.multicloudj.iam.model; + +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for TrustConfiguration builder pattern and functionality. + */ +public class TrustConfigurationTest { + + @Test + public void testTrustConfigurationBuilder() { + TrustConfiguration trustConfig = TrustConfiguration.builder() + .addTrustedPrincipal("arn:aws:iam::123456789012:root") + .addTrustedPrincipal("service-account@project.iam.gserviceaccount.com") + .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") + .addCondition("StringEquals", "aws:userid", "AIDACKCEVSQ6C2EXAMPLE") + .build(); + + List expectedPrincipals = Arrays.asList( + "arn:aws:iam::123456789012:root", + "service-account@project.iam.gserviceaccount.com" + ); + + assertEquals(expectedPrincipals, trustConfig.getTrustedPrincipals()); + + Map> conditions = trustConfig.getConditions(); + assertTrue(conditions.containsKey("StringEquals")); + assertEquals("us-west-2", conditions.get("StringEquals").get("aws:RequestedRegion")); + assertEquals("AIDACKCEVSQ6C2EXAMPLE", conditions.get("StringEquals").get("aws:userid")); + } + + @Test + public void testTrustConfigurationBuilderMinimal() { + TrustConfiguration trustConfig = TrustConfiguration.builder() + .addTrustedPrincipal("arn:aws:iam::987654321098:root") + .build(); + + assertEquals(Arrays.asList("arn:aws:iam::987654321098:root"), trustConfig.getTrustedPrincipals()); + assertTrue(trustConfig.getConditions().isEmpty()); + } + + @Test + public void testTrustConfigurationBuilderMultipleTrustedPrincipals() { + List expectedPrincipals = Arrays.asList( + "arn:aws:iam::111111111111:root", + "arn:aws:iam::222222222222:root", + "arn:aws:iam::333333333333:user/CrossAccountUser" + ); + + TrustConfiguration trustConfig = TrustConfiguration.builder() + .addTrustedPrincipal("arn:aws:iam::111111111111:root") + .addTrustedPrincipal("arn:aws:iam::222222222222:root") + .addTrustedPrincipal("arn:aws:iam::333333333333:user/CrossAccountUser") + .build(); + + assertEquals(expectedPrincipals, trustConfig.getTrustedPrincipals()); + } + + @Test + public void testTrustConfigurationBuilderAddTrustedPrincipals() { + List principalsToAdd = Arrays.asList( + "arn:aws:iam::111111111111:root", + "arn:aws:iam::222222222222:root" + ); + + TrustConfiguration trustConfig = TrustConfiguration.builder() + .addTrustedPrincipal("arn:aws:iam::123456789012:root") + .addTrustedPrincipals(principalsToAdd) + .build(); + + List expectedPrincipals = Arrays.asList( + "arn:aws:iam::123456789012:root", + "arn:aws:iam::111111111111:root", + "arn:aws:iam::222222222222:root" + ); + + assertEquals(expectedPrincipals, trustConfig.getTrustedPrincipals()); + } + + @Test + public void testTrustConfigurationBuilderMultipleConditions() { + TrustConfiguration trustConfig = TrustConfiguration.builder() + .addTrustedPrincipal("arn:aws:iam::123456789012:root") + .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") + .addCondition("DateGreaterThan", "aws:CurrentTime", "2024-01-01T00:00:00Z") + .addCondition("IpAddress", "aws:SourceIp", "203.0.113.0/24") + .build(); + + Map> conditions = trustConfig.getConditions(); + + assertTrue(conditions.containsKey("StringEquals")); + assertTrue(conditions.containsKey("DateGreaterThan")); + assertTrue(conditions.containsKey("IpAddress")); + + assertEquals("us-west-2", conditions.get("StringEquals").get("aws:RequestedRegion")); + assertEquals("2024-01-01T00:00:00Z", conditions.get("DateGreaterThan").get("aws:CurrentTime")); + assertEquals("203.0.113.0/24", conditions.get("IpAddress").get("aws:SourceIp")); + } + + @Test + public void testTrustConfigurationBuilderSameOperatorMultipleConditions() { + TrustConfiguration trustConfig = TrustConfiguration.builder() + .addTrustedPrincipal("arn:aws:iam::123456789012:root") + .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") + .addCondition("StringEquals", "aws:userid", "AIDACKCEVSQ6C2EXAMPLE") + .build(); + + Map> conditions = trustConfig.getConditions(); + assertTrue(conditions.containsKey("StringEquals")); + + Map stringEqualsConditions = conditions.get("StringEquals"); + assertEquals(2, stringEqualsConditions.size()); + assertEquals("us-west-2", stringEqualsConditions.get("aws:RequestedRegion")); + assertEquals("AIDACKCEVSQ6C2EXAMPLE", stringEqualsConditions.get("aws:userid")); + } + + @Test + public void testTrustConfigurationBuilderGcpServiceAccount() { + TrustConfiguration trustConfig = TrustConfiguration.builder() + .addTrustedPrincipal("service-account@my-project.iam.gserviceaccount.com") + .addTrustedPrincipal("another-sa@different-project.iam.gserviceaccount.com") + .build(); + + List expectedPrincipals = Arrays.asList( + "service-account@my-project.iam.gserviceaccount.com", + "another-sa@different-project.iam.gserviceaccount.com" + ); + + assertEquals(expectedPrincipals, trustConfig.getTrustedPrincipals()); + } + + @Test + public void testTrustConfigurationBuilderAliCloudPrincipals() { + TrustConfiguration trustConfig = TrustConfiguration.builder() + .addTrustedPrincipal("1234567890123456") // AliCloud account ID + .addTrustedPrincipal("acs:ram::1234567890123456:user/AliUser") // AliCloud RAM user + .build(); + + List expectedPrincipals = Arrays.asList( + "1234567890123456", + "acs:ram::1234567890123456:user/AliUser" + ); + + assertEquals(expectedPrincipals, trustConfig.getTrustedPrincipals()); + } + + @Test + public void testTrustConfigurationBuilderComplexScenario() { + TrustConfiguration trustConfig = TrustConfiguration.builder() + .addTrustedPrincipal("arn:aws:iam::123456789012:root") // AWS account + .addTrustedPrincipal("service-account@project.iam.gserviceaccount.com") // GCP service account + .addTrustedPrincipal("1234567890123456") // AliCloud account + .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") + .addCondition("StringEquals", "sts:ExternalId", "cross-cloud-external-id") + .addCondition("Bool", "aws:MultiFactorAuthPresent", "true") + .build(); + + List expectedPrincipals = Arrays.asList( + "arn:aws:iam::123456789012:root", + "service-account@project.iam.gserviceaccount.com", + "1234567890123456" + ); + + assertEquals(expectedPrincipals, trustConfig.getTrustedPrincipals()); + + Map> conditions = trustConfig.getConditions(); + assertTrue(conditions.containsKey("StringEquals")); + assertTrue(conditions.containsKey("Bool")); + + assertEquals("us-west-2", conditions.get("StringEquals").get("aws:RequestedRegion")); + assertEquals("cross-cloud-external-id", conditions.get("StringEquals").get("sts:ExternalId")); + assertEquals("true", conditions.get("Bool").get("aws:MultiFactorAuthPresent")); + } + + @Test + public void testTrustConfigurationBuilderEmptyBuilder() { + TrustConfiguration trustConfig = TrustConfiguration.builder().build(); + + assertTrue(trustConfig.getTrustedPrincipals().isEmpty()); + assertTrue(trustConfig.getConditions().isEmpty()); + } + + @Test + public void testTrustConfigurationBuilderIgnoreNullAndEmptyPrincipals() { + TrustConfiguration trustConfig = TrustConfiguration.builder() + .addTrustedPrincipal("arn:aws:iam::123456789012:root") + .addTrustedPrincipal(null) + .addTrustedPrincipal("") + .addTrustedPrincipal(" ") // whitespace only + .addTrustedPrincipal("arn:aws:iam::987654321098:root") + .build(); + + List expectedPrincipals = Arrays.asList( + "arn:aws:iam::123456789012:root", + "arn:aws:iam::987654321098:root" + ); + + assertEquals(expectedPrincipals, trustConfig.getTrustedPrincipals()); + } + + @Test + public void testTrustConfigurationBuilderIgnoreNullConditions() { + TrustConfiguration trustConfig = TrustConfiguration.builder() + .addTrustedPrincipal("arn:aws:iam::123456789012:root") + .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") + .addCondition(null, "key", "value") // null operator + .addCondition("StringEquals", null, "value") // null key + .addCondition("StringEquals", "key", null) // null value + .build(); + + Map> conditions = trustConfig.getConditions(); + assertEquals(1, conditions.size()); + assertTrue(conditions.containsKey("StringEquals")); + assertEquals(1, conditions.get("StringEquals").size()); + assertEquals("us-west-2", conditions.get("StringEquals").get("aws:RequestedRegion")); + } + + @Test + public void testTrustConfigurationEquality() { + TrustConfiguration trustConfig1 = TrustConfiguration.builder() + .addTrustedPrincipal("arn:aws:iam::123456789012:root") + .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") + .build(); + + TrustConfiguration trustConfig2 = TrustConfiguration.builder() + .addTrustedPrincipal("arn:aws:iam::123456789012:root") + .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") + .build(); + + TrustConfiguration trustConfig3 = TrustConfiguration.builder() + .addTrustedPrincipal("arn:aws:iam::987654321098:root") + .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") + .build(); + + assertEquals(trustConfig1, trustConfig2); + assertNotEquals(trustConfig1, trustConfig3); + assertEquals(trustConfig1.hashCode(), trustConfig2.hashCode()); + } + + @Test + public void testTrustConfigurationToString() { + TrustConfiguration trustConfig = TrustConfiguration.builder() + .addTrustedPrincipal("arn:aws:iam::123456789012:root") + .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") + .build(); + + String toString = trustConfig.toString(); + assertTrue(toString.contains("trustedPrincipals")); + assertTrue(toString.contains("conditions")); + assertTrue(toString.contains("arn:aws:iam::123456789012:root")); + } + + @Test + public void testTrustConfigurationImmutable() { + TrustConfiguration trustConfig = TrustConfiguration.builder() + .addTrustedPrincipal("arn:aws:iam::123456789012:root") + .build(); + + List principals = trustConfig.getTrustedPrincipals(); + Map> conditions = trustConfig.getConditions(); + + // Modifying returned lists/maps should not affect the original + principals.add("new-principal"); + conditions.put("NewOperator", Map.of("key", "value")); + + // Original should remain unchanged + assertEquals(1, trustConfig.getTrustedPrincipals().size()); + assertEquals(0, trustConfig.getConditions().size()); + } +} \ No newline at end of file diff --git a/iam/pom.xml b/iam/pom.xml new file mode 100644 index 00000000..9bc5d270 --- /dev/null +++ b/iam/pom.xml @@ -0,0 +1,19 @@ + + + 4.0.0 + + com.salesforce.multicloudj + multicloudj-parent + ${revision} + ../pom.xml + + iam + pom + MultiCloudJ IAM + MultiCloudJ Identity and Access Management + + iam-client + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 27ac0264..dcf18a41 100644 --- a/pom.xml +++ b/pom.xml @@ -32,6 +32,7 @@ + iam blob docstore examples From 4ffe355f291c489c7f309bd9e837427e8c85ce8e Mon Sep 17 00:00:00 2001 From: DaisyModi Date: Thu, 23 Oct 2025 13:12:58 +0530 Subject: [PATCH 02/15] Increasing code coverage --- .../iam/model/CreateOptionsTest.java | 141 ++++++----- .../iam/model/PolicyDocumentTest.java | 231 ++++++++++++++++++ .../multicloudj/iam/model/StatementTest.java | 219 ++++++++++++++--- .../iam/model/TrustConfigurationTest.java | 77 +++--- 4 files changed, 538 insertions(+), 130 deletions(-) diff --git a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/CreateOptionsTest.java b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/CreateOptionsTest.java index 51d83ff3..b55d8102 100644 --- a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/CreateOptionsTest.java +++ b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/CreateOptionsTest.java @@ -33,36 +33,33 @@ public void testCreateOptionsBuilderMinimal() { } @Test - public void testCreateOptionsBuilderWithPath() { - CreateOptions options = CreateOptions.builder() + public void testCreateOptionsBuilderIndividualFields() { + // Test path only + CreateOptions pathOptions = CreateOptions.builder() .path("/application/backend/") .build(); - assertEquals("/application/backend/", options.getPath()); - assertNull(options.getMaxSessionDuration()); - assertNull(options.getPermissionBoundary()); - } + assertEquals("/application/backend/", pathOptions.getPath()); + assertNull(pathOptions.getMaxSessionDuration()); + assertNull(pathOptions.getPermissionBoundary()); - @Test - public void testCreateOptionsBuilderWithMaxSessionDuration() { - CreateOptions options = CreateOptions.builder() + // Test maxSessionDuration only + CreateOptions durationOptions = CreateOptions.builder() .maxSessionDuration(7200) .build(); - assertNull(options.getPath()); - assertEquals(Integer.valueOf(7200), options.getMaxSessionDuration()); - assertNull(options.getPermissionBoundary()); - } + assertNull(durationOptions.getPath()); + assertEquals(Integer.valueOf(7200), durationOptions.getMaxSessionDuration()); + assertNull(durationOptions.getPermissionBoundary()); - @Test - public void testCreateOptionsBuilderWithPermissionBoundary() { - CreateOptions options = CreateOptions.builder() + // Test permissionBoundary only + CreateOptions boundaryOptions = CreateOptions.builder() .permissionBoundary("arn:aws:iam::123456789012:policy/DeveloperBoundary") .build(); - assertNull(options.getPath()); - assertNull(options.getMaxSessionDuration()); - assertEquals("arn:aws:iam::123456789012:policy/DeveloperBoundary", options.getPermissionBoundary()); + assertNull(boundaryOptions.getPath()); + assertNull(boundaryOptions.getMaxSessionDuration()); + assertEquals("arn:aws:iam::123456789012:policy/DeveloperBoundary", boundaryOptions.getPermissionBoundary()); } @Test @@ -86,41 +83,6 @@ public void testCreateOptionsBuilderWithCustomSessionDurations() { assertEquals(Integer.valueOf(7200), commonOptions.getMaxSessionDuration()); } - @Test - public void testCreateOptionsBuilderWithDifferentPaths() { - // Test root path - CreateOptions rootOptions = CreateOptions.builder() - .path("/") - .build(); - assertEquals("/", rootOptions.getPath()); - - // Test nested path - CreateOptions nestedOptions = CreateOptions.builder() - .path("/division/team/service/") - .build(); - assertEquals("/division/team/service/", nestedOptions.getPath()); - - // Test simple path - CreateOptions simpleOptions = CreateOptions.builder() - .path("/service-roles/") - .build(); - assertEquals("/service-roles/", simpleOptions.getPath()); - } - - @Test - public void testCreateOptionsBuilderWithDifferentPermissionBoundaries() { - // Test AWS IAM policy ARN - CreateOptions awsOptions = CreateOptions.builder() - .permissionBoundary("arn:aws:iam::123456789012:policy/PowerUserBoundary") - .build(); - assertEquals("arn:aws:iam::123456789012:policy/PowerUserBoundary", awsOptions.getPermissionBoundary()); - - // Test different policy name - CreateOptions devOptions = CreateOptions.builder() - .permissionBoundary("arn:aws:iam::987654321098:policy/DeveloperBoundary") - .build(); - assertEquals("arn:aws:iam::987654321098:policy/DeveloperBoundary", devOptions.getPermissionBoundary()); - } @Test public void testCreateOptionsBuilderComplexScenario() { @@ -136,7 +98,7 @@ public void testCreateOptionsBuilderComplexScenario() { } @Test - public void testCreateOptionsEquality() { + public void testCreateOptionsEqualsAndHashCode() { CreateOptions options1 = CreateOptions.builder() .path("/test/") .maxSessionDuration(3600) @@ -149,29 +111,69 @@ public void testCreateOptionsEquality() { .permissionBoundary("arn:aws:iam::123456789012:policy/TestBoundary") .build(); - CreateOptions options3 = CreateOptions.builder() + CreateOptions differentPath = CreateOptions.builder() .path("/different/") .maxSessionDuration(3600) .permissionBoundary("arn:aws:iam::123456789012:policy/TestBoundary") .build(); + CreateOptions differentDuration = CreateOptions.builder() + .path("/test/") + .maxSessionDuration(7200) + .permissionBoundary("arn:aws:iam::123456789012:policy/TestBoundary") + .build(); + + CreateOptions nullOptions = CreateOptions.builder().build(); + CreateOptions anotherNullOptions = CreateOptions.builder().build(); + + // Test equals assertEquals(options1, options2); - assertNotEquals(options1, options3); + assertEquals(options1, options1); // same object + assertNotEquals(options1, differentPath); + assertNotEquals(options1, differentDuration); + assertNotEquals(options1, null); + assertNotEquals(options1, "not create options"); + assertEquals(nullOptions, anotherNullOptions); + + // Test hashCode assertEquals(options1.hashCode(), options2.hashCode()); + assertNotEquals(options1.hashCode(), differentPath.hashCode()); + assertEquals(nullOptions.hashCode(), anotherNullOptions.hashCode()); } @Test public void testCreateOptionsToString() { + // Test with all fields populated CreateOptions options = CreateOptions.builder() .path("/test/") .maxSessionDuration(7200) .permissionBoundary("arn:aws:iam::123456789012:policy/TestBoundary") .build(); - String toString = options.toString(); - assertTrue(toString.contains("path='/test/'")); - assertTrue(toString.contains("maxSessionDuration=7200")); - assertTrue(toString.contains("permissionBoundary='arn:aws:iam::123456789012:policy/TestBoundary'")); + String result = options.toString(); + assertTrue(result.contains("path='/test/'")); + assertTrue(result.contains("maxSessionDuration=7200")); + assertTrue(result.contains("permissionBoundary='arn:aws:iam::123456789012:policy/TestBoundary'")); + + // Test with null values + CreateOptions nullOptions = CreateOptions.builder().build(); + String nullResult = nullOptions.toString(); + assertTrue(nullResult.contains("CreateOptions")); + assertTrue(nullResult.contains("path='null'")); + assertTrue(nullResult.contains("maxSessionDuration=null")); + assertTrue(nullResult.contains("permissionBoundary='null'")); + + // Test with partial values + CreateOptions partialOptions = CreateOptions.builder() + .path("/test/") + .maxSessionDuration(null) + .permissionBoundary("arn:aws:iam::123456789012:policy/TestBoundary") + .build(); + + String partialResult = partialOptions.toString(); + assertTrue(partialResult.contains("path='/test/'")); + assertTrue(partialResult.contains("maxSessionDuration=null")); + assertTrue(partialResult.contains("permissionBoundary='arn:aws:iam::123456789012:policy/TestBoundary'")); } @Test @@ -196,4 +198,21 @@ public void testCreateOptionsBuilderNullValues() { assertNull(options.getMaxSessionDuration()); assertNull(options.getPermissionBoundary()); } + + + @Test + public void testCreateOptionsBuilderOverwriteValues() { + CreateOptions options = CreateOptions.builder() + .path("/first/") + .path("/second/") // This should overwrite the first value + .maxSessionDuration(3600) + .maxSessionDuration(7200) // This should overwrite the first value + .permissionBoundary("arn:aws:iam::123456789012:policy/FirstBoundary") + .permissionBoundary("arn:aws:iam::123456789012:policy/SecondBoundary") // This should overwrite + .build(); + + assertEquals("/second/", options.getPath()); + assertEquals(Integer.valueOf(7200), options.getMaxSessionDuration()); + assertEquals("arn:aws:iam::123456789012:policy/SecondBoundary", options.getPermissionBoundary()); + } } \ No newline at end of file diff --git a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java index ce92a989..432b9aa6 100644 --- a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java +++ b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java @@ -3,6 +3,7 @@ import org.junit.jupiter.api.Test; import java.util.Arrays; +import java.util.List; import static org.junit.jupiter.api.Assertions.*; @@ -104,4 +105,234 @@ public void testStatementWithoutActionsThrowsException() { .build(); }); } + + @Test + public void testVersionHandling() { + // Test custom version + PolicyDocument customVersionPolicy = PolicyDocument.builder() + .version("2023-06-01") + .statement("TestStatement") + .effect("Allow") + .addAction("storage:GetObject") + .addResource("storage://test-bucket/*") + .endStatement() + .build(); + + assertEquals("2023-06-01", customVersionPolicy.getVersion()); + + // Test default version + PolicyDocument defaultVersionPolicy = PolicyDocument.builder() + .statement("TestStatement") + .effect("Allow") + .addAction("storage:GetObject") + .addResource("storage://test-bucket/*") + .endStatement() + .build(); + + assertEquals("2024-01-01", defaultVersionPolicy.getVersion()); + } + + @Test + public void testBuilderMethodsWithMultipleValues() { + PolicyDocument policy = PolicyDocument.builder() + .statement("TestStatement") + .effect("Allow") + .addAction("storage:GetObject") + .addAction("storage:PutObject") + .addActions(Arrays.asList("storage:DeleteObject", "storage:ListObjects")) + .addResource("storage://bucket1/*") + .addResource("storage://bucket2/*") + .addResources(Arrays.asList("storage://bucket3/*", "storage://bucket4/*")) + .addPrincipal("principal1") + .addPrincipal("principal2") + .addPrincipals(Arrays.asList("principal3", "principal4")) + .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") + .addCondition("DateGreaterThan", "aws:CurrentTime", "2024-01-01T00:00:00Z") + .endStatement() + .build(); + + Statement statement = policy.getStatements().get(0); + + // Test actions + assertEquals(4, statement.getActions().size()); + assertTrue(statement.getActions().contains("storage:GetObject")); + assertTrue(statement.getActions().contains("storage:PutObject")); + assertTrue(statement.getActions().contains("storage:DeleteObject")); + assertTrue(statement.getActions().contains("storage:ListObjects")); + + // Test resources + assertEquals(4, statement.getResources().size()); + assertTrue(statement.getResources().contains("storage://bucket1/*")); + assertTrue(statement.getResources().contains("storage://bucket2/*")); + assertTrue(statement.getResources().contains("storage://bucket3/*")); + assertTrue(statement.getResources().contains("storage://bucket4/*")); + + // Test principals + assertEquals(4, statement.getPrincipals().size()); + assertTrue(statement.getPrincipals().contains("principal1")); + assertTrue(statement.getPrincipals().contains("principal2")); + assertTrue(statement.getPrincipals().contains("principal3")); + assertTrue(statement.getPrincipals().contains("principal4")); + + // Test conditions + assertTrue(statement.getConditions().containsKey("StringEquals")); + assertTrue(statement.getConditions().containsKey("DateGreaterThan")); + } + + @Test + public void testBuilderStateValidation() { + // Test that trying to use statement methods without calling statement() throws exception + assertThrows(IllegalStateException.class, () -> + PolicyDocument.builder().addAction("storage:GetObject").build()); + + assertThrows(IllegalStateException.class, () -> + PolicyDocument.builder().effect("Allow").build()); + + assertThrows(IllegalStateException.class, () -> + PolicyDocument.builder().addResource("storage://test-bucket/*").build()); + + assertThrows(IllegalStateException.class, () -> + PolicyDocument.builder().addPrincipal("principal1").build()); + + assertThrows(IllegalStateException.class, () -> + PolicyDocument.builder().addCondition("StringEquals", "key", "value").build()); + + assertThrows(IllegalStateException.class, () -> + PolicyDocument.builder().addActions(Arrays.asList("storage:GetObject")).build()); + + assertThrows(IllegalStateException.class, () -> + PolicyDocument.builder().addResources(Arrays.asList("storage://test-bucket/*")).build()); + + assertThrows(IllegalStateException.class, () -> + PolicyDocument.builder().addPrincipals(Arrays.asList("principal1")).build()); + } + + @Test + public void testAddNullStatement() { + PolicyDocument policy = PolicyDocument.builder() + .addStatement(null) + .statement("ValidStatement") + .effect("Allow") + .addAction("storage:GetObject") + .addResource("storage://test-bucket/*") + .endStatement() + .build(); + + assertEquals(1, policy.getStatements().size()); + assertEquals("ValidStatement", policy.getStatements().get(0).getSid()); + } + + @Test + public void testMixingAddStatementAndBuilder() { + Statement preBuiltStatement = Statement.builder() + .sid("PreBuilt") + .effect("Deny") + .addAction("storage:DeleteObject") + .addResource("storage://sensitive-bucket/*") + .build(); + + PolicyDocument policy = PolicyDocument.builder() + .addStatement(preBuiltStatement) + .statement("BuiltInline") + .effect("Allow") + .addAction("storage:GetObject") + .addResource("storage://public-bucket/*") + .endStatement() + .build(); + + assertEquals(2, policy.getStatements().size()); + assertEquals("PreBuilt", policy.getStatements().get(0).getSid()); + assertEquals("BuiltInline", policy.getStatements().get(1).getSid()); + } + + @Test + public void testPolicyDocumentEqualsAndHashCode() { + PolicyDocument policy1 = PolicyDocument.builder() + .version("2024-01-01") + .statement("TestStatement") + .effect("Allow") + .addAction("storage:GetObject") + .addResource("storage://test-bucket/*") + .endStatement() + .build(); + + PolicyDocument policy2 = PolicyDocument.builder() + .version("2024-01-01") + .statement("TestStatement") + .effect("Allow") + .addAction("storage:GetObject") + .addResource("storage://test-bucket/*") + .endStatement() + .build(); + + PolicyDocument policy3 = PolicyDocument.builder() + .version("2023-01-01") + .statement("DifferentStatement") + .effect("Deny") + .addAction("storage:DeleteObject") + .addResource("storage://test-bucket/*") + .endStatement() + .build(); + + // Test equals + assertEquals(policy1, policy2); + assertNotEquals(policy1, policy3); + assertNotEquals(policy1, null); + assertNotEquals(policy1, "not a policy"); + assertEquals(policy1, policy1); // same object + + // Test hashCode + assertEquals(policy1.hashCode(), policy2.hashCode()); + assertNotEquals(policy1.hashCode(), policy3.hashCode()); + } + + @Test + public void testPolicyDocumentToString() { + PolicyDocument policy = PolicyDocument.builder() + .version("2024-01-01") + .statement("TestStatement") + .effect("Allow") + .addAction("storage:GetObject") + .addResource("storage://test-bucket/*") + .endStatement() + .build(); + + String result = policy.toString(); + assertTrue(result.contains("2024-01-01")); + assertTrue(result.contains("TestStatement")); + assertTrue(result.contains("PolicyDocument")); + } + + @Test + public void testGetStatementsReturnsImmutableCopy() { + PolicyDocument policy = PolicyDocument.builder() + .statement("TestStatement") + .effect("Allow") + .addAction("storage:GetObject") + .addResource("storage://test-bucket/*") + .endStatement() + .build(); + + List statements = policy.getStatements(); + statements.clear(); + + // Original should be unaffected + assertFalse(policy.getStatements().isEmpty()); + assertEquals(1, policy.getStatements().size()); + } + + @Test + public void testEndStatementWithoutCurrentStatement() { + // endStatement() should be safe to call even when no statement is being built + PolicyDocument policy = PolicyDocument.builder() + .endStatement() // This should do nothing + .statement("TestStatement") + .effect("Allow") + .addAction("storage:GetObject") + .addResource("storage://test-bucket/*") + .endStatement() + .build(); + + assertEquals(1, policy.getStatements().size()); + } } \ No newline at end of file diff --git a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/StatementTest.java b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/StatementTest.java index ef1fef0d..76a73732 100644 --- a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/StatementTest.java +++ b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/StatementTest.java @@ -4,6 +4,7 @@ import java.util.Arrays; import java.util.List; +import java.util.Map; import static org.junit.jupiter.api.Assertions.*; @@ -51,22 +52,6 @@ public void testStatementBuilderMinimal() { assertTrue(statement.getConditions().isEmpty()); } - @Test - public void testStatementBuilderMultipleActions() { - List expectedActions = Arrays.asList("storage:GetObject", "storage:PutObject", "storage:ListObjects"); - - Statement statement = Statement.builder() - .sid("MultiActionStatement") - .effect("Allow") - .addAction("storage:GetObject") - .addAction("storage:PutObject") - .addAction("storage:ListObjects") - .addResource("storage://multi-action-bucket/*") - .build(); - - assertEquals(expectedActions, statement.getActions()); - } - @Test public void testStatementBuilderMultipleResources() { List expectedResources = Arrays.asList("storage://bucket1/*", "storage://bucket2/*"); @@ -118,19 +103,6 @@ public void testStatementBuilderMultipleConditions() { assertEquals("2024-01-01T00:00:00Z", statement.getConditions().get("DateGreaterThan").get("aws:CurrentTime")); } - @Test - public void testStatementBuilderDenyEffect() { - Statement statement = Statement.builder() - .sid("DenyStatement") - .effect("Deny") - .addAction("*") - .addResource("storage://restricted-bucket/*") - .build(); - - assertEquals("Deny", statement.getEffect()); - assertEquals(Arrays.asList("*"), statement.getActions()); - } - @Test public void testStatementWithoutSid() { Statement statement = Statement.builder() @@ -171,4 +143,193 @@ public void testStatementWithoutActionsThrowsException() { .build(); }); } + + @Test + public void testStatementWithEmptyEffect() { + assertThrows(IllegalArgumentException.class, () -> { + Statement.builder() + .sid("EmptyEffectStatement") + .effect("") + .addAction("storage:GetObject") + .build(); + }); + } + + @Test + public void testStatementWithWhitespaceEffect() { + assertThrows(IllegalArgumentException.class, () -> { + Statement.builder() + .sid("WhitespaceEffectStatement") + .effect(" ") + .addAction("storage:GetObject") + .build(); + }); + } + + @Test + public void testNullAndEmptyValueHandling() { + Statement statement = Statement.builder() + .effect("Allow") + .addAction(null) + .addAction("") + .addAction(" ") + .addAction("storage:GetObject") + .addResource(null) + .addResource("") + .addResource(" ") + .addResource("storage://test-bucket/*") + .addPrincipal(null) + .addPrincipal("") + .addPrincipal(" ") + .addPrincipal("valid-principal") + .addCondition(null, "key", "value") + .addCondition("StringEquals", null, "value") + .addCondition("StringEquals", "key", null) + .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") + .build(); + + assertEquals(1, statement.getActions().size()); + assertEquals("storage:GetObject", statement.getActions().get(0)); + + assertEquals(1, statement.getResources().size()); + assertEquals("storage://test-bucket/*", statement.getResources().get(0)); + + assertEquals(1, statement.getPrincipals().size()); + assertEquals("valid-principal", statement.getPrincipals().get(0)); + + assertEquals(1, statement.getConditions().size()); + assertTrue(statement.getConditions().containsKey("StringEquals")); + assertEquals("us-west-2", statement.getConditions().get("StringEquals").get("aws:RequestedRegion")); + } + + @Test + public void testListMethodsWithNullValues() { + List principals = Arrays.asList("principal1", null, "", " ", "principal2"); + List actions = Arrays.asList("storage:GetObject", null, "", " ", "storage:PutObject"); + List resources = Arrays.asList("storage://bucket1/*", null, "", " ", "storage://bucket2/*"); + + Statement statement = Statement.builder() + .effect("Allow") + .addActions(actions) + .addResources(resources) + .addPrincipals(principals) + .build(); + + assertEquals(2, statement.getActions().size()); + assertTrue(statement.getActions().contains("storage:GetObject")); + assertTrue(statement.getActions().contains("storage:PutObject")); + + assertEquals(2, statement.getResources().size()); + assertTrue(statement.getResources().contains("storage://bucket1/*")); + assertTrue(statement.getResources().contains("storage://bucket2/*")); + + assertEquals(2, statement.getPrincipals().size()); + assertTrue(statement.getPrincipals().contains("principal1")); + assertTrue(statement.getPrincipals().contains("principal2")); + } + + @Test + public void testListMethodsWithNullLists() { + Statement statement = Statement.builder() + .effect("Allow") + .addAction("storage:GetObject") + .addResource("storage://test-bucket/*") + .addPrincipals(null) + .addActions(null) + .addResources(null) + .build(); + + assertEquals(1, statement.getActions().size()); + assertEquals(1, statement.getResources().size()); + assertTrue(statement.getPrincipals().isEmpty()); + } + + @Test + public void testStatementEqualsAndHashCode() { + Statement statement1 = Statement.builder() + .sid("TestStatement") + .effect("Allow") + .addAction("storage:GetObject") + .addResource("storage://test-bucket/*") + .addPrincipal("principal1") + .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") + .build(); + + Statement statement2 = Statement.builder() + .sid("TestStatement") + .effect("Allow") + .addAction("storage:GetObject") + .addResource("storage://test-bucket/*") + .addPrincipal("principal1") + .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") + .build(); + + Statement statement3 = Statement.builder() + .sid("DifferentStatement") + .effect("Allow") + .addAction("storage:GetObject") + .addResource("storage://test-bucket/*") + .build(); + + // Test equals + assertEquals(statement1, statement2); + assertNotEquals(statement1, statement3); + assertNotEquals(statement1, null); + assertNotEquals(statement1, "not a statement"); + assertEquals(statement1, statement1); // same object + + // Test hashCode + assertEquals(statement1.hashCode(), statement2.hashCode()); + assertNotEquals(statement1.hashCode(), statement3.hashCode()); + } + + @Test + public void testStatementToString() { + Statement statement = Statement.builder() + .sid("TestStatement") + .effect("Allow") + .addAction("storage:GetObject") + .addResource("storage://test-bucket/*") + .addPrincipal("principal1") + .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") + .build(); + + String result = statement.toString(); + assertTrue(result.contains("TestStatement")); + assertTrue(result.contains("Allow")); + assertTrue(result.contains("storage:GetObject")); + assertTrue(result.contains("storage://test-bucket/*")); + assertTrue(result.contains("principal1")); + assertTrue(result.contains("StringEquals")); + } + + @Test + public void testGettersReturnImmutableCopies() { + Statement.Builder builder = Statement.builder() + .sid("TestStatement") + .effect("Allow") + .addAction("storage:GetObject") + .addResource("storage://test-bucket/*") + .addPrincipal("principal1") + .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2"); + + Statement statement = builder.build(); + + // Verify that modifying returned lists doesn't affect the original + List actions = statement.getActions(); + actions.clear(); + assertFalse(statement.getActions().isEmpty()); + + List resources = statement.getResources(); + resources.clear(); + assertFalse(statement.getResources().isEmpty()); + + List principals = statement.getPrincipals(); + principals.clear(); + assertFalse(statement.getPrincipals().isEmpty()); + + Map> conditions = statement.getConditions(); + conditions.clear(); + assertFalse(statement.getConditions().isEmpty()); + } } \ No newline at end of file diff --git a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/TrustConfigurationTest.java b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/TrustConfigurationTest.java index 2d37a529..fedd6df5 100644 --- a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/TrustConfigurationTest.java +++ b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/TrustConfigurationTest.java @@ -187,61 +187,35 @@ public void testTrustConfigurationBuilderEmptyBuilder() { } @Test - public void testTrustConfigurationBuilderIgnoreNullAndEmptyPrincipals() { + public void testNullAndEmptyValueHandling() { + // Test null and empty principals TrustConfiguration trustConfig = TrustConfiguration.builder() .addTrustedPrincipal("arn:aws:iam::123456789012:root") .addTrustedPrincipal(null) .addTrustedPrincipal("") .addTrustedPrincipal(" ") // whitespace only .addTrustedPrincipal("arn:aws:iam::987654321098:root") - .build(); - - List expectedPrincipals = Arrays.asList( - "arn:aws:iam::123456789012:root", - "arn:aws:iam::987654321098:root" - ); - - assertEquals(expectedPrincipals, trustConfig.getTrustedPrincipals()); - } - - @Test - public void testTrustConfigurationBuilderIgnoreNullConditions() { - TrustConfiguration trustConfig = TrustConfiguration.builder() - .addTrustedPrincipal("arn:aws:iam::123456789012:root") + .addTrustedPrincipals(null) // null list + .addTrustedPrincipals(Arrays.asList("valid-principal", null, "", " ")) .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") .addCondition(null, "key", "value") // null operator .addCondition("StringEquals", null, "value") // null key .addCondition("StringEquals", "key", null) // null value .build(); - Map> conditions = trustConfig.getConditions(); - assertEquals(1, conditions.size()); - assertTrue(conditions.containsKey("StringEquals")); - assertEquals(1, conditions.get("StringEquals").size()); - assertEquals("us-west-2", conditions.get("StringEquals").get("aws:RequestedRegion")); - } - - @Test - public void testTrustConfigurationEquality() { - TrustConfiguration trustConfig1 = TrustConfiguration.builder() - .addTrustedPrincipal("arn:aws:iam::123456789012:root") - .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") - .build(); + // Should only have valid principals + assertEquals(3, trustConfig.getTrustedPrincipals().size()); + assertTrue(trustConfig.getTrustedPrincipals().contains("arn:aws:iam::123456789012:root")); + assertTrue(trustConfig.getTrustedPrincipals().contains("arn:aws:iam::987654321098:root")); + assertTrue(trustConfig.getTrustedPrincipals().contains("valid-principal")); - TrustConfiguration trustConfig2 = TrustConfiguration.builder() - .addTrustedPrincipal("arn:aws:iam::123456789012:root") - .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") - .build(); + // Should only have valid condition + assertEquals(1, trustConfig.getConditions().size()); + assertTrue(trustConfig.getConditions().containsKey("StringEquals")); + assertEquals("us-west-2", trustConfig.getConditions().get("StringEquals").get("aws:RequestedRegion")); + } - TrustConfiguration trustConfig3 = TrustConfiguration.builder() - .addTrustedPrincipal("arn:aws:iam::987654321098:root") - .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") - .build(); - assertEquals(trustConfig1, trustConfig2); - assertNotEquals(trustConfig1, trustConfig3); - assertEquals(trustConfig1.hashCode(), trustConfig2.hashCode()); - } @Test public void testTrustConfigurationToString() { @@ -273,4 +247,27 @@ public void testTrustConfigurationImmutable() { assertEquals(1, trustConfig.getTrustedPrincipals().size()); assertEquals(0, trustConfig.getConditions().size()); } + + @Test + public void testTrustConfigurationEqualsAndHashCodeWithNullChecks() { + TrustConfiguration trust1 = TrustConfiguration.builder() + .addTrustedPrincipal("arn:aws:iam::123456789012:root") + .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") + .build(); + + TrustConfiguration trust2 = TrustConfiguration.builder() + .addTrustedPrincipal("arn:aws:iam::123456789012:root") + .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") + .build(); + + // Test equals with null and different types + assertNotEquals(trust1, null); + assertNotEquals(trust1, "not a trust config"); + assertEquals(trust1, trust1); // same object + assertEquals(trust1, trust2); // equal objects + + // Test hashCode consistency + assertEquals(trust1.hashCode(), trust2.hashCode()); + } + } \ No newline at end of file From 6e4e918a293f72ec2162c55e711661d9148c9af2 Mon Sep 17 00:00:00 2001 From: DaisyModi Date: Tue, 28 Oct 2025 12:27:21 +0530 Subject: [PATCH 03/15] using lombok for getters --- iam/iam-client/pom.xml | 8 +++ .../multicloudj/iam/client/IamClient.java | 2 - .../multicloudj/iam/model/CreateOptions.java | 35 ++---------- .../multicloudj/iam/model/PolicyDocument.java | 21 +------ .../multicloudj/iam/model/Statement.java | 57 +------------------ .../iam/model/TrustConfiguration.java | 22 +------ .../iam/model/PolicyDocumentTest.java | 17 ------ .../multicloudj/iam/model/StatementTest.java | 29 ---------- .../iam/model/TrustConfigurationTest.java | 17 ------ 9 files changed, 22 insertions(+), 186 deletions(-) diff --git a/iam/iam-client/pom.xml b/iam/iam-client/pom.xml index e371cf45..e0efd5db 100644 --- a/iam/iam-client/pom.xml +++ b/iam/iam-client/pom.xml @@ -24,6 +24,14 @@ sts-client + + + org.projectlombok + lombok + 1.18.34 + provided + + org.junit.jupiter diff --git a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/client/IamClient.java b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/client/IamClient.java index 8189e09a..989d1c4b 100644 --- a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/client/IamClient.java +++ b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/client/IamClient.java @@ -40,8 +40,6 @@ * // Attach policy * client.attachInlinePolicy(policy, "123456789012", "us-west-2", "my-bucket"); * - * - * @since 0.3.0 */ public class IamClient { diff --git a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/CreateOptions.java b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/CreateOptions.java index 3da2946d..898c37a7 100644 --- a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/CreateOptions.java +++ b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/CreateOptions.java @@ -1,5 +1,7 @@ package com.salesforce.multicloudj.iam.model; +import lombok.Getter; + import java.util.Objects; /** @@ -11,14 +13,13 @@ *

Usage example: *

  * CreateOptions options = CreateOptions.builder()
- *     .path("/orgstore/")
+ *     .path("/foo/")
  *     .maxSessionDuration(43200) // 12 hours
  *     .permissionBoundary("arn:aws:iam::123456789012:policy/PowerUserBoundary")
  *     .build();
  * 
- * - * @since 0.3.0 */ +@Getter public class CreateOptions { private final String path; private final Integer maxSessionDuration; @@ -39,32 +40,6 @@ public static Builder builder() { return new Builder(); } - /** - * Gets the path for the identity. - * - * @return the path, or null if not set - */ - public String getPath() { - return path; - } - - /** - * Gets the maximum session duration in seconds. - * - * @return the maximum session duration, or null if not set - */ - public Integer getMaxSessionDuration() { - return maxSessionDuration; - } - - /** - * Gets the permission boundary ARN. - * - * @return the permission boundary ARN, or null if not set - */ - public String getPermissionBoundary() { - return permissionBoundary; - } @Override public boolean equals(Object o) { @@ -104,7 +79,7 @@ private Builder() { /** * Sets the path for the identity. * - * @param path the path (e.g., "/orgstore/") for organizing identities + * @param path the path (e.g., "/foo/") for organizing identities * @return this Builder instance */ public Builder path(String path) { diff --git a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/PolicyDocument.java b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/PolicyDocument.java index c6acae2e..5cd12f0c 100644 --- a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/PolicyDocument.java +++ b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/PolicyDocument.java @@ -1,5 +1,7 @@ package com.salesforce.multicloudj.iam.model; +import lombok.Getter; + import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -25,9 +27,8 @@ * .endStatement() * .build(); * - * - * @since 0.3.0 */ +@Getter public class PolicyDocument { private final String version; private final List statements; @@ -46,23 +47,7 @@ public static Builder builder() { return new Builder(); } - /** - * Gets the policy version. - * - * @return the policy version - */ - public String getVersion() { - return version; - } - /** - * Gets the list of statements. - * - * @return an immutable copy of the statements list - */ - public List getStatements() { - return new ArrayList<>(statements); - } @Override public boolean equals(Object o) { diff --git a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/Statement.java b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/Statement.java index d14ea250..0e2bc253 100644 --- a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/Statement.java +++ b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/Statement.java @@ -1,5 +1,7 @@ package com.salesforce.multicloudj.iam.model; +import lombok.Getter; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -24,9 +26,8 @@ * .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") * .build(); * - * - * @since 0.3.0 */ +@Getter public class Statement { private final String sid; private final String effect; @@ -53,59 +54,7 @@ public static Builder builder() { return new Builder(); } - /** - * Gets the statement ID. - * - * @return the statement ID, or null if not set - */ - public String getSid() { - return sid; - } - - /** - * Gets the effect (Allow or Deny). - * - * @return the effect - */ - public String getEffect() { - return effect; - } - - /** - * Gets the list of principals. - * - * @return an immutable copy of the principals list - */ - public List getPrincipals() { - return new ArrayList<>(principals); - } - - /** - * Gets the list of actions. - * - * @return an immutable copy of the actions list - */ - public List getActions() { - return new ArrayList<>(actions); - } - /** - * Gets the list of resources. - * - * @return an immutable copy of the resources list - */ - public List getResources() { - return new ArrayList<>(resources); - } - - /** - * Gets the conditions map. - * - * @return an immutable copy of the conditions map - */ - public Map> getConditions() { - return new HashMap<>(conditions); - } @Override public boolean equals(Object o) { diff --git a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/TrustConfiguration.java b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/TrustConfiguration.java index 20f9f114..9003fbe2 100644 --- a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/TrustConfiguration.java +++ b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/TrustConfiguration.java @@ -1,5 +1,7 @@ package com.salesforce.multicloudj.iam.model; +import lombok.Getter; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -20,9 +22,8 @@ * .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") * .build(); * - * - * @since 0.3.0 */ +@Getter public class TrustConfiguration { private final List trustedPrincipals; private final Map> conditions; @@ -41,23 +42,6 @@ public static Builder builder() { return new Builder(); } - /** - * Gets the list of trusted principals. - * - * @return an immutable copy of the trusted principals list - */ - public List getTrustedPrincipals() { - return new ArrayList<>(trustedPrincipals); - } - - /** - * Gets the trust conditions. - * - * @return an immutable copy of the conditions map - */ - public Map> getConditions() { - return new HashMap<>(conditions); - } @Override public boolean equals(Object o) { diff --git a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java index 432b9aa6..5086312b 100644 --- a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java +++ b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java @@ -303,23 +303,6 @@ public void testPolicyDocumentToString() { assertTrue(result.contains("PolicyDocument")); } - @Test - public void testGetStatementsReturnsImmutableCopy() { - PolicyDocument policy = PolicyDocument.builder() - .statement("TestStatement") - .effect("Allow") - .addAction("storage:GetObject") - .addResource("storage://test-bucket/*") - .endStatement() - .build(); - - List statements = policy.getStatements(); - statements.clear(); - - // Original should be unaffected - assertFalse(policy.getStatements().isEmpty()); - assertEquals(1, policy.getStatements().size()); - } @Test public void testEndStatementWithoutCurrentStatement() { diff --git a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/StatementTest.java b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/StatementTest.java index 76a73732..220b3c3a 100644 --- a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/StatementTest.java +++ b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/StatementTest.java @@ -303,33 +303,4 @@ public void testStatementToString() { assertTrue(result.contains("StringEquals")); } - @Test - public void testGettersReturnImmutableCopies() { - Statement.Builder builder = Statement.builder() - .sid("TestStatement") - .effect("Allow") - .addAction("storage:GetObject") - .addResource("storage://test-bucket/*") - .addPrincipal("principal1") - .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2"); - - Statement statement = builder.build(); - - // Verify that modifying returned lists doesn't affect the original - List actions = statement.getActions(); - actions.clear(); - assertFalse(statement.getActions().isEmpty()); - - List resources = statement.getResources(); - resources.clear(); - assertFalse(statement.getResources().isEmpty()); - - List principals = statement.getPrincipals(); - principals.clear(); - assertFalse(statement.getPrincipals().isEmpty()); - - Map> conditions = statement.getConditions(); - conditions.clear(); - assertFalse(statement.getConditions().isEmpty()); - } } \ No newline at end of file diff --git a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/TrustConfigurationTest.java b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/TrustConfigurationTest.java index fedd6df5..c612e59e 100644 --- a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/TrustConfigurationTest.java +++ b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/TrustConfigurationTest.java @@ -230,23 +230,6 @@ public void testTrustConfigurationToString() { assertTrue(toString.contains("arn:aws:iam::123456789012:root")); } - @Test - public void testTrustConfigurationImmutable() { - TrustConfiguration trustConfig = TrustConfiguration.builder() - .addTrustedPrincipal("arn:aws:iam::123456789012:root") - .build(); - - List principals = trustConfig.getTrustedPrincipals(); - Map> conditions = trustConfig.getConditions(); - - // Modifying returned lists/maps should not affect the original - principals.add("new-principal"); - conditions.put("NewOperator", Map.of("key", "value")); - - // Original should remain unchanged - assertEquals(1, trustConfig.getTrustedPrincipals().size()); - assertEquals(0, trustConfig.getConditions().size()); - } @Test public void testTrustConfigurationEqualsAndHashCodeWithNullChecks() { From 26581402bd74baeea018295e9a0e8e134c11551b Mon Sep 17 00:00:00 2001 From: DaisyModi Date: Tue, 28 Oct 2025 12:47:56 +0530 Subject: [PATCH 04/15] Removing wildcard imports --- iam/iam-client/pom.xml | 2 -- .../salesforce/multicloudj/iam/client/IamClientTest.java | 4 +++- .../salesforce/multicloudj/iam/model/CreateOptionsTest.java | 6 +++++- .../multicloudj/iam/model/PolicyDocumentTest.java | 5 ++++- .../com/salesforce/multicloudj/iam/model/StatementTest.java | 6 +++++- .../multicloudj/iam/model/TrustConfigurationTest.java | 4 +++- pom.xml | 6 ++++++ 7 files changed, 26 insertions(+), 7 deletions(-) diff --git a/iam/iam-client/pom.xml b/iam/iam-client/pom.xml index e0efd5db..6ffd692a 100644 --- a/iam/iam-client/pom.xml +++ b/iam/iam-client/pom.xml @@ -28,8 +28,6 @@ org.projectlombok lombok - 1.18.34 - provided diff --git a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/client/IamClientTest.java b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/client/IamClientTest.java index d802fea8..89c5449c 100644 --- a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/client/IamClientTest.java +++ b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/client/IamClientTest.java @@ -4,7 +4,9 @@ import java.net.URI; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; /** * Unit tests for IamClient builder pattern and basic functionality. diff --git a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/CreateOptionsTest.java b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/CreateOptionsTest.java index b55d8102..512577ce 100644 --- a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/CreateOptionsTest.java +++ b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/CreateOptionsTest.java @@ -2,7 +2,11 @@ import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Unit tests for CreateOptions builder pattern and functionality. diff --git a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java index 5086312b..0298a35d 100644 --- a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java +++ b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java @@ -5,7 +5,10 @@ import java.util.Arrays; import java.util.List; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Unit tests for PolicyDocument builder pattern. diff --git a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/StatementTest.java b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/StatementTest.java index 220b3c3a..65f1446f 100644 --- a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/StatementTest.java +++ b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/StatementTest.java @@ -6,7 +6,11 @@ import java.util.List; import java.util.Map; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Unit tests for Statement builder pattern and functionality. diff --git a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/TrustConfigurationTest.java b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/TrustConfigurationTest.java index c612e59e..98c3f7d0 100644 --- a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/TrustConfigurationTest.java +++ b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/TrustConfigurationTest.java @@ -6,7 +6,9 @@ import java.util.List; import java.util.Map; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Unit tests for TrustConfiguration builder pattern and functionality. diff --git a/pom.xml b/pom.xml index dcf18a41..cabccc55 100644 --- a/pom.xml +++ b/pom.xml @@ -164,6 +164,12 @@ ${project.version} test-jar
+ + org.projectlombok + lombok + 1.18.34 + provided + From c1eeef15b13a612b278c3cb9a51446c53fcd468c Mon Sep 17 00:00:00 2001 From: DaisyModi Date: Tue, 28 Oct 2025 13:51:30 +0530 Subject: [PATCH 05/15] Removing unnecessary imports --- iam/iam-client/pom.xml | 40 ++++++++-------------------------------- pom.xml | 6 ------ 2 files changed, 8 insertions(+), 38 deletions(-) diff --git a/iam/iam-client/pom.xml b/iam/iam-client/pom.xml index 6ffd692a..cec6e465 100644 --- a/iam/iam-client/pom.xml +++ b/iam/iam-client/pom.xml @@ -28,21 +28,23 @@ org.projectlombok lombok + 1.18.34 + provided - - org.junit.jupiter - junit-jupiter-api - 5.12.1 - test - org.mockito mockito-core 5.16.1 test + + org.junit.jupiter + junit-jupiter-api + 5.12.1 + test + org.mockito mockito-junit-jupiter @@ -72,32 +74,6 @@ - - org.apache.maven.plugins - maven-surefire-plugin - 3.4.0 - - - org.apache.maven.plugins - maven-failsafe-plugin - 3.4.0 - - - run-integration-tests - integration-test - - integration-test - - - - verify-integration-results - verify - - verify - - - - \ No newline at end of file diff --git a/pom.xml b/pom.xml index cabccc55..dcf18a41 100644 --- a/pom.xml +++ b/pom.xml @@ -164,12 +164,6 @@ ${project.version} test-jar - - org.projectlombok - lombok - 1.18.34 - provided - From addd0bb3a4c7e94b590a3ececd78fcb4f17bb238 Mon Sep 17 00:00:00 2001 From: DaisyModi Date: Tue, 28 Oct 2025 14:19:44 +0530 Subject: [PATCH 06/15] Throwing correct exceptions --- .../multicloudj/iam/model/PolicyDocument.java | 7 +++--- .../multicloudj/iam/model/Statement.java | 7 +++--- .../iam/model/PolicyDocumentTest.java | 24 +++++++++---------- .../multicloudj/iam/model/StatementTest.java | 12 +++++----- 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/PolicyDocument.java b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/PolicyDocument.java index 5cd12f0c..92705519 100644 --- a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/PolicyDocument.java +++ b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/PolicyDocument.java @@ -1,5 +1,6 @@ package com.salesforce.multicloudj.iam.model; +import com.salesforce.multicloudj.common.exceptions.InvalidArgumentException; import lombok.Getter; import java.util.ArrayList; @@ -231,19 +232,19 @@ public Builder addStatement(Statement statement) { * Builds and returns a PolicyDocument instance. * * @return a new PolicyDocument instance - * @throws IllegalArgumentException if no statements are defined + * @throws InvalidArgumentException if no statements are defined */ public PolicyDocument build() { finalizeCurrentStatement(); if (statements.isEmpty()) { - throw new IllegalArgumentException("at least one statement is required"); + throw new InvalidArgumentException("at least one statement is required"); } return new PolicyDocument(this); } private void validateCurrentStatement() { if (currentStatementBuilder == null) { - throw new IllegalStateException("No statement is currently being built. Call statement(sid) first."); + throw new InvalidArgumentException("No statement is currently being built. Call statement(sid) first."); } } diff --git a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/Statement.java b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/Statement.java index 0e2bc253..bc28a40e 100644 --- a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/Statement.java +++ b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/Statement.java @@ -1,5 +1,6 @@ package com.salesforce.multicloudj.iam.model; +import com.salesforce.multicloudj.common.exceptions.InvalidArgumentException; import lombok.Getter; import java.util.ArrayList; @@ -225,14 +226,14 @@ public Builder addCondition(String operator, String key, Object value) { * Builds and returns a Statement instance. * * @return a new Statement instance - * @throws IllegalArgumentException if required fields are missing + * @throws InvalidArgumentException if required fields are missing */ public Statement build() { if (effect == null || effect.trim().isEmpty()) { - throw new IllegalArgumentException("effect is required"); + throw new InvalidArgumentException("effect is required"); } if (actions.isEmpty()) { - throw new IllegalArgumentException("at least one action is required"); + throw new InvalidArgumentException("at least one action is required"); } return new Statement(this); } diff --git a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java index 0298a35d..0544f151 100644 --- a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java +++ b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java @@ -1,9 +1,9 @@ package com.salesforce.multicloudj.iam.model; +import com.salesforce.multicloudj.common.exceptions.InvalidArgumentException; import org.junit.jupiter.api.Test; import java.util.Arrays; -import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -82,14 +82,14 @@ public void testAddCompleteStatement() { @Test public void testEmptyPolicyThrowsException() { - assertThrows(IllegalArgumentException.class, () -> { + assertThrows(InvalidArgumentException.class, () -> { PolicyDocument.builder().build(); }); } @Test public void testStatementWithoutEffectThrowsException() { - assertThrows(IllegalArgumentException.class, () -> { + assertThrows(InvalidArgumentException.class, () -> { PolicyDocument.builder() .statement("TestStatement") .addAction("storage:GetObject") @@ -100,7 +100,7 @@ public void testStatementWithoutEffectThrowsException() { @Test public void testStatementWithoutActionsThrowsException() { - assertThrows(IllegalArgumentException.class, () -> { + assertThrows(InvalidArgumentException.class, () -> { PolicyDocument.builder() .statement("TestStatement") .effect("Allow") @@ -185,28 +185,28 @@ public void testBuilderMethodsWithMultipleValues() { @Test public void testBuilderStateValidation() { // Test that trying to use statement methods without calling statement() throws exception - assertThrows(IllegalStateException.class, () -> + assertThrows(InvalidArgumentException.class, () -> PolicyDocument.builder().addAction("storage:GetObject").build()); - assertThrows(IllegalStateException.class, () -> + assertThrows(InvalidArgumentException.class, () -> PolicyDocument.builder().effect("Allow").build()); - assertThrows(IllegalStateException.class, () -> + assertThrows(InvalidArgumentException.class, () -> PolicyDocument.builder().addResource("storage://test-bucket/*").build()); - assertThrows(IllegalStateException.class, () -> + assertThrows(InvalidArgumentException.class, () -> PolicyDocument.builder().addPrincipal("principal1").build()); - assertThrows(IllegalStateException.class, () -> + assertThrows(InvalidArgumentException.class, () -> PolicyDocument.builder().addCondition("StringEquals", "key", "value").build()); - assertThrows(IllegalStateException.class, () -> + assertThrows(InvalidArgumentException.class, () -> PolicyDocument.builder().addActions(Arrays.asList("storage:GetObject")).build()); - assertThrows(IllegalStateException.class, () -> + assertThrows(InvalidArgumentException.class, () -> PolicyDocument.builder().addResources(Arrays.asList("storage://test-bucket/*")).build()); - assertThrows(IllegalStateException.class, () -> + assertThrows(InvalidArgumentException.class, () -> PolicyDocument.builder().addPrincipals(Arrays.asList("principal1")).build()); } diff --git a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/StatementTest.java b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/StatementTest.java index 65f1446f..e965b69f 100644 --- a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/StatementTest.java +++ b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/StatementTest.java @@ -1,10 +1,10 @@ package com.salesforce.multicloudj.iam.model; +import com.salesforce.multicloudj.common.exceptions.InvalidArgumentException; import org.junit.jupiter.api.Test; import java.util.Arrays; import java.util.List; -import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -121,14 +121,14 @@ public void testStatementWithoutSid() { @Test public void testEmptyStatementThrowsException() { - assertThrows(IllegalArgumentException.class, () -> { + assertThrows(InvalidArgumentException.class, () -> { Statement.builder().build(); }); } @Test public void testStatementWithoutEffectThrowsException() { - assertThrows(IllegalArgumentException.class, () -> { + assertThrows(InvalidArgumentException.class, () -> { Statement.builder() .sid("NoEffectStatement") .addAction("storage:GetObject") @@ -139,7 +139,7 @@ public void testStatementWithoutEffectThrowsException() { @Test public void testStatementWithoutActionsThrowsException() { - assertThrows(IllegalArgumentException.class, () -> { + assertThrows(InvalidArgumentException.class, () -> { Statement.builder() .sid("NoActionsStatement") .effect("Allow") @@ -150,7 +150,7 @@ public void testStatementWithoutActionsThrowsException() { @Test public void testStatementWithEmptyEffect() { - assertThrows(IllegalArgumentException.class, () -> { + assertThrows(InvalidArgumentException.class, () -> { Statement.builder() .sid("EmptyEffectStatement") .effect("") @@ -161,7 +161,7 @@ public void testStatementWithEmptyEffect() { @Test public void testStatementWithWhitespaceEffect() { - assertThrows(IllegalArgumentException.class, () -> { + assertThrows(InvalidArgumentException.class, () -> { Statement.builder() .sid("WhitespaceEffectStatement") .effect(" ") From d9b3f21fb023dc958120f95f4bdfa65d1e7ea07f Mon Sep 17 00:00:00 2001 From: DaisyModi Date: Tue, 28 Oct 2025 16:58:58 +0530 Subject: [PATCH 07/15] Removing default values for version --- .../multicloudj/iam/client/IamClient.java | 2 +- .../multicloudj/iam/model/PolicyDocument.java | 11 +++++--- .../iam/model/PolicyDocumentTest.java | 26 ++++++++++++++++++- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/client/IamClient.java b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/client/IamClient.java index 989d1c4b..ef61d0bd 100644 --- a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/client/IamClient.java +++ b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/client/IamClient.java @@ -29,7 +29,7 @@ * * // Create policy * PolicyDocument policy = PolicyDocument.builder() - * .version("2024-01-01") + * .version("2012-10-17") // Use provider-specific version (AWS example) * .statement("StorageAccess") * .effect("Allow") * .addAction("storage:GetObject") diff --git a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/PolicyDocument.java b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/PolicyDocument.java index 92705519..6038bd36 100644 --- a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/PolicyDocument.java +++ b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/PolicyDocument.java @@ -17,7 +17,7 @@ *

Usage example: *

  * PolicyDocument policy = PolicyDocument.builder()
- *     .version("2024-01-01")
+ *     .version("2012-10-17")  // Use provider-specific version (AWS example)
  *     .statement("StorageAccess")
  *         .effect("Allow")
  *         .addAction("storage:GetObject")
@@ -76,7 +76,7 @@ public String toString() {
      * Builder class for PolicyDocument.
      */
     public static class Builder {
-        private String version = "2024-01-01";
+        private String version;
         private final List statements = new ArrayList<>();
         private Statement.Builder currentStatementBuilder;
 
@@ -86,7 +86,7 @@ private Builder() {
         /**
          * Sets the policy version.
          *
-         * @param version the policy version (default: "2024-01-01")
+         * @param version the policy version (required)
          * @return this Builder instance
          */
         public Builder version(String version) {
@@ -232,10 +232,13 @@ public Builder addStatement(Statement statement) {
          * Builds and returns a PolicyDocument instance.
          *
          * @return a new PolicyDocument instance
-         * @throws InvalidArgumentException if no statements are defined
+         * @throws InvalidArgumentException if version or statements are missing
          */
         public PolicyDocument build() {
             finalizeCurrentStatement();
+            if (version == null || version.trim().isEmpty()) {
+                throw new InvalidArgumentException("version is required");
+            }
             if (statements.isEmpty()) {
                 throw new InvalidArgumentException("at least one statement is required");
             }
diff --git a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java
index 0544f151..b3d40ea0 100644
--- a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java
+++ b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java
@@ -15,6 +15,8 @@
  */
 public class PolicyDocumentTest {
 
+    private static final String TEST_VERSION = "TEST_VERSION";
+
     @Test
     public void testPolicyDocumentBuilder() {
         PolicyDocument policy = PolicyDocument.builder()
@@ -46,6 +48,7 @@ public void testPolicyDocumentBuilder() {
     @Test
     public void testMultipleStatements() {
         PolicyDocument policy = PolicyDocument.builder()
+            .version(TEST_VERSION)
             .statement("ReadAccess")
                 .effect("Allow")
                 .addAction("storage:GetObject")
@@ -73,6 +76,7 @@ public void testAddCompleteStatement() {
             .build();
 
         PolicyDocument policy = PolicyDocument.builder()
+            .version(TEST_VERSION)
             .addStatement(statement)
             .build();
 
@@ -87,10 +91,24 @@ public void testEmptyPolicyThrowsException() {
         });
     }
 
+    @Test
+    public void testMissingVersionThrowsException() {
+        assertThrows(InvalidArgumentException.class, () -> {
+            PolicyDocument.builder()
+                .statement("TestStatement")
+                    .effect("Allow")
+                    .addAction("storage:GetObject")
+                    .addResource("storage://test-bucket/*")
+                .endStatement()
+                .build();
+        });
+    }
+
     @Test
     public void testStatementWithoutEffectThrowsException() {
         assertThrows(InvalidArgumentException.class, () -> {
             PolicyDocument.builder()
+                .version(TEST_VERSION)
                 .statement("TestStatement")
                     .addAction("storage:GetObject")
                 .endStatement()
@@ -102,6 +120,7 @@ public void testStatementWithoutEffectThrowsException() {
     public void testStatementWithoutActionsThrowsException() {
         assertThrows(InvalidArgumentException.class, () -> {
             PolicyDocument.builder()
+                .version(TEST_VERSION)
                 .statement("TestStatement")
                     .effect("Allow")
                 .endStatement()
@@ -125,6 +144,7 @@ public void testVersionHandling() {
 
         // Test default version
         PolicyDocument defaultVersionPolicy = PolicyDocument.builder()
+            .version(TEST_VERSION)
             .statement("TestStatement")
                 .effect("Allow")
                 .addAction("storage:GetObject")
@@ -132,12 +152,13 @@ public void testVersionHandling() {
             .endStatement()
             .build();
 
-        assertEquals("2024-01-01", defaultVersionPolicy.getVersion());
+        assertEquals(TEST_VERSION, defaultVersionPolicy.getVersion());
     }
 
     @Test
     public void testBuilderMethodsWithMultipleValues() {
         PolicyDocument policy = PolicyDocument.builder()
+            .version(TEST_VERSION)
             .statement("TestStatement")
                 .effect("Allow")
                 .addAction("storage:GetObject")
@@ -213,6 +234,7 @@ public void testBuilderStateValidation() {
     @Test
     public void testAddNullStatement() {
         PolicyDocument policy = PolicyDocument.builder()
+            .version(TEST_VERSION)
             .addStatement(null)
             .statement("ValidStatement")
                 .effect("Allow")
@@ -235,6 +257,7 @@ public void testMixingAddStatementAndBuilder() {
             .build();
 
         PolicyDocument policy = PolicyDocument.builder()
+            .version(TEST_VERSION)
             .addStatement(preBuiltStatement)
             .statement("BuiltInline")
                 .effect("Allow")
@@ -311,6 +334,7 @@ public void testPolicyDocumentToString() {
     public void testEndStatementWithoutCurrentStatement() {
         // endStatement() should be safe to call even when no statement is being built
         PolicyDocument policy = PolicyDocument.builder()
+            .version(TEST_VERSION)
             .endStatement() // This should do nothing
             .statement("TestStatement")
                 .effect("Allow")

From 264903f5353eb6ac4ddee30c568ff87c60a9e00d Mon Sep 17 00:00:00 2001
From: DaisyModi 
Date: Tue, 28 Oct 2025 21:23:56 +0530
Subject: [PATCH 08/15] Removing unnecessary tests from IamClientTest

---
 .../multicloudj/iam/client/IamClientTest.java | 85 -------------------
 1 file changed, 85 deletions(-)

diff --git a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/client/IamClientTest.java b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/client/IamClientTest.java
index 89c5449c..4bb1283e 100644
--- a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/client/IamClientTest.java
+++ b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/client/IamClientTest.java
@@ -6,7 +6,6 @@
 
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertSame;
-import static org.junit.jupiter.api.Assertions.assertThrows;
 
 /**
  * Unit tests for IamClient builder pattern and basic functionality.
@@ -63,88 +62,4 @@ public void testIamClientBuildWithEndpoint() {
         assertNotNull(client);
     }
 
-    // TODO: Modify this test to verify actual createIdentity functionality
-    // Expected behavior: Should return the unique identifier of the created identity
-    @Test
-    public void testCreateIdentity() {
-        IamClient client = IamClient.builder("aws").build();
-
-        // Currently throws UnsupportedOperationException, will be implemented later
-        assertThrows(UnsupportedOperationException.class, () -> {
-            client.createIdentity("TestRole", "Test description", "123456789012", "us-west-2",
-                java.util.Optional.empty(), java.util.Optional.empty());
-        });
-    }
-
-    // TODO: Modify this test to verify actual attachInlinePolicy functionality
-    // Expected behavior: Should successfully attach policy without throwing exceptions
-    @Test
-    public void testAttachInlinePolicy() {
-        IamClient client = IamClient.builder("aws").build();
-
-        // Currently throws UnsupportedOperationException, will be implemented later
-        assertThrows(UnsupportedOperationException.class, () -> {
-            client.attachInlinePolicy(null, "123456789012", "us-west-2", "test-resource");
-        });
-    }
-
-    // TODO: Modify this test to verify actual getInlinePolicyDetails functionality
-    // Expected behavior: Should return the policy document details as a string
-    @Test
-    public void testGetInlinePolicyDetails() {
-        IamClient client = IamClient.builder("aws").build();
-
-        // Currently throws UnsupportedOperationException, will be implemented later
-        assertThrows(UnsupportedOperationException.class, () -> {
-            client.getInlinePolicyDetails("TestRole", "TestPolicy", "123456789012", "us-west-2");
-        });
-    }
-
-    // TODO: Modify this test to verify actual getAttachedPolicies functionality
-    // Expected behavior: Should return a list of policy names attached to the identity
-    @Test
-    public void testGetAttachedPolicies() {
-        IamClient client = IamClient.builder("aws").build();
-
-        // Currently throws UnsupportedOperationException, will be implemented later
-        assertThrows(UnsupportedOperationException.class, () -> {
-            client.getAttachedPolicies("TestRole", "123456789012", "us-west-2");
-        });
-    }
-
-    // TODO: Modify this test to verify actual removePolicy functionality
-    // Expected behavior: Should successfully remove policy without throwing exceptions
-    @Test
-    public void testRemovePolicy() {
-        IamClient client = IamClient.builder("aws").build();
-
-        // Currently throws UnsupportedOperationException, will be implemented later
-        assertThrows(UnsupportedOperationException.class, () -> {
-            client.removePolicy("TestRole", "TestPolicy", "123456789012", "us-west-2");
-        });
-    }
-
-    // TODO: Modify this test to verify actual deleteIdentity functionality
-    // Expected behavior: Should successfully delete identity without throwing exceptions
-    @Test
-    public void testDeleteIdentity() {
-        IamClient client = IamClient.builder("aws").build();
-
-        // Currently throws UnsupportedOperationException, will be implemented later
-        assertThrows(UnsupportedOperationException.class, () -> {
-            client.deleteIdentity("TestRole", "123456789012", "us-west-2");
-        });
-    }
-
-    // TODO: Modify this test to verify actual getIdentity functionality
-    // Expected behavior: Should return the unique identity identifier (ARN, email, or roleId)
-    @Test
-    public void testGetIdentity() {
-        IamClient client = IamClient.builder("aws").build();
-
-        // Currently throws UnsupportedOperationException, will be implemented later
-        assertThrows(UnsupportedOperationException.class, () -> {
-            client.getIdentity("TestRole", "123456789012", "us-west-2");
-        });
-    }
 }
\ No newline at end of file

From 0ed5f9755ccee561ef87af2208a4c7d1b4980a8f Mon Sep 17 00:00:00 2001
From: DaisyModi 
Date: Tue, 28 Oct 2025 22:01:09 +0530
Subject: [PATCH 09/15] Initializing statement during builder initialization

---
 .../multicloudj/iam/model/PolicyDocument.java | 23 ++++-----
 .../multicloudj/iam/model/Statement.java      |  9 ++++
 .../iam/model/PolicyDocumentTest.java         | 47 ++++++++++---------
 3 files changed, 42 insertions(+), 37 deletions(-)

diff --git a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/PolicyDocument.java b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/PolicyDocument.java
index 6038bd36..820fe01b 100644
--- a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/PolicyDocument.java
+++ b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/PolicyDocument.java
@@ -81,6 +81,7 @@ public static class Builder {
         private Statement.Builder currentStatementBuilder;
 
         private Builder() {
+            this.currentStatementBuilder = Statement.builder();
         }
 
         /**
@@ -113,7 +114,6 @@ public Builder statement(String sid) {
          * @return this Builder instance
          */
         public Builder effect(String effect) {
-            validateCurrentStatement();
             this.currentStatementBuilder.effect(effect);
             return this;
         }
@@ -125,7 +125,6 @@ public Builder effect(String effect) {
          * @return this Builder instance
          */
         public Builder addPrincipal(String principal) {
-            validateCurrentStatement();
             this.currentStatementBuilder.addPrincipal(principal);
             return this;
         }
@@ -137,7 +136,6 @@ public Builder addPrincipal(String principal) {
          * @return this Builder instance
          */
         public Builder addPrincipals(List principals) {
-            validateCurrentStatement();
             this.currentStatementBuilder.addPrincipals(principals);
             return this;
         }
@@ -149,7 +147,6 @@ public Builder addPrincipals(List principals) {
          * @return this Builder instance
          */
         public Builder addAction(String action) {
-            validateCurrentStatement();
             this.currentStatementBuilder.addAction(action);
             return this;
         }
@@ -161,7 +158,6 @@ public Builder addAction(String action) {
          * @return this Builder instance
          */
         public Builder addActions(List actions) {
-            validateCurrentStatement();
             this.currentStatementBuilder.addActions(actions);
             return this;
         }
@@ -173,7 +169,6 @@ public Builder addActions(List actions) {
          * @return this Builder instance
          */
         public Builder addResource(String resource) {
-            validateCurrentStatement();
             this.currentStatementBuilder.addResource(resource);
             return this;
         }
@@ -185,7 +180,6 @@ public Builder addResource(String resource) {
          * @return this Builder instance
          */
         public Builder addResources(List resources) {
-            validateCurrentStatement();
             this.currentStatementBuilder.addResources(resources);
             return this;
         }
@@ -199,7 +193,6 @@ public Builder addResources(List resources) {
          * @return this Builder instance
          */
         public Builder addCondition(String operator, String key, Object value) {
-            validateCurrentStatement();
             this.currentStatementBuilder.addCondition(operator, key, value);
             return this;
         }
@@ -245,16 +238,16 @@ public PolicyDocument build() {
             return new PolicyDocument(this);
         }
 
-        private void validateCurrentStatement() {
-            if (currentStatementBuilder == null) {
-                throw new InvalidArgumentException("No statement is currently being built. Call statement(sid) first.");
-            }
-        }
 
         private void finalizeCurrentStatement() {
             if (currentStatementBuilder != null) {
-                statements.add(currentStatementBuilder.build());
-                currentStatementBuilder = null;
+                // Only finalize statements that have the minimum required content
+                if (currentStatementBuilder.hasMinimumContent()) {
+                    Statement statement = currentStatementBuilder.build();
+                    statements.add(statement);
+                }
+                // Always reinitialize for the next statement
+                currentStatementBuilder = Statement.builder();
             }
         }
     }
diff --git a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/Statement.java b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/Statement.java
index bc28a40e..9449ed5d 100644
--- a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/Statement.java
+++ b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/Statement.java
@@ -222,6 +222,15 @@ public Builder addCondition(String operator, String key, Object value) {
             return this;
         }
 
+        /**
+         * Checks if the statement has the minimum required content to be built.
+         *
+         * @return true if the statement has both effect and at least one action
+         */
+        public boolean hasMinimumContent() {
+            return effect != null && !effect.trim().isEmpty() && !actions.isEmpty();
+        }
+
         /**
          * Builds and returns a Statement instance.
          *
diff --git a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java
index b3d40ea0..829c24e8 100644
--- a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java
+++ b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java
@@ -204,31 +204,34 @@ public void testBuilderMethodsWithMultipleValues() {
     }
 
     @Test
-    public void testBuilderStateValidation() {
-        // Test that trying to use statement methods without calling statement() throws exception
-        assertThrows(InvalidArgumentException.class, () ->
-            PolicyDocument.builder().addAction("storage:GetObject").build());
-
-        assertThrows(InvalidArgumentException.class, () ->
-            PolicyDocument.builder().effect("Allow").build());
-
-        assertThrows(InvalidArgumentException.class, () ->
-            PolicyDocument.builder().addResource("storage://test-bucket/*").build());
-
-        assertThrows(InvalidArgumentException.class, () ->
-            PolicyDocument.builder().addPrincipal("principal1").build());
-
-        assertThrows(InvalidArgumentException.class, () ->
-            PolicyDocument.builder().addCondition("StringEquals", "key", "value").build());
+    public void testBuilderAutoInitialization() {
+        // Test that statement methods work without calling statement() first due to auto-initialization
+        PolicyDocument policy1 = PolicyDocument.builder()
+            .version("2024-01-01")
+            .addAction("storage:GetObject")
+            .effect("Allow")
+            .addResource("storage://test-bucket/*")
+            .build();
 
-        assertThrows(InvalidArgumentException.class, () ->
-            PolicyDocument.builder().addActions(Arrays.asList("storage:GetObject")).build());
+        assertEquals(1, policy1.getStatements().size());
+        assertEquals("Allow", policy1.getStatements().get(0).getEffect());
 
-        assertThrows(InvalidArgumentException.class, () ->
-            PolicyDocument.builder().addResources(Arrays.asList("storage://test-bucket/*")).build());
+        PolicyDocument policy2 = PolicyDocument.builder()
+            .version("2024-01-01")
+            .addPrincipal("principal1")
+            .addCondition("StringEquals", "key", "value")
+            .addActions(Arrays.asList("storage:GetObject"))
+            .addResources(Arrays.asList("storage://test-bucket/*"))
+            .addPrincipals(Arrays.asList("principal2"))
+            .effect("Allow")
+            .build();
 
-        assertThrows(InvalidArgumentException.class, () ->
-            PolicyDocument.builder().addPrincipals(Arrays.asList("principal1")).build());
+        assertEquals(1, policy2.getStatements().size());
+        Statement statement = policy2.getStatements().get(0);
+        assertEquals("Allow", statement.getEffect());
+        assertEquals(1, statement.getActions().size());
+        assertEquals(1, statement.getResources().size());
+        assertEquals(2, statement.getPrincipals().size());
     }
 
     @Test

From 1bb588e7899d26d4a622a0acbeec97f9950d45a5 Mon Sep 17 00:00:00 2001
From: DaisyModi 
Date: Wed, 29 Oct 2025 01:37:31 +0530
Subject: [PATCH 10/15] Using cloud-agnostics formats and documentation for
 permission boundary

---
 .../multicloudj/iam/model/CreateOptions.java  | 29 ++++++++++++--
 .../iam/model/TrustConfiguration.java         | 20 ++++------
 .../iam/model/CreateOptionsTest.java          | 38 ++++++++++++++++++-
 .../iam/model/TrustConfigurationTest.java     | 10 +++++
 4 files changed, 80 insertions(+), 17 deletions(-)

diff --git a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/CreateOptions.java b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/CreateOptions.java
index 898c37a7..2b84f4bc 100644
--- a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/CreateOptions.java
+++ b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/CreateOptions.java
@@ -10,13 +10,33 @@
  * 

This class provides additional options that can be set during identity creation, * such as path specifications, session duration limits, and permission boundaries. * - *

Usage example: + *

Permission boundary identifiers are provider-specific and translated internally: + * - AWS: IAM Policy ARN format (arn:aws:iam::account:policy/name) + * - GCP: Organization Policy constraint name or IAM Condition expression + * - AliCloud: Not supported (AliCloud RAM does not have permission boundaries) + * + *

Usage examples by provider: *

- * CreateOptions options = CreateOptions.builder()
+ * // AWS Example
+ * CreateOptions awsOptions = CreateOptions.builder()
  *     .path("/foo/")
  *     .maxSessionDuration(43200) // 12 hours
  *     .permissionBoundary("arn:aws:iam::123456789012:policy/PowerUserBoundary")
  *     .build();
+ *
+ * // GCP Example (using organization policy constraint)
+ * CreateOptions gcpOptions = CreateOptions.builder()
+ *     .path("/foo/")
+ *     .maxSessionDuration(3600)  // 1 hour
+ *     .permissionBoundary("constraints/compute.restrictLoadBalancerCreationForTypes")
+ *     .build();
+ *
+ * // AliCloud Example (permission boundaries not supported)
+ * CreateOptions aliOptions = CreateOptions.builder()
+ *     .path("/foo/")
+ *     .maxSessionDuration(7200)  // 2 hours
+ *     // .permissionBoundary() - Not supported in AliCloud RAM
+ *     .build();
  * 
*/ @Getter @@ -99,9 +119,10 @@ public Builder maxSessionDuration(Integer maxSessionDuration) { } /** - * Sets the permission boundary ARN. + * Sets the permission boundary policy identifier. * - * @param permissionBoundary the ARN of the policy that acts as a permissions boundary + * @param permissionBoundary the cloud-native identifier of the policy that acts as a permission boundary + * (AWS: policy ARN, GCP: constraint name, AliCloud: not supported) * @return this Builder instance */ public Builder permissionBoundary(String permissionBoundary) { diff --git a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/TrustConfiguration.java b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/TrustConfiguration.java index 9003fbe2..06f8dce2 100644 --- a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/TrustConfiguration.java +++ b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/TrustConfiguration.java @@ -14,14 +14,10 @@ *

This class defines which principals can assume or impersonate the identity being created, * along with any conditions that must be met for the trust relationship to be valid. * - *

Usage example: - *

- * TrustConfiguration trust = TrustConfiguration.builder()
- *     .addTrustedPrincipal("arn:aws:iam::111122223333:root")
- *     .addTrustedPrincipal("arn:aws:iam::444455556666:user/ExampleUser")
- *     .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2")
- *     .build();
- * 
+ *

Principal identifiers are accepted in their native cloud format and translated internally: + * - AWS: ARN format (arn:aws:iam::account:type/name) + * - GCP: Email format (serviceaccount@project.iam.gserviceaccount.com) + * - AliCloud: ACS format (acs:ram::account:type/name) or account ID */ @Getter public class TrustConfiguration { @@ -78,7 +74,7 @@ private Builder() { /** * Adds a trusted principal to the trust configuration. * - * @param principal the principal ARN or identifier that can assume this identity + * @param principal the principal identifier in cloud-native format (AWS ARN, GCP email, AliCloud ACS ARN or account ID) * @return this Builder instance */ public Builder addTrustedPrincipal(String principal) { @@ -91,7 +87,7 @@ public Builder addTrustedPrincipal(String principal) { /** * Adds multiple trusted principals to the trust configuration. * - * @param principals the list of principal ARNs or identifiers + * @param principals the list of principal identifiers in cloud-native formats * @return this Builder instance */ public Builder addTrustedPrincipals(List principals) { @@ -106,8 +102,8 @@ public Builder addTrustedPrincipals(List principals) { /** * Adds a condition to the trust configuration. * - * @param operator the condition operator (e.g., "StringEquals", "IpAddress") - * @param key the condition key (e.g., "aws:RequestedRegion", "aws:SourceIp") + * @param operator the condition operator (e.g., "StringEquals", "IpAddress", "DateGreaterThan") + * @param key the condition key in cloud-native format (e.g., "aws:RequestedRegion", ""aws:SourceIp") * @param value the condition value * @return this Builder instance */ diff --git a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/CreateOptionsTest.java b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/CreateOptionsTest.java index 512577ce..5f46953f 100644 --- a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/CreateOptionsTest.java +++ b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/CreateOptionsTest.java @@ -56,7 +56,7 @@ public void testCreateOptionsBuilderIndividualFields() { assertEquals(Integer.valueOf(7200), durationOptions.getMaxSessionDuration()); assertNull(durationOptions.getPermissionBoundary()); - // Test permissionBoundary only + // Test permissionBoundary only (AWS example) CreateOptions boundaryOptions = CreateOptions.builder() .permissionBoundary("arn:aws:iam::123456789012:policy/DeveloperBoundary") .build(); @@ -219,4 +219,40 @@ public void testCreateOptionsBuilderOverwriteValues() { assertEquals(Integer.valueOf(7200), options.getMaxSessionDuration()); assertEquals("arn:aws:iam::123456789012:policy/SecondBoundary", options.getPermissionBoundary()); } + + @Test + public void testCreateOptionsBuilderProviderSpecificExamples() { + // AWS Example + CreateOptions awsOptions = CreateOptions.builder() + .path("/foo/") + .maxSessionDuration(43200) // 12 hours + .permissionBoundary("arn:aws:iam::123456789012:policy/PowerUserBoundary") + .build(); + + assertEquals("/foo/", awsOptions.getPath()); + assertEquals(Integer.valueOf(43200), awsOptions.getMaxSessionDuration()); + assertEquals("arn:aws:iam::123456789012:policy/PowerUserBoundary", awsOptions.getPermissionBoundary()); + + // GCP Example + CreateOptions gcpOptions = CreateOptions.builder() + .path("/foo/") + .maxSessionDuration(3600) // 1 hour + .permissionBoundary("constraints/compute.restrictLoadBalancerCreationForTypes") + .build(); + + assertEquals("/foo/", gcpOptions.getPath()); + assertEquals(Integer.valueOf(3600), gcpOptions.getMaxSessionDuration()); + assertEquals("constraints/compute.restrictLoadBalancerCreationForTypes", gcpOptions.getPermissionBoundary()); + + // AliCloud Example (permission boundaries not supported) + CreateOptions aliOptions = CreateOptions.builder() + .path("/foo/") + .maxSessionDuration(7200) // 2 hours + // Permission boundaries not supported in AliCloud RAM + .build(); + + assertEquals("/foo/", aliOptions.getPath()); + assertEquals(Integer.valueOf(7200), aliOptions.getMaxSessionDuration()); + assertNull(aliOptions.getPermissionBoundary()); // AliCloud doesn't support permission boundaries + } } \ No newline at end of file diff --git a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/TrustConfigurationTest.java b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/TrustConfigurationTest.java index 98c3f7d0..afc39e1c 100644 --- a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/TrustConfigurationTest.java +++ b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/TrustConfigurationTest.java @@ -127,6 +127,7 @@ public void testTrustConfigurationBuilderGcpServiceAccount() { TrustConfiguration trustConfig = TrustConfiguration.builder() .addTrustedPrincipal("service-account@my-project.iam.gserviceaccount.com") .addTrustedPrincipal("another-sa@different-project.iam.gserviceaccount.com") + .addCondition("expression", "location", "resource.name.startsWith('projects/my-project/zones/us-west')") .build(); List expectedPrincipals = Arrays.asList( @@ -135,6 +136,10 @@ public void testTrustConfigurationBuilderGcpServiceAccount() { ); assertEquals(expectedPrincipals, trustConfig.getTrustedPrincipals()); + + Map> conditions = trustConfig.getConditions(); + assertTrue(conditions.containsKey("expression")); + assertEquals("resource.name.startsWith('projects/my-project/zones/us-west')", conditions.get("expression").get("location")); } @Test @@ -142,6 +147,7 @@ public void testTrustConfigurationBuilderAliCloudPrincipals() { TrustConfiguration trustConfig = TrustConfiguration.builder() .addTrustedPrincipal("1234567890123456") // AliCloud account ID .addTrustedPrincipal("acs:ram::1234567890123456:user/AliUser") // AliCloud RAM user + .addCondition("StringEquals", "acs:CurrentRegion", "us-west-1") .build(); List expectedPrincipals = Arrays.asList( @@ -150,6 +156,10 @@ public void testTrustConfigurationBuilderAliCloudPrincipals() { ); assertEquals(expectedPrincipals, trustConfig.getTrustedPrincipals()); + + Map> conditions = trustConfig.getConditions(); + assertTrue(conditions.containsKey("StringEquals")); + assertEquals("us-west-1", conditions.get("StringEquals").get("acs:CurrentRegion")); } @Test From be140afea7cf2e370928a691e0dbbbd5f7c0014a Mon Sep 17 00:00:00 2001 From: DaisyModi Date: Wed, 29 Oct 2025 13:04:25 +0530 Subject: [PATCH 11/15] Using lombok builder and control policy --- .../multicloudj/iam/model/CreateOptions.java | 78 ++----------------- .../iam/model/CreateOptionsTest.java | 8 +- 2 files changed, 9 insertions(+), 77 deletions(-) diff --git a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/CreateOptions.java b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/CreateOptions.java index 2b84f4bc..37391676 100644 --- a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/CreateOptions.java +++ b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/CreateOptions.java @@ -1,5 +1,6 @@ package com.salesforce.multicloudj.iam.model; +import lombok.Builder; import lombok.Getter; import java.util.Objects; @@ -13,7 +14,7 @@ *

Permission boundary identifiers are provider-specific and translated internally: * - AWS: IAM Policy ARN format (arn:aws:iam::account:policy/name) * - GCP: Organization Policy constraint name or IAM Condition expression - * - AliCloud: Not supported (AliCloud RAM does not have permission boundaries) + * - AliCloud: Control Policy name or ID (Resource Directory Control Policies) * *

Usage examples by provider: *

@@ -31,35 +32,21 @@
  *     .permissionBoundary("constraints/compute.restrictLoadBalancerCreationForTypes")
  *     .build();
  *
- * // AliCloud Example (permission boundaries not supported)
+ * // AliCloud Example (using Control Policy)
  * CreateOptions aliOptions = CreateOptions.builder()
  *     .path("/foo/")
  *     .maxSessionDuration(7200)  // 2 hours
- *     // .permissionBoundary() - Not supported in AliCloud RAM
+ *     .permissionBoundary("cp-bp1example") // Control Policy ID
  *     .build();
  * 
*/ @Getter +@Builder public class CreateOptions { private final String path; private final Integer maxSessionDuration; private final String permissionBoundary; - private CreateOptions(Builder builder) { - this.path = builder.path; - this.maxSessionDuration = builder.maxSessionDuration; - this.permissionBoundary = builder.permissionBoundary; - } - - /** - * Creates a new builder for CreateOptions. - * - * @return a new Builder instance - */ - public static Builder builder() { - return new Builder(); - } - @Override public boolean equals(Object o) { @@ -84,59 +71,4 @@ public String toString() { ", permissionBoundary='" + permissionBoundary + '\'' + '}'; } - - /** - * Builder class for CreateOptions. - */ - public static class Builder { - private String path; - private Integer maxSessionDuration; - private String permissionBoundary; - - private Builder() { - } - - /** - * Sets the path for the identity. - * - * @param path the path (e.g., "/foo/") for organizing identities - * @return this Builder instance - */ - public Builder path(String path) { - this.path = path; - return this; - } - - /** - * Sets the maximum session duration in seconds. - * - * @param maxSessionDuration the maximum session duration (typically up to 12 hours = 43200 seconds) - * @return this Builder instance - */ - public Builder maxSessionDuration(Integer maxSessionDuration) { - this.maxSessionDuration = maxSessionDuration; - return this; - } - - /** - * Sets the permission boundary policy identifier. - * - * @param permissionBoundary the cloud-native identifier of the policy that acts as a permission boundary - * (AWS: policy ARN, GCP: constraint name, AliCloud: not supported) - * @return this Builder instance - */ - public Builder permissionBoundary(String permissionBoundary) { - this.permissionBoundary = permissionBoundary; - return this; - } - - /** - * Builds and returns a CreateOptions instance. - * - * @return a new CreateOptions instance - */ - public CreateOptions build() { - return new CreateOptions(this); - } - } } \ No newline at end of file diff --git a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/CreateOptionsTest.java b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/CreateOptionsTest.java index 5f46953f..9feb46b7 100644 --- a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/CreateOptionsTest.java +++ b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/CreateOptionsTest.java @@ -182,7 +182,7 @@ public void testCreateOptionsToString() { @Test public void testCreateOptionsBuilderMethodChaining() { - CreateOptions.Builder builder = CreateOptions.builder(); + CreateOptions.CreateOptionsBuilder builder = CreateOptions.builder(); // Test that each method returns the same builder instance assertSame(builder, builder.path("/test/")); @@ -244,15 +244,15 @@ public void testCreateOptionsBuilderProviderSpecificExamples() { assertEquals(Integer.valueOf(3600), gcpOptions.getMaxSessionDuration()); assertEquals("constraints/compute.restrictLoadBalancerCreationForTypes", gcpOptions.getPermissionBoundary()); - // AliCloud Example (permission boundaries not supported) + // AliCloud Example (using Control Policy) CreateOptions aliOptions = CreateOptions.builder() .path("/foo/") .maxSessionDuration(7200) // 2 hours - // Permission boundaries not supported in AliCloud RAM + .permissionBoundary("cp-bp1example") // Control Policy ID .build(); assertEquals("/foo/", aliOptions.getPath()); assertEquals(Integer.valueOf(7200), aliOptions.getMaxSessionDuration()); - assertNull(aliOptions.getPermissionBoundary()); // AliCloud doesn't support permission boundaries + assertEquals("cp-bp1example", aliOptions.getPermissionBoundary()); // AliCloud Control Policy } } \ No newline at end of file From 8b719d59a8eb8dd6dc717a27ae6910e4c40f5685 Mon Sep 17 00:00:00 2001 From: DaisyModi Date: Wed, 29 Oct 2025 15:11:51 +0530 Subject: [PATCH 12/15] Using lombok builder wherever possible --- .../multicloudj/iam/model/CreateOptions.java | 32 +-- .../multicloudj/iam/model/PolicyDocument.java | 228 +++-------------- .../multicloudj/iam/model/Statement.java | 232 ++++-------------- .../iam/model/PolicyDocumentTest.java | 209 +++++++++------- .../multicloudj/iam/model/StatementTest.java | 122 +++++---- 5 files changed, 261 insertions(+), 562 deletions(-) diff --git a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/CreateOptions.java b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/CreateOptions.java index 37391676..76bb4146 100644 --- a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/CreateOptions.java +++ b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/CreateOptions.java @@ -11,32 +11,16 @@ *

This class provides additional options that can be set during identity creation, * such as path specifications, session duration limits, and permission boundaries. * - *

Permission boundary identifiers are provider-specific and translated internally: - * - AWS: IAM Policy ARN format (arn:aws:iam::account:policy/name) - * - GCP: Organization Policy constraint name or IAM Condition expression - * - AliCloud: Control Policy name or ID (Resource Directory Control Policies) + *

Permission boundary identifiers are provider-specific and translated internally + * by the implementation layer. The client accepts the native format for the target + * cloud provider. * - *

Usage examples by provider: + *

Usage example: *

- * // AWS Example
- * CreateOptions awsOptions = CreateOptions.builder()
- *     .path("/foo/")
- *     .maxSessionDuration(43200) // 12 hours
- *     .permissionBoundary("arn:aws:iam::123456789012:policy/PowerUserBoundary")
- *     .build();
- *
- * // GCP Example (using organization policy constraint)
- * CreateOptions gcpOptions = CreateOptions.builder()
- *     .path("/foo/")
- *     .maxSessionDuration(3600)  // 1 hour
- *     .permissionBoundary("constraints/compute.restrictLoadBalancerCreationForTypes")
- *     .build();
- *
- * // AliCloud Example (using Control Policy)
- * CreateOptions aliOptions = CreateOptions.builder()
- *     .path("/foo/")
- *     .maxSessionDuration(7200)  // 2 hours
- *     .permissionBoundary("cp-bp1example") // Control Policy ID
+ * CreateOptions options = CreateOptions.builder()
+ *     .path("/service-roles/")
+ *     .maxSessionDuration(3600) // 1 hour in seconds
+ *     .permissionBoundary("policy-identifier") // Provider-specific format
  *     .build();
  * 
*/ diff --git a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/PolicyDocument.java b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/PolicyDocument.java index 820fe01b..a849d7ce 100644 --- a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/PolicyDocument.java +++ b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/PolicyDocument.java @@ -1,9 +1,10 @@ package com.salesforce.multicloudj.iam.model; import com.salesforce.multicloudj.common.exceptions.InvalidArgumentException; +import lombok.Builder; import lombok.Getter; +import lombok.Singular; -import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -17,15 +18,16 @@ *

Usage example: *

  * PolicyDocument policy = PolicyDocument.builder()
- *     .version("2012-10-17")  // Use provider-specific version (AWS example)
- *     .statement("StorageAccess")
+ *     .version("2012-10-17")
+ *     .statement(Statement.builder()
+ *         .sid("StorageAccess")
  *         .effect("Allow")
- *         .addAction("storage:GetObject")
- *         .addAction("storage:PutObject")
- *         .addPrincipal("arn:aws:iam::123456789012:user/ExampleUser")
- *         .addResource("storage://my-bucket/*")
- *         .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2")
- *     .endStatement()
+ *         .action("storage:GetObject")
+ *         .action("storage:PutObject")
+ *         .principal("arn:aws:iam::123456789012:user/ExampleUser")
+ *         .resource("storage://my-bucket/*")
+ *         .condition("StringEquals", "aws:RequestedRegion", "us-west-2")
+ *         .build())
  *     .build();
  * 
*/ @@ -34,18 +36,24 @@ public class PolicyDocument { private final String version; private final List statements; - private PolicyDocument(Builder builder) { - this.version = builder.version; - this.statements = new ArrayList<>(builder.statements); - } + @Builder + private PolicyDocument(String version, @Singular List statements) { + // Validate version is provided + if (version == null) { + throw new InvalidArgumentException("Version is required"); + } + + // Filter out null statements and validate at least one exists + List filteredStatements = statements != null + ? statements.stream().filter(Objects::nonNull).collect(java.util.stream.Collectors.toList()) + : new java.util.ArrayList<>(); + + if (filteredStatements.isEmpty()) { + throw new InvalidArgumentException("At least one statement is required"); + } - /** - * Creates a new builder for PolicyDocument. - * - * @return a new Builder instance - */ - public static Builder builder() { - return new Builder(); + this.version = version; + this.statements = filteredStatements; } @@ -71,184 +79,4 @@ public String toString() { ", statements=" + statements + '}'; } - - /** - * Builder class for PolicyDocument. - */ - public static class Builder { - private String version; - private final List statements = new ArrayList<>(); - private Statement.Builder currentStatementBuilder; - - private Builder() { - this.currentStatementBuilder = Statement.builder(); - } - - /** - * Sets the policy version. - * - * @param version the policy version (required) - * @return this Builder instance - */ - public Builder version(String version) { - this.version = version; - return this; - } - - /** - * Starts building a new statement with the given SID. - * - * @param sid the statement ID - * @return this Builder instance configured for statement building - */ - public Builder statement(String sid) { - finalizeCurrentStatement(); - this.currentStatementBuilder = Statement.builder().sid(sid); - return this; - } - - /** - * Sets the effect for the current statement. - * - * @param effect "Allow" or "Deny" - * @return this Builder instance - */ - public Builder effect(String effect) { - this.currentStatementBuilder.effect(effect); - return this; - } - - /** - * Adds a principal to the current statement. - * - * @param principal the principal (fully qualified principal required) - * @return this Builder instance - */ - public Builder addPrincipal(String principal) { - this.currentStatementBuilder.addPrincipal(principal); - return this; - } - - /** - * Adds multiple principals to the current statement. - * - * @param principals the list of principals - * @return this Builder instance - */ - public Builder addPrincipals(List principals) { - this.currentStatementBuilder.addPrincipals(principals); - return this; - } - - /** - * Adds an action to the current statement. - * - * @param action the action in substrate-neutral format - * @return this Builder instance - */ - public Builder addAction(String action) { - this.currentStatementBuilder.addAction(action); - return this; - } - - /** - * Adds multiple actions to the current statement. - * - * @param actions the list of actions - * @return this Builder instance - */ - public Builder addActions(List actions) { - this.currentStatementBuilder.addActions(actions); - return this; - } - - /** - * Adds a resource to the current statement. - * - * @param resource the resource in URI format - * @return this Builder instance - */ - public Builder addResource(String resource) { - this.currentStatementBuilder.addResource(resource); - return this; - } - - /** - * Adds multiple resources to the current statement. - * - * @param resources the list of resources - * @return this Builder instance - */ - public Builder addResources(List resources) { - this.currentStatementBuilder.addResources(resources); - return this; - } - - /** - * Adds a condition to the current statement. - * - * @param operator the condition operator - * @param key the condition key - * @param value the condition value - * @return this Builder instance - */ - public Builder addCondition(String operator, String key, Object value) { - this.currentStatementBuilder.addCondition(operator, key, value); - return this; - } - - /** - * Ends the current statement and adds it to the policy. - * - * @return this Builder instance - */ - public Builder endStatement() { - finalizeCurrentStatement(); - return this; - } - - /** - * Adds a complete statement to the policy document. - * - * @param statement the statement to add - * @return this Builder instance - */ - public Builder addStatement(Statement statement) { - finalizeCurrentStatement(); - if (statement != null) { - this.statements.add(statement); - } - return this; - } - - /** - * Builds and returns a PolicyDocument instance. - * - * @return a new PolicyDocument instance - * @throws InvalidArgumentException if version or statements are missing - */ - public PolicyDocument build() { - finalizeCurrentStatement(); - if (version == null || version.trim().isEmpty()) { - throw new InvalidArgumentException("version is required"); - } - if (statements.isEmpty()) { - throw new InvalidArgumentException("at least one statement is required"); - } - return new PolicyDocument(this); - } - - - private void finalizeCurrentStatement() { - if (currentStatementBuilder != null) { - // Only finalize statements that have the minimum required content - if (currentStatementBuilder.hasMinimumContent()) { - Statement statement = currentStatementBuilder.build(); - statements.add(statement); - } - // Always reinitialize for the next statement - currentStatementBuilder = Statement.builder(); - } - } - } } \ No newline at end of file diff --git a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/Statement.java b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/Statement.java index 9449ed5d..cc23bba6 100644 --- a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/Statement.java +++ b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/Statement.java @@ -1,10 +1,10 @@ package com.salesforce.multicloudj.iam.model; import com.salesforce.multicloudj.common.exceptions.InvalidArgumentException; +import lombok.Builder; import lombok.Getter; +import lombok.Singular; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -20,11 +20,11 @@ * Statement statement = Statement.builder() * .sid("StorageAccess") * .effect("Allow") - * .addAction("storage:GetObject") - * .addAction("storage:PutObject") - * .addPrincipal("arn:aws:iam::123456789012:user/ExampleUser") - * .addResource("storage://my-bucket/*") - * .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") + * .action("storage:GetObject") + * .action("storage:PutObject") + * .principal("arn:aws:iam::123456789012:user/ExampleUser") + * .resource("storage://my-bucket/*") + * .condition("StringEquals", "aws:RequestedRegion", "us-west-2") * .build(); *
*/ @@ -37,22 +37,51 @@ public class Statement { private final List resources; private final Map> conditions; - private Statement(Builder builder) { - this.sid = builder.sid; - this.effect = builder.effect; - this.principals = new ArrayList<>(builder.principals); - this.actions = new ArrayList<>(builder.actions); - this.resources = new ArrayList<>(builder.resources); - this.conditions = new HashMap<>(builder.conditions); + @Builder + private Statement(String sid, String effect, + @Singular List principals, + @Singular List actions, + @Singular List resources, + Map> conditions) { + // Validate effect + if (effect == null || effect.trim().isEmpty()) { + throw new InvalidArgumentException("Effect is required and cannot be empty"); + } + + // Filter out null/empty/whitespace values and validate actions + this.sid = sid; + this.effect = effect; + this.principals = filterValidStrings(principals); + this.actions = filterValidStrings(actions); + this.resources = filterValidStrings(resources); + + // Validate that at least one action exists after filtering + if (this.actions.isEmpty()) { + throw new InvalidArgumentException("At least one action is required"); + } + + this.conditions = conditions != null ? conditions : new java.util.HashMap<>(); } - /** - * Creates a new builder for Statement. - * - * @return a new Builder instance - */ - public static Builder builder() { - return new Builder(); + private static List filterValidStrings(List input) { + if (input == null) { + return new java.util.ArrayList<>(); + } + return input.stream() + .filter(s -> s != null && !s.trim().isEmpty()) + .collect(java.util.stream.Collectors.toList()); + } + + public static class StatementBuilder { + public StatementBuilder condition(String operator, String key, Object value) { + if (operator != null && key != null && value != null) { + if (this.conditions == null) { + this.conditions = new java.util.HashMap<>(); + } + this.conditions.computeIfAbsent(operator, k -> new java.util.HashMap<>()).put(key, value); + } + return this; + } } @@ -86,165 +115,4 @@ public String toString() { ", conditions=" + conditions + '}'; } - - /** - * Builder class for Statement. - */ - public static class Builder { - private String sid; - private String effect; - private final List principals = new ArrayList<>(); - private final List actions = new ArrayList<>(); - private final List resources = new ArrayList<>(); - private final Map> conditions = new HashMap<>(); - - private Builder() { - } - - /** - * Sets the statement ID. - * - * @param sid the unique identifier for this statement within the policy - * @return this Builder instance - */ - public Builder sid(String sid) { - this.sid = sid; - return this; - } - - /** - * Sets the effect. - * - * @param effect "Allow" or "Deny" - * @return this Builder instance - */ - public Builder effect(String effect) { - this.effect = effect; - return this; - } - - /** - * Adds a principal to the statement. - * - * @param principal the principal (fully qualified principal required) - * @return this Builder instance - */ - public Builder addPrincipal(String principal) { - if (principal != null && !principal.trim().isEmpty()) { - this.principals.add(principal); - } - return this; - } - - /** - * Adds multiple principals to the statement. - * - * @param principals the list of principals - * @return this Builder instance - */ - public Builder addPrincipals(List principals) { - if (principals != null) { - principals.stream() - .filter(p -> p != null && !p.trim().isEmpty()) - .forEach(this.principals::add); - } - return this; - } - - /** - * Adds an action to the statement. - * - * @param action the action in substrate-neutral format (e.g., "storage:GetObject") - * @return this Builder instance - */ - public Builder addAction(String action) { - if (action != null && !action.trim().isEmpty()) { - this.actions.add(action); - } - return this; - } - - /** - * Adds multiple actions to the statement. - * - * @param actions the list of actions - * @return this Builder instance - */ - public Builder addActions(List actions) { - if (actions != null) { - actions.stream() - .filter(a -> a != null && !a.trim().isEmpty()) - .forEach(this.actions::add); - } - return this; - } - - /** - * Adds a resource to the statement. - * - * @param resource the resource in URI format (e.g., "storage://my-bucket/*") - * @return this Builder instance - */ - public Builder addResource(String resource) { - if (resource != null && !resource.trim().isEmpty()) { - this.resources.add(resource); - } - return this; - } - - /** - * Adds multiple resources to the statement. - * - * @param resources the list of resources - * @return this Builder instance - */ - public Builder addResources(List resources) { - if (resources != null) { - resources.stream() - .filter(r -> r != null && !r.trim().isEmpty()) - .forEach(this.resources::add); - } - return this; - } - - /** - * Adds a condition to the statement. - * - * @param operator the condition operator (e.g., "StringEquals", "IpAddress") - * @param key the condition key (e.g., "aws:RequestedRegion") - * @param value the condition value - * @return this Builder instance - */ - public Builder addCondition(String operator, String key, Object value) { - if (operator != null && key != null && value != null) { - conditions.computeIfAbsent(operator, k -> new HashMap<>()).put(key, value); - } - return this; - } - - /** - * Checks if the statement has the minimum required content to be built. - * - * @return true if the statement has both effect and at least one action - */ - public boolean hasMinimumContent() { - return effect != null && !effect.trim().isEmpty() && !actions.isEmpty(); - } - - /** - * Builds and returns a Statement instance. - * - * @return a new Statement instance - * @throws InvalidArgumentException if required fields are missing - */ - public Statement build() { - if (effect == null || effect.trim().isEmpty()) { - throw new InvalidArgumentException("effect is required"); - } - if (actions.isEmpty()) { - throw new InvalidArgumentException("at least one action is required"); - } - return new Statement(this); - } - } } \ No newline at end of file diff --git a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java index 829c24e8..5fece1c8 100644 --- a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java +++ b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/PolicyDocumentTest.java @@ -7,6 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -21,14 +22,15 @@ public class PolicyDocumentTest { public void testPolicyDocumentBuilder() { PolicyDocument policy = PolicyDocument.builder() .version("2024-01-01") - .statement("StorageAccess") + .statement(Statement.builder() + .sid("StorageAccess") .effect("Allow") - .addAction("storage:GetObject") - .addAction("storage:PutObject") - .addPrincipal("arn:aws:iam::123456789012:user/ExampleUser") - .addResource("storage://my-bucket/*") - .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") - .endStatement() + .action("storage:GetObject") + .action("storage:PutObject") + .principal("arn:aws:iam::123456789012:user/ExampleUser") + .resource("storage://my-bucket/*") + .condition("StringEquals", "aws:RequestedRegion", "us-west-2") + .build()) .build(); assertEquals("2024-01-01", policy.getVersion()); @@ -49,16 +51,18 @@ public void testPolicyDocumentBuilder() { public void testMultipleStatements() { PolicyDocument policy = PolicyDocument.builder() .version(TEST_VERSION) - .statement("ReadAccess") + .statement(Statement.builder() + .sid("ReadAccess") .effect("Allow") - .addAction("storage:GetObject") - .addResource("storage://my-bucket/*") - .endStatement() - .statement("WriteAccess") + .action("storage:GetObject") + .resource("storage://my-bucket/*") + .build()) + .statement(Statement.builder() + .sid("WriteAccess") .effect("Allow") - .addAction("storage:PutObject") - .addResource("storage://my-bucket/*") - .endStatement() + .action("storage:PutObject") + .resource("storage://my-bucket/*") + .build()) .build(); assertEquals(2, policy.getStatements().size()); @@ -71,13 +75,13 @@ public void testAddCompleteStatement() { Statement statement = Statement.builder() .sid("TestStatement") .effect("Allow") - .addAction("storage:GetObject") - .addResource("storage://my-bucket/*") + .action("storage:GetObject") + .resource("storage://my-bucket/*") .build(); PolicyDocument policy = PolicyDocument.builder() .version(TEST_VERSION) - .addStatement(statement) + .statement(statement) .build(); assertEquals(1, policy.getStatements().size()); @@ -95,11 +99,12 @@ public void testEmptyPolicyThrowsException() { public void testMissingVersionThrowsException() { assertThrows(InvalidArgumentException.class, () -> { PolicyDocument.builder() - .statement("TestStatement") + .statement(Statement.builder() + .sid("TestStatement") .effect("Allow") - .addAction("storage:GetObject") - .addResource("storage://test-bucket/*") - .endStatement() + .action("storage:GetObject") + .resource("storage://test-bucket/*") + .build()) .build(); }); } @@ -109,9 +114,10 @@ public void testStatementWithoutEffectThrowsException() { assertThrows(InvalidArgumentException.class, () -> { PolicyDocument.builder() .version(TEST_VERSION) - .statement("TestStatement") - .addAction("storage:GetObject") - .endStatement() + .statement(Statement.builder() + .sid("TestStatement") + .action("storage:GetObject") + .build()) .build(); }); } @@ -121,9 +127,10 @@ public void testStatementWithoutActionsThrowsException() { assertThrows(InvalidArgumentException.class, () -> { PolicyDocument.builder() .version(TEST_VERSION) - .statement("TestStatement") + .statement(Statement.builder() + .sid("TestStatement") .effect("Allow") - .endStatement() + .build()) .build(); }); } @@ -133,11 +140,12 @@ public void testVersionHandling() { // Test custom version PolicyDocument customVersionPolicy = PolicyDocument.builder() .version("2023-06-01") - .statement("TestStatement") + .statement(Statement.builder() + .sid("TestStatement") .effect("Allow") - .addAction("storage:GetObject") - .addResource("storage://test-bucket/*") - .endStatement() + .action("storage:GetObject") + .resource("storage://test-bucket/*") + .build()) .build(); assertEquals("2023-06-01", customVersionPolicy.getVersion()); @@ -145,11 +153,12 @@ public void testVersionHandling() { // Test default version PolicyDocument defaultVersionPolicy = PolicyDocument.builder() .version(TEST_VERSION) - .statement("TestStatement") + .statement(Statement.builder() + .sid("TestStatement") .effect("Allow") - .addAction("storage:GetObject") - .addResource("storage://test-bucket/*") - .endStatement() + .action("storage:GetObject") + .resource("storage://test-bucket/*") + .build()) .build(); assertEquals(TEST_VERSION, defaultVersionPolicy.getVersion()); @@ -159,20 +168,24 @@ public void testVersionHandling() { public void testBuilderMethodsWithMultipleValues() { PolicyDocument policy = PolicyDocument.builder() .version(TEST_VERSION) - .statement("TestStatement") + .statement(Statement.builder() + .sid("TestStatement") .effect("Allow") - .addAction("storage:GetObject") - .addAction("storage:PutObject") - .addActions(Arrays.asList("storage:DeleteObject", "storage:ListObjects")) - .addResource("storage://bucket1/*") - .addResource("storage://bucket2/*") - .addResources(Arrays.asList("storage://bucket3/*", "storage://bucket4/*")) - .addPrincipal("principal1") - .addPrincipal("principal2") - .addPrincipals(Arrays.asList("principal3", "principal4")) - .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") - .addCondition("DateGreaterThan", "aws:CurrentTime", "2024-01-01T00:00:00Z") - .endStatement() + .action("storage:GetObject") + .action("storage:PutObject") + .action("storage:DeleteObject") + .action("storage:ListObjects") + .resource("storage://bucket1/*") + .resource("storage://bucket2/*") + .resource("storage://bucket3/*") + .resource("storage://bucket4/*") + .principal("principal1") + .principal("principal2") + .principal("principal3") + .principal("principal4") + .condition("StringEquals", "aws:RequestedRegion", "us-west-2") + .condition("DateGreaterThan", "aws:CurrentTime", "2024-01-01T00:00:00Z") + .build()) .build(); Statement statement = policy.getStatements().get(0); @@ -205,12 +218,14 @@ public void testBuilderMethodsWithMultipleValues() { @Test public void testBuilderAutoInitialization() { - // Test that statement methods work without calling statement() first due to auto-initialization + // Test simple statement creation with Lombok builder PolicyDocument policy1 = PolicyDocument.builder() .version("2024-01-01") - .addAction("storage:GetObject") - .effect("Allow") - .addResource("storage://test-bucket/*") + .statement(Statement.builder() + .effect("Allow") + .action("storage:GetObject") + .resource("storage://test-bucket/*") + .build()) .build(); assertEquals(1, policy1.getStatements().size()); @@ -218,12 +233,14 @@ public void testBuilderAutoInitialization() { PolicyDocument policy2 = PolicyDocument.builder() .version("2024-01-01") - .addPrincipal("principal1") - .addCondition("StringEquals", "key", "value") - .addActions(Arrays.asList("storage:GetObject")) - .addResources(Arrays.asList("storage://test-bucket/*")) - .addPrincipals(Arrays.asList("principal2")) - .effect("Allow") + .statement(Statement.builder() + .effect("Allow") + .principal("principal1") + .principal("principal2") + .action("storage:GetObject") + .resource("storage://test-bucket/*") + .condition("StringEquals", "key", "value") + .build()) .build(); assertEquals(1, policy2.getStatements().size()); @@ -238,15 +255,16 @@ public void testBuilderAutoInitialization() { public void testAddNullStatement() { PolicyDocument policy = PolicyDocument.builder() .version(TEST_VERSION) - .addStatement(null) - .statement("ValidStatement") + .statement((Statement) null) + .statement(Statement.builder() + .sid("ValidStatement") .effect("Allow") - .addAction("storage:GetObject") - .addResource("storage://test-bucket/*") - .endStatement() + .action("storage:GetObject") + .resource("storage://test-bucket/*") + .build()) .build(); - assertEquals(1, policy.getStatements().size()); + assertEquals(1, policy.getStatements().size()); // Null statements are filtered out assertEquals("ValidStatement", policy.getStatements().get(0).getSid()); } @@ -255,18 +273,19 @@ public void testMixingAddStatementAndBuilder() { Statement preBuiltStatement = Statement.builder() .sid("PreBuilt") .effect("Deny") - .addAction("storage:DeleteObject") - .addResource("storage://sensitive-bucket/*") + .action("storage:DeleteObject") + .resource("storage://sensitive-bucket/*") .build(); PolicyDocument policy = PolicyDocument.builder() .version(TEST_VERSION) - .addStatement(preBuiltStatement) - .statement("BuiltInline") + .statement(preBuiltStatement) + .statement(Statement.builder() + .sid("BuiltInline") .effect("Allow") - .addAction("storage:GetObject") - .addResource("storage://public-bucket/*") - .endStatement() + .action("storage:GetObject") + .resource("storage://public-bucket/*") + .build()) .build(); assertEquals(2, policy.getStatements().size()); @@ -278,29 +297,32 @@ public void testMixingAddStatementAndBuilder() { public void testPolicyDocumentEqualsAndHashCode() { PolicyDocument policy1 = PolicyDocument.builder() .version("2024-01-01") - .statement("TestStatement") + .statement(Statement.builder() + .sid("TestStatement") .effect("Allow") - .addAction("storage:GetObject") - .addResource("storage://test-bucket/*") - .endStatement() + .action("storage:GetObject") + .resource("storage://test-bucket/*") + .build()) .build(); PolicyDocument policy2 = PolicyDocument.builder() .version("2024-01-01") - .statement("TestStatement") + .statement(Statement.builder() + .sid("TestStatement") .effect("Allow") - .addAction("storage:GetObject") - .addResource("storage://test-bucket/*") - .endStatement() + .action("storage:GetObject") + .resource("storage://test-bucket/*") + .build()) .build(); PolicyDocument policy3 = PolicyDocument.builder() .version("2023-01-01") - .statement("DifferentStatement") + .statement(Statement.builder() + .sid("DifferentStatement") .effect("Deny") - .addAction("storage:DeleteObject") - .addResource("storage://test-bucket/*") - .endStatement() + .action("storage:DeleteObject") + .resource("storage://test-bucket/*") + .build()) .build(); // Test equals @@ -319,11 +341,12 @@ public void testPolicyDocumentEqualsAndHashCode() { public void testPolicyDocumentToString() { PolicyDocument policy = PolicyDocument.builder() .version("2024-01-01") - .statement("TestStatement") + .statement(Statement.builder() + .sid("TestStatement") .effect("Allow") - .addAction("storage:GetObject") - .addResource("storage://test-bucket/*") - .endStatement() + .action("storage:GetObject") + .resource("storage://test-bucket/*") + .build()) .build(); String result = policy.toString(); @@ -335,15 +358,15 @@ public void testPolicyDocumentToString() { @Test public void testEndStatementWithoutCurrentStatement() { - // endStatement() should be safe to call even when no statement is being built + // Simple test with Lombok builder PolicyDocument policy = PolicyDocument.builder() .version(TEST_VERSION) - .endStatement() // This should do nothing - .statement("TestStatement") + .statement(Statement.builder() + .sid("TestStatement") .effect("Allow") - .addAction("storage:GetObject") - .addResource("storage://test-bucket/*") - .endStatement() + .action("storage:GetObject") + .resource("storage://test-bucket/*") + .build()) .build(); assertEquals(1, policy.getStatements().size()); diff --git a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/StatementTest.java b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/StatementTest.java index e965b69f..e31f95c4 100644 --- a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/StatementTest.java +++ b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/model/StatementTest.java @@ -22,11 +22,11 @@ public void testStatementBuilder() { Statement statement = Statement.builder() .sid("TestStatement") .effect("Allow") - .addAction("storage:GetObject") - .addAction("storage:PutObject") - .addResource("storage://my-bucket/*") - .addPrincipal("arn:aws:iam::123456789012:user/TestUser") - .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") + .action("storage:GetObject") + .action("storage:PutObject") + .resource("storage://my-bucket/*") + .principal("arn:aws:iam::123456789012:user/TestUser") + .condition("StringEquals", "aws:RequestedRegion", "us-west-2") .build(); assertEquals("TestStatement", statement.getSid()); @@ -44,8 +44,8 @@ public void testStatementBuilderMinimal() { Statement statement = Statement.builder() .sid("MinimalStatement") .effect("Deny") - .addAction("storage:DeleteObject") - .addResource("storage://sensitive-bucket/*") + .action("storage:DeleteObject") + .resource("storage://sensitive-bucket/*") .build(); assertEquals("MinimalStatement", statement.getSid()); @@ -63,9 +63,9 @@ public void testStatementBuilderMultipleResources() { Statement statement = Statement.builder() .sid("MultiResourceStatement") .effect("Allow") - .addAction("storage:GetObject") - .addResource("storage://bucket1/*") - .addResource("storage://bucket2/*") + .action("storage:GetObject") + .resource("storage://bucket1/*") + .resource("storage://bucket2/*") .build(); assertEquals(expectedResources, statement.getResources()); @@ -81,10 +81,10 @@ public void testStatementBuilderMultiplePrincipals() { Statement statement = Statement.builder() .sid("MultiPrincipalStatement") .effect("Allow") - .addAction("storage:GetObject") - .addResource("storage://shared-bucket/*") - .addPrincipal("arn:aws:iam::123456789012:user/User1") - .addPrincipal("arn:aws:iam::123456789012:user/User2") + .action("storage:GetObject") + .resource("storage://shared-bucket/*") + .principal("arn:aws:iam::123456789012:user/User1") + .principal("arn:aws:iam::123456789012:user/User2") .build(); assertEquals(expectedPrincipals, statement.getPrincipals()); @@ -95,10 +95,10 @@ public void testStatementBuilderMultipleConditions() { Statement statement = Statement.builder() .sid("MultiConditionStatement") .effect("Allow") - .addAction("storage:GetObject") - .addResource("storage://conditional-bucket/*") - .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") - .addCondition("DateGreaterThan", "aws:CurrentTime", "2024-01-01T00:00:00Z") + .action("storage:GetObject") + .resource("storage://conditional-bucket/*") + .condition("StringEquals", "aws:RequestedRegion", "us-west-2") + .condition("DateGreaterThan", "aws:CurrentTime", "2024-01-01T00:00:00Z") .build(); assertTrue(statement.getConditions().containsKey("StringEquals")); @@ -111,8 +111,8 @@ public void testStatementBuilderMultipleConditions() { public void testStatementWithoutSid() { Statement statement = Statement.builder() .effect("Allow") - .addAction("storage:GetObject") - .addResource("storage://no-sid-bucket/*") + .action("storage:GetObject") + .resource("storage://no-sid-bucket/*") .build(); assertNull(statement.getSid()); @@ -131,8 +131,8 @@ public void testStatementWithoutEffectThrowsException() { assertThrows(InvalidArgumentException.class, () -> { Statement.builder() .sid("NoEffectStatement") - .addAction("storage:GetObject") - .addResource("storage://test-bucket/*") + .action("storage:GetObject") + .resource("storage://test-bucket/*") .build(); }); } @@ -143,7 +143,7 @@ public void testStatementWithoutActionsThrowsException() { Statement.builder() .sid("NoActionsStatement") .effect("Allow") - .addResource("storage://test-bucket/*") + .resource("storage://test-bucket/*") .build(); }); } @@ -154,7 +154,7 @@ public void testStatementWithEmptyEffect() { Statement.builder() .sid("EmptyEffectStatement") .effect("") - .addAction("storage:GetObject") + .action("storage:GetObject") .build(); }); } @@ -165,7 +165,7 @@ public void testStatementWithWhitespaceEffect() { Statement.builder() .sid("WhitespaceEffectStatement") .effect(" ") - .addAction("storage:GetObject") + .action("storage:GetObject") .build(); }); } @@ -174,22 +174,22 @@ public void testStatementWithWhitespaceEffect() { public void testNullAndEmptyValueHandling() { Statement statement = Statement.builder() .effect("Allow") - .addAction(null) - .addAction("") - .addAction(" ") - .addAction("storage:GetObject") - .addResource(null) - .addResource("") - .addResource(" ") - .addResource("storage://test-bucket/*") - .addPrincipal(null) - .addPrincipal("") - .addPrincipal(" ") - .addPrincipal("valid-principal") - .addCondition(null, "key", "value") - .addCondition("StringEquals", null, "value") - .addCondition("StringEquals", "key", null) - .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") + .action(null) + .action("") + .action(" ") + .action("storage:GetObject") + .resource(null) + .resource("") + .resource(" ") + .resource("storage://test-bucket/*") + .principal(null) + .principal("") + .principal(" ") + .principal("valid-principal") + .condition(null, "key", "value") + .condition("StringEquals", null, "value") + .condition("StringEquals", "key", null) + .condition("StringEquals", "aws:RequestedRegion", "us-west-2") .build(); assertEquals(1, statement.getActions().size()); @@ -214,9 +214,9 @@ public void testListMethodsWithNullValues() { Statement statement = Statement.builder() .effect("Allow") - .addActions(actions) - .addResources(resources) - .addPrincipals(principals) + .actions(actions) + .resources(resources) + .principals(principals) .build(); assertEquals(2, statement.getActions().size()); @@ -234,13 +234,11 @@ public void testListMethodsWithNullValues() { @Test public void testListMethodsWithNullLists() { + // Test that individual actions and resources are preserved even without using list methods Statement statement = Statement.builder() .effect("Allow") - .addAction("storage:GetObject") - .addResource("storage://test-bucket/*") - .addPrincipals(null) - .addActions(null) - .addResources(null) + .action("storage:GetObject") + .resource("storage://test-bucket/*") .build(); assertEquals(1, statement.getActions().size()); @@ -253,26 +251,24 @@ public void testStatementEqualsAndHashCode() { Statement statement1 = Statement.builder() .sid("TestStatement") .effect("Allow") - .addAction("storage:GetObject") - .addResource("storage://test-bucket/*") - .addPrincipal("principal1") - .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") + .action("storage:GetObject") + .resource("storage://test-bucket/*") + .principal("principal1") .build(); Statement statement2 = Statement.builder() .sid("TestStatement") .effect("Allow") - .addAction("storage:GetObject") - .addResource("storage://test-bucket/*") - .addPrincipal("principal1") - .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") + .action("storage:GetObject") + .resource("storage://test-bucket/*") + .principal("principal1") .build(); Statement statement3 = Statement.builder() .sid("DifferentStatement") .effect("Allow") - .addAction("storage:GetObject") - .addResource("storage://test-bucket/*") + .action("storage:GetObject") + .resource("storage://test-bucket/*") .build(); // Test equals @@ -292,10 +288,10 @@ public void testStatementToString() { Statement statement = Statement.builder() .sid("TestStatement") .effect("Allow") - .addAction("storage:GetObject") - .addResource("storage://test-bucket/*") - .addPrincipal("principal1") - .addCondition("StringEquals", "aws:RequestedRegion", "us-west-2") + .action("storage:GetObject") + .resource("storage://test-bucket/*") + .principal("principal1") + .condition("StringEquals", "aws:RequestedRegion", "us-west-2") .build(); String result = statement.toString(); From 1f5f223cdf567378bc32c2227705de242754a956 Mon Sep 17 00:00:00 2001 From: DaisyModi Date: Thu, 30 Oct 2025 10:15:09 +0530 Subject: [PATCH 13/15] Fixing checkstyle violations --- .../multicloudj/iam/model/CreateOptions.java | 54 +++--- .../multicloudj/iam/model/PolicyDocument.java | 82 ++++----- .../multicloudj/iam/model/Statement.java | 163 ++++++++++-------- 3 files changed, 160 insertions(+), 139 deletions(-) diff --git a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/CreateOptions.java b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/CreateOptions.java index 76bb4146..833491d6 100644 --- a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/CreateOptions.java +++ b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/CreateOptions.java @@ -1,10 +1,9 @@ package com.salesforce.multicloudj.iam.model; +import java.util.Objects; import lombok.Builder; import lombok.Getter; -import java.util.Objects; - /** * Optional configuration for identity creation operations. * @@ -27,32 +26,35 @@ @Getter @Builder public class CreateOptions { - private final String path; - private final Integer maxSessionDuration; - private final String permissionBoundary; - + private final String path; + private final Integer maxSessionDuration; + private final String permissionBoundary; - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - CreateOptions that = (CreateOptions) o; - return Objects.equals(path, that.path) && - Objects.equals(maxSessionDuration, that.maxSessionDuration) && - Objects.equals(permissionBoundary, that.permissionBoundary); + @Override + public boolean equals(Object o) { + if (this == o) { + return true; } - - @Override - public int hashCode() { - return Objects.hash(path, maxSessionDuration, permissionBoundary); + if (o == null || getClass() != o.getClass()) { + return false; } + CreateOptions that = (CreateOptions) o; + return Objects.equals(path, that.path) + && Objects.equals(maxSessionDuration, that.maxSessionDuration) + && Objects.equals(permissionBoundary, that.permissionBoundary); + } - @Override - public String toString() { - return "CreateOptions{" + - "path='" + path + '\'' + - ", maxSessionDuration=" + maxSessionDuration + - ", permissionBoundary='" + permissionBoundary + '\'' + - '}'; - } + @Override + public int hashCode() { + return Objects.hash(path, maxSessionDuration, permissionBoundary); + } + + @Override + public String toString() { + return "CreateOptions{" + + "path='" + path + '\'' + + ", maxSessionDuration=" + maxSessionDuration + + ", permissionBoundary='" + permissionBoundary + '\'' + + '}'; + } } \ No newline at end of file diff --git a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/PolicyDocument.java b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/PolicyDocument.java index a849d7ce..54f74340 100644 --- a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/PolicyDocument.java +++ b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/PolicyDocument.java @@ -1,13 +1,12 @@ package com.salesforce.multicloudj.iam.model; import com.salesforce.multicloudj.common.exceptions.InvalidArgumentException; +import java.util.List; +import java.util.Objects; import lombok.Builder; import lombok.Getter; import lombok.Singular; -import java.util.List; -import java.util.Objects; - /** * Represents a substrate-neutral policy document containing multiple statements. * @@ -33,50 +32,55 @@ */ @Getter public class PolicyDocument { - private final String version; - private final List statements; - - @Builder - private PolicyDocument(String version, @Singular List statements) { - // Validate version is provided - if (version == null) { - throw new InvalidArgumentException("Version is required"); - } + private final String version; + private final List statements; - // Filter out null statements and validate at least one exists - List filteredStatements = statements != null - ? statements.stream().filter(Objects::nonNull).collect(java.util.stream.Collectors.toList()) - : new java.util.ArrayList<>(); + @Builder + private PolicyDocument(String version, @Singular List statements) { + // Validate version is provided + if (version == null) { + throw new InvalidArgumentException("Version is required"); + } - if (filteredStatements.isEmpty()) { - throw new InvalidArgumentException("At least one statement is required"); - } + // Filter out null statements and validate at least one exists + List filteredStatements = statements != null + ? statements.stream().filter(Objects::nonNull) + .collect(java.util.stream.Collectors.toList()) + : new java.util.ArrayList<>(); - this.version = version; - this.statements = filteredStatements; + if (filteredStatements.isEmpty()) { + throw new InvalidArgumentException("At least one statement is required"); } + this.version = version; + this.statements = filteredStatements; + } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - PolicyDocument that = (PolicyDocument) o; - return Objects.equals(version, that.version) && - Objects.equals(statements, that.statements); - } - @Override - public int hashCode() { - return Objects.hash(version, statements); + @Override + public boolean equals(Object o) { + if (this == o) { + return true; } - - @Override - public String toString() { - return "PolicyDocument{" + - "version='" + version + '\'' + - ", statements=" + statements + - '}'; + if (o == null || getClass() != o.getClass()) { + return false; } + PolicyDocument that = (PolicyDocument) o; + return Objects.equals(version, that.version) + && Objects.equals(statements, that.statements); + } + + @Override + public int hashCode() { + return Objects.hash(version, statements); + } + + @Override + public String toString() { + return "PolicyDocument{" + + "version='" + version + '\'' + + ", statements=" + statements + + '}'; + } } \ No newline at end of file diff --git a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/Statement.java b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/Statement.java index cc23bba6..d98921ff 100644 --- a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/Statement.java +++ b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/Statement.java @@ -1,13 +1,12 @@ package com.salesforce.multicloudj.iam.model; import com.salesforce.multicloudj.common.exceptions.InvalidArgumentException; -import lombok.Builder; -import lombok.Getter; -import lombok.Singular; - import java.util.List; import java.util.Map; import java.util.Objects; +import lombok.Builder; +import lombok.Getter; +import lombok.Singular; /** * Represents a single statement within a policy document. @@ -30,89 +29,105 @@ */ @Getter public class Statement { - private final String sid; - private final String effect; - private final List principals; - private final List actions; - private final List resources; - private final Map> conditions; - - @Builder - private Statement(String sid, String effect, - @Singular List principals, - @Singular List actions, - @Singular List resources, - Map> conditions) { - // Validate effect - if (effect == null || effect.trim().isEmpty()) { - throw new InvalidArgumentException("Effect is required and cannot be empty"); - } + private final String sid; + private final String effect; + private final List principals; + private final List actions; + private final List resources; + private final Map> conditions; - // Filter out null/empty/whitespace values and validate actions - this.sid = sid; - this.effect = effect; - this.principals = filterValidStrings(principals); - this.actions = filterValidStrings(actions); - this.resources = filterValidStrings(resources); + @Builder + private Statement(String sid, String effect, + @Singular List principals, + @Singular List actions, + @Singular List resources, + Map> conditions) { + // Validate effect + if (effect == null || effect.trim().isEmpty()) { + throw new InvalidArgumentException("Effect is required and cannot be empty"); + } - // Validate that at least one action exists after filtering - if (this.actions.isEmpty()) { - throw new InvalidArgumentException("At least one action is required"); - } + // Filter out null/empty/whitespace values and validate actions + this.sid = sid; + this.effect = effect; + this.principals = filterValidStrings(principals); + this.actions = filterValidStrings(actions); + this.resources = filterValidStrings(resources); - this.conditions = conditions != null ? conditions : new java.util.HashMap<>(); + // Validate that at least one action exists after filtering + if (this.actions.isEmpty()) { + throw new InvalidArgumentException("At least one action is required"); } - private static List filterValidStrings(List input) { - if (input == null) { - return new java.util.ArrayList<>(); - } - return input.stream() - .filter(s -> s != null && !s.trim().isEmpty()) - .collect(java.util.stream.Collectors.toList()); + this.conditions = conditions != null ? conditions : new java.util.HashMap<>(); + } + + private static List filterValidStrings(List input) { + if (input == null) { + return new java.util.ArrayList<>(); } + return input.stream() + .filter(s -> s != null && !s.trim().isEmpty()) + .collect(java.util.stream.Collectors.toList()); + } - public static class StatementBuilder { - public StatementBuilder condition(String operator, String key, Object value) { - if (operator != null && key != null && value != null) { - if (this.conditions == null) { - this.conditions = new java.util.HashMap<>(); - } - this.conditions.computeIfAbsent(operator, k -> new java.util.HashMap<>()).put(key, value); - } - return this; + /** + * Custom builder for Statement to handle conditions. + */ + public static class StatementBuilder { + /** + * Adds a condition to the statement. + * + * @param operator the condition operator + * @param key the condition key + * @param value the condition value + * @return this builder + */ + public StatementBuilder condition(String operator, String key, Object value) { + if (operator != null && key != null && value != null) { + if (this.conditions == null) { + this.conditions = new java.util.HashMap<>(); } + this.conditions.computeIfAbsent(operator, k -> new java.util.HashMap<>()) + .put(key, value); + } + return this; } + } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Statement statement = (Statement) o; - return Objects.equals(sid, statement.sid) && - Objects.equals(effect, statement.effect) && - Objects.equals(principals, statement.principals) && - Objects.equals(actions, statement.actions) && - Objects.equals(resources, statement.resources) && - Objects.equals(conditions, statement.conditions); + @Override + public boolean equals(Object o) { + if (this == o) { + return true; } - - @Override - public int hashCode() { - return Objects.hash(sid, effect, principals, actions, resources, conditions); + if (o == null || getClass() != o.getClass()) { + return false; } + Statement statement = (Statement) o; + return Objects.equals(sid, statement.sid) + && Objects.equals(effect, statement.effect) + && Objects.equals(principals, statement.principals) + && Objects.equals(actions, statement.actions) + && Objects.equals(resources, statement.resources) + && Objects.equals(conditions, statement.conditions); + } - @Override - public String toString() { - return "Statement{" + - "sid='" + sid + '\'' + - ", effect='" + effect + '\'' + - ", principals=" + principals + - ", actions=" + actions + - ", resources=" + resources + - ", conditions=" + conditions + - '}'; - } + @Override + public int hashCode() { + return Objects.hash(sid, effect, principals, actions, resources, conditions); + } + + @Override + public String toString() { + return "Statement{" + + "sid='" + sid + '\'' + + ", effect='" + effect + '\'' + + ", principals=" + principals + + ", actions=" + actions + + ", resources=" + resources + + ", conditions=" + conditions + + '}'; + } } \ No newline at end of file From 08f934e63f4e4b3813173cd03a92421756d2447c Mon Sep 17 00:00:00 2001 From: DaisyModi Date: Thu, 30 Oct 2025 12:22:03 +0530 Subject: [PATCH 14/15] Adding iam dependencies to parent pom --- .../multicloudj/iam/model/TrustConfiguration.java | 3 +-- pom.xml | 11 +++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/TrustConfiguration.java b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/TrustConfiguration.java index 06f8dce2..aab639a0 100644 --- a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/TrustConfiguration.java +++ b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/TrustConfiguration.java @@ -1,12 +1,11 @@ package com.salesforce.multicloudj.iam.model; -import lombok.Getter; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import lombok.Getter; /** * Configuration for trust relationships in identity creation. diff --git a/pom.xml b/pom.xml index dcf18a41..7d252f2f 100644 --- a/pom.xml +++ b/pom.xml @@ -153,6 +153,17 @@ ${project.version} test-jar + + com.salesforce.multicloudj + iam-client + ${project.version} + + + com.salesforce.multicloudj + iam-client + ${project.version} + test-jar + com.salesforce.multicloudj docstore-client From 67198809dd2de96f3ca96ae147eed9e79e020d75 Mon Sep 17 00:00:00 2001 From: DaisyModi Date: Thu, 30 Oct 2025 15:36:23 +0530 Subject: [PATCH 15/15] Removing circular dependencies --- .../multicloudj/iam/model/TrustConfiguration.java | 1 + pom.xml | 11 ----------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/TrustConfiguration.java b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/TrustConfiguration.java index aab639a0..e059a2ab 100644 --- a/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/TrustConfiguration.java +++ b/iam/iam-client/src/main/java/com/salesforce/multicloudj/iam/model/TrustConfiguration.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; + import lombok.Getter; /** diff --git a/pom.xml b/pom.xml index 7d252f2f..dcf18a41 100644 --- a/pom.xml +++ b/pom.xml @@ -153,17 +153,6 @@ ${project.version} test-jar - - com.salesforce.multicloudj - iam-client - ${project.version} - - - com.salesforce.multicloudj - iam-client - ${project.version} - test-jar - com.salesforce.multicloudj docstore-client