diff --git a/.brazil.json b/.brazil.json index 1f0931d0747b..bce97f55c9f9 100644 --- a/.brazil.json +++ b/.brazil.json @@ -18,6 +18,7 @@ "codegen": { "packageName": "AwsJavaSdk-Codegen" }, "dynamodb-enhanced": { "packageName": "AwsJavaSdk-DynamoDb-Enhanced" }, "http-client-spi": { "packageName": "AwsJavaSdk-HttpClient" }, + "iam-policy-builder": { "packageName": "AwsJavaSdk-Iam-PolicyBuilder" }, "json-utils": { "packageName": "AwsJavaSdk-Core-JsonUtils" }, "metrics-spi": { "packageName": "AwsJavaSdk-Core-MetricsSpi" }, "endpoints-spi": { "packageName": "AwsJavaSdk-Core-EndpointsSpi" }, diff --git a/.changes/next-release/feature-AWSSDKforJavav2-a3a039b.json b/.changes/next-release/feature-AWSSDKforJavav2-a3a039b.json new file mode 100644 index 000000000000..d508c5b55f2a --- /dev/null +++ b/.changes/next-release/feature-AWSSDKforJavav2-a3a039b.json @@ -0,0 +1,6 @@ +{ + "type": "feature", + "category": "AWS SDK for Java v2", + "contributor": "", + "description": "Added support for IamPolicy in new module software.amazon.awssdk:iam-policy-builder, a class to simplify the use of AWS policies." +} diff --git a/core/annotations/src/main/java/software/amazon/awssdk/annotations/NotNull.java b/core/annotations/src/main/java/software/amazon/awssdk/annotations/NotNull.java new file mode 100644 index 000000000000..eb101d7b7cb0 --- /dev/null +++ b/core/annotations/src/main/java/software/amazon/awssdk/annotations/NotNull.java @@ -0,0 +1,39 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The annotated element must not be null. Accepts any type. + *

+ * This is useful to tell linting and testing tools that a particular value will never be null. It's not meant to be used on + * public interfaces as something that customers should rely on. + */ +@Documented +@Target({ElementType.METHOD, + ElementType.FIELD, + ElementType.ANNOTATION_TYPE, + ElementType.CONSTRUCTOR, + ElementType.PARAMETER}) +@Retention(RetentionPolicy.CLASS) +@SdkProtectedApi +public @interface NotNull { +} diff --git a/core/json-utils/src/main/java/software/amazon/awssdk/protocols/jsoncore/JsonWriter.java b/core/json-utils/src/main/java/software/amazon/awssdk/protocols/jsoncore/JsonWriter.java index a36d6efc2efe..a7b6a89d0e9f 100644 --- a/core/json-utils/src/main/java/software/amazon/awssdk/protocols/jsoncore/JsonWriter.java +++ b/core/json-utils/src/main/java/software/amazon/awssdk/protocols/jsoncore/JsonWriter.java @@ -20,6 +20,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.math.BigDecimal; import java.math.BigInteger; import java.nio.ByteBuffer; @@ -36,17 +37,17 @@ */ @SdkProtectedApi public class JsonWriter implements SdkAutoCloseable { - private static final int DEFAULT_BUFFER_SIZE = 1024; - private final JsonFactory jsonFactory; private final ByteArrayOutputStream baos; private final JsonGenerator generator; private JsonWriter(Builder builder) { - jsonFactory = builder.jsonFactory != null ? builder.jsonFactory : DEFAULT_JSON_FACTORY; + JsonGeneratorFactory jsonGeneratorFactory = builder.jsonGeneratorFactory != null + ? builder.jsonGeneratorFactory + : DEFAULT_JSON_FACTORY::createGenerator; try { baos = new ByteArrayOutputStream(DEFAULT_BUFFER_SIZE); - generator = jsonFactory.createGenerator(baos); + generator = jsonGeneratorFactory.createGenerator(baos); } catch (IOException e) { throw new JsonGenerationException(e); } @@ -170,7 +171,7 @@ private JsonWriter unsafeWrite(FunctionalUtils.UnsafeRunnable r) { * A builder for configuring and creating {@link JsonWriter}. Created via {@link #builder()}. */ public static final class Builder { - private JsonFactory jsonFactory; + private JsonGeneratorFactory jsonGeneratorFactory; private Builder() { } @@ -179,13 +180,31 @@ private Builder() { * The {@link JsonFactory} implementation to be used when parsing the input. This allows JSON extensions like CBOR or * Ion to be supported. * - *

It's highly recommended us use a shared {@code JsonFactory} where possible, so they should be stored statically: + *

It's highly recommended to use a shared {@code JsonFactory} where possible, so they should be stored statically: * http://wiki.fasterxml.com/JacksonBestPracticesPerformance * *

By default, this is {@link JsonNodeParser#DEFAULT_JSON_FACTORY}. + * + *

Setting this value will also override any values set via {@link #jsonGeneratorFactory}. */ public JsonWriter.Builder jsonFactory(JsonFactory jsonFactory) { - this.jsonFactory = jsonFactory; + jsonGeneratorFactory(jsonFactory::createGenerator); + return this; + } + + /** + * A factory for {@link JsonGenerator}s based on an {@link OutputStream}. This allows custom JSON generator + * configuration, like pretty-printing output. + * + *

It's highly recommended to use a shared {@code JsonFactory} within this generator factory, where possible, so they + * should be stored statically: http://wiki.fasterxml.com/JacksonBestPracticesPerformance + * + *

By default, this delegates to {@link JsonNodeParser#DEFAULT_JSON_FACTORY} to create the generator. + * + *

Setting this value will also override any values set via {@link #jsonFactory}. + */ + public JsonWriter.Builder jsonGeneratorFactory(JsonGeneratorFactory jsonGeneratorFactory) { + this.jsonGeneratorFactory = jsonGeneratorFactory; return this; } @@ -197,6 +216,14 @@ public JsonWriter build() { } } + /** + * Generate a {@link JsonGenerator} for a {@link OutputStream}. This will get called once for each "write" call. + */ + @FunctionalInterface + public interface JsonGeneratorFactory { + JsonGenerator createGenerator(OutputStream outputStream) throws IOException; + } + /** * Indicates an issue writing JSON content. */ diff --git a/services-custom/iam-policy-builder/pom.xml b/services-custom/iam-policy-builder/pom.xml new file mode 100644 index 000000000000..54a4327e06c7 --- /dev/null +++ b/services-custom/iam-policy-builder/pom.xml @@ -0,0 +1,134 @@ + + + + + 4.0.0 + + software.amazon.awssdk + aws-sdk-java-pom + 2.20.104-SNAPSHOT + ../../pom.xml + + iam-policy-builder + ${awsjavasdk.version} + AWS Java SDK :: IAM :: Policy Builder + + Library simplifying the building, marshalling and unmarshalling of IAM Policies. + + https://aws.amazon.com/sdkforjava + + + 1.8 + ${project.parent.version} + + + + + + software.amazon.awssdk + bom-internal + ${awsjavasdk.version} + pom + import + + + + + + + software.amazon.awssdk + utils + ${awsjavasdk.version} + + + software.amazon.awssdk + annotations + ${awsjavasdk.version} + + + software.amazon.awssdk + json-utils + ${awsjavasdk.version} + + + software.amazon.awssdk + third-party-jackson-core + ${awsjavasdk.version} + + + + + org.junit.jupiter + junit-jupiter + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit5.version} + test + + + org.assertj + assertj-core + test + + + org.mockito + mockito-core + test + + + org.apache.logging.log4j + log4j-api + test + + + org.apache.logging.log4j + log4j-core + test + + + org.apache.logging.log4j + log4j-slf4j-impl + test + + + nl.jqno.equalsverifier + equalsverifier + test + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + software.amazon.awssdk.policybuilder.iam + + + + + + + + diff --git a/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamAction.java b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamAction.java new file mode 100644 index 000000000000..4c0d82d42acb --- /dev/null +++ b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamAction.java @@ -0,0 +1,42 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam; + +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.annotations.ThreadSafe; +import software.amazon.awssdk.policybuilder.iam.internal.DefaultIamAction; + +/** + * The {@code Action} element of a {@link IamStatement}, specifying which service actions the statement applies to. + * + * @see Action user guide + */ +@SdkPublicApi +@ThreadSafe +public interface IamAction extends IamValue { + /** + * An {@link IamAction} representing ALL actions. When used on a statement, it means the policy should apply to + * every action. + */ + IamAction ALL = create("*"); + + /** + * Create a new {@code IamAction} element with the provided {@link #value()}. + */ + static IamAction create(String value) { + return new DefaultIamAction(value); + } +} diff --git a/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamCondition.java b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamCondition.java new file mode 100644 index 000000000000..5db7a752850d --- /dev/null +++ b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamCondition.java @@ -0,0 +1,198 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam; + +import static java.util.Collections.emptyList; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.annotations.ThreadSafe; +import software.amazon.awssdk.policybuilder.iam.internal.DefaultIamCondition; +import software.amazon.awssdk.utils.builder.CopyableBuilder; +import software.amazon.awssdk.utils.builder.ToCopyableBuilder; + +/** + * The {@code Condition} element of a {@link IamStatement}, specifying the conditions in which the statement is in effect. + * + * @see + * Condition user guide + */ +@SdkPublicApi +@ThreadSafe +public interface IamCondition extends ToCopyableBuilder { + /** + * Create an {@link IamCondition} of the supplied operator, key and value (see + * {@link Builder#operator(IamConditionOperator)}}, {@link Builder#key(IamConditionKey)} and {@link Builder#value(String)}). + *

+ * All of operator, key and value are required. This is equivalent to {@code IamCondition.builder().operator(operator) + * .key(key).value(value).build()}. + */ + static IamCondition create(IamConditionOperator operator, IamConditionKey key, String value) { + return builder().operator(operator).key(key).value(value).build(); + } + + /** + * Create an {@link IamCondition} of the supplied operator, key and value (see + * {@link Builder#operator(IamConditionOperator)}}, {@link Builder#key(String)} and {@link Builder#value(String)}). + *

+ * All of operator, key and value are required. This is equivalent to {@code IamCondition.builder().operator(operator) + * .key(key).value(value).build()}. + */ + static IamCondition create(IamConditionOperator operator, String key, String value) { + return builder().operator(operator).key(key).value(value).build(); + } + + /** + * Create an {@link IamCondition} of the supplied operator, key and value (see + * {@link Builder#operator(String)}}, {@link Builder#key(String)} and {@link Builder#value(String)}). + *

+ * All of operator, key and value are required. This is equivalent to {@code IamCondition.builder().operator(operator) + * .key(key).value(value).build()}. + */ + static IamCondition create(String operator, String key, String value) { + return builder().operator(operator).key(key).value(value).build(); + } + + /** + * Create multiple {@link IamCondition}s with the same {@link IamConditionOperator} and {@link IamConditionKey}, but + * different values (see {@link Builder#operator(IamConditionOperator)}}, {@link Builder#key(IamConditionKey)} and + * {@link Builder#value(String)}). + *

+ * Operator and key are required, and the values in the value list must not be null. This is equivalent to calling + * {@link #create(IamConditionOperator, IamConditionKey, String)} multiple times and collecting the results into a list. + */ + static List createAll(IamConditionOperator operator, IamConditionKey key, Collection values) { + if (values == null) { + return emptyList(); + } + return values.stream().map(value -> create(operator, key, value)).collect(Collectors.toCollection(ArrayList::new)); + } + + /** + * Create multiple {@link IamCondition}s with the same {@link IamConditionOperator} and {@link IamConditionKey}, but + * different values (see {@link Builder#operator(IamConditionOperator)}}, {@link Builder#key(String)} and + * {@link Builder#value(String)}). + *

+ * Operator and key are required, and the values in the value list must not be null. This is equivalent to calling + * {@link #create(IamConditionOperator, String, String)} multiple times and collecting the results into a list. + */ + static List createAll(IamConditionOperator operator, String key, Collection values) { + if (values == null) { + return emptyList(); + } + return values.stream().map(value -> create(operator, key, value)).collect(Collectors.toCollection(ArrayList::new)); + } + + /** + * Create multiple {@link IamCondition}s with the same {@link IamConditionOperator} and {@link IamConditionKey}, but + * different values (see {@link Builder#operator(String)}}, {@link Builder#key(String)} and {@link Builder#value(String)}). + *

+ * Operator and key are required, and the values in the value list must not be null. This is equivalent to calling + * {@link #create(String, String, String)} multiple times and collecting the results into a list. + */ + static List createAll(String operator, String key, Collection values) { + if (values == null) { + return emptyList(); + } + + return values.stream().map(value -> create(operator, key, value)).collect(Collectors.toCollection(ArrayList::new)); + } + + /** + * Create a {@link Builder} for an {@code IamCondition}. + */ + static Builder builder() { + return DefaultIamCondition.builder(); + } + + + /** + * Retrieve the value set by {@link Builder#operator(IamConditionOperator)}. + */ + IamConditionOperator operator(); + + /** + * Retrieve the value set by {@link Builder#key(IamConditionKey)}. + */ + IamConditionKey key(); + + /** + * Retrieve the value set by {@link Builder#value(String)}. + */ + String value(); + + /** + * @see #builder() + */ + interface Builder extends CopyableBuilder { + /** + * Set the {@link IamConditionOperator} of this condition. + *

+ * This value is required. + * + * @see IamConditionOperator + * @see Condition + * user guide + */ + Builder operator(IamConditionOperator operator); + + /** + * Set the {@link IamConditionOperator} of this condition. + *

+ * This is the same as {@link #operator(IamConditionOperator)}, except you do not need to call + * {@code IamConditionOperator.create()}. This value is required. + * + * @see IamConditionOperator + * @see Condition + * user guide + */ + Builder operator(String operator); + + /** + * Set the {@link IamConditionKey} of this condition. + *

+ * This value is required. + * + * @see IamConditionKey + * @see Condition + * user guide + */ + Builder key(IamConditionKey key); + + /** + * Set the {@link IamConditionKey} of this condition. + *

+ * This is the same as {@link #key(IamConditionKey)}, except you do not need to call + * {@code IamConditionKey.create()}. This value is required. + * + * @see IamConditionKey + * @see Condition + * user guide + */ + Builder key(String key); + + /** + * Set the "right hand side" value of this condition. + * + * @see Condition + * user guide + */ + Builder value(String value); + } +} diff --git a/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamConditionKey.java b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamConditionKey.java new file mode 100644 index 000000000000..b2b7a41c89d3 --- /dev/null +++ b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamConditionKey.java @@ -0,0 +1,38 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam; + +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.annotations.ThreadSafe; +import software.amazon.awssdk.policybuilder.iam.internal.DefaultIamConditionKey; + +/** + * The {@code IamConditionKey} specifies the "left hand side" of an {@link IamCondition}. + * + * @see IamCondition + * @see Condition + * user guide + */ +@SdkPublicApi +@ThreadSafe +public interface IamConditionKey extends IamValue { + /** + * Create a new {@code IamConditionKey} element with the provided {@link #value()}. + */ + static IamConditionKey create(String value) { + return new DefaultIamConditionKey(value); + } +} diff --git a/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamConditionOperator.java b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamConditionOperator.java new file mode 100644 index 000000000000..02e77e81c321 --- /dev/null +++ b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamConditionOperator.java @@ -0,0 +1,305 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam; + +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.annotations.ThreadSafe; +import software.amazon.awssdk.policybuilder.iam.internal.DefaultIamConditionOperator; + +/** + * The {@code IamConditionOperator} specifies the operator that should be applied to compare the {@link IamConditionKey} to an + * expected value in an {@link IamCondition}. + * + * @see IamCondition + * @see Condition + * user guide + */ +@SdkPublicApi +@ThreadSafe +public interface IamConditionOperator extends IamValue { + /** + * A string comparison of the {@link IamCondition#key()} and {@link IamCondition#value()}. + * + * @see + * + * String conditions + */ + IamConditionOperator STRING_EQUALS = create("StringEquals"); + + /** + * A negated string comparison of the {@link IamCondition#key()} and {@link IamCondition#value()}. + * + * @see + * + * String conditions + */ + IamConditionOperator STRING_NOT_EQUALS = create("StringNotEquals"); + + /** + * A string comparison, ignoring casing, of the {@link IamCondition#key()} and {@link IamCondition#value()}. + * + * @see + * + * String conditions + */ + IamConditionOperator STRING_EQUALS_IGNORE_CASE = create("StringEqualsIgnoreCase"); + + /** + * A negated string comparison, ignoring casing, of the {@link IamCondition#key()} and {@link IamCondition#value()}. + * + * @see + * + * String conditions + */ + IamConditionOperator STRING_NOT_EQUALS_IGNORE_CASE = create("StringNotEqualsIgnoreCase"); + + /** + * A case-sensitive pattern match between the {@link IamCondition#key()} and {@link IamCondition#value()}. + * + * @see + * + * String conditions + */ + IamConditionOperator STRING_LIKE = create("StringLike"); + + /** + * A negated case-sensitive pattern match between the {@link IamCondition#key()} and {@link IamCondition#value()}. + * + * @see + * + * String conditions + */ + IamConditionOperator STRING_NOT_LIKE = create("StringNotLike"); + + /** + * A numeric comparison of the {@link IamCondition#key()} and {@link IamCondition#value()}. + * + * @see + * + * Numeric conditions + */ + IamConditionOperator NUMERIC_EQUALS = create("NumericEquals"); + + /** + * A negated numeric comparison of the {@link IamCondition#key()} and {@link IamCondition#value()}. + * + * @see + * + * Numeric conditions + */ + IamConditionOperator NUMERIC_NOT_EQUALS = create("NumericNotEquals"); + + /** + * A numeric comparison of whether the {@link IamCondition#key()} is "less than" the {@link IamCondition#value()}. + * + * @see + * + * Numeric conditions + */ + IamConditionOperator NUMERIC_LESS_THAN = create("NumericLessThan"); + + /** + * A numeric comparison of whether the {@link IamCondition#key()} is "less than or equal to" the {@link IamCondition#value()}. + * + * @see + * + * Numeric conditions + */ + IamConditionOperator NUMERIC_LESS_THAN_EQUALS = create("NumericLessThanEquals"); + + /** + * A numeric comparison of whether the {@link IamCondition#key()} is "greater than" the {@link IamCondition#value()}. + * + * @see + * + * Numeric conditions + */ + IamConditionOperator NUMERIC_GREATER_THAN = create("NumericGreaterThan"); + + /** + * A numeric comparison of whether the {@link IamCondition#key()} is "greater than or equal to" the + * {@link IamCondition#value()}. + * + * @see + * + * Numeric conditions + */ + IamConditionOperator NUMERIC_GREATER_THAN_EQUALS = create("NumericGreaterThanEquals"); + + /** + * A date comparison of the {@link IamCondition#key()} and {@link IamCondition#value()}. + * + * @see + * + * Date conditions + */ + IamConditionOperator DATE_EQUALS = create("DateEquals"); + + /** + * A negated date comparison of the {@link IamCondition#key()} and {@link IamCondition#value()}. + * + * @see + * + * Date conditions + */ + IamConditionOperator DATE_NOT_EQUALS = create("DateNotEquals"); + + /** + * A date comparison of whether the {@link IamCondition#key()} "is earlier than" the {@link IamCondition#value()}. + * + * @see + * + * Date conditions + */ + IamConditionOperator DATE_LESS_THAN = create("DateLessThan"); + + /** + * A date comparison of whether the {@link IamCondition#key()} "is earlier than or the same date as" the + * {@link IamCondition#value()}. + * + * @see + * + * Date conditions + */ + IamConditionOperator DATE_LESS_THAN_EQUALS = create("DateLessThanEquals"); + + /** + * A date comparison of whether the {@link IamCondition#key()} "is later than" the {@link IamCondition#value()}. + * + * @see + * + * Date conditions + */ + IamConditionOperator DATE_GREATER_THAN = create("DateGreaterThan"); + + /** + * A date comparison of whether the {@link IamCondition#key()} "is later than or the same date as" the + * {@link IamCondition#value()}. + * + * @see + * + * Date conditions + */ + IamConditionOperator DATE_GREATER_THAN_EQUALS = create("DateGreaterThanEquals"); + + /** + * A boolean comparison of the {@link IamCondition#key()} and the {@link IamCondition#value()}. + * + * @see + * + * Boolean conditions + */ + IamConditionOperator BOOL = create("Bool"); + + /** + * A binary comparison of the {@link IamCondition#key()} and the {@link IamCondition#value()}. + * + * @see + * + * Binary conditions + */ + IamConditionOperator BINARY_EQUALS = create("BinaryEquals"); + + /** + * An IP address comparison of the {@link IamCondition#key()} and the {@link IamCondition#value()}. + * + * @see + * + * IP Address conditions + */ + IamConditionOperator IP_ADDRESS = create("IpAddress"); + + /** + * A negated IP address comparison of the {@link IamCondition#key()} and the {@link IamCondition#value()}. + * + * @see + * + * IP Address conditions + */ + IamConditionOperator NOT_IP_ADDRESS = create("NotIpAddress"); + + /** + * An Amazon Resource Name (ARN) comparison of the {@link IamCondition#key()} and the {@link IamCondition#value()}. + * + * @see + * + * ARN conditions + */ + IamConditionOperator ARN_EQUALS = create("ArnEquals"); + + /** + * A negated Amazon Resource Name (ARN) comparison of the {@link IamCondition#key()} and the {@link IamCondition#value()}. + * + * @see + * + * ARN conditions + */ + IamConditionOperator ARN_NOT_EQUALS = create("ArnNotEquals"); + + /** + * A pattern match of the Amazon Resource Names (ARNs) in the {@link IamCondition#key()} and the {@link IamCondition#value()}. + * + * @see + * + * ARN conditions + */ + IamConditionOperator ARN_LIKE = create("ArnLike"); + + /** + * A negated pattern match of the Amazon Resource Names (ARNs) in the {@link IamCondition#key()} and the + * {@link IamCondition#value()}. + * + * @see + * + * ARN conditions + */ + IamConditionOperator ARN_NOT_LIKE = create("ArnNotLike"); + + /** + * A check to determine whether the {@link IamCondition#key()} is present (use "false" in the {@link IamCondition#value()}) + * or not present (use "true" in the {@link IamCondition#value()}). + * + * @see + * + * ARN conditions + */ + IamConditionOperator NULL = create("Null"); + + /** + * Create a new {@link IamConditionOperator} with the provided string added as a prefix. + *

+ * This is useful when adding + * the + * "ForAllValues:" or "ForAnyValues:" prefixes to an operator. + */ + IamConditionOperator addPrefix(String prefix); + + /** + * Create a new {@link IamConditionOperator} with the provided string added as a suffix. + *

+ * This is useful when adding + * + * the "IfExists" suffix to an operator. + */ + IamConditionOperator addSuffix(String suffix); + + /** + * Create a new {@code IamConditionOperator} element with the provided {@link #value()}. + */ + static IamConditionOperator create(String value) { + return new DefaultIamConditionOperator(value); + } +} \ No newline at end of file diff --git a/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamEffect.java b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamEffect.java new file mode 100644 index 000000000000..40dbf23ac8ae --- /dev/null +++ b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamEffect.java @@ -0,0 +1,50 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam; + +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.annotations.ThreadSafe; +import software.amazon.awssdk.policybuilder.iam.internal.DefaultIamEffect; + +/** + * The {@code Effect} element of a {@link IamStatement}, specifying whether the statement should ALLOW or DENY certain actions. + * + * @see Effect user guide + */ +@SdkPublicApi +@ThreadSafe +public interface IamEffect extends IamValue { + /** + * The {@link IamStatement} to which this effect is attached should ALLOW the actions described in the policy, and DENY + * everything else. + */ + IamEffect ALLOW = create("Allow"); + + /** + * The {@link IamStatement} to which this effect is attached should DENY the actions described in the policy. This takes + * precedence over any other ALLOW statements. See the + * policy evaluation + * logic guide for more information on how to use the DENY effect. + */ + IamEffect DENY = create("Deny"); + + /** + * Create a new {@code IamEffect} element with the provided {@link #value()}. + */ + static IamEffect create(String value) { + return new DefaultIamEffect(value); + } +} diff --git a/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamPolicy.java b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamPolicy.java new file mode 100644 index 000000000000..fd3b4d663b93 --- /dev/null +++ b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamPolicy.java @@ -0,0 +1,309 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam; + +import java.util.Collection; +import java.util.List; +import java.util.function.Consumer; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.annotations.ThreadSafe; +import software.amazon.awssdk.policybuilder.iam.internal.DefaultIamPolicy; +import software.amazon.awssdk.utils.builder.CopyableBuilder; +import software.amazon.awssdk.utils.builder.ToCopyableBuilder; + +/** + * An AWS access control policy is a object that acts as a container for one or + * more statements, which specify fine grained rules for allowing or denying + * various types of actions from being performed on your AWS resources. + *

+ * By default, all requests to use your resource coming from anyone but you are + * denied. Access control polices can override that by allowing different types + * of access to your resources, or by explicitly denying different types of + * access. + *

+ * Each statement in an AWS access control policy takes the form: + * "A has permission to do B to C where D applies". + *

+ *

+ * For more information, see The IAM User Guide + * + *

Usage Examples

+ * Create a new IAM identity policy that allows a role to write items to an Amazon DynamoDB table. + * {@snippet : + * // IamClient requires a dependency on software.amazon.awssdk:iam + * try (IamClient iam = IamClient.create()) { + * IamPolicy policy = + * IamPolicy.builder() + * .addStatement(IamStatement.builder() + * .effect(IamEffect.ALLOW) + * .addAction("dynamodb:PutItem") + * .addResource("arn:aws:dynamodb:us-east-2:123456789012:table/books") + * .build()) + * .build(); + * iam.createPolicy(r -> r.policyName("AllowWriteBookMetadata") + * .policyDocument(policy.toJson())); + * } + * } + * + *

+ * Download the policy uploaded in the previous example and create a new policy with "read" access added to it. + * {@snippet : + * // IamClient requires a dependency on software.amazon.awssdk:iam + * try (IamClient iam = IamClient.create()) { + * String policyArn = "arn:aws:iam::123456789012:policy/AllowWriteBookMetadata"; + * GetPolicyResponse getPolicyResponse = iam.getPolicy(r -> r.policyArn(policyArn)); + * + * String policyVersion = getPolicyResponse.defaultVersionId(); + * GetPolicyVersionResponse getPolicyVersionResponse = + * iam.getPolicyVersion(r -> r.policyArn(policyArn).versionId(policyVersion)); + * + * IamPolicy policy = IamPolicy.fromJson(getPolicyVersionResponse.policyVersion().document()); + * + * IamStatement newStatement = policy.statements().get(0).copy(s -> s.addAction("dynamodb:GetItem")); + * IamPolicy newPolicy = policy.copy(p -> p.statements(Arrays.asList(newStatement))); + * + * iam.createPolicy(r -> r.policyName("AllowReadWriteBookMetadata") + * .policyDocument(newPolicy.toJson())); + * } + * } + * + * @see IamPolicyReader + * @see IamPolicyWriter + * @see IamStatement + * @see IAM User Guide + */ +@SdkPublicApi +@ThreadSafe +public interface IamPolicy extends ToCopyableBuilder { + /** + * Create an {@code IamPolicy} from an IAM policy in JSON form. + *

+ * This will raise an exception if the provided JSON is invalid or does not appear to represent a valid policy document. + *

+ * This is equivalent to {@code IamPolicyReader.create().read(json)}. + */ + static IamPolicy fromJson(String json) { + return IamPolicyReader.create().read(json); + } + + /** + * Create an {@code IamPolicy} containing the provided statements. + *

+ * At least one statement is required. + *

+ * This is equivalent to {@code IamPolicy.builder().statements(statements).build()} + */ + static IamPolicy create(Collection statements) { + return builder().statements(statements).build(); + } + + /** + * Create a {@link Builder} for an {@code IamPolicy}. + */ + static Builder builder() { + return DefaultIamPolicy.builder(); + } + + /** + * Retrieve the value set by {@link Builder#id(String)}. + */ + String id(); + + /** + * Retrieve the value set by {@link Builder#version(String)}. + */ + String version(); + + /** + * Retrieve the value set by {@link Builder#statements(Collection)}. + */ + List statements(); + + /** + * Convert this policy to the JSON format that is accepted by AWS services. + *

+ * This is equivalent to {@code IamPolicyWriter.create().writeToString(policy)} + *

+ * {@snippet : + * IamPolicy policy = + * IamPolicy.builder() + * .addStatement(IamStatement.builder() + * .effect(IamEffect.ALLOW) + * .addAction("dynamodb:PutItem") + * .addResource("arn:aws:dynamodb:us-east-2:123456789012:table/books") + * .build()) + * .build(); + * System.out.println("Policy:\n" + policy.toJson()); + * } + */ + String toJson(); + + /** + * Convert this policy to the JSON format that is accepted by AWS services, using the provided writer. + *

+ * This is equivalent to {@code writer.writeToString(policy)} + *

+ * {@snippet : + * IamPolicyWriter prettyWriter = + * IamPolicyWriter.builder() + * .prettyPrint(true) + * .build(); + * IamPolicy policy = + * IamPolicy.builder() + * .addStatement(IamStatement.builder() + * .effect(IamEffect.ALLOW) + * .addAction("dynamodb:PutItem") + * .addResource("arn:aws:dynamodb:us-east-2:123456789012:table/books") + * .build()) + * .build(); + * System.out.println("Policy:\n" + policy.toJson(prettyWriter)); + * } + */ + String toJson(IamPolicyWriter writer); + + /** + * @see #builder() + */ + interface Builder extends CopyableBuilder { + /** + * Configure the {@code + * Id} element of the policy, specifying an optional identifier for the policy. + *

+ * The ID is used differently in different services. ID is allowed in resource-based policies, but not in + * identity-based policies. + *

+ * For services that let you set an ID element, we recommend you use a UUID (GUID) for the value, or incorporate a UUID + * as part of the ID to ensure uniqueness. + *

+ * This value is optional. + *

+ * {@snippet : + * IamPolicy policy = + * IamPolicy.builder() + * .id("cd3ad3d9-2776-4ef1-a904-4c229d1642ee") // An identifier for the policy + * .addStatement(IamStatement.builder() + * .effect(IamEffect.DENY) + * .addAction(IamAction.ALL) + * .build()) + * .build(); + *} + * + * @see ID user guide + */ + Builder id(String id); + + /** + * Configure the + * {@code Version} + * element of the policy, specifying the language syntax rules that are to be used to + * process the policy. + *

+ * By default, this value is {@code 2012-10-17}. + *

+ * {@snippet : + * IamPolicy policy = + * IamPolicy.builder() + * .version("2012-10-17") // The IAM policy language syntax version to use + * .addStatement(IamStatement.builder() + * .effect(IamEffect.DENY) + * .addAction(IamAction.ALL) + * .build()) + * .build(); + * } + * + * @see Version + * user guide + */ + Builder version(String version); + + /** + * Configure the + * {@code + * Statement} element of the policy, specifying the access rules for this policy. + *

+ * This will replace any other statements already added to the policy. At least one statement is required to + * create a policy. + *

+ * {@snippet : + * IamPolicy policy = + * IamPolicy.builder() + * // Add a statement to this policy that denies all actions: + * .statements(Arrays.asList(IamStatement.builder() + * .effect(IamEffect.DENY) + * .addAction(IamAction.ALL) + * .build())) + * .build(); + * } + * @see + * Statement user guide + */ + Builder statements(Collection statements); + + /** + * Append a + * {@code + * Statement} element to this policy to specify additional access rules. + *

+ * At least one statement is required to create a policy. + *

+ * {@snippet : + * IamPolicy policy = + * IamPolicy.builder() + * // Add a statement to this policy that denies all actions: + * .addStatement(IamStatement.builder() + * .effect(IamEffect.DENY) + * .addAction(IamAction.ALL) + * .build()) + * .build(); + * } + * @see + * Statement user guide + */ + Builder addStatement(IamStatement statement); + + /** + * Append a + * {@code + * Statement} element to this policy to specify additional access rules. + *

+ * This works the same as {@link #addStatement(IamStatement)}, except you do not need to specify {@code IamStatement + * .builder()} or {@code build()}. At least one statement is required to create a policy. + *

+ * {@snippet : + * IamPolicy policy = + * IamPolicy.builder() + * // Add a statement to this policy that denies all actions: + * .addStatement(s -> s.effect(IamEffect.DENY) + * .addAction(IamAction.ALL)) + * .build(); + * } + * @see + * Statement user guide + */ + Builder addStatement(Consumer statement); + } +} diff --git a/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamPolicyReader.java b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamPolicyReader.java new file mode 100644 index 000000000000..92e6505548ae --- /dev/null +++ b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamPolicyReader.java @@ -0,0 +1,86 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam; + +import java.io.InputStream; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.annotations.ThreadSafe; +import software.amazon.awssdk.policybuilder.iam.internal.DefaultIamPolicyReader; + +/** + * The {@link IamPolicyReader} converts a JSON policy into an {@link IamPolicy}. + * + *

Usage Examples

+ * Log the number of statements in a policy downloaded from IAM. + * {@snippet : + * // IamClient requires a dependency on software.amazon.awssdk:iam + * try (IamClient iam = IamClient.create()) { + * String policyArn = "arn:aws:iam::123456789012:policy/AllowWriteBookMetadata"; + * GetPolicyResponse getPolicyResponse = iam.getPolicy(r -> r.policyArn(policyArn)); + * + * String policyVersion = getPolicyResponse.defaultVersionId(); + * GetPolicyVersionResponse getPolicyVersionResponse = + * iam.getPolicyVersion(r -> r.policyArn(policyArn).versionId(policyVersion)); + * + * IamPolicy policy = IamPolicyReader.create().read(getPolicyVersionResponse.policyVersion().document()); + * + * System.out.println("Number of statements in the " + policyArn + ": " + policy.statements().size()); + * } + * } + * + * @see IamPolicy#fromJson(String) + */ +@SdkPublicApi +@ThreadSafe +public interface IamPolicyReader { + /** + * Create a new {@link IamPolicyReader}. + *

+ * This method is inexpensive, allowing the creation of readers wherever they are needed. + */ + static IamPolicyReader create() { + return new DefaultIamPolicyReader(); + } + + /** + * Read a policy from a {@link String}. + *

+ * This only performs minimal validation on the provided policy. + * + * @throws RuntimeException If the provided policy is not valid JSON or is missing a minimal set of required fields. + */ + IamPolicy read(String policy); + + /** + * Read a policy from an {@link InputStream}. + *

+ * The stream must provide a UTF-8 encoded string representing the policy. This only performs minimal validation on the + * provided policy. + * + * @throws RuntimeException If the provided policy is not valid JSON or is missing a minimal set of required fields. + */ + IamPolicy read(InputStream policy); + + /** + * Read a policy from a {@code byte} array. + *

+ * The stream must provide a UTF-8 encoded string representing the policy. This only performs minimal validation on the + * provided policy. + * + * @throws RuntimeException If the provided policy is not valid JSON or is missing a minimal set of required fields. + */ + IamPolicy read(byte[] policy); +} diff --git a/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamPolicyWriter.java b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamPolicyWriter.java new file mode 100644 index 000000000000..2b64ccfb2ba8 --- /dev/null +++ b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamPolicyWriter.java @@ -0,0 +1,114 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam; + +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.annotations.ThreadSafe; +import software.amazon.awssdk.policybuilder.iam.internal.DefaultIamPolicyWriter; +import software.amazon.awssdk.utils.builder.CopyableBuilder; +import software.amazon.awssdk.utils.builder.ToCopyableBuilder; + +/** + * The {@link IamPolicyReader} converts an {@link IamPolicy} into JSON. + * + *

Usage Examples

+ * Create a new IAM identity policy that allows a role to write items to an Amazon DynamoDB table. + * {@snippet : + * // IamClient requires a dependency on software.amazon.awssdk:iam + * try (IamClient iam = IamClient.create()) { + * IamPolicy policy = + * IamPolicy.builder() + * .addStatement(IamStatement.builder() + * .effect(IamEffect.ALLOW) + * .addAction("dynamodb:PutItem") + * .addResource("arn:aws:dynamodb:us-east-2:123456789012:table/books") + * .build()) + * .build(); + * + * IamPolicyWriter writer = IamPolicyWriter.create(); + * iam.createPolicy(r -> r.policyName("AllowWriteBookMetadata") + * .policyDocument(writer.writeToString(policy))); + * } + * } + * + * Create and use a writer that pretty-prints the IAM policy JSON: + * {@snippet : + * IamPolicyWriter prettyWriter = + * IamPolicyWriter.builder() + * .prettyPrint(true) + * .build(); + * IamPolicy policy = + * IamPolicy.builder() + * .addStatement(IamStatement.builder() + * .effect(IamEffect.ALLOW) + * .addAction("dynamodb:PutItem") + * .addResource("arn:aws:dynamodb:us-east-2:123456789012:table/books") + * .build()) + * .build(); + * System.out.println("Policy:\n" + policy.toJson(prettyWriter)); + * } + * + * @see IamPolicy#toJson() + * @see IamPolicy#toJson(IamPolicyWriter) + */ +@SdkPublicApi +@ThreadSafe +public interface IamPolicyWriter extends ToCopyableBuilder { + /** + * Create a new {@link IamPolicyReader}. + *

+ * This method is inexpensive, allowing the creation of writers wherever they are needed. + */ + static IamPolicyWriter create() { + return DefaultIamPolicyWriter.create(); + } + + /** + * Create a {@link Builder} for an {@code IamPolicyWriter}. + */ + static Builder builder() { + return DefaultIamPolicyWriter.builder(); + } + + /** + * Write a policy to a {@link String}. + *

+ * This does not validate that the provided policy is correct or valid. + */ + String writeToString(IamPolicy policy); + + /** + * Write a policy to a {@code byte} array. + *

+ * This does not validate that the provided policy is correct or valid. + */ + byte[] writeToBytes(IamPolicy policy); + + /** + * @see #builder() + */ + interface Builder extends CopyableBuilder { + /** + * Configure whether the writer should "pretty-print" the output. + *

+ * When set to true, this will add new lines and indentation to the output to make it easier for a human to read, at + * the expense of extra data (white space) being output. + *

+ * By default, this is {@code false}. + */ + Builder prettyPrint(Boolean prettyPrint); + } +} diff --git a/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamPrincipal.java b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamPrincipal.java new file mode 100644 index 000000000000..b4dc1a44f288 --- /dev/null +++ b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamPrincipal.java @@ -0,0 +1,156 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam; + +import static java.util.Collections.emptyList; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.annotations.ThreadSafe; +import software.amazon.awssdk.policybuilder.iam.internal.DefaultIamPrincipal; +import software.amazon.awssdk.utils.Validate; +import software.amazon.awssdk.utils.builder.CopyableBuilder; +import software.amazon.awssdk.utils.builder.ToCopyableBuilder; + +/** + * The {@code Principal} element of a {@link IamStatement}, specifying who the statement should apply to. + * + * @see Principal + * user guide + */ +@SdkPublicApi +@ThreadSafe +public interface IamPrincipal extends ToCopyableBuilder { + /** + * An {@link IamPrincipal} representing ALL principals. When used on a statement, it means the policy should apply to + * everyone. + */ + IamPrincipal ALL = create("*", "*"); + + /** + * Create an {@link IamPrincipal} of the supplied type and ID (see {@link Builder#type(IamPrincipalType)} and + * {@link Builder#id(String)}). + *

+ * Both type and ID are required. This is equivalent to {@code IamPrincipal.builder().type(principalType).id(principalId) + * .build()}. + */ + static IamPrincipal create(IamPrincipalType principalType, String principalId) { + return builder().type(principalType).id(principalId).build(); + } + + /** + * Create an {@link IamPrincipal} of the supplied type and ID (see {@link Builder#type(String)} and + * {@link Builder#id(String)}). + *

+ * Both type and ID are required. This is equivalent to {@link #create(IamPrincipalType, String)}, except you do not need + * to call {@code IamPrincipalType.create()}. + */ + static IamPrincipal create(String principalType, String principalId) { + return builder().type(principalType).id(principalId).build(); + } + + /** + * Create multiple {@link IamPrincipal}s with the same {@link IamPrincipalType} and different IDs (see + * {@link Builder#type(IamPrincipalType)} and {@link Builder#id(String)}). + *

+ * Type is required, and the IDs in the IDs list must not be null. This is equivalent to calling + * {@link #create(IamPrincipalType, String)} multiple times and collecting the results into a list. + */ + static List createAll(IamPrincipalType principalType, Collection principalIds) { + Validate.paramNotNull(principalType, "principalType"); + if (principalIds == null) { + return emptyList(); + } + return principalIds.stream() + .map(principalId -> create(principalType, principalId)) + .collect(Collectors.toCollection(ArrayList::new)); + } + + /** + * Create multiple {@link IamPrincipal}s with the same {@link IamPrincipalType} and different IDs (see + * {@link Builder#type(String)} and {@link Builder#id(String)}). + *

+ * Type is required, and the IDs in the IDs list must not be null. This is equivalent to calling + * {@link #create(String, String)} multiple times and collecting the results into a list. + */ + static List createAll(String principalType, Collection principalIds) { + Validate.paramNotNull(principalType, "principalType"); + if (principalIds == null) { + return emptyList(); + } + return principalIds.stream() + .map(principalId -> create(principalType, principalId)) + .collect(Collectors.toCollection(ArrayList::new)); + } + + /** + * Create a {@link IamStatement.Builder} for an {@code IamPrincipal}. + */ + static Builder builder() { + return DefaultIamPrincipal.builder(); + } + + /** + * Retrieve the value set by {@link Builder#type(IamPrincipalType)}. + */ + IamPrincipalType type(); + + /** + * Retrieve the value set by {@link Builder#id(String)}. + */ + String id(); + + /** + * @see #builder() + */ + interface Builder extends CopyableBuilder { + /** + * Set the {@link IamPrincipalType} associated with this principal. + *

+ * This value is required. + * + * @see IamPrincipalType + * @see Principal + * user guide + */ + Builder type(IamPrincipalType type); + + /** + * Set the {@link IamPrincipalType} associated with this principal. + *

+ * This is the same as {@link #type(IamPrincipalType)}, except you do not need to call {@code IamPrincipalType.create()}. + * This value is required. + * + * @see IamPrincipalType + * @see Principal + * user guide + */ + Builder type(String type); + + /** + * Set the identifier of the principal. + *

+ * The identifiers that can be used depend on the {@link #type(IamPrincipalType)} of the principal. + * + * @see Principal + * user guide + */ + Builder id(String id); + } +} diff --git a/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamPrincipalType.java b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamPrincipalType.java new file mode 100644 index 000000000000..315feed83caa --- /dev/null +++ b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamPrincipalType.java @@ -0,0 +1,80 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam; + +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.annotations.ThreadSafe; +import software.amazon.awssdk.policybuilder.iam.internal.DefaultIamPrincipalType; + +/** + * The {@code IamPrincipalType} identifies what type of entity that the {@link IamPrincipal} refers to. + * + * @see Principal + * user guide + */ +@SdkPublicApi +@ThreadSafe +public interface IamPrincipalType extends IamValue { + /** + * An {@code AWS} principal. + *

+ * For example, this includes AWS accounts, IAM users, IAM roles, IAM role sessions or STS federated users. + * + * @see Principal + * user guide + */ + IamPrincipalType AWS = create("AWS"); + + /** + * A {@code Federated} principal. + *

+ * This grants an external web identity, SAML identity provider, etc. permission to perform actions on your resources. For + * example, cognito-identity.amazonaws.com or www.amazon.com. + * + * @see Principal + * user guide + */ + IamPrincipalType FEDERATED = create("Federated"); + + /** + * A {@code Service} principal. + *

+ * This grants other AWS services permissions to perform actions on your resources. Identifiers are usually in the format + * service-name.amazonaws.com. For example, ecs.amazonaws.com or lambda.amazonaws.com. + * + * @see Principal + * user guide + */ + IamPrincipalType SERVICE = create("Service"); + + /** + * A {@code CanonicalUser} principal. + *

+ * Some services support a canonical user ID to identify your account without requiring your account ID to be shared. Such + * identifiers are often a 64-digit alphanumeric value. + * + * @see Principal + * user guide + */ + IamPrincipalType CANONICAL_USER = create("CanonicalUser"); + + /** + * Create a new {@code IamPrincipalType} element with the provided {@link #value()}. + */ + static IamPrincipalType create(String value) { + return new DefaultIamPrincipalType(value); + } +} \ No newline at end of file diff --git a/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamResource.java b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamResource.java new file mode 100644 index 000000000000..b653c8dba956 --- /dev/null +++ b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamResource.java @@ -0,0 +1,43 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam; + +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.annotations.ThreadSafe; +import software.amazon.awssdk.policybuilder.iam.internal.DefaultIamResource; + +/** + * The {@code Resource} element of a {@link IamStatement}, specifying which resource the statement applies to. + * + * @see + * Resource user guide + */ +@SdkPublicApi +@ThreadSafe +public interface IamResource extends IamValue { + /** + * An {@link IamResource} representing ALL resources. When used on a statement, it means the policy should apply to + * every resource. + */ + IamResource ALL = create("*"); + + /** + * Create a new {@code IamResource} element with the provided {@link #value()}. + */ + static IamResource create(String value) { + return new DefaultIamResource(value); + } +} diff --git a/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamStatement.java b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamStatement.java new file mode 100644 index 000000000000..e2480bf4fd39 --- /dev/null +++ b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamStatement.java @@ -0,0 +1,1201 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam; + +import java.util.Collection; +import java.util.List; +import java.util.function.Consumer; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.annotations.ThreadSafe; +import software.amazon.awssdk.policybuilder.iam.internal.DefaultIamStatement; +import software.amazon.awssdk.utils.builder.CopyableBuilder; +import software.amazon.awssdk.utils.builder.ToCopyableBuilder; + +/** + * A statement is the formal description of a single permission, and is always + * contained within a policy object. + *

+ * A statement describes a rule for allowing or denying access to a specific AWS + * resource based on how the resource is being accessed, and who is attempting + * to access the resource. Statements can also optionally contain a list of + * conditions that specify when a statement is to be honored. + *

+ * For example, consider a statement that: + *

+ * + *

+ * Statements takes the form: "A has permission to do B to C where D applies". + *

+ * + *

+ * There are many resources and conditions available for use in statements, and + * you can combine them to form fine grained custom access control polices. + * + *

+ * Statements are typically attached to a {@link IamPolicy}. + * + *

+ * For more information, see The IAM User guide + * + *

Usage Examples

+ * Create an + * identity-based policy + * statement that allows a role to write items to an Amazon DynamoDB table. + * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantWriteBookMetadata") + * .effect(IamEffect.ALLOW) + * .addAction("dynamodb:PutItem") + * .addResource("arn:aws:dynamodb:us-east-2:123456789012:table/books") + * .build(); + * } + * + *

+ * Create a + * resource-based policy + * statement that denies access to all users. + * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .effect(IamEffect.DENY) + * .addPrincipal(IamPrincipal.ALL) + * .build(); + * } + * + * @see IamPolicy + * @see Statement user + * guide + */ +@SdkPublicApi +@ThreadSafe +public interface IamStatement extends ToCopyableBuilder { + /** + * Create a {@link Builder} for an {@code IamStatement}. + */ + static Builder builder() { + return DefaultIamStatement.builder(); + } + + /** + * Retrieve the value set by {@link Builder#sid(String)}. + */ + String sid(); + + /** + * Retrieve the value set by {@link Builder#effect(IamEffect)}. + */ + IamEffect effect(); + + /** + * Retrieve the value set by {@link Builder#principals(Collection)}. + */ + List principals(); + + /** + * Retrieve the value set by {@link Builder#notPrincipals(Collection)}. + */ + List notPrincipals(); + + /** + * Retrieve the value set by {@link Builder#actions(Collection)}. + */ + List actions(); + + /** + * Retrieve the value set by {@link Builder#notActions(Collection)}. + */ + List notActions(); + + /** + * Retrieve the value set by {@link Builder#resources(Collection)}. + */ + List resources(); + + /** + * Retrieve the value set by {@link Builder#notResources(Collection)}. + */ + List notResources(); + + /** + * Retrieve the value set by {@link Builder#conditions(Collection)}. + */ + List conditions(); + + /** + * @see #builder() + */ + interface Builder extends CopyableBuilder { + /** + * Configure the {@code + * Sid} element of the policy, specifying an identifier for the statement. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBookMetadata") // An identifier for the statement + * .effect(IamEffect.ALLOW) + * .addAction("dynamodb:GetItem") + * .addResource("arn:aws:dynamodb:us-east-2:123456789012:table/books") + * .build(); + * } + * + * @see Sid user + * guide + */ + Builder sid(String sid); + + /** + * Configure the + * {@code Effect} + * element of the policy, specifying whether the statement results in an allow or deny. + *

+ * This value is required. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBookMetadata") + * .effect(IamEffect.ALLOW) // The statement ALLOWS access + * .addAction("dynamodb:GetItem") + * .addResource("arn:aws:dynamodb:us-east-2:123456789012:table/books") + * .build(); + * } + * + * @see IamEffect + * @see Effect user + * guide + */ + Builder effect(IamEffect effect); + + /** + * Configure the + * {@code Effect} + * element of the policy, specifying whether the statement results in an allow or deny. + *

+ * This works the same as {@link #effect(IamEffect)}, except you do not need to {@link IamEffect}. This value is required. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBookMetadata") + * .effect("Allow") // The statement ALLOWs access + * .addAction("dynamodb:GetItem") + * .addResource("arn:aws:dynamodb:us-east-2:123456789012:table/books") + * .build(); + * } + * + * @see IamEffect + * @see Effect user + * guide + */ + Builder effect(String effect); + + /** + * Configure the + * {@code + * Principal} element of the statement, specifying the principals that are allowed or denied + * access to a resource. + *

+ * This will replace any other principals already added to the statement. + *

+ * {@snippet : + * List bookReaderRoles = + * IamPrincipal.createAll("AWS", + * Arrays.asList("arn:aws:iam::123456789012:role/books-service", + * "arn:aws:iam::123456789012:role/books-operator")); + * + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBookContent") + * .effect(IamEffect.ALLOW) + * .principals(bookReaderRoles) // This statement allows access to the books service and operators + * .addAction("s3:GetObject") + * .addResource("arn:aws:s3:us-west-2:123456789012:accesspoint/book-content/object/*") + * .build(); + * } + * + * @see IamPrincipal + * @see Principal + * user guide + */ + Builder principals(Collection principals); + + /** + * Append a + * {@code + * Principal} to this statement, specifying a principal that is allowed or denied access to + * a resource. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBookContent") + * .effect(IamEffect.ALLOW) + * // This statement allows access to the books service: + * .addPrincipal(IamPrincipal.create("AWS", "arn:aws:iam::123456789012:role/books-service")) + * .addAction("s3:GetObject") + * .addResource("arn:aws:s3:us-west-2:123456789012:accesspoint/book-content/object/*") + * .build(); + * } + * @see Principal + * user guide + */ + Builder addPrincipal(IamPrincipal principal); + + /** + * Append a + * {@code + * Principal} to this statement, specifying a principal that is allowed or denied access to + * a resource. + *

+ * This works the same as {@link #addPrincipal(IamPrincipal)}, except you do not need to specify {@code IamPrincipal + * .builder()} or {@code build()}. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBookContent") + * .effect(IamEffect.ALLOW) + * // This statement allows access to the books service: + * .addPrincipal(p -> p.type("AWS").id("arn:aws:iam::123456789012:role/books-service")) + * .addAction("s3:GetObject") + * .addResource("arn:aws:s3:us-west-2:123456789012:accesspoint/book-content/object/*") + * .build(); + * } + * @see Principal + * user guide + */ + Builder addPrincipal(Consumer principal); + + /** + * Append a + * {@code + * Principal} to this statement, specifying a principal that is allowed or denied access to + * a resource. + *

+ * This works the same as {@link #addPrincipal(IamPrincipal)}, except you do not need to specify {@code IamPrincipal + * .create()}. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBookContent") + * .effect(IamEffect.ALLOW) + * // This statement allows access to the books service: + * .addPrincipal(IamPrincipalType.AWS, "arn:aws:iam::123456789012:role/books-service") + * .addAction("s3:GetObject") + * .addResource("arn:aws:s3:us-west-2:123456789012:accesspoint/book-content/object/*") + * .build(); + * } + * @see Principal + * user guide + */ + Builder addPrincipal(IamPrincipalType iamPrincipalType, String principal); + + /** + * Append a + * {@code + * Principal} to this statement, specifying a principal that is allowed or denied access to + * a resource. + *

+ * This works the same as {@link #addPrincipal(IamPrincipalType, String)}, except you do not need to specify {@code + * IamPrincipalType.create()}. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBookContent") + * .effect(IamEffect.ALLOW) + * // This statement allows access to the books service: + * .addPrincipal("AWS", "arn:aws:iam::123456789012:role/books-service") + * .addAction("s3:GetObject") + * .addResource("arn:aws:s3:us-west-2:123456789012:accesspoint/book-content/object/*") + * .build(); + * } + * @see Principal + * user guide + */ + Builder addPrincipal(String iamPrincipalType, String principal); + + /** + * Append multiple + * {@code + * Principal}s to this statement, specifying principals that are allowed or denied access to + * a resource. + *

+ * This works the same as calling {@link #addPrincipal(IamPrincipalType, String)} multiple times with the same + * {@link IamPrincipalType}. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBookContent") + * .effect(IamEffect.ALLOW) + * // This statement allows access to the books service and operators: + * .addPrincipals(IamPrincipalType.AWS, + * Arrays.asList("arn:aws:iam::123456789012:role/books-service", + * "arn:aws:iam::123456789012:role/books-operator")) + * .addAction("s3:GetObject") + * .addResource("arn:aws:s3:us-west-2:123456789012:accesspoint/book-content/object/*") + * .build(); + * } + * @see Principal + * user guide + */ + Builder addPrincipals(IamPrincipalType iamPrincipalType, Collection principals); + + /** + * Append multiple + * {@code + * Principal}s to this statement, specifying principals that are allowed or denied access to + * a resource. + *

+ * This works the same as calling {@link #addPrincipal(String, String)} multiple times with the same + * {@link IamPrincipalType}. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBookContent") + * .effect(IamEffect.ALLOW) + * // This statement allows access to the books service and operators: + * .addPrincipals("AWS", Arrays.asList("arn:aws:iam::123456789012:role/books-service", + * "arn:aws:iam::123456789012:role/books-operator")) + * .addAction("s3:GetObject") + * .addResource("arn:aws:s3:us-west-2:123456789012:accesspoint/book-content/object/*") + * .build(); + * } + * @see Principal + * user guide + */ + Builder addPrincipals(String iamPrincipalType, Collection principals); + + /** + * Configure the + * {@code + * NotPrincipal} element of the statement, specifying that all principals are affected by the policy except the + * ones listed. + *

+ * Very few scenarios require the use of {@code NotPrincipal}. We recommend that you explore other authorization options + * before you decide to use {@code NotPrincipal}. {@code NotPrincipal} can only be used with {@link IamEffect#DENY} + * statements. + *

+ * This will replace any other not-principals already added to the statement. + *

+ * {@snippet : + * List bookReaderRoles = + * IamPrincipal.createAll("AWS", + * Arrays.asList("arn:aws:iam::123456789012:role/books-service", + * "arn:aws:iam::123456789012:role/books-operator")); + * + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBookContent") + * .effect(IamEffect.DENY) + * // This statement denies access to everyone except the books service and operators: + * .notPrincipals(bookReaderRoles) + * .addAction("s3:GetObject") + * .addResource("arn:aws:s3:us-west-2:123456789012:accesspoint/book-content/object/*") + * .build(); + * } + * @see + * NotPrincipal user guide + */ + Builder notPrincipals(Collection notPrincipals); + + /** + * Append a + * {@code + * NotPrincipal} to this statement, specifying that all principals are affected by the policy except the + * ones listed. + *

+ * Very few scenarios require the use of {@code NotPrincipal}. We recommend that you explore other authorization options + * before you decide to use {@code NotPrincipal}. {@code NotPrincipal} can only be used with {@link IamEffect#DENY} + * statements. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBookContent") + * .effect(IamEffect.DENY) + * // This statement denies access to everyone except the books service: + * .addNotPrincipal(IamPrincipal.create("AWS", "arn:aws:iam::123456789012:role/books-service")) + * .addAction("s3:GetObject") + * .addResource("arn:aws:s3:us-west-2:123456789012:accesspoint/book-content/object/*") + * .build(); + * } + * @see + * NotPrincipal user guide + */ + Builder addNotPrincipal(IamPrincipal notPrincipal); + + /** + * Append a + * {@code + * NotPrincipal} to this statement, specifying that all principals are affected by the policy except the + * ones listed. + *

+ * Very few scenarios require the use of {@code NotPrincipal}. We recommend that you explore other authorization options + * before you decide to use {@code NotPrincipal}. {@code NotPrincipal} can only be used with {@link IamEffect#DENY} + * statements. + *

+ * This works the same as {@link #addNotPrincipal(IamPrincipal)}, except you do not need to specify {@code IamPrincipal + * .builder()} or {@code build()}. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBookContent") + * .effect(IamEffect.DENY) + * // This statement denies access to everyone except the books service: + * .addNotPrincipal(p -> p.type("AWS").id("arn:aws:iam::123456789012:role/books-service")) + * .addAction("s3:GetObject") + * .addResource("arn:aws:s3:us-west-2:123456789012:accesspoint/book-content/object/*") + * .build(); + * } + * @see + * NotPrincipal user guide + */ + Builder addNotPrincipal(Consumer notPrincipal); + + /** + * Append a + * {@code + * NotPrincipal} to this statement, specifying that all principals are affected by the policy except the + * ones listed. + *

+ * Very few scenarios require the use of {@code NotPrincipal}. We recommend that you explore other authorization options + * before you decide to use {@code NotPrincipal}. {@code NotPrincipal} can only be used with {@link IamEffect#DENY} + * statements. + *

+ * This works the same as {@link #addNotPrincipal(IamPrincipal)}, except you do not need to specify {@code IamPrincipal + * .create()}. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBookContent") + * .effect(IamEffect.DENY) + * // This statement denies access to everyone except the books service: + * .addNotPrincipal(IamPrincipalType.AWS, "arn:aws:iam::123456789012:role/books-service") + * .addAction("s3:GetObject") + * .addResource("arn:aws:s3:us-west-2:123456789012:accesspoint/book-content/object/*") + * .build(); + * } + * @see + * NotPrincipal user guide + */ + Builder addNotPrincipal(IamPrincipalType iamPrincipalType, String notPrincipal); + + /** + * Append a + * {@code + * NotPrincipal} to this statement, specifying that all principals are affected by the policy except the + * ones listed. + *

+ * Very few scenarios require the use of {@code NotPrincipal}. We recommend that you explore other authorization options + * before you decide to use {@code NotPrincipal}. {@code NotPrincipal} can only be used with {@link IamEffect#DENY} + * statements. + *

+ * This works the same as {@link #addNotPrincipal(IamPrincipalType, String)}, except you do not need to specify {@code + * IamPrincipalType.create()}. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBookContent") + * .effect(IamEffect.DENY) + * // This statement denies access to everyone except the books service: + * .addNotPrincipal("AWS", "arn:aws:iam::123456789012:role/books-service") + * .addAction("s3:GetObject") + * .addResource("arn:aws:s3:us-west-2:123456789012:accesspoint/book-content/object/*") + * .build(); + * } + * @see + * NotPrincipal user guide + */ + Builder addNotPrincipal(String iamPrincipalType, String notPrincipal); + + /** + * Append multiple + * {@code + * NotPrincipal}s to this statement, specifying that all principals are affected by the policy except the + * ones listed. + *

+ * Very few scenarios require the use of {@code NotPrincipal}. We recommend that you explore other authorization options + * before you decide to use {@code NotPrincipal}. {@code NotPrincipal} can only be used with {@link IamEffect#DENY} + * statements. + *

+ * This works the same as calling {@link #addNotPrincipal(IamPrincipalType, String)} multiple times with the same + * {@link IamPrincipalType}. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBookContent") + * .effect(IamEffect.DENY) + * // This statement denies access to everyone except the books service and operators: + * .addNotPrincipals(IamPrincipalType.AWS, + * Arrays.asList("arn:aws:iam::123456789012:role/books-service", + * "arn:aws:iam::123456789012:role/books-operator")) + * .addAction("s3:GetObject") + * .addResource("arn:aws:s3:us-west-2:123456789012:accesspoint/book-content/object/*") + * .build(); + * } + * @see + * NotPrincipal user guide + */ + Builder addNotPrincipals(IamPrincipalType iamPrincipalType, Collection notPrincipals); + + /** + * Append multiple + * {@code + * NotPrincipal}s to this statement, specifying that all principals are affected by the policy except the + * ones listed. + *

+ * Very few scenarios require the use of {@code NotPrincipal}. We recommend that you explore other authorization options + * before you decide to use {@code NotPrincipal}. {@code NotPrincipal} can only be used with {@link IamEffect#DENY} + * statements. + *

+ * This works the same as calling {@link #addNotPrincipal(String, String)} multiple times with the same + * {@link IamPrincipalType}. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBookContent") + * .effect(IamEffect.DENY) + * // This statement denies access to everyone except the books service and operators: + * .addNotPrincipals("AWS", Arrays.asList("arn:aws:iam::123456789012:role/books-service", + * "arn:aws:iam::123456789012:role/books-operator")) + * .addAction("s3:GetObject") + * .addResource("arn:aws:s3:us-west-2:123456789012:accesspoint/book-content/object/*") + * .build(); + * } + * @see + * NotPrincipal user guide + */ + Builder addNotPrincipals(String iamPrincipalType, Collection notPrincipals); + + /** + * Configure the + * {@code Action} + * element of the statement, specifying the actions that are allowed or denied. + *

+ * This will replace any other actions already added to the statement. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadWriteBookMetadata") + * .effect(IamEffect.ALLOW) + * // This statement grants access to read and write items in Amazon DynamoDB: + * .actions(Arrays.asList(IamAction.create("dynamodb:PutItem"), + * IamAction.create("dynamodb:GetItem"))) + * .addResource("arn:aws:dynamodb:us-east-2:123456789012:table/books") + * .build(); + * } + * @see Action user + * guide + */ + Builder actions(Collection actions); + + /** + * Configure the + * {@code Action} + * element of the statement, specifying the actions that are allowed or denied. + *

+ * This works the same as {@link #actions(Collection)}, except you do not need to call {@code IamAction.create() + * } on each action. This will replace any other actions already added to the statement. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadWriteBookMetadata") + * .effect(IamEffect.ALLOW) + * // This statement grants access to read and write items in Amazon DynamoDB: + * .actionIds(Arrays.asList("dynamodb:PutItem", "dynamodb:GetItem")) + * .addResource("arn:aws:dynamodb:us-east-2:123456789012:table/books") + * .build(); + * } + * @see Action user + * guide + */ + Builder actionIds(Collection actions); + + /** + * Append an {@code + * Action} element to this statement, specifying an action that is allowed or denied. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBookMetadata") + * .effect(IamEffect.ALLOW) + * // This statement grants access to read items in Amazon DynamoDB: + * .addAction(IamAction.create("dynamodb:GetItem")) + * .addResource("arn:aws:dynamodb:us-east-2:123456789012:table/books") + * .build(); + * } + * @see Action user + * guide + */ + Builder addAction(IamAction action); + + /** + * Append an {@code + * Action} element to this statement, specifying an action that is allowed or denied. + *

+ * This works the same as {@link #addAction(IamAction)}, except you do not need to call {@code IamAction.create()}. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBookMetadata") + * .effect(IamEffect.ALLOW) + * // This statement grants access to read items in Amazon DynamoDB: + * .addAction("dynamodb:GetItem") + * .addResource("arn:aws:dynamodb:us-east-2:123456789012:table/books") + * .build(); + * } + * @see Action user + * guide + */ + Builder addAction(String action); + + /** + * Configure the + * {@code + * NotAction} element of the statement, specifying actions that are denied or allowed. + *

+ * This will replace any other not-actions already added to the statement. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantAllButDeleteBookMetadataTable") + * .effect(IamEffect.ALLOW) + * // This statement grants access to do ALL CURRENT AND FUTURE actions against the books table, except + * // dynamodb:DeleteTable + * .notActions(Arrays.asList(IamAction.create("dynamodb:DeleteTable"))) + * .addResource("arn:aws:dynamodb:us-east-2:123456789012:table/books") + * .build(); + * } + * @see NotAction + * user guide + */ + Builder notActions(Collection actions); + + /** + * Configure the + * {@code + * NotAction} element of the statement, specifying actions that are denied or allowed. + *

+ * This works the same as {@link #notActions(Collection)}, except you do not need to call {@code IamAction.create()} + * on each action. This will replace any other not-actions already added to the statement. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantAllButDeleteBookMetadataTable") + * .effect(IamEffect.ALLOW) + * // This statement grants access to do ALL CURRENT AND FUTURE actions against the books table, except + * // dynamodb:DeleteTable + * .notActionIds(Arrays.asList("dynamodb:DeleteTable")) + * .addResource("arn:aws:dynamodb:us-east-2:123456789012:table/books") + * .build(); + * } + * @see NotAction + * user guide + */ + Builder notActionIds(Collection actions); + + /** + * Append a + * {@code + * NotAction} element to this statement, specifying an action that is denied or allowed. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantAllButDeleteBookMetadataTable") + * .effect(IamEffect.ALLOW) + * // This statement grants access to do ALL CURRENT AND FUTURE actions against the books table, except + * // dynamodb:DeleteTable + * .addNotAction(IamAction.create("dynamodb:DeleteTable")) + * .addResource("arn:aws:dynamodb:us-east-2:123456789012:table/books") + * .build(); + * } + * @see NotAction + * user guide + */ + Builder addNotAction(IamAction action); + + /** + * Append a + * {@code + * NotAction} element to this statement, specifying an action that is denied or allowed. + *

+ * This works the same as {@link #addNotAction(IamAction)}, except you do not need to call {@code IamAction.create()}. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantAllButDeleteBookMetadataTable") + * .effect(IamEffect.ALLOW) + * // This statement grants access to do ALL CURRENT AND FUTURE actions against the books table, except + * // dynamodb:DeleteTable + * .addNotAction("dynamodb:DeleteTable") + * .addResource("arn:aws:dynamodb:us-east-2:123456789012:table/books") + * .build(); + * } + * @see NotAction + * user guide + */ + Builder addNotAction(String action); + + /** + * Configure the + * {@code Resource} + * element of the statement, specifying the resource(s) that the statement covers. + *

+ * This will replace any other resources already added to the statement. + *

+ * {@snippet : + * List resources = + * Arrays.asList(IamResource.create("arn:aws:dynamodb:us-east-2:123456789012:table/books"), + * IamResource.create("arn:aws:dynamodb:us-east-2:123456789012:table/customers")); + * + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBookAndCustomersMetadata") + * .effect(IamEffect.ALLOW) + * .addAction("dynamodb:GetItem") + * // This statement grants access to the books and customers tables: + * .resources(resources) + * .build(); + * } + * @see Resource + * user guide + */ + Builder resources(Collection resources); + + /** + * Configure the + * {@code Resource} + * element of the statement, specifying the resource(s) that the statement covers. + *

+ * This works the same as {@link #resources(Collection)}, except you do not need to call {@code IamResource.create()} + * on each resource. This will replace any other resources already added to the statement. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBookAndCustomersMetadata") + * .effect(IamEffect.ALLOW) + * .addAction("dynamodb:GetItem") + * // This statement grants access to the books and customers tables: + * .resourceIds(Arrays.asList("arn:aws:dynamodb:us-east-2:123456789012:table/books", + * "arn:aws:dynamodb:us-east-2:123456789012:table/customers")) + * .build(); + * } + * @see Resource + * user guide + */ + Builder resourceIds(Collection resources); + + /** + * Append a + * {@code Resource} + * element to the statement, specifying a resource that the statement covers. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBookMetadata") + * .effect(IamEffect.ALLOW) + * .addAction("dynamodb:GetItem") + * // This statement grants access to the books table: + * .addResource(IamResource.create("arn:aws:dynamodb:us-east-2:123456789012:table/books")) + * .build(); + * } + * @see Resource + * user guide + */ + Builder addResource(IamResource resource); + + /** + * Append a + * {@code Resource} + * element to the statement, specifying a resource that the statement covers. + *

+ * This works the same as {@link #addResource(IamResource)}, except you do not need to call {@code IamResource.create()}. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBookMetadata") + * .effect(IamEffect.ALLOW) + * .addAction("dynamodb:GetItem") + * // This statement grants access to the books table: + * .addResource("arn:aws:dynamodb:us-east-2:123456789012:table/books") + * .build(); + * } + * @see Resource + * user guide + */ + Builder addResource(String resource); + + /** + * Configure the + * {@code + * NotResource} element of the statement, specifying that the statement should apply to every resource except the + * ones listed. + *

+ * This will replace any other not-resources already added to the statement. + *

+ * {@snippet : + * List notResources = + * Arrays.asList(IamResource.create("arn:aws:dynamodb:us-east-2:123456789012:table/customers")); + * + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadNotCustomers") + * .effect(IamEffect.ALLOW) + * .addAction("dynamodb:GetItem") + * // This statement grants access to EVERY CURRENT AND FUTURE RESOURCE except the customers table: + * .notResources(notResources) + * .build(); + * } + * @see + * NotResource user guide + */ + Builder notResources(Collection resources); + + /** + * Configure the + * {@code + * NotResource} element of the statement, specifying that the statement should apply to every resource except the + * ones listed. + *

+ * This works the same as {@link #notResources(Collection)}, except you do not need to call {@code IamResource.create()} + * on each resource. This will replace any other not-resources already added to the statement. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadNotCustomers") + * .effect(IamEffect.ALLOW) + * .addAction("dynamodb:GetItem") + * // This statement grants access to EVERY CURRENT AND FUTURE RESOURCE except the customers table: + * .notResourceIds(Arrays.asList("arn:aws:dynamodb:us-east-2:123456789012:table/customers")) + * .build(); + * } + * @see + * NotResource user guide + */ + Builder notResourceIds(Collection resources); + + /** + * Append a + * {@code + * NotResource} element to the statement, specifying that the statement should apply to every resource except the + * ones listed. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadNotCustomers") + * .effect(IamEffect.ALLOW) + * .addAction("dynamodb:GetItem") + * // This statement grants access to EVERY CURRENT AND FUTURE RESOURCE except the customers table: + * .addNotResource(IamResource.create("arn:aws:dynamodb:us-east-2:123456789012:table/customers")) + * .build(); + * } + * @see + * NotResource user guide + */ + Builder addNotResource(IamResource resource); + + /** + * Append a + * {@code + * NotResource} element to the statement, specifying that the statement should apply to every resource except the + * ones listed. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadNotCustomers") + * .effect(IamEffect.ALLOW) + * .addAction("dynamodb:GetItem") + * // This statement grants access to EVERY CURRENT AND FUTURE RESOURCE except the customers table: + * .addNotResource("arn:aws:dynamodb:us-east-2:123456789012:table/customers") + * .build(); + * } + * @see + * NotResource user guide + */ + Builder addNotResource(String resource); + + /** + * Configure the + * {@code + * Condition} element of the statement, specifying the conditions in which the statement is in effect. + *

+ * This will replace any other conditions already added to the statement. + *

+ * {@snippet : + * IamCondition startTime = IamCondition.create(IamConditionOperator.DATE_GREATER_THAN, + * "aws:CurrentTime", + * "1988-05-21T00:00:00Z"); + * IamCondition endTime = IamCondition.create(IamConditionOperator.DATE_LESS_THAN, + * "aws:CurrentTime", + * "2065-09-01T00:00:00Z"); + * + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBooks") + * .effect(IamEffect.ALLOW) + * .addAction("dynamodb:GetItem") + * .addResource("arn:aws:dynamodb:us-east-2:123456789012:table/books") + * // This statement grants access between the specified start and end times: + * .conditions(Arrays.asList(startTime, endTime)) + * .build(); + * } + * @see Condition + * user guide + */ + Builder conditions(Collection conditions); + + /** + * Append a + * {@code + * Condition} to the statement, specifying a condition in which the statement is in effect. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBooks") + * .effect(IamEffect.ALLOW) + * .addAction("dynamodb:GetItem") + * .addResource("arn:aws:dynamodb:us-east-2:123456789012:table/books") + * // This statement grants access after a specified start time: + * .addCondition(IamCondition.create(IamConditionOperator.DATE_GREATER_THAN, + * "aws:CurrentTime", + * "1988-05-21T00:00:00Z")) + * .build(); + * } + * @see Condition + * user guide + */ + Builder addCondition(IamCondition condition); + + /** + * Append a + * {@code + * Condition} to the statement, specifying a condition in which the statement is in effect. + *

+ * This works the same as {@link #addCondition(IamCondition)}, except you do not need to specify {@code IamCondition + * .builder()} or {@code build()}. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBooks") + * .effect(IamEffect.ALLOW) + * .addAction("dynamodb:GetItem") + * .addResource("arn:aws:dynamodb:us-east-2:123456789012:table/books") + * // This statement grants access after a specified start time: + * .addCondition(c -> c.operator(IamConditionOperator.DATE_GREATER_THAN) + * .key("aws:CurrentTime") + * .value("1988-05-21T00:00:00Z")) + * .build(); + * } + * @see Condition + * user guide + */ + Builder addCondition(Consumer condition); + + /** + * Append a + * {@code + * Condition} to the statement, specifying a condition in which the statement is in effect. + *

+ * This works the same as {@link #addCondition(IamCondition)}, except you do not need to specify {@code IamCondition + * .create()}. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBooks") + * .effect(IamEffect.ALLOW) + * .addAction("dynamodb:GetItem") + * .addResource("arn:aws:dynamodb:us-east-2:123456789012:table/books") + * // This statement grants access after a specified start time: + * .addCondition(IamConditionOperator.DATE_GREATER_THAN, + * IamConditionKey.create("aws:CurrentTime"), + * "1988-05-21T00:00:00Z") + * .build(); + * } + * @see Condition + * user guide + */ + Builder addCondition(IamConditionOperator operator, IamConditionKey key, String value); + + /** + * Append a + * {@code + * Condition} to the statement, specifying a condition in which the statement is in effect. + *

+ * This works the same as {@link #addCondition(IamCondition)}, except you do not need to specify {@code IamCondition + * .create()}. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBooks") + * .effect(IamEffect.ALLOW) + * .addAction("dynamodb:GetItem") + * .addResource("arn:aws:dynamodb:us-east-2:123456789012:table/books") + * // This statement grants access after a specified start time: + * .addCondition(IamConditionOperator.DATE_GREATER_THAN, "aws:CurrentTime", "1988-05-21T00:00:00Z") + * .build(); + * } + * @see Condition + * user guide + */ + Builder addCondition(IamConditionOperator operator, String key, String value); + + /** + * Append a + * {@code + * Condition} to the statement, specifying a condition in which the statement is in effect. + *

+ * This works the same as {@link #addCondition(IamCondition)}, except you do not need to specify {@code IamCondition + * .create()}. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBooks") + * .effect(IamEffect.ALLOW) + * .addAction("dynamodb:GetItem") + * .addResource("arn:aws:dynamodb:us-east-2:123456789012:table/books") + * // This statement grants access after a specified start time: + * .addCondition("DateGreaterThan", "aws:CurrentTime", "1988-05-21T00:00:00Z") + * .build(); + * } + * @see Condition + * user guide + */ + Builder addCondition(String operator, String key, String values); + + /** + * Append multiple + * {@code + * Condition}s to the statement, specifying conditions in which the statement is in effect. + *

+ * This works the same as {@link #addCondition(IamConditionOperator, IamConditionKey, String)} multiple times with the + * same operator and key, but different values. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBooks") + * .effect(IamEffect.ALLOW) + * .addAction("dynamodb:GetItem") + * .addResource("arn:aws:dynamodb:us-east-2:123456789012:table/books") + * // This statement grants access only in the us-east-1 and us-west-2 regions: + * .addConditions(IamConditionOperator.STRING_EQUALS, + * IamConditionKey.create("aws:RequestedRegion"), + * Arrays.asList("us-east-1", "us-west-2")) + * .build(); + * } + * @see Condition + * user guide + */ + Builder addConditions(IamConditionOperator operator, IamConditionKey key, Collection values); + + /** + * Append multiple + * {@code + * Condition}s to the statement, specifying conditions in which the statement is in effect. + *

+ * This works the same as {@link #addCondition(IamConditionOperator, String, String)} multiple times with the + * same operator and key, but different values. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBooks") + * .effect(IamEffect.ALLOW) + * .addAction("dynamodb:GetItem") + * .addResource("arn:aws:dynamodb:us-east-2:123456789012:table/books") + * // This statement grants access only in the us-east-1 and us-west-2 regions: + * .addConditions(IamConditionOperator.STRING_EQUALS, + * "aws:RequestedRegion", + * Arrays.asList("us-east-1", "us-west-2")) + * .build(); + * } + * @see Condition + * user guide + */ + Builder addConditions(IamConditionOperator operator, String key, Collection values); + + /** + * Append multiple + * {@code + * Condition}s to the statement, specifying conditions in which the statement is in effect. + *

+ * This works the same as {@link #addCondition(String, String, String)} multiple times with the + * same operator and key, but different values. + *

+ * {@snippet : + * IamStatement statement = + * IamStatement.builder() + * .sid("GrantReadBooks") + * .effect(IamEffect.ALLOW) + * .addAction("dynamodb:GetItem") + * .addResource("arn:aws:dynamodb:us-east-2:123456789012:table/books") + * // This statement grants access only in the us-east-1 and us-west-2 regions: + * .addConditions("StringEquals", "aws:RequestedRegion", Arrays.asList("us-east-1", "us-west-2")) + * .build(); + * } + * @see Condition + * user guide + */ + Builder addConditions(String operator, String key, Collection values); + } +} diff --git a/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamValue.java b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamValue.java new file mode 100644 index 000000000000..69fa6f4061f4 --- /dev/null +++ b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/IamValue.java @@ -0,0 +1,27 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam; + +import software.amazon.awssdk.annotations.SdkPublicApi; + +@SdkPublicApi +public interface IamValue { + /** + * Retrieve the string that should represent this element in the serialized IAM policy when it is marshalled via + * {@link IamPolicyWriter}. + */ + String value(); +} diff --git a/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamAction.java b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamAction.java new file mode 100644 index 000000000000..2cc335a78c71 --- /dev/null +++ b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamAction.java @@ -0,0 +1,67 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam.internal; + +import software.amazon.awssdk.annotations.NotNull; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.policybuilder.iam.IamAction; +import software.amazon.awssdk.utils.ToString; +import software.amazon.awssdk.utils.Validate; + +/** + * Default implementation of {@link IamAction}. + * + * @see IamAction#create + */ +@SdkInternalApi +public final class DefaultIamAction implements IamAction { + @NotNull private final String value; + + public DefaultIamAction(String value) { + this.value = Validate.paramNotNull(value, "actionValue"); + } + + @Override + public String value() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + DefaultIamAction that = (DefaultIamAction) o; + + return value.equals(that.value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public String toString() { + return ToString.builder("IamAction") + .add("value", value) + .build(); + } +} diff --git a/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamCondition.java b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamCondition.java new file mode 100644 index 000000000000..9634e5c84925 --- /dev/null +++ b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamCondition.java @@ -0,0 +1,153 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam.internal; + +import software.amazon.awssdk.annotations.NotNull; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.policybuilder.iam.IamCondition; +import software.amazon.awssdk.policybuilder.iam.IamConditionKey; +import software.amazon.awssdk.policybuilder.iam.IamConditionOperator; +import software.amazon.awssdk.utils.ToString; +import software.amazon.awssdk.utils.Validate; + +/** + * Default implementation of {@link IamCondition}. + * + * @see IamCondition#create + */ +@SdkInternalApi +public final class DefaultIamCondition implements IamCondition { + @NotNull private final IamConditionOperator operator; + @NotNull private final IamConditionKey key; + @NotNull private final String value; + + private DefaultIamCondition(Builder builder) { + this.operator = Validate.paramNotNull(builder.operator, "conditionOperator"); + this.key = Validate.paramNotNull(builder.key, "conditionKey"); + this.value = Validate.paramNotNull(builder.value, "conditionValue"); + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public IamConditionOperator operator() { + return operator; + } + + @Override + public IamConditionKey key() { + return key; + } + + @Override + public String value() { + return value; + } + + @Override + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + DefaultIamCondition that = (DefaultIamCondition) o; + + if (!operator.equals(that.operator)) { + return false; + } + if (!key.equals(that.key)) { + return false; + } + return value.equals(that.value); + } + + @Override + public int hashCode() { + int result = operator.hashCode(); + result = 31 * result + key.hashCode(); + result = 31 * result + value.hashCode(); + return result; + } + + @Override + public String toString() { + return ToString.builder("IamCondition") + .add("operator", operator.value()) + .add("key", key.value()) + .add("value", value) + .build(); + } + + public static class Builder implements IamCondition.Builder { + private IamConditionOperator operator; + private IamConditionKey key; + private String value; + + private Builder() { + } + + private Builder(DefaultIamCondition condition) { + this.operator = condition.operator; + this.key = condition.key; + this.value = condition.value; + } + + @Override + public IamCondition.Builder operator(IamConditionOperator operator) { + this.operator = operator; + return this; + } + + @Override + public IamCondition.Builder operator(String operator) { + this.operator = operator == null ? null : IamConditionOperator.create(operator); + return this; + } + + @Override + public IamCondition.Builder key(IamConditionKey key) { + this.key = key; + return this; + } + + @Override + public IamCondition.Builder key(String key) { + this.key = key == null ? null : IamConditionKey.create(key); + return this; + } + + @Override + public IamCondition.Builder value(String value) { + this.value = value; + return this; + } + + @Override + public IamCondition build() { + return new DefaultIamCondition(this); + } + } +} diff --git a/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamConditionKey.java b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamConditionKey.java new file mode 100644 index 000000000000..658cfd1ac5df --- /dev/null +++ b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamConditionKey.java @@ -0,0 +1,67 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam.internal; + +import software.amazon.awssdk.annotations.NotNull; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.policybuilder.iam.IamConditionKey; +import software.amazon.awssdk.utils.ToString; +import software.amazon.awssdk.utils.Validate; + +/** + * Default implementation of {@link IamConditionKey}. + * + * @see IamConditionKey#create + */ +@SdkInternalApi +public final class DefaultIamConditionKey implements IamConditionKey { + @NotNull private final String value; + + public DefaultIamConditionKey(String value) { + this.value = Validate.paramNotNull(value, "conditionKeyValue"); + } + + @Override + public String value() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + DefaultIamConditionKey that = (DefaultIamConditionKey) o; + + return value.equals(that.value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public String toString() { + return ToString.builder("IamConditionKey") + .add("value", value) + .build(); + } +} diff --git a/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamConditionOperator.java b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamConditionOperator.java new file mode 100644 index 000000000000..92554b7674d3 --- /dev/null +++ b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamConditionOperator.java @@ -0,0 +1,79 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam.internal; + +import software.amazon.awssdk.annotations.NotNull; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.policybuilder.iam.IamConditionOperator; +import software.amazon.awssdk.utils.ToString; +import software.amazon.awssdk.utils.Validate; + +/** + * Default implementation of {@link IamConditionOperator}. + * + * @see IamConditionOperator#create + */ +@SdkInternalApi +public final class DefaultIamConditionOperator implements IamConditionOperator { + @NotNull private final String value; + + public DefaultIamConditionOperator(String value) { + this.value = Validate.paramNotNull(value, "conditionOperatorValue"); + } + + @Override + public IamConditionOperator addPrefix(String prefix) { + Validate.paramNotNull(prefix, "prefix"); + return IamConditionOperator.create(prefix + value); + } + + @Override + public IamConditionOperator addSuffix(String suffix) { + Validate.paramNotNull(suffix, "suffix"); + return IamConditionOperator.create(value + suffix); + } + + @Override + public String value() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + DefaultIamConditionOperator that = (DefaultIamConditionOperator) o; + + return value.equals(that.value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public String toString() { + return ToString.builder("IamConditionOperator") + .add("value", value) + .build(); + } +} diff --git a/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamEffect.java b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamEffect.java new file mode 100644 index 000000000000..1194882c0171 --- /dev/null +++ b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamEffect.java @@ -0,0 +1,67 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam.internal; + +import software.amazon.awssdk.annotations.NotNull; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.policybuilder.iam.IamEffect; +import software.amazon.awssdk.utils.ToString; +import software.amazon.awssdk.utils.Validate; + +/** + * Default implementation of {@link IamEffect}. + * + * @see IamEffect#create + */ +@SdkInternalApi +public final class DefaultIamEffect implements IamEffect { + @NotNull private final String value; + + public DefaultIamEffect(String value) { + this.value = Validate.paramNotNull(value, "effectValue"); + } + + @Override + public String value() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + DefaultIamEffect that = (DefaultIamEffect) o; + + return value.equals(that.value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public String toString() { + return ToString.builder("IamEffect") + .add("value", value) + .build(); + } +} diff --git a/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamPolicy.java b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamPolicy.java new file mode 100644 index 000000000000..9b9c1279433e --- /dev/null +++ b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamPolicy.java @@ -0,0 +1,180 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam.internal; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import software.amazon.awssdk.annotations.NotNull; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.policybuilder.iam.IamPolicy; +import software.amazon.awssdk.policybuilder.iam.IamPolicyWriter; +import software.amazon.awssdk.policybuilder.iam.IamStatement; +import software.amazon.awssdk.utils.ToString; +import software.amazon.awssdk.utils.Validate; + +/** + * Default implementation of {@link IamPolicy}. + * + * @see IamPolicy#create + * @see IamPolicy#fromJson(String) + * @see IamPolicy#builder() + */ +@SdkInternalApi +public final class DefaultIamPolicy implements IamPolicy { + private final String id; + @NotNull private final String version; + @NotNull private final List statements; + + public DefaultIamPolicy(Builder builder) { + this.id = builder.id; + this.version = builder.version != null ? builder.version : "2012-10-17"; + this.statements = new ArrayList<>(Validate.notEmpty(builder.statements, + "At least one policy statement is required.")); + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public String id() { + return id; + } + + @Override + public String version() { + return version; + } + + @Override + public List statements() { + return Collections.unmodifiableList(statements); + } + + @Override + public String toJson() { + return toJson(IamPolicyWriter.create()); + } + + @Override + public String toJson(IamPolicyWriter writer) { + return writer.writeToString(this); + } + + @Override + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + DefaultIamPolicy that = (DefaultIamPolicy) o; + + if (!Objects.equals(id, that.id)) { + return false; + } + if (!version.equals(that.version)) { + return false; + } + if (!statements.equals(that.statements)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + version.hashCode(); + result = 31 * result + statements.hashCode(); + return result; + } + + @Override + public String toString() { + return ToString.builder("IamPolicy") + .add("id", id) + .add("version", version) + .add("statements", statements.isEmpty() ? null : statements) + .build(); + } + + public static class Builder implements IamPolicy.Builder { + private String id; + private String version; + private final List statements = new ArrayList<>(); + + private Builder() { + } + + private Builder(DefaultIamPolicy policy) { + this.id = policy.id; + this.version = policy.version; + this.statements.addAll(policy.statements); + } + + @Override + public IamPolicy.Builder id(String id) { + this.id = id; + return this; + } + + @Override + public IamPolicy.Builder version(String version) { + this.version = version; + return this; + } + + @Override + public IamPolicy.Builder statements(Collection statements) { + this.statements.clear(); + if (statements != null) { + this.statements.addAll(statements); + } + return this; + } + + @Override + public IamPolicy.Builder addStatement(IamStatement statement) { + Validate.paramNotNull(statement, "statement"); + this.statements.add(statement); + return this; + } + + @Override + public IamPolicy.Builder addStatement(Consumer statement) { + Validate.paramNotNull(statement, "statement"); + this.statements.add(IamStatement.builder().applyMutation(statement).build()); + return this; + } + + @Override + public IamPolicy build() { + return new DefaultIamPolicy(this); + } + } +} diff --git a/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamPolicyReader.java b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamPolicyReader.java new file mode 100644 index 000000000000..aafdeaeffd4c --- /dev/null +++ b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamPolicyReader.java @@ -0,0 +1,212 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam.internal; + +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.toList; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.policybuilder.iam.IamCondition; +import software.amazon.awssdk.policybuilder.iam.IamPolicy; +import software.amazon.awssdk.policybuilder.iam.IamPolicyReader; +import software.amazon.awssdk.policybuilder.iam.IamPrincipal; +import software.amazon.awssdk.policybuilder.iam.IamStatement; +import software.amazon.awssdk.protocols.jsoncore.JsonNode; +import software.amazon.awssdk.protocols.jsoncore.JsonNodeParser; +import software.amazon.awssdk.utils.Validate; + +/** + * Default implementation of {@link IamPolicyReader}. + * + * @see IamPolicyReader#create + */ +@SdkInternalApi +public final class DefaultIamPolicyReader implements IamPolicyReader { + private static final JsonNodeParser JSON_NODE_PARSER = JsonNodeParser.create(); + + @Override + public IamPolicy read(String policy) { + return read(policy.getBytes(StandardCharsets.UTF_8)); + } + + @Override + public IamPolicy read(byte[] policy) { + return read(new ByteArrayInputStream(policy)); + } + + @Override + public IamPolicy read(InputStream policy) { + return readPolicy(JSON_NODE_PARSER.parse(policy)); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return 0; + } + + private IamPolicy readPolicy(JsonNode policyNode) { + Map policyObject = expectObject(policyNode, "Policy did not start with {"); + + return IamPolicy.builder() + .version(getString(policyObject, "Version")) + .id(getString(policyObject, "Id")) + .statements(readStatements(policyObject.get("Statement"))) + .build(); + } + + private List readStatements(JsonNode statementsNode) { + if (statementsNode == null) { + return null; + } + + if (statementsNode.isArray()) { + return statementsNode.asArray() + .stream() + .map(n -> expectObject(n, "Statement entry")) + .map(this::readStatement) + .collect(toList()); + } + + if (statementsNode.isObject()) { + return singletonList(readStatement(statementsNode.asObject())); + } + + throw new IllegalArgumentException("Statement was not an array or object."); + } + + private IamStatement readStatement(Map statementObject) { + return IamStatement.builder() + .sid(getString(statementObject, "Sid")) + .effect(getString(statementObject, "Effect")) + .principals(readPrincipals(statementObject, "Principal")) + .notPrincipals(readPrincipals(statementObject, "NotPrincipal")) + .actionIds(readStringArray(statementObject, "Action")) + .notActionIds(readStringArray(statementObject, "NotAction")) + .resourceIds(readStringArray(statementObject, "Resource")) + .notResourceIds(readStringArray(statementObject, "NotResource")) + .conditions(readConditions(statementObject.get("Condition"))) + .build(); + } + + private List readPrincipals(Map statementObject, String name) { + JsonNode principalsNode = statementObject.get(name); + + if (principalsNode == null) { + return null; + } + + if (principalsNode.isString() && principalsNode.asString().equals(IamPrincipal.ALL.id())) { + return singletonList(IamPrincipal.ALL); + } + + if (principalsNode.isObject()) { + List result = new ArrayList<>(); + principalsNode.asObject().forEach((id, value) -> { + result.add(IamPrincipal.create(id, expectString(value, name + " entry value"))); + }); + return result; + } + + throw new IllegalArgumentException(name + " was not \"" + IamPrincipal.ALL.id() + "\" or an object"); + } + + private List readConditions(JsonNode conditionNode) { + if (conditionNode == null) { + return null; + } + + Map conditionObject = expectObject(conditionNode, "Condition"); + + List result = new ArrayList<>(); + + conditionObject.forEach((operator, keyValueNode) -> { + Map keyValueObject = expectObject(keyValueNode, "Condition key"); + keyValueObject.forEach((key, value) -> { + if (value.isString()) { + result.add(IamCondition.create(operator, key, value.asString())); + } else if (value.isArray()) { + List values = + value.asArray() + .stream() + .map(valueNode -> expectString(valueNode, "Condition values entry")) + .collect(toList()); + result.addAll(IamCondition.createAll(operator, key, values)); + } + }); + + }); + + return result; + } + + private List readStringArray(Map statementObject, String nodeKey) { + JsonNode node = statementObject.get(nodeKey); + + if (node == null) { + return null; + } + + if (node.isString()) { + return singletonList(node.asString()); + } + + if (node.isArray()) { + return node.asArray() + .stream() + .map(n -> expectString(n, nodeKey + " entry")) + .collect(toList()); + } + + throw new IllegalArgumentException(nodeKey + " was not an array or string"); + } + + private String getString(Map object, String key) { + JsonNode node = object.get(key); + if (node == null) { + return null; + } + + return expectString(node, key); + } + + private String expectString(JsonNode node, String name) { + Validate.isTrue(node.isString(), "%s was not a string", name); + return node.asString(); + } + + private Map expectObject(JsonNode node, String name) { + Validate.isTrue(node.isObject(), "%s was not an object", name); + return node.asObject(); + } +} diff --git a/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamPolicyWriter.java b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamPolicyWriter.java new file mode 100644 index 000000000000..553df49d3e10 --- /dev/null +++ b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamPolicyWriter.java @@ -0,0 +1,269 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam.internal; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import software.amazon.awssdk.annotations.NotNull; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.policybuilder.iam.IamCondition; +import software.amazon.awssdk.policybuilder.iam.IamConditionKey; +import software.amazon.awssdk.policybuilder.iam.IamConditionOperator; +import software.amazon.awssdk.policybuilder.iam.IamPolicy; +import software.amazon.awssdk.policybuilder.iam.IamPolicyWriter; +import software.amazon.awssdk.policybuilder.iam.IamPrincipal; +import software.amazon.awssdk.policybuilder.iam.IamPrincipalType; +import software.amazon.awssdk.policybuilder.iam.IamStatement; +import software.amazon.awssdk.policybuilder.iam.IamValue; +import software.amazon.awssdk.protocols.jsoncore.JsonNodeParser; +import software.amazon.awssdk.protocols.jsoncore.JsonWriter; +import software.amazon.awssdk.protocols.jsoncore.JsonWriter.JsonGeneratorFactory; +import software.amazon.awssdk.thirdparty.jackson.core.JsonGenerator; +import software.amazon.awssdk.utils.Validate; + +/** + * Default implementation of {@link IamPolicyWriter}. + * + * @see IamPolicyWriter#create + */ +@SdkInternalApi +public final class DefaultIamPolicyWriter implements IamPolicyWriter { + private static final IamPolicyWriter INSTANCE = IamPolicyWriter.builder().build(); + + private final Boolean prettyPrint; + @NotNull private final transient JsonGeneratorFactory jsonGeneratorFactory; + + public DefaultIamPolicyWriter(Builder builder) { + this.prettyPrint = builder.prettyPrint; + if (Boolean.TRUE.equals(builder.prettyPrint)) { + this.jsonGeneratorFactory = os -> { + JsonGenerator generator = JsonNodeParser.DEFAULT_JSON_FACTORY.createGenerator(os); + generator.useDefaultPrettyPrinter(); + return generator; + }; + } else { + this.jsonGeneratorFactory = null; + } + } + + public static IamPolicyWriter create() { + return INSTANCE; + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public String writeToString(IamPolicy policy) { + return new String(writeToBytes(policy), StandardCharsets.UTF_8); + } + + @Override + public byte[] writeToBytes(IamPolicy policy) { + return writePolicy(policy).getBytes(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + DefaultIamPolicyWriter that = (DefaultIamPolicyWriter) o; + + return Objects.equals(prettyPrint, that.prettyPrint); + } + + @Override + public int hashCode() { + return prettyPrint != null ? prettyPrint.hashCode() : 0; + } + + private JsonWriter writePolicy(IamPolicy policy) { + JsonWriter writer = + JsonWriter.builder() + .jsonGeneratorFactory(jsonGeneratorFactory) + .build(); + + writer.writeStartObject(); + + writeFieldIfNotNull(writer, "Version", policy.version()); + writeFieldIfNotNull(writer, "Id", policy.id()); + writeStatements(writer, policy.statements()); + + writer.writeEndObject(); + return writer; + } + + private void writeStatements(JsonWriter writer, List statements) { + if (statements.isEmpty()) { + return; + } + + writer.writeFieldName("Statement"); + + if (statements.size() == 1) { + writeStatement(writer, statements.get(0)); + return; + } + + writer.writeStartArray(); + statements.forEach(statement -> { + writeStatement(writer, statement); + }); + writer.writeEndArray(); + } + + private void writeStatement(JsonWriter writer, IamStatement statement) { + writer.writeStartObject(); + writeFieldIfNotNull(writer, "Sid", statement.sid()); + writeFieldIfNotNull(writer, "Effect", statement.effect()); + writePrincipals(writer, "Principal", statement.principals()); + writePrincipals(writer, "NotPrincipal", statement.notPrincipals()); + writeValueArrayField(writer, "Action", statement.actions()); + writeValueArrayField(writer, "NotAction", statement.notActions()); + writeValueArrayField(writer, "Resource", statement.actions()); + writeValueArrayField(writer, "NotResource", statement.notResources()); + writeConditions(writer, statement.conditions()); + writer.writeEndObject(); + } + + private void writePrincipals(JsonWriter writer, String fieldName, List principals) { + if (principals.isEmpty()) { + return; + } + + if (principals.size() == 1 && principals.get(0).equals(IamPrincipal.ALL)) { + writeFieldIfNotNull(writer, fieldName, IamPrincipal.ALL.id()); + return; + } + + principals.forEach(p -> Validate.isTrue(!IamPrincipal.ALL.equals(p), + "IamPrincipal.ALL must not be combined with other principals.")); + + Map> aggregatedPrincipals = new LinkedHashMap<>(); + principals.forEach(principal -> { + aggregatedPrincipals.computeIfAbsent(principal.type(), t -> new ArrayList<>()) + .add(principal.id()); + }); + + writer.writeFieldName(fieldName); + writer.writeStartObject(); + aggregatedPrincipals.forEach((principalType, ids) -> { + writeArrayField(writer, principalType.value(), ids); + }); + writer.writeEndObject(); + } + + + private void writeConditions(JsonWriter writer, List conditions) { + if (conditions.isEmpty()) { + return; + } + + Map>> aggregatedConditions = new LinkedHashMap<>(); + conditions.forEach(condition -> { + aggregatedConditions.computeIfAbsent(condition.operator(), t -> new LinkedHashMap<>()) + .computeIfAbsent(condition.key(), t -> new ArrayList<>()) + .add(condition.value()); + }); + + writer.writeFieldName("Condition"); + writer.writeStartObject(); + aggregatedConditions.forEach((operator, keyValues) -> { + writer.writeFieldName(operator.value()); + writer.writeStartObject(); + keyValues.forEach((key, values) -> { + writeArrayField(writer, key.value(), values); + }); + writer.writeEndObject(); + }); + writer.writeEndObject(); + } + + private void writeValueArrayField(JsonWriter writer, String fieldName, List fieldValues) { + List values = new ArrayList<>(fieldValues.size()); + fieldValues.forEach(v -> values.add(v.value())); + writeArrayField(writer, fieldName, values); + } + + private void writeArrayField(JsonWriter writer, + String fieldName, List fieldValues) { + if (fieldValues.isEmpty()) { + return; + } + + if (fieldValues.size() == 1) { + writeFieldIfNotNull(writer, fieldName, fieldValues.get(0)); + return; + } + + writer.writeFieldName(fieldName); + writer.writeStartArray(); + fieldValues.forEach(writer::writeValue); + writer.writeEndArray(); + } + + private void writeFieldIfNotNull(JsonWriter writer, String key, IamValue value) { + if (value == null) { + return; + } + + writeFieldIfNotNull(writer, key, value.value()); + } + + private void writeFieldIfNotNull(JsonWriter writer, String key, String value) { + if (value != null) { + writer.writeFieldName(key); + writer.writeValue(value); + } + } + + @Override + public Builder toBuilder() { + return new Builder(this); + } + + public static class Builder implements IamPolicyWriter.Builder { + private Boolean prettyPrint; + + private Builder() { + } + + private Builder(DefaultIamPolicyWriter writer) { + this.prettyPrint = writer.prettyPrint; + } + + @Override + public IamPolicyWriter.Builder prettyPrint(Boolean prettyPrint) { + this.prettyPrint = prettyPrint; + return this; + } + + @Override + public IamPolicyWriter build() { + return new DefaultIamPolicyWriter(this); + } + } +} diff --git a/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamPrincipal.java b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamPrincipal.java new file mode 100644 index 000000000000..9e99d45ba8cb --- /dev/null +++ b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamPrincipal.java @@ -0,0 +1,126 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam.internal; + +import software.amazon.awssdk.annotations.NotNull; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.policybuilder.iam.IamPrincipal; +import software.amazon.awssdk.policybuilder.iam.IamPrincipalType; +import software.amazon.awssdk.utils.ToString; +import software.amazon.awssdk.utils.Validate; + +/** + * Default implementation of {@link IamPrincipal}. + * + * @see IamPrincipal#create + */ +@SdkInternalApi +public final class DefaultIamPrincipal implements IamPrincipal { + @NotNull private final IamPrincipalType type; + @NotNull private final String id; + + private DefaultIamPrincipal(Builder builder) { + this.type = Validate.paramNotNull(builder.type, "principalType"); + this.id = Validate.paramNotNull(builder.id, "principalId"); + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public IamPrincipalType type() { + return type; + } + + @Override + public String id() { + return id; + } + + @Override + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + DefaultIamPrincipal that = (DefaultIamPrincipal) o; + + if (!type.equals(that.type)) { + return false; + } + return id.equals(that.id); + } + + @Override + public int hashCode() { + int result = type.hashCode(); + result = 31 * result + id.hashCode(); + return result; + } + + @Override + public String toString() { + return ToString.builder("IamPrincipal") + .add("type", type.value()) + .add("id", id) + .build(); + } + + public static class Builder implements IamPrincipal.Builder { + private IamPrincipalType type; + private String id; + + private Builder() { + } + + private Builder(DefaultIamPrincipal principal) { + this.type = principal.type; + this.id = principal.id; + } + + @Override + public IamPrincipal.Builder type(IamPrincipalType type) { + this.type = type; + return this; + } + + @Override + public IamPrincipal.Builder type(String type) { + this.type = type == null ? null : IamPrincipalType.create(type); + return this; + } + + @Override + public IamPrincipal.Builder id(String id) { + this.id = id; + return this; + } + + @Override + public IamPrincipal build() { + return new DefaultIamPrincipal(this); + } + } +} diff --git a/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamPrincipalType.java b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamPrincipalType.java new file mode 100644 index 000000000000..2e909220d135 --- /dev/null +++ b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamPrincipalType.java @@ -0,0 +1,67 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam.internal; + +import software.amazon.awssdk.annotations.NotNull; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.policybuilder.iam.IamPrincipalType; +import software.amazon.awssdk.utils.ToString; +import software.amazon.awssdk.utils.Validate; + +/** + * Default implementation of {@link IamPrincipalType}. + * + * @see IamPrincipalType#create + */ +@SdkInternalApi +public final class DefaultIamPrincipalType implements IamPrincipalType { + @NotNull private final String value; + + public DefaultIamPrincipalType(String value) { + this.value = Validate.paramNotNull(value, "principalTypeValue"); + } + + @Override + public String value() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + DefaultIamPrincipalType that = (DefaultIamPrincipalType) o; + + return value.equals(that.value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public String toString() { + return ToString.builder("IamPrincipalType") + .add("value", value) + .build(); + } +} diff --git a/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamResource.java b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamResource.java new file mode 100644 index 000000000000..8e928dad37b1 --- /dev/null +++ b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamResource.java @@ -0,0 +1,67 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam.internal; + +import software.amazon.awssdk.annotations.NotNull; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.policybuilder.iam.IamResource; +import software.amazon.awssdk.utils.ToString; +import software.amazon.awssdk.utils.Validate; + +/** + * Default implementation of {@link IamResource}. + * + * @see IamResource#create + */ +@SdkInternalApi +public final class DefaultIamResource implements IamResource { + @NotNull private final String value; + + public DefaultIamResource(String value) { + this.value = Validate.paramNotNull(value, "resourceValue"); + } + + @Override + public String value() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + DefaultIamResource that = (DefaultIamResource) o; + + return value.equals(that.value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public String toString() { + return ToString.builder("IamResource") + .add("value", value) + .build(); + } +} diff --git a/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamStatement.java b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamStatement.java new file mode 100644 index 000000000000..274da7e75e01 --- /dev/null +++ b/services-custom/iam-policy-builder/src/main/java/software/amazon/awssdk/policybuilder/iam/internal/DefaultIamStatement.java @@ -0,0 +1,519 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam.internal; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import software.amazon.awssdk.annotations.NotNull; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.policybuilder.iam.IamAction; +import software.amazon.awssdk.policybuilder.iam.IamCondition; +import software.amazon.awssdk.policybuilder.iam.IamConditionKey; +import software.amazon.awssdk.policybuilder.iam.IamConditionOperator; +import software.amazon.awssdk.policybuilder.iam.IamEffect; +import software.amazon.awssdk.policybuilder.iam.IamPrincipal; +import software.amazon.awssdk.policybuilder.iam.IamPrincipalType; +import software.amazon.awssdk.policybuilder.iam.IamResource; +import software.amazon.awssdk.policybuilder.iam.IamStatement; +import software.amazon.awssdk.utils.ToString; +import software.amazon.awssdk.utils.Validate; + +/** + * Default implementation of {@link IamStatement}. + * + * @see IamStatement#builder + */ +@SdkInternalApi +public final class DefaultIamStatement implements IamStatement { + private final String sid; + @NotNull private final IamEffect effect; + @NotNull private final List principals; + @NotNull private final List notPrincipals; + @NotNull private final List actions; + @NotNull private final List notActions; + @NotNull private final List resources; + @NotNull private final List notResources; + @NotNull private final List conditions; + + public DefaultIamStatement(Builder builder) { + this.sid = builder.sid; + this.effect = Validate.paramNotNull(builder.effect, "statementEffect"); + this.principals = new ArrayList<>(builder.principals); + this.notPrincipals = new ArrayList<>(builder.notPrincipals); + this.actions = new ArrayList<>(builder.actions); + this.notActions = new ArrayList<>(builder.notActions); + this.resources = new ArrayList<>(builder.resources); + this.notResources = new ArrayList<>(builder.notResources); + this.conditions = new ArrayList<>(builder.conditions); + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public String sid() { + return sid; + } + + @Override + public IamEffect effect() { + return effect; + } + + @Override + public List principals() { + return Collections.unmodifiableList(principals); + } + + @Override + public List notPrincipals() { + return Collections.unmodifiableList(notPrincipals); + } + + @Override + public List actions() { + return Collections.unmodifiableList(actions); + } + + @Override + public List notActions() { + return Collections.unmodifiableList(notActions); + } + + @Override + public List resources() { + return Collections.unmodifiableList(resources); + } + + @Override + public List notResources() { + return Collections.unmodifiableList(notResources); + } + + @Override + public List conditions() { + return Collections.unmodifiableList(conditions); + } + + @Override + public IamStatement.Builder toBuilder() { + return new Builder(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + DefaultIamStatement that = (DefaultIamStatement) o; + + if (!Objects.equals(sid, that.sid)) { + return false; + } + if (!effect.equals(that.effect)) { + return false; + } + if (!principals.equals(that.principals)) { + return false; + } + if (!notPrincipals.equals(that.notPrincipals)) { + return false; + } + if (!actions.equals(that.actions)) { + return false; + } + if (!notActions.equals(that.notActions)) { + return false; + } + if (!resources.equals(that.resources)) { + return false; + } + if (!notResources.equals(that.notResources)) { + return false; + } + if (!conditions.equals(that.conditions)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int result = sid != null ? sid.hashCode() : 0; + result = 31 * result + effect.hashCode(); + result = 31 * result + principals.hashCode(); + result = 31 * result + notPrincipals.hashCode(); + result = 31 * result + actions.hashCode(); + result = 31 * result + notActions.hashCode(); + result = 31 * result + resources.hashCode(); + result = 31 * result + notResources.hashCode(); + result = 31 * result + conditions.hashCode(); + return result; + } + + @Override + public String toString() { + return ToString.builder("IamStatement") + .add("sid", sid) + .add("effect", effect) + .add("principals", principals.isEmpty() ? null : principals) + .add("notPrincipals", notPrincipals.isEmpty() ? null : notPrincipals) + .add("actions", actions.isEmpty() ? null : actions) + .add("notActions", notActions.isEmpty() ? null : notActions) + .add("resources", resources.isEmpty() ? null : resources) + .add("notResources", notResources.isEmpty() ? null : notResources) + .add("conditions", conditions.isEmpty() ? null : conditions) + .build(); + } + + public static class Builder implements IamStatement.Builder { + private String sid; + private IamEffect effect; + private final List principals = new ArrayList<>(); + private final List notPrincipals = new ArrayList<>(); + private final List actions = new ArrayList<>(); + private final List notActions = new ArrayList<>(); + private final List resources = new ArrayList<>(); + private final List notResources = new ArrayList<>(); + private final List conditions = new ArrayList<>(); + + private Builder() { + } + + private Builder(DefaultIamStatement statement) { + this.sid = statement.sid; + this.effect = statement.effect; + this.principals.addAll(statement.principals); + this.notPrincipals.addAll(statement.notPrincipals); + this.actions.addAll(statement.actions); + this.notActions.addAll(statement.notActions); + this.resources.addAll(statement.resources); + this.notResources.addAll(statement.notResources); + this.conditions.addAll(statement.conditions); + + } + + @Override + public IamStatement.Builder sid(String sid) { + this.sid = sid; + return this; + } + + @Override + public IamStatement.Builder effect(IamEffect effect) { + this.effect = effect; + return this; + } + + @Override + public IamStatement.Builder effect(String effect) { + this.effect = IamEffect.create(effect); + return this; + } + + @Override + public IamStatement.Builder principals(Collection principals) { + this.principals.clear(); + if (principals != null) { + this.principals.addAll(principals); + } + return this; + } + + @Override + public IamStatement.Builder addPrincipal(IamPrincipal principal) { + Validate.paramNotNull(principal, "principal"); + this.principals.add(principal); + return this; + } + + @Override + public IamStatement.Builder addPrincipal(Consumer principal) { + Validate.paramNotNull(principal, "principal"); + this.principals.add(IamPrincipal.builder().applyMutation(principal).build()); + return this; + } + + @Override + public IamStatement.Builder addPrincipal(IamPrincipalType iamPrincipalType, String principal) { + return addPrincipal(IamPrincipal.create(iamPrincipalType, principal)); + } + + @Override + public IamStatement.Builder addPrincipal(String iamPrincipalType, String principal) { + return addPrincipal(IamPrincipal.create(iamPrincipalType, principal)); + } + + @Override + public IamStatement.Builder addPrincipals(IamPrincipalType principalType, Collection principals) { + Validate.paramNotNull(principalType, "principals"); + for (String principal : principals) { + this.principals.add(IamPrincipal.create(principalType, principal)); + } + return this; + } + + @Override + public IamStatement.Builder addPrincipals(String principalType, Collection principals) { + return addPrincipals(IamPrincipalType.create(principalType), principals); + } + + @Override + public IamStatement.Builder notPrincipals(Collection notPrincipals) { + this.notPrincipals.clear(); + if (notPrincipals != null) { + this.notPrincipals.addAll(notPrincipals); + } + return this; + } + + @Override + public IamStatement.Builder addNotPrincipal(IamPrincipal notPrincipal) { + Validate.paramNotNull(notPrincipal, "notPrincipal"); + this.notPrincipals.add(notPrincipal); + return this; + } + + @Override + public IamStatement.Builder addNotPrincipal(Consumer notPrincipal) { + Validate.paramNotNull(notPrincipal, "notPrincipal"); + this.notPrincipals.add(IamPrincipal.builder().applyMutation(notPrincipal).build()); + return this; + } + + @Override + public IamStatement.Builder addNotPrincipal(IamPrincipalType iamPrincipalType, String principal) { + return addNotPrincipal(IamPrincipal.create(iamPrincipalType, principal)); + } + + @Override + public IamStatement.Builder addNotPrincipal(String iamPrincipalType, String principal) { + return addNotPrincipal(IamPrincipal.create(iamPrincipalType, principal)); + } + + @Override + public IamStatement.Builder addNotPrincipals(IamPrincipalType notPrincipalType, Collection notPrincipals) { + Validate.paramNotNull(notPrincipals, "notPrincipals"); + for (String notPrincipal : notPrincipals) { + this.notPrincipals.add(IamPrincipal.create(notPrincipalType, notPrincipal)); + } + return this; + } + + @Override + public IamStatement.Builder addNotPrincipals(String notPrincipalType, Collection notPrincipals) { + return addNotPrincipals(IamPrincipalType.create(notPrincipalType), notPrincipals); + } + + @Override + public IamStatement.Builder actions(Collection actions) { + this.actions.clear(); + if (actions != null) { + this.actions.addAll(actions); + } + return this; + } + + @Override + public IamStatement.Builder actionIds(Collection actions) { + this.actions.clear(); + if (actions != null) { + actions.forEach(this::addAction); + } + return this; + } + + @Override + public IamStatement.Builder addAction(IamAction action) { + Validate.paramNotNull(action, "action"); + this.actions.add(action); + return this; + } + + @Override + public IamStatement.Builder addAction(String action) { + this.actions.add(IamAction.create(action)); + return this; + } + + @Override + public IamStatement.Builder notActions(Collection notActions) { + this.notActions.clear(); + if (notActions != null) { + this.notActions.addAll(notActions); + } + return this; + } + + @Override + public IamStatement.Builder notActionIds(Collection notActions) { + this.notActions.clear(); + if (notActions != null) { + notActions.forEach(this::addNotAction); + } + return this; + } + + @Override + public IamStatement.Builder addNotAction(IamAction notAction) { + Validate.paramNotNull(notAction, "notAction"); + this.notActions.add(notAction); + return this; + } + + @Override + public IamStatement.Builder addNotAction(String notAction) { + this.notActions.add(IamAction.create(notAction)); + return this; + } + + @Override + public IamStatement.Builder resources(Collection resources) { + this.resources.clear(); + if (resources != null) { + this.resources.addAll(resources); + } + return this; + } + + @Override + public IamStatement.Builder resourceIds(Collection resources) { + this.resources.clear(); + if (resources != null) { + resources.forEach(this::addResource); + } + return this; + } + + @Override + public IamStatement.Builder addResource(IamResource resource) { + Validate.paramNotNull(resource, "resource"); + this.resources.add(resource); + return this; + } + + @Override + public IamStatement.Builder addResource(String resource) { + this.resources.add(IamResource.create(resource)); + return this; + } + + @Override + public IamStatement.Builder notResources(Collection notResources) { + this.notResources.clear(); + if (notResources != null) { + this.notResources.addAll(notResources); + } + return this; + } + + @Override + public IamStatement.Builder notResourceIds(Collection notResources) { + this.notResources.clear(); + if (notResources != null) { + notResources.forEach(this::addNotResource); + } + return this; + } + + @Override + public IamStatement.Builder addNotResource(IamResource notResource) { + this.notResources.add(notResource); + return this; + } + + @Override + public IamStatement.Builder addNotResource(String notResource) { + this.notResources.add(IamResource.create(notResource)); + return this; + } + + @Override + public IamStatement.Builder conditions(Collection conditions) { + this.conditions.clear(); + if (conditions != null) { + this.conditions.addAll(conditions); + } + return this; + } + + @Override + public IamStatement.Builder addCondition(IamCondition condition) { + Validate.paramNotNull(condition, "condition"); + this.conditions.add(condition); + return this; + } + + @Override + public IamStatement.Builder addCondition(Consumer condition) { + Validate.paramNotNull(condition, "condition"); + this.conditions.add(IamCondition.builder().applyMutation(condition).build()); + return this; + } + + @Override + public IamStatement.Builder addCondition(IamConditionOperator operator, IamConditionKey key, String value) { + this.conditions.add(IamCondition.create(operator, key, value)); + return this; + } + + @Override + public IamStatement.Builder addCondition(IamConditionOperator operator, String key, String value) { + return addCondition(operator, IamConditionKey.create(key), value); + } + + @Override + public IamStatement.Builder addCondition(String operator, String key, String value) { + this.conditions.add(IamCondition.create(operator, key, value)); + return this; + } + + @Override + public IamStatement.Builder addConditions(IamConditionOperator operator, + IamConditionKey key, + Collection values) { + Validate.paramNotNull(values, "values"); + for (String value : values) { + this.conditions.add(IamCondition.create(operator, key, value)); + } + return this; + } + + @Override + public IamStatement.Builder addConditions(IamConditionOperator operator, String key, Collection values) { + return addConditions(operator, IamConditionKey.create(key), values); + } + + @Override + public IamStatement.Builder addConditions(String operator, String key, Collection values) { + return addConditions(IamConditionOperator.create(operator), IamConditionKey.create(key), values); + } + + @Override + public IamStatement build() { + return new DefaultIamStatement(this); + } + } +} diff --git a/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/EqualsHashCodeTest.java b/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/EqualsHashCodeTest.java new file mode 100644 index 000000000000..e0f11fc61c79 --- /dev/null +++ b/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/EqualsHashCodeTest.java @@ -0,0 +1,28 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; + +public class EqualsHashCodeTest { + @Test + public void allClasses_equalsHashCode_isCorrect() { + EqualsVerifier.forPackage("software.amazon.awssdk.policybuilder.iam", true) + .except(c -> c.isMemberClass() || c.isAnonymousClass()) + .verify(); + } +} diff --git a/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamActionTest.java b/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamActionTest.java new file mode 100644 index 000000000000..f1f588d790e1 --- /dev/null +++ b/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamActionTest.java @@ -0,0 +1,27 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; + +class IamActionTest { + @Test + public void iamActionValueNotNull() { + assertThatThrownBy(() -> IamAction.create(null)).hasMessageMatching(".*[Aa]ction.*"); + } +} \ No newline at end of file diff --git a/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamConditionKeyTest.java b/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamConditionKeyTest.java new file mode 100644 index 000000000000..28b635ed4abd --- /dev/null +++ b/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamConditionKeyTest.java @@ -0,0 +1,27 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; + +class IamConditionKeyTest { + @Test + public void iamConditionKeyValueNotNull() { + assertThatThrownBy(() -> IamConditionKey.create(null)).hasMessageMatching(".*[Cc]ondition.*[Kk]ey.*"); + } +} \ No newline at end of file diff --git a/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamConditionOperatorTest.java b/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamConditionOperatorTest.java new file mode 100644 index 000000000000..e13f092469fb --- /dev/null +++ b/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamConditionOperatorTest.java @@ -0,0 +1,27 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; + +class IamConditionOperatorTest { + @Test + public void iamConditionOperatorValueNotNull() { + assertThatThrownBy(() -> IamConditionOperator.create(null)).hasMessageMatching(".*[Cc]ondition.*[Oo]perator.*"); + } +} \ No newline at end of file diff --git a/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamConditionTest.java b/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamConditionTest.java new file mode 100644 index 000000000000..10e2542a9806 --- /dev/null +++ b/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamConditionTest.java @@ -0,0 +1,97 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.function.Consumer; +import org.junit.jupiter.api.Test; + +class IamConditionTest { + private static final IamCondition FULL_CONDITION = + IamCondition.builder() + .operator("Operator") + .key("Key") + .value("Value") // TODO: Id? + .build(); + + @Test + public void constructorsWork() { + assertThat(IamCondition.create("Operator", "Key", "Value")).isEqualTo(FULL_CONDITION); + + assertThat(IamCondition.create(IamConditionOperator.create("Operator"), + IamConditionKey.create("Key"), + "Value")) + .isEqualTo(FULL_CONDITION); + + assertThat(IamCondition.create(IamConditionOperator.create("Operator"), + "Key", + "Value")) + .isEqualTo(FULL_CONDITION); + + assertThat(IamCondition.createAll("Operator", + "Key", + asList("Value1", "Value2"))) + .containsExactly(IamCondition.create("Operator", "Key", "Value1"), + IamCondition.create("Operator", "Key", "Value2")); + + assertThat(IamCondition.createAll(IamConditionOperator.create("Operator"), + "Key", + asList("Value1", "Value2"))) + .containsExactly(IamCondition.create("Operator", "Key", "Value1"), + IamCondition.create("Operator", "Key", "Value2")); + + assertThat(IamCondition.createAll(IamConditionOperator.create("Operator"), + IamConditionKey.create("Key"), + asList("Value1", "Value2"))) + .containsExactly(IamCondition.create("Operator", "Key", "Value1"), + IamCondition.create("Operator", "Key", "Value2")); + } + + @Test + public void simpleGettersSettersWork() { + assertThat(FULL_CONDITION.operator().value()).isEqualTo("Operator"); + assertThat(FULL_CONDITION.key().value()).isEqualTo("Key"); + assertThat(FULL_CONDITION.value()).isEqualTo("Value"); + } + + @Test + public void toBuilderPreservesValues() { + assertThat(FULL_CONDITION.toBuilder().build()).isEqualTo(FULL_CONDITION); + } + + @Test + public void operatorSettersWork() { + assertThat(condition(c -> c.operator("Operator")).operator().value()).isEqualTo("Operator"); + assertThat(condition(c -> c.operator(IamConditionOperator.create("Operator"))).operator().value()).isEqualTo("Operator"); + } + + @Test + public void keySettersWork() { + assertThat(condition(c -> c.key("Key")).key().value()).isEqualTo("Key"); + assertThat(condition(c -> c.key(IamConditionKey.create("Key"))).key().value()).isEqualTo("Key"); + } + + public IamCondition condition(Consumer condition) { + return IamCondition.builder() + .operator("Operator") + .key("Key") + .value("Value") + .applyMutation(condition) + .build(); + } +} \ No newline at end of file diff --git a/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamEffectTest.java b/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamEffectTest.java new file mode 100644 index 000000000000..58c688d24da1 --- /dev/null +++ b/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamEffectTest.java @@ -0,0 +1,27 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; + +class IamEffectTest { + @Test + public void iamEffectValueNotNull() { + assertThatThrownBy(() -> IamEffect.create(null)).hasMessageMatching(".*[Ee]ffect.*"); + } +} \ No newline at end of file diff --git a/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamPolicyReaderTest.java b/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamPolicyReaderTest.java new file mode 100644 index 000000000000..4b1f5d341b0c --- /dev/null +++ b/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamPolicyReaderTest.java @@ -0,0 +1,188 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static software.amazon.awssdk.policybuilder.iam.IamEffect.ALLOW; + +import org.junit.jupiter.api.Test; + +class IamPolicyReaderTest { + private static final IamPrincipal PRINCIPAL_1 = IamPrincipal.ALL; + private static final IamPrincipal PRINCIPAL_2 = IamPrincipal.create("2", "*"); + private static final IamResource RESOURCE_1 = IamResource.create("1"); + private static final IamResource RESOURCE_2 = IamResource.create("2"); + private static final IamAction ACTION_1 = IamAction.create("1"); + private static final IamAction ACTION_2 = IamAction.create("2"); + private static final IamCondition CONDITION_1 = IamCondition.create("1", "K1", "V1"); + private static final IamCondition CONDITION_2 = IamCondition.create("1", "K2", "V1"); + private static final IamCondition CONDITION_3 = IamCondition.create("1", "K2", "V2"); + private static final IamCondition CONDITION_4 = IamCondition.create("2", "K1", "V1"); + + private static final IamStatement FULL_STATEMENT = + IamStatement.builder() + .effect(ALLOW) + .sid("Sid") + .principals(asList(PRINCIPAL_1, PRINCIPAL_2)) + .notPrincipals(asList(PRINCIPAL_1, PRINCIPAL_2)) + .resources(asList(RESOURCE_1, RESOURCE_2)) + .notResources(asList(RESOURCE_1, RESOURCE_2)) + .actions(asList(ACTION_1, ACTION_2)) + .notActions(asList(ACTION_1, ACTION_2)) + .conditions(asList(CONDITION_1, CONDITION_2, CONDITION_3, CONDITION_4)) + .build(); + + private static final IamPolicy FULL_POLICY = + IamPolicy.builder() + .id("Id") + .version("Version") + .statements(asList(FULL_STATEMENT, FULL_STATEMENT)) + .build(); + + private static final IamStatement MINIMAL_STATEMENT = IamStatement.builder().effect(ALLOW).build(); + + private static final IamPolicy MINIMAL_POLICY = + IamPolicy.builder() + .version("Version") + .statements(singletonList(MINIMAL_STATEMENT)) + .build(); + + private static final IamStatement ONE_ELEMENT_LISTS_STATEMENT = + IamStatement.builder() + .effect(ALLOW) + .sid("Sid") + .principals(singletonList(PRINCIPAL_1)) + .notPrincipals(singletonList(PRINCIPAL_1)) + .resources(singletonList(RESOURCE_1)) + .notResources(singletonList(RESOURCE_1)) + .actions(singletonList(ACTION_1)) + .notActions(singletonList(ACTION_1)) + .conditions(singletonList(CONDITION_1)) + .build(); + + private static final IamPolicy ONE_ELEMENT_LISTS_POLICY = + IamPolicy.builder() + .version("Version") + .statements(singletonList(ONE_ELEMENT_LISTS_STATEMENT)) + .build(); + + private static final IamPolicyReader READER = IamPolicyReader.create(); + + @Test + public void readFullPolicyWorks() { + assertThat(READER.read("{\"Version\":\"Version\"," + + "\"Id\":\"Id\"," + + "\"Statement\":[" + + "{\"Sid\":\"Sid\",\"Effect\":\"Allow\",\"Principal\":{\"*\":\"*\",\"2\":\"*\"},\"NotPrincipal\":{\"*\":\"*\",\"2\":\"*\"},\"Action\":[\"1\",\"2\"],\"NotAction\":[\"1\",\"2\"],\"Resource\":[\"1\",\"2\"],\"NotResource\":[\"1\",\"2\"],\"Condition\":{\"1\":{\"K1\":\"V1\",\"K2\":[\"V1\",\"V2\"]},\"2\":{\"K1\":\"V1\"}}}," + + "{\"Sid\":\"Sid\",\"Effect\":\"Allow\",\"Principal\":{\"*\":\"*\",\"2\":\"*\"},\"NotPrincipal\":{\"*\":\"*\",\"2\":\"*\"},\"Action\":[\"1\",\"2\"],\"NotAction\":[\"1\",\"2\"],\"Resource\":[\"1\",\"2\"],\"NotResource\":[\"1\",\"2\"],\"Condition\":{\"1\":{\"K1\":\"V1\",\"K2\":[\"V1\",\"V2\"]},\"2\":{\"K1\":\"V1\"}}}" + + "]}")) + .isEqualTo(FULL_POLICY); + } + + @Test + public void prettyWriteFullPolicyWorks() { + assertThat(READER.read("{\n" + + " \"Version\" : \"Version\",\n" + + " \"Id\" : \"Id\",\n" + + " \"Statement\" : [ {\n" + + " \"Sid\" : \"Sid\",\n" + + " \"Effect\" : \"Allow\",\n" + + " \"Principal\" : {\n" + + " \"*\" : \"*\",\n" + + " \"2\" : \"*\"\n" + + " },\n" + + " \"NotPrincipal\" : {\n" + + " \"*\" : \"*\",\n" + + " \"2\" : \"*\"\n" + + " },\n" + + " \"Action\" : [ \"1\", \"2\" ],\n" + + " \"NotAction\" : [ \"1\", \"2\" ],\n" + + " \"Resource\" : [ \"1\", \"2\" ],\n" + + " \"NotResource\" : [ \"1\", \"2\" ],\n" + + " \"Condition\" : {\n" + + " \"1\" : {\n" + + " \"K1\" : \"V1\",\n" + + " \"K2\" : [ \"V1\", \"V2\" ]\n" + + " },\n" + + " \"2\" : {\n" + + " \"K1\" : \"V1\"\n" + + " }\n" + + " }\n" + + " }, {\n" + + " \"Sid\" : \"Sid\",\n" + + " \"Effect\" : \"Allow\",\n" + + " \"Principal\" : {\n" + + " \"*\" : \"*\",\n" + + " \"2\" : \"*\"\n" + + " },\n" + + " \"NotPrincipal\" : {\n" + + " \"*\" : \"*\",\n" + + " \"2\" : \"*\"\n" + + " },\n" + + " \"Action\" : [ \"1\", \"2\" ],\n" + + " \"NotAction\" : [ \"1\", \"2\" ],\n" + + " \"Resource\" : [ \"1\", \"2\" ],\n" + + " \"NotResource\" : [ \"1\", \"2\" ],\n" + + " \"Condition\" : {\n" + + " \"1\" : {\n" + + " \"K1\" : \"V1\",\n" + + " \"K2\" : [ \"V1\", \"V2\" ]\n" + + " },\n" + + " \"2\" : {\n" + + " \"K1\" : \"V1\"\n" + + " }\n" + + " }\n" + + " } ]\n" + + "}")) + .isEqualTo(FULL_POLICY); + } + + @Test + public void writeMinimalPolicyWorks() { + assertThat(READER.read("{\n" + + " \"Version\" : \"Version\",\n" + + " \"Statement\" : {\n" + + " \"Effect\" : \"Allow\"\n" + + " }\n" + + "}")) + .isEqualTo(MINIMAL_POLICY); + } + + @Test + public void singleElementListsAreWrittenAsNonArrays() { + assertThat(READER.read("{\n" + + " \"Version\" : \"Version\",\n" + + " \"Statement\" : {\n" + + " \"Sid\" : \"Sid\",\n" + + " \"Effect\" : \"Allow\",\n" + + " \"Principal\" : \"*\",\n" + + " \"NotPrincipal\" : \"*\",\n" + + " \"Action\" : \"1\",\n" + + " \"NotAction\" : \"1\",\n" + + " \"Resource\" : \"1\",\n" + + " \"NotResource\" : \"1\",\n" + + " \"Condition\" : {\n" + + " \"1\" : {\n" + + " \"K1\" : \"V1\"\n" + + " }\n" + + " }\n" + + " }\n" + + "}")) + .isEqualTo(ONE_ELEMENT_LISTS_POLICY); + } +} \ No newline at end of file diff --git a/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamPolicyTest.java b/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamPolicyTest.java new file mode 100644 index 000000000000..7127e32e943b --- /dev/null +++ b/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamPolicyTest.java @@ -0,0 +1,91 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam; + + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static software.amazon.awssdk.policybuilder.iam.IamEffect.ALLOW; +import static software.amazon.awssdk.policybuilder.iam.IamEffect.DENY; + +import java.util.function.Consumer; +import org.junit.jupiter.api.Test; + +class IamPolicyTest { + private static final IamStatement ALLOW_STATEMENT = IamStatement.builder().effect(ALLOW).build(); + private static final IamStatement DENY_STATEMENT = IamStatement.builder().effect(DENY).build(); + private static final String SMALLEST_POLICY_JSON = "{\"Version\":\"2012-10-17\",\"Statement\":{\"Effect\":\"Allow\"}}"; + private static final IamPolicy SMALLEST_POLICY = IamPolicy.builder().addStatement(ALLOW_STATEMENT).build(); + + + private static final IamPolicy FULL_POLICY = IamPolicy.builder() + .id("Id") + .version("Version") + .statements(singletonList(ALLOW_STATEMENT)) + .build(); + + @Test + public void fromJson_delegatesToIamPolicyReader() { + IamPolicy iamPolicy = IamPolicy.fromJson(SMALLEST_POLICY_JSON); + assertThat(iamPolicy.version()).isNotNull(); + assertThat(iamPolicy.statements()).containsExactly(ALLOW_STATEMENT); + } + + @Test + public void toJson_delegatesToIamPolicyWriter() { + assertThat(SMALLEST_POLICY.toJson()).isEqualTo(SMALLEST_POLICY_JSON); + } + + @Test + public void simpleGettersSettersWork() { + assertThat(FULL_POLICY.id()).isEqualTo("Id"); + assertThat(FULL_POLICY.version()).isEqualTo("Version"); + assertThat(FULL_POLICY.statements()).containsExactly(ALLOW_STATEMENT); + } + + @Test + public void toBuilderPreservesValues() { + assertThat(FULL_POLICY.toBuilder().build()).isEqualTo(FULL_POLICY); + } + + @Test + public void toStringIncludesAllValues() { + assertThat(FULL_POLICY.toString()) + .isEqualTo("IamPolicy(id=Id, version=Version, statements=[IamStatement(effect=IamEffect(value=Allow))])"); + } + + @Test + public void statementGettersSettersWork() { + assertThat(policy(p -> p.statements(asList(ALLOW_STATEMENT, DENY_STATEMENT))).statements()) + .containsExactly(ALLOW_STATEMENT, DENY_STATEMENT); + assertThat(policy(p -> p.addStatement(ALLOW_STATEMENT).addStatement(DENY_STATEMENT)).statements()) + .containsExactly(ALLOW_STATEMENT, DENY_STATEMENT); + assertThat(policy(p -> p.addStatement(s -> s.effect(ALLOW)).addStatement(s -> s.effect(DENY))).statements()) + .containsExactly(ALLOW_STATEMENT, DENY_STATEMENT); + } + + @Test + public void statementCollectionSettersResetsList() { + assertThat(policy(p -> p.statements(asList(ALLOW_STATEMENT, DENY_STATEMENT)) + .statements(singletonList(DENY_STATEMENT))).statements()) + .containsExactly(DENY_STATEMENT); + } + + private IamPolicy policy(Consumer policy) { + return IamPolicy.builder().applyMutation(policy).build(); + } +} \ No newline at end of file diff --git a/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamPolicyWriterTest.java b/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamPolicyWriterTest.java new file mode 100644 index 000000000000..8e2c6811b66b --- /dev/null +++ b/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamPolicyWriterTest.java @@ -0,0 +1,194 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static software.amazon.awssdk.policybuilder.iam.IamEffect.ALLOW; + +import org.junit.jupiter.api.Test; + +class IamPolicyWriterTest { + private static final IamPrincipal PRINCIPAL_1 = IamPrincipal.create("1", "*"); + private static final IamPrincipal PRINCIPAL_2 = IamPrincipal.create("2", "*"); + private static final IamResource RESOURCE_1 = IamResource.create("1"); + private static final IamResource RESOURCE_2 = IamResource.create("2"); + private static final IamAction ACTION_1 = IamAction.create("1"); + private static final IamAction ACTION_2 = IamAction.create("2"); + private static final IamCondition CONDITION_1 = IamCondition.create("1", "K1", "V1"); + private static final IamCondition CONDITION_2 = IamCondition.create("2", "K1", "V1"); + private static final IamCondition CONDITION_3 = IamCondition.create("1", "K2", "V1"); + private static final IamCondition CONDITION_4 = IamCondition.create("1", "K2", "V2"); + + private static final IamStatement FULL_STATEMENT = + IamStatement.builder() + .effect(ALLOW) + .sid("Sid") + .principals(asList(PRINCIPAL_1, PRINCIPAL_2)) + .notPrincipals(asList(PRINCIPAL_1, PRINCIPAL_2)) + .resources(asList(RESOURCE_1, RESOURCE_2)) + .notResources(asList(RESOURCE_1, RESOURCE_2)) + .actions(asList(ACTION_1, ACTION_2)) + .notActions(asList(ACTION_1, ACTION_2)) + .conditions(asList(CONDITION_1, CONDITION_2, CONDITION_3, CONDITION_4)) + .build(); + + private static final IamPolicy FULL_POLICY = + IamPolicy.builder() + .id("Id") + .version("Version") + .statements(asList(FULL_STATEMENT, FULL_STATEMENT)) + .build(); + + private static final IamStatement MINIMAL_STATEMENT = IamStatement.builder().effect(ALLOW).build(); + + private static final IamPolicy MINIMAL_POLICY = + IamPolicy.builder() + .version("Version") + .statements(singletonList(MINIMAL_STATEMENT)) + .build(); + + private static final IamStatement ONE_ELEMENT_LISTS_STATEMENT = + IamStatement.builder() + .effect(ALLOW) + .sid("Sid") + .principals(singletonList(IamPrincipal.ALL)) + .notPrincipals(singletonList(IamPrincipal.ALL)) + .resources(singletonList(RESOURCE_1)) + .notResources(singletonList(RESOURCE_1)) + .actions(singletonList(ACTION_1)) + .notActions(singletonList(ACTION_1)) + .conditions(singletonList(CONDITION_1)) + .build(); + + private static final IamPolicy ONE_ELEMENT_LISTS_POLICY = + IamPolicy.builder() + .version("Version") + .statements(singletonList(ONE_ELEMENT_LISTS_STATEMENT)) + .build(); + + private static final IamPolicyWriter DEFAULT_WRITER = IamPolicyWriter.create(); + private static final IamPolicyWriter PRETTY_WRITER = IamPolicyWriter.builder().prettyPrint(true).build(); + + @Test + public void toBuilderPreservesSettings() { + assertThat(PRETTY_WRITER.toBuilder().build()).isEqualTo(PRETTY_WRITER); + } + + @Test + public void writeFullPolicyWorks() { + assertThat(DEFAULT_WRITER.writeToString(FULL_POLICY)) + .isEqualTo("{\"Version\":\"Version\"," + + "\"Id\":\"Id\"," + + "\"Statement\":[" + + "{\"Sid\":\"Sid\",\"Effect\":\"Allow\",\"Principal\":{\"1\":\"*\",\"2\":\"*\"},\"NotPrincipal\":{\"1\":\"*\",\"2\":\"*\"},\"Action\":[\"1\",\"2\"],\"NotAction\":[\"1\",\"2\"],\"Resource\":[\"1\",\"2\"],\"NotResource\":[\"1\",\"2\"],\"Condition\":{\"1\":{\"K1\":\"V1\",\"K2\":[\"V1\",\"V2\"]},\"2\":{\"K1\":\"V1\"}}}," + + "{\"Sid\":\"Sid\",\"Effect\":\"Allow\",\"Principal\":{\"1\":\"*\",\"2\":\"*\"},\"NotPrincipal\":{\"1\":\"*\",\"2\":\"*\"},\"Action\":[\"1\",\"2\"],\"NotAction\":[\"1\",\"2\"],\"Resource\":[\"1\",\"2\"],\"NotResource\":[\"1\",\"2\"],\"Condition\":{\"1\":{\"K1\":\"V1\",\"K2\":[\"V1\",\"V2\"]},\"2\":{\"K1\":\"V1\"}}}" + + "]}"); + } + + @Test + public void prettyWriteFullPolicyWorks() { + assertThat(PRETTY_WRITER.writeToString(FULL_POLICY)) + .isEqualTo("{\n" + + " \"Version\" : \"Version\",\n" + + " \"Id\" : \"Id\",\n" + + " \"Statement\" : [ {\n" + + " \"Sid\" : \"Sid\",\n" + + " \"Effect\" : \"Allow\",\n" + + " \"Principal\" : {\n" + + " \"1\" : \"*\",\n" + + " \"2\" : \"*\"\n" + + " },\n" + + " \"NotPrincipal\" : {\n" + + " \"1\" : \"*\",\n" + + " \"2\" : \"*\"\n" + + " },\n" + + " \"Action\" : [ \"1\", \"2\" ],\n" + + " \"NotAction\" : [ \"1\", \"2\" ],\n" + + " \"Resource\" : [ \"1\", \"2\" ],\n" + + " \"NotResource\" : [ \"1\", \"2\" ],\n" + + " \"Condition\" : {\n" + + " \"1\" : {\n" + + " \"K1\" : \"V1\",\n" + + " \"K2\" : [ \"V1\", \"V2\" ]\n" + + " },\n" + + " \"2\" : {\n" + + " \"K1\" : \"V1\"\n" + + " }\n" + + " }\n" + + " }, {\n" + + " \"Sid\" : \"Sid\",\n" + + " \"Effect\" : \"Allow\",\n" + + " \"Principal\" : {\n" + + " \"1\" : \"*\",\n" + + " \"2\" : \"*\"\n" + + " },\n" + + " \"NotPrincipal\" : {\n" + + " \"1\" : \"*\",\n" + + " \"2\" : \"*\"\n" + + " },\n" + + " \"Action\" : [ \"1\", \"2\" ],\n" + + " \"NotAction\" : [ \"1\", \"2\" ],\n" + + " \"Resource\" : [ \"1\", \"2\" ],\n" + + " \"NotResource\" : [ \"1\", \"2\" ],\n" + + " \"Condition\" : {\n" + + " \"1\" : {\n" + + " \"K1\" : \"V1\",\n" + + " \"K2\" : [ \"V1\", \"V2\" ]\n" + + " },\n" + + " \"2\" : {\n" + + " \"K1\" : \"V1\"\n" + + " }\n" + + " }\n" + + " } ]\n" + + "}"); + } + + @Test + public void writeMinimalPolicyWorks() { + assertThat(PRETTY_WRITER.writeToString(MINIMAL_POLICY)) + .isEqualTo("{\n" + + " \"Version\" : \"Version\",\n" + + " \"Statement\" : {\n" + + " \"Effect\" : \"Allow\"\n" + + " }\n" + + "}"); + } + + @Test + public void singleElementListsAreWrittenAsNonArrays() { + assertThat(PRETTY_WRITER.writeToString(ONE_ELEMENT_LISTS_POLICY)) + .isEqualTo("{\n" + + " \"Version\" : \"Version\",\n" + + " \"Statement\" : {\n" + + " \"Sid\" : \"Sid\",\n" + + " \"Effect\" : \"Allow\",\n" + + " \"Principal\" : \"*\",\n" + + " \"NotPrincipal\" : \"*\",\n" + + " \"Action\" : \"1\",\n" + + " \"NotAction\" : \"1\",\n" + + " \"Resource\" : \"1\",\n" + + " \"NotResource\" : \"1\",\n" + + " \"Condition\" : {\n" + + " \"1\" : {\n" + + " \"K1\" : \"V1\"\n" + + " }\n" + + " }\n" + + " }\n" + + "}"); + } +} \ No newline at end of file diff --git a/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamPrincipalTest.java b/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamPrincipalTest.java new file mode 100644 index 000000000000..63ce795d9700 --- /dev/null +++ b/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamPrincipalTest.java @@ -0,0 +1,58 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +class IamPrincipalTest { + private static final IamPrincipal FULL_PRINCIPAL = + IamPrincipal.builder() + .type("Type") + .id("Id") + .build(); + + @Test + public void constructorsWork() { + assertThat(IamPrincipal.create("Type", "Id")).isEqualTo(FULL_PRINCIPAL); + assertThat(IamPrincipal.create(IamPrincipalType.create("Type"), "Id")).isEqualTo(FULL_PRINCIPAL); + assertThat(IamPrincipal.createAll("Type", asList("Id1", "Id2"))) + .containsExactly(IamPrincipal.create("Type", "Id1"), IamPrincipal.create("Type", "Id2")); + assertThat(IamPrincipal.createAll(IamPrincipalType.create("Type"), asList("Id1", "Id2"))) + .containsExactly(IamPrincipal.create("Type", "Id1"), IamPrincipal.create("Type", "Id2")); + } + + @Test + public void simpleGettersSettersWork() { + assertThat(FULL_PRINCIPAL.id()).isEqualTo("Id"); + assertThat(FULL_PRINCIPAL.type().value()).isEqualTo("Type"); + } + + @Test + public void toBuilderPreservesValues() { + IamPrincipal principal = FULL_PRINCIPAL.toBuilder().build(); + assertThat(principal.id()).isEqualTo("Id"); + assertThat(principal.type().value()).isEqualTo("Type"); + } + + @Test + public void typeSettersWork() { + assertThat(IamPrincipal.builder().type("Type").id("Id").build().type().value()).isEqualTo("Type"); + assertThat(IamPrincipal.builder().type(IamPrincipalType.create("Type")).id("Id").build().type().value()).isEqualTo("Type"); + } +} \ No newline at end of file diff --git a/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamPrincipalTypeTest.java b/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamPrincipalTypeTest.java new file mode 100644 index 000000000000..8000f47befbd --- /dev/null +++ b/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamPrincipalTypeTest.java @@ -0,0 +1,27 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; + +class IamPrincipalTypeTest { + @Test + public void iamPrincipalTypeValueNotNull() { + assertThatThrownBy(() -> IamPrincipalType.create(null)).hasMessageMatching(".*[Pp]rincipal.*[Tt]ype.*"); + } +} \ No newline at end of file diff --git a/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamResourceTest.java b/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamResourceTest.java new file mode 100644 index 000000000000..7926fd41fa5e --- /dev/null +++ b/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamResourceTest.java @@ -0,0 +1,27 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; + +class IamResourceTest { + @Test + public void iamResourceValueNotNull() { + assertThatThrownBy(() -> IamResource.create(null)).hasMessageMatching(".*[Rr]esource.*"); + } +} \ No newline at end of file diff --git a/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamStatementTest.java b/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamStatementTest.java new file mode 100644 index 000000000000..68cec4766eb6 --- /dev/null +++ b/services-custom/iam-policy-builder/src/test/java/software/amazon/awssdk/policybuilder/iam/IamStatementTest.java @@ -0,0 +1,275 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.policybuilder.iam; + + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static software.amazon.awssdk.policybuilder.iam.IamEffect.ALLOW; + +import java.util.function.Consumer; +import org.junit.jupiter.api.Test; + +class IamStatementTest { + private static final IamPrincipal PRINCIPAL_1 = IamPrincipal.create("1", "*"); + private static final IamPrincipal PRINCIPAL_2 = IamPrincipal.create("2", "*"); + private static final IamResource RESOURCE_1 = IamResource.create("1"); + private static final IamResource RESOURCE_2 = IamResource.create("2"); + private static final IamAction ACTION_1 = IamAction.create("1"); + private static final IamAction ACTION_2 = IamAction.create("2"); + private static final IamCondition CONDITION_1 = IamCondition.create("1", "K", "V"); + private static final IamCondition CONDITION_2 = IamCondition.create("2", "K", "V"); + + private static final IamStatement FULL_STATEMENT = + IamStatement.builder() + .effect(ALLOW) + .sid("Sid") + .principals(singletonList(PRINCIPAL_1)) + .notPrincipals(singletonList(PRINCIPAL_2)) + .resources(singletonList(RESOURCE_1)) + .notResources(singletonList(RESOURCE_2)) + .actions(singletonList(ACTION_1)) + .notActions(singletonList(ACTION_2)) + .conditions(singletonList(CONDITION_1)) + .build(); + + @Test + public void simpleGettersSettersWork() { + assertThat(FULL_STATEMENT.sid()).isEqualTo("Sid"); + assertThat(FULL_STATEMENT.effect()).isEqualTo(ALLOW); + assertThat(FULL_STATEMENT.principals()).containsExactly(PRINCIPAL_1); + assertThat(FULL_STATEMENT.notPrincipals()).containsExactly(PRINCIPAL_2); + assertThat(FULL_STATEMENT.resources()).containsExactly(RESOURCE_1); + assertThat(FULL_STATEMENT.notResources()).containsExactly(RESOURCE_2); + assertThat(FULL_STATEMENT.actions()).containsExactly(ACTION_1); + assertThat(FULL_STATEMENT.notActions()).containsExactly(ACTION_2); + assertThat(FULL_STATEMENT.conditions()).containsExactly(CONDITION_1); + } + + @Test + public void toBuilderPreservesValues() { + assertThat(FULL_STATEMENT.toBuilder().build()).isEqualTo(FULL_STATEMENT); + } + + @Test + public void toStringIncludesAllValues() { + assertThat(FULL_STATEMENT.toString()) + .isEqualTo("IamStatement(" + + "sid=Sid, " + + "effect=IamEffect(value=Allow), " + + "principals=[IamPrincipal(type=1, id=*)], " + + "notPrincipals=[IamPrincipal(type=2, id=*)], " + + "actions=[IamAction(value=1)], " + + "notActions=[IamAction(value=2)], " + + "resources=[IamResource(value=1)], " + + "notResources=[IamResource(value=2)], " + + "conditions=[IamCondition(operator=1, key=K, value=V)])"); + } + + @Test + public void effectIsRequired() { + assertThatThrownBy(() -> IamStatement.builder().build()).hasMessageMatching(".*[Ee]ffect.*"); + } + + @Test + public void effectGettersSettersWork() { + assertThat(statement(s -> s.effect(ALLOW)).effect()).isEqualTo(ALLOW); + assertThat(statement(s -> s.effect("Allow")).effect()).isEqualTo(ALLOW); + } + + @Test + public void principalGettersSettersWork() { + assertThat(statement(s -> s.principals(asList(PRINCIPAL_1, PRINCIPAL_2))).principals()) + .containsExactly(PRINCIPAL_1, PRINCIPAL_2); + assertThat(statement(s -> s.addPrincipal(PRINCIPAL_1) + .addPrincipal(PRINCIPAL_2)).principals()) + .containsExactly(PRINCIPAL_1, PRINCIPAL_2); + assertThat(statement(s -> s.addPrincipal(p -> p.type("1").id("*")) + .addPrincipal(p -> p.type("2").id("*"))).principals()) + .containsExactly(PRINCIPAL_1, PRINCIPAL_2); + assertThat(statement(s -> s.addPrincipal("1", "*") + .addPrincipal("2", "*")).principals()) + .containsExactly(PRINCIPAL_1, PRINCIPAL_2); + assertThat(statement(s -> s.addPrincipal(IamPrincipalType.create("1"), "*") + .addPrincipal(IamPrincipalType.create("2"), "*")).principals()) + .containsExactly(PRINCIPAL_1, PRINCIPAL_2); + assertThat(statement(s -> s.addPrincipals(IamPrincipalType.create("1"), asList("x", "y"))).principals()) + .containsExactly(IamPrincipal.create("1", "x"), + IamPrincipal.create("1", "y")); + assertThat(statement(s -> s.addPrincipals("1", asList("x", "y"))).principals()) + .containsExactly(IamPrincipal.create("1", "x"), + IamPrincipal.create("1", "y")); + } + + @Test + public void principalsCollectionSettersResetsList() { + assertThat(statement(s -> s.principals(asList(PRINCIPAL_1, PRINCIPAL_2)) + .principals(singletonList(PRINCIPAL_1))).principals()) + .containsExactly(PRINCIPAL_1); + } + + @Test + public void notPrincipalGettersSettersWork() { + assertThat(statement(s -> s.notPrincipals(asList(PRINCIPAL_1, PRINCIPAL_2))).notPrincipals()) + .containsExactly(PRINCIPAL_1, PRINCIPAL_2); + assertThat(statement(s -> s.addNotPrincipal(PRINCIPAL_1) + .addNotPrincipal(PRINCIPAL_2)).notPrincipals()) + .containsExactly(PRINCIPAL_1, PRINCIPAL_2); + assertThat(statement(s -> s.addNotPrincipal(p -> p.type("1").id("*")) + .addNotPrincipal(p -> p.type("2").id("*"))).notPrincipals()) + .containsExactly(PRINCIPAL_1, PRINCIPAL_2); + assertThat(statement(s -> s.addNotPrincipal("1", "*") + .addNotPrincipal("2", "*")).notPrincipals()) + .containsExactly(PRINCIPAL_1, PRINCIPAL_2); + assertThat(statement(s -> s.addNotPrincipal(IamPrincipalType.create("1"), "*") + .addNotPrincipal(IamPrincipalType.create("2"), "*")).notPrincipals()) + .containsExactly(PRINCIPAL_1, PRINCIPAL_2); + assertThat(statement(s -> s.addNotPrincipals(IamPrincipalType.create("1"), asList("x", "y"))).notPrincipals()) + .containsExactly(IamPrincipal.create("1", "x"), + IamPrincipal.create("1", "y")); + assertThat(statement(s -> s.addNotPrincipals("1", asList("x", "y"))).notPrincipals()) + .containsExactly(IamPrincipal.create("1", "x"), + IamPrincipal.create("1", "y")); + } + + @Test + public void notPrincipalsCollectionSettersResetsList() { + assertThat(statement(s -> s.notPrincipals(asList(PRINCIPAL_1, PRINCIPAL_2)) + .notPrincipals(singletonList(PRINCIPAL_1))).notPrincipals()) + .containsExactly(PRINCIPAL_1); + } + + @Test + public void actionGettersSettersWork() { + assertThat(statement(s -> s.actions(asList(ACTION_1, ACTION_2))).actions()) + .containsExactly(ACTION_1, ACTION_2); + assertThat(statement(s -> s.actionIds(asList("1", "2"))).actions()) + .containsExactly(ACTION_1, ACTION_2); + assertThat(statement(s -> s.addAction(ACTION_1).addAction(ACTION_2)).actions()) + .containsExactly(ACTION_1, ACTION_2); + assertThat(statement(s -> s.addAction("1").addAction("2")).actions()) + .containsExactly(ACTION_1, ACTION_2); + } + + @Test + public void actionCollectionSettersResetsList() { + assertThat(statement(s -> s.actions(asList(ACTION_1, ACTION_2)) + .actions(singletonList(ACTION_2))).actions()) + .containsExactly(ACTION_2); + } + + @Test + public void notActionGettersSettersWork() { + assertThat(statement(s -> s.notActions(asList(ACTION_1, ACTION_2))).notActions()) + .containsExactly(ACTION_1, ACTION_2); + assertThat(statement(s -> s.notActionIds(asList("1", "2"))).notActions()) + .containsExactly(ACTION_1, ACTION_2); + assertThat(statement(s -> s.addNotAction(ACTION_1).addNotAction(ACTION_2)).notActions()) + .containsExactly(ACTION_1, ACTION_2); + assertThat(statement(s -> s.addNotAction("1").addNotAction("2")).notActions()) + .containsExactly(ACTION_1, ACTION_2); + } + + @Test + public void notActionCollectionSettersResetsList() { + assertThat(statement(s -> s.notActions(asList(ACTION_1, ACTION_2)) + .notActions(singletonList(ACTION_2))).notActions()) + .containsExactly(ACTION_2); + } + + @Test + public void resourceGettersSettersWork() { + assertThat(statement(s -> s.resources(asList(RESOURCE_1, RESOURCE_2))).resources()) + .containsExactly(RESOURCE_1, RESOURCE_2); + assertThat(statement(s -> s.resourceIds(asList("1", "2"))).resources()) + .containsExactly(RESOURCE_1, RESOURCE_2); + assertThat(statement(s -> s.addResource(RESOURCE_1).addResource(RESOURCE_2)).resources()) + .containsExactly(RESOURCE_1, RESOURCE_2); + assertThat(statement(s -> s.addResource("1").addResource("2")).resources()) + .containsExactly(RESOURCE_1, RESOURCE_2); + } + + @Test + public void resourceCollectionSettersResetsList() { + assertThat(statement(s -> s.resources(asList(RESOURCE_1, RESOURCE_2)) + .resources(singletonList(RESOURCE_2))).resources()) + .containsExactly(RESOURCE_2); + } + + @Test + public void notResourceGettersSettersWork() { + assertThat(statement(s -> s.notResources(asList(RESOURCE_1, RESOURCE_2))).notResources()) + .containsExactly(RESOURCE_1, RESOURCE_2); + assertThat(statement(s -> s.notResourceIds(asList("1", "2"))).notResources()) + .containsExactly(RESOURCE_1, RESOURCE_2); + assertThat(statement(s -> s.addNotResource(RESOURCE_1).addNotResource(RESOURCE_2)).notResources()) + .containsExactly(RESOURCE_1, RESOURCE_2); + assertThat(statement(s -> s.addNotResource("1").addNotResource("2")).notResources()) + .containsExactly(RESOURCE_1, RESOURCE_2); + } + + @Test + public void notResourceCollectionSettersResetsList() { + assertThat(statement(s -> s.notResources(asList(RESOURCE_1, RESOURCE_2)) + .notResources(singletonList(RESOURCE_2))).notResources()) + .containsExactly(RESOURCE_2); + } + + @Test + public void conditionGettersSettersWork() { + assertThat(statement(s -> s.conditions(asList(CONDITION_1, CONDITION_2))).conditions()) + .containsExactly(CONDITION_1, CONDITION_2); + assertThat(statement(s -> s.addCondition(CONDITION_1) + .addCondition(CONDITION_2)).conditions()) + .containsExactly(CONDITION_1, CONDITION_2); + assertThat(statement(s -> s.addCondition(p -> p.operator("1").key("K").value("V")) + .addCondition(p -> p.operator("2").key("K").value("V"))).conditions()) + .containsExactly(CONDITION_1, CONDITION_2); + assertThat(statement(s -> s.addCondition(IamConditionOperator.create("1"), IamConditionKey.create("K"), "V") + .addCondition(IamConditionOperator.create("2"), IamConditionKey.create("K"), "V")).conditions()) + .containsExactly(CONDITION_1, CONDITION_2); + assertThat(statement(s -> s.addCondition(IamConditionOperator.create("1"), "K", "V") + .addCondition(IamConditionOperator.create("2"), "K", "V")).conditions()) + .containsExactly(CONDITION_1, CONDITION_2); + assertThat(statement(s -> s.addCondition("1", "K", "V") + .addCondition("2", "K", "V")).conditions()) + .containsExactly(CONDITION_1, CONDITION_2); + assertThat(statement(s -> s.addConditions(IamConditionOperator.create("1"), + IamConditionKey.create("K"), + asList("V1", "V2"))).conditions()) + .containsExactly(IamCondition.create("1", "K", "V1"), + IamCondition.create("1", "K", "V2")); + assertThat(statement(s -> s.addConditions(IamConditionOperator.create("1"), "K", asList("V1", "V2"))).conditions()) + .containsExactly(IamCondition.create("1", "K", "V1"), + IamCondition.create("1", "K", "V2")); + assertThat(statement(s -> s.addConditions("1", "K", asList("V1", "V2"))).conditions()) + .containsExactly(IamCondition.create("1", "K", "V1"), + IamCondition.create("1", "K", "V2")); + } + + @Test + public void conditionsCollectionSettersResetsList() { + assertThat(statement(s -> s.conditions(asList(CONDITION_1, CONDITION_1)) + .conditions(singletonList(CONDITION_1))).conditions()) + .containsExactly(CONDITION_1); + } + + private IamStatement statement(Consumer statement) { + return IamStatement.builder().effect(ALLOW).applyMutation(statement).build(); + } +} \ No newline at end of file diff --git a/services-custom/iam-policy-builder/src/test/resources/log4j2.properties b/services-custom/iam-policy-builder/src/test/resources/log4j2.properties new file mode 100644 index 000000000000..827f0c09a093 --- /dev/null +++ b/services-custom/iam-policy-builder/src/test/resources/log4j2.properties @@ -0,0 +1,41 @@ +# +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). +# You may not use this file except in compliance with the License. +# A copy of the License is located at +# +# http://aws.amazon.com/apache2.0 +# +# or in the "license" file accompanying this file. This file is distributed +# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +# express or implied. See the License for the specific language governing +# permissions and limitations under the License. +# + +status = warn + +appender.console.type = Console +appender.console.name = ConsoleAppender +appender.console.layout.type = PatternLayout +appender.console.layout.pattern = %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n%throwable + +rootLogger.level = info +rootLogger.appenderRef.stdout.ref = ConsoleAppender + +# Uncomment below to enable more specific logging +# +#logger.sdk.name = software.amazon.awssdk +#logger.sdk.level = debug +# +#logger.tm.name = software.amazon.awssdk.transfer.s3 +#logger.tm.level = info +# +#logger.request.name = software.amazon.awssdk.request +#logger.request.level = debug +# +#logger.apache.name = org.apache.http.wire +#logger.apache.level = debug +# +#logger.netty.name = io.netty.handler.logging +#logger.netty.level = debug diff --git a/services-custom/pom.xml b/services-custom/pom.xml index 56f3cd257afc..97cbd3d9b85b 100644 --- a/services-custom/pom.xml +++ b/services-custom/pom.xml @@ -30,6 +30,7 @@ dynamodb-enhanced s3-transfer-manager + iam-policy-builder