From 3fd8bb7aa71be0830e80c6ec192cbb97b308c920 Mon Sep 17 00:00:00 2001 From: Hiroyuki Yamada Date: Tue, 24 Dec 2024 20:02:44 +0900 Subject: [PATCH] Backport to branch(3) : Add admin interface and operation attributes things for Attribute-Based Access Control (#2441) Co-authored-by: Toshihiro Suzuki --- .../java/com/scalar/db/api/AbacAdmin.java | 786 ++++++++++++++++++ .../db/api/AbacOperationAttributes.java | 47 ++ .../java/com/scalar/db/api/DeleteBuilder.java | 64 +- .../db/api/DistributedTransactionAdmin.java | 2 +- .../java/com/scalar/db/api/GetBuilder.java | 80 +- .../com/scalar/db/api/OperationBuilder.java | 60 ++ .../java/com/scalar/db/api/ScanBuilder.java | 102 ++- .../java/com/scalar/db/api/UpdateBuilder.java | 64 +- .../java/com/scalar/db/api/UpsertBuilder.java | 66 +- .../DecoratedDistributedTransactionAdmin.java | 222 +++++ .../com/scalar/db/common/error/CoreError.java | 6 + .../db/api/AbacOperationAttributesTest.java | 152 ++++ .../com/scalar/db/api/DeleteBuilderTest.java | 64 +- .../com/scalar/db/api/GetBuilderTest.java | 103 ++- .../com/scalar/db/api/ScanBuilderTest.java | 167 +++- .../com/scalar/db/api/UpdateBuilderTest.java | 102 ++- .../com/scalar/db/api/UpsertBuilderTest.java | 100 ++- 17 files changed, 2082 insertions(+), 105 deletions(-) create mode 100644 core/src/main/java/com/scalar/db/api/AbacAdmin.java create mode 100644 core/src/main/java/com/scalar/db/api/AbacOperationAttributes.java create mode 100644 core/src/test/java/com/scalar/db/api/AbacOperationAttributesTest.java diff --git a/core/src/main/java/com/scalar/db/api/AbacAdmin.java b/core/src/main/java/com/scalar/db/api/AbacAdmin.java new file mode 100644 index 0000000000..4ae41b7cfa --- /dev/null +++ b/core/src/main/java/com/scalar/db/api/AbacAdmin.java @@ -0,0 +1,786 @@ +package com.scalar.db.api; + +import com.scalar.db.common.error.CoreError; +import com.scalar.db.exception.storage.ExecutionException; +import java.util.List; +import java.util.Optional; +import javax.annotation.Nullable; + +/** An interface for administrative operations for attribute-based access control. */ +public interface AbacAdmin { + + /** + * Creates a policy with the given name and data tag column name. + * + * @param policyName the policy name + * @param dataTagColumnName the data tag column name. If null, the default data tag column name is + * used + * @throws ExecutionException if the operation fails + */ + default void createPolicy(String policyName, @Nullable String dataTagColumnName) + throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Enables a policy that has the given name. + * + * @param policyName the policy name + * @throws ExecutionException if the operation fails + */ + default void enablePolicy(String policyName) throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Disables a policy that has the given name. + * + * @param policyName the policy name + * @throws ExecutionException if the operation fails + */ + default void disablePolicy(String policyName) throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Retrieves a policy that has the given name. + * + * @param policyName the policy name + * @return the policy + * @throws ExecutionException if the operation fails + */ + default Optional getPolicy(String policyName) throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Retrieves all policies. + * + * @return the policies + * @throws ExecutionException if the operation fails + */ + default List getPolicies() throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Creates a level with the given short name, long name and level number for the given policy. + * + * @param policyName the policy name + * @param levelShortName the short name of the level + * @param levelLongName the long name of the level + * @param levelNumber the level number + * @throws ExecutionException if the operation fails + */ + default void createLevel( + String policyName, String levelShortName, String levelLongName, int levelNumber) + throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Drops a level that has the given short name for the given policy. + * + * @param policyName the policy name + * @param levelShortName the short name of the level + * @throws ExecutionException if the operation fails + */ + default void dropLevel(String policyName, String levelShortName) throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Retrieves a level that has the given short name for the given policy. + * + * @param policyName the policy name + * @param levelShortName the short name of the level + * @return the level + * @throws ExecutionException if the operation fails + */ + default Optional getLevel(String policyName, String levelShortName) + throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Retrieves all levels for the given policy. + * + * @param policyName the policy name + * @return the levels + * @throws ExecutionException if the operation fails + */ + default List getLevels(String policyName) throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Creates a compartment with the given short name and long name for the given policy. + * + * @param policyName the policy name + * @param compartmentShortName the short name of the compartment + * @param compartmentLongName the long name of the compartment + * @throws ExecutionException if the operation fails + */ + default void createCompartment( + String policyName, String compartmentShortName, String compartmentLongName) + throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Drops a compartment that has the given short name for the given policy. + * + * @param policyName the policy name + * @param compartmentShortName the short name of the compartment + * @throws ExecutionException if the operation fails + */ + default void dropCompartment(String policyName, String compartmentShortName) + throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Retrieves a compartment that has the given short name for the given policy. + * + * @param policyName the policy name + * @param compartmentShortName the short name of the compartment + * @return the compartment + * @throws ExecutionException if the operation fails + */ + default Optional getCompartment(String policyName, String compartmentShortName) + throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Retrieves all compartments for the given policy. + * + * @param policyName the policy name + * @return the compartments + * @throws ExecutionException if the operation fails + */ + default List getCompartments(String policyName) throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Creates a group with the given short name, long name, and the short name of the parent group + * for the given policy. + * + * @param policyName the policy name + * @param groupShortName the short name of the group + * @param groupLongName the long name of the group + * @param parentGroupShortName the short name of the parent group. If null, the group is a + * top-level group + * @throws ExecutionException if the operation fails + */ + default void createGroup( + String policyName, + String groupShortName, + String groupLongName, + @Nullable String parentGroupShortName) + throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Drops a group that has the given short name for the given policy. + * + * @param policyName the policy name + * @param groupShortName the short name of the group + * @throws ExecutionException if the operation fails + */ + default void dropGroup(String policyName, String groupShortName) throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Retrieves a group that has the given short name for the given policy. + * + * @param policyName the policy name + * @param groupShortName the short name of the group + * @return the group + * @throws ExecutionException if the operation fails + */ + default Optional getGroup(String policyName, String groupShortName) + throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Retrieves all groups for the given policy. + * + * @param policyName the policy name + * @return the groups + * @throws ExecutionException if the operation fails + */ + default List getGroups(String policyName) throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Sets the given levels of the given policy to a user that has the given username. + * + * @param policyName the policy name + * @param username the username + * @param levelShortName the short name of the level to set + * @param defaultLevelShortName the short name of the default level. If null, the {@code + * levelShortName} will be used as the default level + * @param rowLevelShortName the short name of the row level. If null, the {@code + * defaultLevelShortName} will be used as the row level + * @throws ExecutionException if the operation fails + */ + default void setLevelsToUser( + String policyName, + String username, + String levelShortName, + @Nullable String defaultLevelShortName, + @Nullable String rowLevelShortName) + throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Adds the given compartment of the given policy to a user that has the given username. Before + * adding the compartment, levels must be set to the user. + * + * @param policyName the policy name + * @param username the username + * @param compartmentShortName the short name of the compartment + * @param accessMode the access mode + * @param defaultCompartment whether the compartment is a default compartment + * @param rowCompartment whether the compartment is a row compartment + * @throws ExecutionException if the operation fails + */ + default void addCompartmentToUser( + String policyName, + String username, + String compartmentShortName, + AccessMode accessMode, + boolean defaultCompartment, + boolean rowCompartment) + throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Removes the given compartment of the given policy from a user that has the given username. + * + * @param policyName the policy name + * @param username the username + * @param compartmentShortName the short name of the compartment + * @throws ExecutionException if the operation fails + */ + default void removeCompartmentFromUser( + String policyName, String username, String compartmentShortName) throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Adds the given group of the given policy to a user that has the given username. Before adding + * the group, levels must be set to the user. + * + * @param policyName the policy name + * @param username the username + * @param groupShortName the short name of the group + * @param accessMode the access mode + * @param defaultGroup whether the group is a default group + * @param rowGroup whether the group is a row group + * @throws ExecutionException if the operation fails + */ + default void addGroupToUser( + String policyName, + String username, + String groupShortName, + AccessMode accessMode, + boolean defaultGroup, + boolean rowGroup) + throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Removes the given group of the given policy from a user that has the given username. + * + * @param policyName the policy name + * @param username the username + * @param groupShortName the short name of the group + * @throws ExecutionException if the operation fails + */ + default void removeGroupFromUser(String policyName, String username, String groupShortName) + throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Drops the user tag information of a user with the given username for the given policy. + * + * @param policyName the policy name + * @param username the username + * @throws ExecutionException if the operation fails + */ + default void dropUserTagInfoFromUser(String policyName, String username) + throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Retrieves the user tag information of a user with the given username for the given policy. + * + * @param policyName the policy name + * @param username the username + * @return the user tag information. If the user tag information is not registered, returns an + * empty optional + * @throws ExecutionException if the operation fails + */ + default Optional getUserTagInfo(String policyName, String username) + throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Applies the given policy to the given namespace. + * + * @param policyName the policy name + * @param namespaceName the namespace name + * @throws ExecutionException if the operation fails + */ + default void applyPolicyToNamespace(String policyName, String namespaceName) + throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Enables the given policy for the given namespace. + * + * @param policyName the policy name + * @param namespaceName the namespace name + * @throws ExecutionException if the operation fails + */ + default void enableNamespacePolicy(String policyName, String namespaceName) + throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Disables the given policy for the given namespace. + * + * @param policyName the policy name + * @param namespaceName the namespace name + * @throws ExecutionException if the operation fails + */ + default void disableNamespacePolicy(String policyName, String namespaceName) + throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Retrieves the namespace policy for the given namespace. + * + * @param policyName the policy name + * @param namespaceName the namespace name + * @return the namespace policy. If the policy is not applied to the namespace, returns an empty + * optional + * @throws ExecutionException if the operation fails + */ + default Optional getNamespacePolicy(String policyName, String namespaceName) + throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Retrieves all namespace policies. + * + * @return the namespaces policies + * @throws ExecutionException if the operation fails + */ + default List getNamespacePolicies() throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Applies the given policy to the given table of the given namespace. + * + * @param policyName the policy name + * @param namespaceName the namespace name + * @param tableName the table name + * @throws ExecutionException if the operation fails + */ + default void applyPolicyToTable(String policyName, String namespaceName, String tableName) + throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Enables the given policy of the given table of the given namespace. + * + * @param policyName the policy name + * @param namespaceName the namespace name + * @param tableName the table name + * @throws ExecutionException if the operation fails + */ + default void enableTablePolicy(String policyName, String namespaceName, String tableName) + throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Disables the given policy of the given table of the given namespace. + * + * @param policyName the policy name + * @param namespaceName the namespace name + * @param tableName the table name + * @throws ExecutionException if the operation fails + */ + default void disableTablePolicy(String policyName, String namespaceName, String tableName) + throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Retrieves the table policy for the given table of the given namespace. + * + * @param policyName the policy name + * @param namespaceName the namespace name + * @param tableName the table name + * @return the table policy. If the policy is not applied to the table, returns an empty optional + * @throws ExecutionException if the operation fails + */ + default Optional getTablePolicy( + String policyName, String namespaceName, String tableName) throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** + * Retrieves all table policies. + * + * @return the table policies + * @throws ExecutionException if the operation fails + */ + default List getTablePolicies() throws ExecutionException { + throw new UnsupportedOperationException(CoreError.ABAC_NOT_ENABLED.buildMessage()); + } + + /** The state of a policy. */ + enum PolicyState { + /** The policy is enabled. */ + ENABLED, + + /** The policy is disabled. */ + DISABLED + } + + /** The access mode for compartments and groups. */ + enum AccessMode { + /** The access mode for read only. */ + READ_ONLY, + + /** The access mode for read and write. */ + READ_WRITE + } + + /** A policy for ABAC. All components of ABAC are associated with a policy. */ + interface Policy { + /** + * Returns the policy name. + * + * @return the policy name + */ + String getName(); + + /** + * Returns the data tag column name. + * + * @return the data tag column name + */ + String getDataTagColumnName(); + + /** + * Returns the state of the policy. + * + * @return the state + */ + PolicyState getState(); + } + + /** A level that is one of the components of a tag in ABAC. */ + interface Level { + /** + * Returns the policy name. + * + * @return the policy name + */ + String getPolicyName(); + + /** + * Returns the short name of the level. + * + * @return the short name of the level + */ + String getShortName(); + + /** + * Returns the long name of the level. + * + * @return the long name of the level + */ + String getLongName(); + + /** + * Returns the level number. + * + * @return the level number + */ + int getLevelNumber(); + } + + /** A compartment that is one of the components of a tag in ABAC. */ + interface Compartment { + /** + * Returns the policy name. + * + * @return the policy name + */ + String getPolicyName(); + + /** + * Returns the short name of the compartment. + * + * @return the short name of the compartment + */ + String getShortName(); + + /** + * Returns the long name of the compartment. + * + * @return the long name of the compartment + */ + String getLongName(); + } + + /** A group that is one of the components of a tag in ABAC. */ + interface Group { + /** + * Returns the policy name. + * + * @return the policy name + */ + String getPolicyName(); + + /** + * Returns the short name of the group. + * + * @return the short name of the group + */ + String getShortName(); + + /** + * Returns the long name of the group. + * + * @return the long name + */ + String getLongName(); + + /** + * Returns the parent group short name if the group is not a top-level group. + * + * @return the parent group short name. If the group is a top-level group, returns an empty + * optional + */ + Optional getParentGroupShortName(); + } + + /** The user tag information of a user for a policy in ABAC. */ + interface UserTagInfo { + + /** The level information. */ + interface LevelInfo { + /** + * Returns the short name of the level. + * + * @return the short name of the level + */ + String getLevelShortName(); + + /** + * Returns the short name of the default level. + * + * @return the short name of the default level + */ + String getDefaultLevelShortName(); + + /** + * Returns the short name of the row level. + * + * @return the short name of the row level + */ + String getRowLevelShortName(); + } + + /** The compartment information. */ + interface CompartmentInfo { + /** + * Returns the short names of the compartments that the user has read access to. + * + * @return the short names of the compartments that the user has read access to + */ + List getReadCompartmentShortNames(); + + /** + * Returns the short names of the compartments that the user has write access to. + * + * @return the short names of the compartments that the user has write access to + */ + List getWriteCompartmentShortNames(); + + /** + * Returns the short names of the default compartments that the user has read access to. + * + * @return the short names of the default compartments that the user has read access to + */ + List getDefaultReadCompartmentShortNames(); + + /** + * Returns the short names of the default compartments that the user has write access to. + * + * @return the short names of + */ + List getDefaultWriteCompartmentShortNames(); + + /** + * Returns the short names of the row compartments. + * + * @return the short names of the row compartments + */ + List getRowCompartmentShortNames(); + } + + /** The group information. */ + interface GroupInfo { + /** + * Returns the short names of the groups that the user has read access to. + * + * @return the short names of the groups that the user has read access to + */ + List getReadGroupShortNames(); + + /** + * Returns the short names of the groups that the user has write access to. + * + * @return the short names of the groups that the user has write access to + */ + List getWriteGroupShortNames(); + + /** + * Returns the short names of the default groups that the user has read access to. + * + * @return the short names of the default groups that the user has read access to + */ + List getDefaultReadGroupShortNames(); + + /** + * Returns the short names of the default groups that the user has write access to. + * + * @return the short names of the default groups that the user has write access to + */ + List getDefaultWriteGroupShortNames(); + + /** + * Returns the short names of the row groups. + * + * @return the short names of the row groups. + */ + List getRowGroupShortNames(); + } + + /** + * Returns the policy name. + * + * @return the policy name + */ + String getPolicyName(); + + /** + * Returns the username. + * + * @return the username + */ + String getUsername(); + + /** + * Returns the level information. + * + * @return the level information + */ + LevelInfo getLevelInfo(); + + /** + * Returns the compartment information. + * + * @return the compartment information + */ + CompartmentInfo getCompartmentInfo(); + + /** + * Returns the group information. + * + * @return the group information + */ + GroupInfo getGroupInfo(); + } + + /** The namespace policy. */ + interface NamespacePolicy { + /** + * Returns the policy name. + * + * @return the policy name + */ + String getPolicyName(); + + /** + * Returns the namespace name. + * + * @return the namespace name + */ + String getNamespaceName(); + + /** + * Returns the state of the policy. + * + * @return the state of the policy + */ + PolicyState getState(); + } + + /** The table policy. */ + interface TablePolicy { + /** + * Returns the policy name. + * + * @return the policy name + */ + String getPolicyName(); + + /** + * Returns the namespace name. + * + * @return the namespace name + */ + String getNamespaceName(); + + /** + * Returns the table name. + * + * @return the table name + */ + String getTableName(); + + /** + * Returns the state of the policy. + * + * @return the state of the policy + */ + PolicyState getState(); + } +} diff --git a/core/src/main/java/com/scalar/db/api/AbacOperationAttributes.java b/core/src/main/java/com/scalar/db/api/AbacOperationAttributes.java new file mode 100644 index 0000000000..b558f8726a --- /dev/null +++ b/core/src/main/java/com/scalar/db/api/AbacOperationAttributes.java @@ -0,0 +1,47 @@ +package com.scalar.db.api; + +import java.util.Map; +import java.util.Optional; + +/** A utility class to manipulate the operation attributes for attribute-based access control. */ +public final class AbacOperationAttributes { + + private static final String OPERATION_ATTRIBUTE_PREFIX = "abac-"; + public static final String READ_TAG_PREFIX = OPERATION_ATTRIBUTE_PREFIX + "read-tag-"; + public static final String WRITE_TAG_PREFIX = OPERATION_ATTRIBUTE_PREFIX + "write-tag-"; + + private AbacOperationAttributes() {} + + public static void setReadTag(Map attributes, String policyName, String readTag) { + attributes.put(READ_TAG_PREFIX + policyName, readTag); + } + + public static void clearReadTag(Map attributes, String policyName) { + attributes.remove(READ_TAG_PREFIX + policyName); + } + + public static void clearReadTags(Map attributes) { + attributes.entrySet().removeIf(e -> e.getKey().startsWith(READ_TAG_PREFIX)); + } + + public static void setWriteTag( + Map attributes, String policyName, String writeTag) { + attributes.put(WRITE_TAG_PREFIX + policyName, writeTag); + } + + public static void clearWriteTag(Map attributes, String policyName) { + attributes.remove(WRITE_TAG_PREFIX + policyName); + } + + public static void clearWriteTags(Map attributes) { + attributes.entrySet().removeIf(e -> e.getKey().startsWith(WRITE_TAG_PREFIX)); + } + + public static Optional getReadTag(Operation operation, String policyName) { + return operation.getAttribute(READ_TAG_PREFIX + policyName); + } + + public static Optional getWriteTag(Operation operation, String policyName) { + return operation.getAttribute(WRITE_TAG_PREFIX + policyName); + } +} diff --git a/core/src/main/java/com/scalar/db/api/DeleteBuilder.java b/core/src/main/java/com/scalar/db/api/DeleteBuilder.java index 0b2831d53a..e312d7d1a3 100644 --- a/core/src/main/java/com/scalar/db/api/DeleteBuilder.java +++ b/core/src/main/java/com/scalar/db/api/DeleteBuilder.java @@ -3,7 +3,11 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.collect.ImmutableMap; +import com.scalar.db.api.OperationBuilder.AbacReadTagAttribute; +import com.scalar.db.api.OperationBuilder.AbacWriteTagAttribute; import com.scalar.db.api.OperationBuilder.Attribute; +import com.scalar.db.api.OperationBuilder.ClearAbacReadTagAttribute; +import com.scalar.db.api.OperationBuilder.ClearAbacWriteTagAttribute; import com.scalar.db.api.OperationBuilder.ClearAttribute; import com.scalar.db.api.OperationBuilder.ClearClusteringKey; import com.scalar.db.api.OperationBuilder.ClearCondition; @@ -68,7 +72,9 @@ public static class Buildable extends OperationBuilder.Buildable implements ClusteringKey, Consistency, Condition, - Attribute { + Attribute, + AbacReadTagAttribute, + AbacWriteTagAttribute { @Nullable Key clusteringKey; @Nullable com.scalar.db.api.Consistency consistency; @Nullable MutationCondition condition; @@ -114,6 +120,22 @@ public Buildable attributes(Map attributes) { return this; } + @Override + public Buildable readTag(String policyName, String readTag) { + checkNotNull(policyName); + checkNotNull(readTag); + AbacOperationAttributes.setReadTag(attributes, policyName, readTag); + return this; + } + + @Override + public Buildable writeTag(String policyName, String writeTag) { + checkNotNull(policyName); + checkNotNull(writeTag); + AbacOperationAttributes.setWriteTag(attributes, policyName, writeTag); + return this; + } + @Override public Delete build() { return new Delete( @@ -134,7 +156,9 @@ public static class BuildableFromExisting extends Buildable ClearCondition, ClearClusteringKey, ClearNamespace, - ClearAttribute { + ClearAttribute, + ClearAbacReadTagAttribute, + ClearAbacWriteTagAttribute { BuildableFromExisting(Delete delete) { super( @@ -192,6 +216,18 @@ public BuildableFromExisting attributes(Map attributes) { return this; } + @Override + public Buildable readTag(String policyName, String readTag) { + super.readTag(policyName, readTag); + return this; + } + + @Override + public Buildable writeTag(String policyName, String writeTag) { + super.writeTag(policyName, writeTag); + return this; + } + @Override public BuildableFromExisting condition(MutationCondition condition) { super.condition(condition); @@ -227,5 +263,29 @@ public BuildableFromExisting clearAttribute(String name) { this.attributes.remove(name); return this; } + + @Override + public BuildableFromExisting clearReadTag(String policyName) { + AbacOperationAttributes.clearReadTag(attributes, policyName); + return this; + } + + @Override + public BuildableFromExisting clearReadTags() { + AbacOperationAttributes.clearReadTags(attributes); + return this; + } + + @Override + public BuildableFromExisting clearWriteTag(String policyName) { + AbacOperationAttributes.clearWriteTag(attributes, policyName); + return this; + } + + @Override + public BuildableFromExisting clearWriteTags() { + AbacOperationAttributes.clearWriteTags(attributes); + return this; + } } } diff --git a/core/src/main/java/com/scalar/db/api/DistributedTransactionAdmin.java b/core/src/main/java/com/scalar/db/api/DistributedTransactionAdmin.java index 608a520062..3617e14270 100644 --- a/core/src/main/java/com/scalar/db/api/DistributedTransactionAdmin.java +++ b/core/src/main/java/com/scalar/db/api/DistributedTransactionAdmin.java @@ -8,7 +8,7 @@ * An administrative interface for distributed transaction implementations. The user can execute * administrative operations with it like createNamespace/createTable/getTableMetadata. */ -public interface DistributedTransactionAdmin extends Admin, AuthAdmin, AutoCloseable { +public interface DistributedTransactionAdmin extends Admin, AuthAdmin, AbacAdmin, AutoCloseable { /** * Creates coordinator namespace and tables. diff --git a/core/src/main/java/com/scalar/db/api/GetBuilder.java b/core/src/main/java/com/scalar/db/api/GetBuilder.java index 1ce8882862..0b4d2f8de9 100644 --- a/core/src/main/java/com/scalar/db/api/GetBuilder.java +++ b/core/src/main/java/com/scalar/db/api/GetBuilder.java @@ -4,9 +4,11 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.scalar.db.api.OperationBuilder.AbacReadTagAttribute; import com.scalar.db.api.OperationBuilder.And; import com.scalar.db.api.OperationBuilder.Attribute; import com.scalar.db.api.OperationBuilder.Buildable; +import com.scalar.db.api.OperationBuilder.ClearAbacReadTagAttribute; import com.scalar.db.api.OperationBuilder.ClearAttribute; import com.scalar.db.api.OperationBuilder.ClearClusteringKey; import com.scalar.db.api.OperationBuilder.ClearConditions; @@ -93,7 +95,8 @@ public static class BuildableGet extends Buildable implements ClusteringKey, Consistency, Projection, - Attribute { + Attribute, + AbacReadTagAttribute { final List projections = new ArrayList<>(); @Nullable Key clusteringKey; @Nullable com.scalar.db.api.Consistency consistency; @@ -158,6 +161,14 @@ public BuildableGet attributes(Map attributes) { return this; } + @Override + public BuildableGet readTag(String policyName, String readTag) { + checkNotNull(policyName); + checkNotNull(readTag); + AbacOperationAttributes.setReadTag(attributes, policyName, readTag); + return this; + } + @Override public Get build() { return build(ImmutableSet.of()); @@ -227,6 +238,12 @@ public BuildableGetWithPartitionKey attributes(Map attributes) { return this; } + @Override + public BuildableGet readTag(String policyName, String readTag) { + super.readTag(policyName, readTag); + return this; + } + @Override public BuildableGetWithOngoingWhere where(ConditionalExpression condition) { checkNotNull(condition); @@ -390,6 +407,7 @@ public static class BuildableGetWithIndex implements Consistency, Projection, Attribute, + AbacReadTagAttribute, OperationBuilder.Where, WhereAnd, WhereOr { @@ -447,6 +465,14 @@ public BuildableGetWithIndex attributes(Map attributes) { return this; } + @Override + public BuildableGetWithIndex readTag(String policyName, String readTag) { + checkNotNull(policyName); + checkNotNull(readTag); + AbacOperationAttributes.setReadTag(attributes, policyName, readTag); + return this; + } + @Override public BuildableGetWithIndexOngoingWhere where(ConditionalExpression condition) { checkNotNull(condition); @@ -602,7 +628,8 @@ public BuildableGetWithIndexOngoingWhereOr or(AndConditionSet andConditionSet) { public static class BuildableGetWithIndexWhere implements Consistency, Projection, - Attribute { + Attribute, + AbacReadTagAttribute { BuildableGetWithIndex buildableGetWithIndex; final SelectionBuilder.Where where; @@ -657,6 +684,12 @@ public BuildableGetWithIndexWhere attributes(Map attributes) { return this; } + @Override + public BuildableGetWithIndexWhere readTag(String policyName, String readTag) { + buildableGetWithIndex = buildableGetWithIndex.readTag(policyName, readTag); + return this; + } + public Get build() { return buildableGetWithIndex.build(getConjunctions(where)); } @@ -674,7 +707,8 @@ public static class BuildableGetOrGetWithIndexFromExisting extends BuildableGet ClearProjections, ClearClusteringKey, ClearNamespace, - ClearAttribute { + ClearAttribute, + ClearAbacReadTagAttribute { private Key indexKey; private final boolean isGetWithIndex; @@ -770,6 +804,12 @@ public BuildableGetOrGetWithIndexFromExisting attributes(Map att return this; } + @Override + public BuildableGet readTag(String policyName, String readTag) { + super.readTag(policyName, readTag); + return this; + } + @Override public BuildableGetFromExistingWithOngoingWhere where(ConditionalExpression condition) { checkConditionsEmpty(); @@ -845,6 +885,18 @@ public BuildableGetOrGetWithIndexFromExisting clearAttribute(String name) { return this; } + @Override + public BuildableGetOrGetWithIndexFromExisting clearReadTag(String policyName) { + AbacOperationAttributes.clearReadTag(attributes, policyName); + return this; + } + + @Override + public BuildableGetOrGetWithIndexFromExisting clearReadTags() { + AbacOperationAttributes.clearReadTags(attributes); + return this; + } + private void checkNotGet() { if (!isGetWithIndex) { throw new UnsupportedOperationException( @@ -910,9 +962,11 @@ public static class BuildableGetFromExistingWithWhere Consistency, Projection, Attribute, + AbacReadTagAttribute, ClearProjections, ClearNamespace, - ClearAttribute { + ClearAttribute, + ClearAbacReadTagAttribute { private final BuildableGetOrGetWithIndexFromExisting BuildableGetFromExisting; final SelectionBuilder.Where where; @@ -999,6 +1053,12 @@ public BuildableGetFromExistingWithWhere attributes(Map attribut return this; } + @Override + public BuildableGetFromExistingWithWhere readTag(String policyName, String readTag) { + BuildableGetFromExisting.readTag(policyName, readTag); + return this; + } + @Override public BuildableGetFromExistingWithWhere clearProjections() { BuildableGetFromExisting.clearProjections(); @@ -1023,6 +1083,18 @@ public BuildableGetFromExistingWithWhere clearAttribute(String name) { return this; } + @Override + public BuildableGetFromExistingWithWhere clearReadTag(String policyName) { + BuildableGetFromExisting.clearReadTag(policyName); + return this; + } + + @Override + public BuildableGetFromExistingWithWhere clearReadTags() { + BuildableGetFromExisting.clearReadTags(); + return this; + } + public Get build() { return BuildableGetFromExisting.build(getConjunctions(where)); } diff --git a/core/src/main/java/com/scalar/db/api/OperationBuilder.java b/core/src/main/java/com/scalar/db/api/OperationBuilder.java index 7c54695ace..6838222025 100644 --- a/core/src/main/java/com/scalar/db/api/OperationBuilder.java +++ b/core/src/main/java/com/scalar/db/api/OperationBuilder.java @@ -586,6 +586,66 @@ public interface ClearAttribute { T clearAttribute(String name); } + public interface AbacReadTagAttribute { + /** + * Adds a read tag attribute for the specified policy. This is a utility method for + * attribute-based access control. + * + * @param policyName the policy name + * @param readTag the read tag + * @return the operation builder + */ + T readTag(String policyName, String readTag); + } + + public interface AbacWriteTagAttribute { + /** + * Adds a write tag attribute for the specified policy. This is a utility method for + * attribute-based access control. + * + * @param policyName the policy name + * @param writeTag the write tag + * @return the operation builder + */ + T writeTag(String policyName, String writeTag); + } + + public interface ClearAbacReadTagAttribute { + /** + * Clear the read tag attribute for the specified policy. This is a utility method for + * attribute-based access control. + * + * @param policyName the policy name + * @return the operation builder + */ + T clearReadTag(String policyName); + + /** + * Clear all read tags. This is a utility method for attribute-based access control. + * + * @return the operation builder + */ + T clearReadTags(); + } + + public interface ClearAbacWriteTagAttribute { + /** + * Clear the write tag attribute for the specified policy. This is a utility method for + * attribute-based access control. + * + * @param policyName the policy name + * @return the operation builder + */ + T clearWriteTag(String policyName); + + /** + * Clear all write tags. This is a utility method for attribute-based access control. + * + * @return the operation builder + */ + T clearWriteTags(); + } + public abstract static class TableBuilder implements Table { final String namespace; diff --git a/core/src/main/java/com/scalar/db/api/ScanBuilder.java b/core/src/main/java/com/scalar/db/api/ScanBuilder.java index 32a38c16bb..116c6c9bb2 100644 --- a/core/src/main/java/com/scalar/db/api/ScanBuilder.java +++ b/core/src/main/java/com/scalar/db/api/ScanBuilder.java @@ -4,10 +4,12 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.scalar.db.api.OperationBuilder.AbacReadTagAttribute; import com.scalar.db.api.OperationBuilder.All; import com.scalar.db.api.OperationBuilder.And; import com.scalar.db.api.OperationBuilder.Attribute; import com.scalar.db.api.OperationBuilder.Buildable; +import com.scalar.db.api.OperationBuilder.ClearAbacReadTagAttribute; import com.scalar.db.api.OperationBuilder.ClearAttribute; import com.scalar.db.api.OperationBuilder.ClearBoundaries; import com.scalar.db.api.OperationBuilder.ClearConditions; @@ -106,7 +108,8 @@ public static class BuildableScan extends Buildable Consistency, Projection, Limit, - Attribute { + Attribute, + AbacReadTagAttribute { final List orderings = new ArrayList<>(); final List projections = new ArrayList<>(); @Nullable Key startClusteringKey; @@ -215,6 +218,14 @@ public BuildableScan attributes(Map attributes) { return this; } + @Override + public BuildableScan readTag(String policyName, String readTag) { + checkNotNull(policyName); + checkNotNull(readTag); + AbacOperationAttributes.setReadTag(attributes, policyName, readTag); + return this; + } + @Override public Scan build() { return build(ImmutableSet.of()); @@ -330,6 +341,12 @@ public BuildableScanWithPartitionKey attributes(Map attributes) return this; } + @Override + public BuildableScan readTag(String policyName, String readTag) { + super.readTag(policyName, readTag); + return this; + } + @Override public BuildableScanWithOngoingWhere where(ConditionalExpression condition) { checkNotNull(condition); @@ -498,7 +515,8 @@ public static class BuildableScanWithIndex WhereAnd, WhereOr, Limit, - Attribute { + Attribute, + AbacReadTagAttribute { @Nullable private final String namespaceName; private final String tableName; private final Key indexKey; @@ -560,6 +578,14 @@ public BuildableScanWithIndex attributes(Map attributes) { return this; } + @Override + public BuildableScanWithIndex readTag(String policyName, String readTag) { + checkNotNull(policyName); + checkNotNull(readTag); + AbacOperationAttributes.setReadTag(attributes, policyName, readTag); + return this; + } + @Override public BuildableScanWithIndexOngoingWhere where(ConditionalExpression condition) { checkNotNull(condition); @@ -717,7 +743,8 @@ public static class BuildableScanWithIndexWhere implements Consistency, Projection, Limit, - Attribute { + Attribute, + AbacReadTagAttribute { BuildableScanWithIndex buildableScanWithIndex; final Where where; @@ -778,6 +805,12 @@ public BuildableScanWithIndexWhere attributes(Map attributes) { return this; } + @Override + public BuildableScanWithIndexWhere readTag(String policyName, String readTag) { + buildableScanWithIndex = buildableScanWithIndex.readTag(policyName, readTag); + return this; + } + public Scan build() { return buildableScanWithIndex.build(getConjunctions(where)); } @@ -791,7 +824,8 @@ public static class BuildableScanAll WhereAnd, WhereOr, Limit, - Attribute { + Attribute, + AbacReadTagAttribute { private final String namespaceName; private final String tableName; private final List orderings = new ArrayList<>(); @@ -871,6 +905,14 @@ public BuildableScanAll attributes(Map attributes) { return this; } + @Override + public BuildableScanAll readTag(String policyName, String readTag) { + checkNotNull(policyName); + checkNotNull(readTag); + AbacOperationAttributes.setReadTag(attributes, policyName, readTag); + return this; + } + @Override public BuildableScanAllWithOngoingWhere where(ConditionalExpression condition) { checkNotNull(condition); @@ -1028,7 +1070,8 @@ public static class BuildableScanAllWithWhere Projection, Ordering, Limit, - Attribute { + Attribute, + AbacReadTagAttribute { final BuildableScanAll buildableScanAll; final Where where; @@ -1106,6 +1149,12 @@ public BuildableScanAllWithWhere attributes(Map attributes) { return this; } + @Override + public BuildableScanAllWithWhere readTag(String policyName, String readTag) { + buildableScanAll.readTag(policyName, readTag); + return this; + } + public Scan build() { return buildableScanAll.build(getConjunctions(where)); } @@ -1124,7 +1173,8 @@ public static class BuildableScanOrScanAllFromExisting extends BuildableScan ClearOrderings, ClearBoundaries, ClearNamespace, - ClearAttribute { + ClearAttribute, + ClearAbacReadTagAttribute { private final boolean isScanWithIndex; private final boolean isScanAll; @@ -1210,6 +1260,12 @@ public BuildableScanOrScanAllFromExisting attributes(Map attribu return this; } + @Override + public BuildableScan readTag(String policyName, String readTag) { + super.readTag(policyName, readTag); + return this; + } + @Override public BuildableScanOrScanAllFromExisting projection(String projection) { super.projection(projection); @@ -1374,6 +1430,18 @@ public BuildableScanOrScanAllFromExisting clearAttribute(String name) { return this; } + @Override + public BuildableScanOrScanAllFromExisting clearReadTag(String policyName) { + AbacOperationAttributes.clearReadTag(attributes, policyName); + return this; + } + + @Override + public BuildableScanOrScanAllFromExisting clearReadTags() { + AbacOperationAttributes.clearReadTags(attributes); + return this; + } + private void checkNotScanWithIndexOrScanAll() { if (isScanWithIndex || isScanAll) { throw new UnsupportedOperationException( @@ -1466,10 +1534,12 @@ public static class BuildableScanFromExistingWithWhere Ordering, Limit, Attribute, + AbacReadTagAttribute, ClearProjections, ClearOrderings, ClearNamespace, - ClearAttribute { + ClearAttribute, + ClearAbacReadTagAttribute { private final BuildableScanOrScanAllFromExisting buildableScanFromExisting; final Where where; @@ -1584,6 +1654,12 @@ public BuildableScanFromExistingWithWhere attributes(Map attribu return this; } + @Override + public BuildableScanFromExistingWithWhere readTag(String policyName, String readTag) { + buildableScanFromExisting.readTag(policyName, readTag); + return this; + } + @Override public BuildableScanFromExistingWithWhere clearProjections() { buildableScanFromExisting.clearProjections(); @@ -1614,6 +1690,18 @@ public BuildableScanFromExistingWithWhere clearAttribute(String name) { return this; } + @Override + public BuildableScanFromExistingWithWhere clearReadTag(String policyName) { + buildableScanFromExisting.clearReadTag(policyName); + return this; + } + + @Override + public BuildableScanFromExistingWithWhere clearReadTags() { + buildableScanFromExisting.clearReadTags(); + return this; + } + public Scan build() { return buildableScanFromExisting.build(getConjunctions(where)); } diff --git a/core/src/main/java/com/scalar/db/api/UpdateBuilder.java b/core/src/main/java/com/scalar/db/api/UpdateBuilder.java index 3b9d1fdf03..69975a81eb 100644 --- a/core/src/main/java/com/scalar/db/api/UpdateBuilder.java +++ b/core/src/main/java/com/scalar/db/api/UpdateBuilder.java @@ -3,7 +3,11 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.collect.ImmutableMap; +import com.scalar.db.api.OperationBuilder.AbacReadTagAttribute; +import com.scalar.db.api.OperationBuilder.AbacWriteTagAttribute; import com.scalar.db.api.OperationBuilder.Attribute; +import com.scalar.db.api.OperationBuilder.ClearAbacReadTagAttribute; +import com.scalar.db.api.OperationBuilder.ClearAbacWriteTagAttribute; import com.scalar.db.api.OperationBuilder.ClearAttribute; import com.scalar.db.api.OperationBuilder.ClearClusteringKey; import com.scalar.db.api.OperationBuilder.ClearCondition; @@ -79,7 +83,9 @@ public static class Buildable extends OperationBuilder.Buildable implements ClusteringKey, Condition, Values, - Attribute { + Attribute, + AbacReadTagAttribute, + AbacWriteTagAttribute { final Map> columns = new LinkedHashMap<>(); @Nullable Key clusteringKey; @Nullable MutationCondition condition; @@ -111,6 +117,22 @@ public Buildable attributes(Map attributes) { return this; } + @Override + public Buildable readTag(String policyName, String readTag) { + checkNotNull(policyName); + checkNotNull(readTag); + AbacOperationAttributes.setReadTag(attributes, policyName, readTag); + return this; + } + + @Override + public Buildable writeTag(String policyName, String writeTag) { + checkNotNull(policyName); + checkNotNull(writeTag); + AbacOperationAttributes.setWriteTag(attributes, policyName, writeTag); + return this; + } + @Override public Buildable condition(MutationCondition condition) { checkNotNull(condition); @@ -238,7 +260,9 @@ public static class BuildableFromExisting extends Buildable ClearValues, ClearCondition, ClearNamespace, - ClearAttribute { + ClearAttribute, + ClearAbacReadTagAttribute, + ClearAbacWriteTagAttribute { BuildableFromExisting(Update update) { super( @@ -290,6 +314,18 @@ public BuildableFromExisting attributes(Map attributes) { return this; } + @Override + public Buildable readTag(String policyName, String readTag) { + super.readTag(policyName, readTag); + return this; + } + + @Override + public Buildable writeTag(String policyName, String writeTag) { + super.writeTag(policyName, writeTag); + return this; + } + @Override public BuildableFromExisting condition(MutationCondition condition) { super.condition(condition); @@ -421,5 +457,29 @@ public BuildableFromExisting clearAttribute(String name) { attributes.remove(name); return this; } + + @Override + public BuildableFromExisting clearReadTag(String policyName) { + AbacOperationAttributes.clearReadTag(attributes, policyName); + return this; + } + + @Override + public BuildableFromExisting clearReadTags() { + AbacOperationAttributes.clearReadTags(attributes); + return this; + } + + @Override + public BuildableFromExisting clearWriteTag(String policyName) { + AbacOperationAttributes.clearWriteTag(attributes, policyName); + return this; + } + + @Override + public BuildableFromExisting clearWriteTags() { + AbacOperationAttributes.clearWriteTags(attributes); + return this; + } } } diff --git a/core/src/main/java/com/scalar/db/api/UpsertBuilder.java b/core/src/main/java/com/scalar/db/api/UpsertBuilder.java index f5d7189c94..f30f5dff24 100644 --- a/core/src/main/java/com/scalar/db/api/UpsertBuilder.java +++ b/core/src/main/java/com/scalar/db/api/UpsertBuilder.java @@ -3,7 +3,11 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.collect.ImmutableMap; +import com.scalar.db.api.OperationBuilder.AbacReadTagAttribute; +import com.scalar.db.api.OperationBuilder.AbacWriteTagAttribute; import com.scalar.db.api.OperationBuilder.Attribute; +import com.scalar.db.api.OperationBuilder.ClearAbacReadTagAttribute; +import com.scalar.db.api.OperationBuilder.ClearAbacWriteTagAttribute; import com.scalar.db.api.OperationBuilder.ClearAttribute; import com.scalar.db.api.OperationBuilder.ClearClusteringKey; import com.scalar.db.api.OperationBuilder.ClearNamespace; @@ -74,7 +78,11 @@ public Buildable partitionKey(Key partitionKey) { } public static class Buildable extends OperationBuilder.Buildable - implements ClusteringKey, Values, Attribute { + implements ClusteringKey, + Values, + Attribute, + AbacReadTagAttribute, + AbacWriteTagAttribute { final Map> columns = new LinkedHashMap<>(); @Nullable Key clusteringKey; final Map attributes = new HashMap<>(); @@ -105,6 +113,22 @@ public Buildable attributes(Map attributes) { return this; } + @Override + public Buildable readTag(String policyName, String readTag) { + checkNotNull(policyName); + checkNotNull(readTag); + AbacOperationAttributes.setReadTag(attributes, policyName, readTag); + return this; + } + + @Override + public Buildable writeTag(String policyName, String writeTag) { + checkNotNull(policyName); + checkNotNull(writeTag); + AbacOperationAttributes.setWriteTag(attributes, policyName, writeTag); + return this; + } + @Override public Buildable booleanValue(String columnName, boolean value) { columns.put(columnName, BooleanColumn.of(columnName, value)); @@ -223,7 +247,9 @@ public static class BuildableFromExisting extends Buildable ClearClusteringKey, ClearValues, ClearNamespace, - ClearAttribute { + ClearAttribute, + ClearAbacReadTagAttribute, + ClearAbacWriteTagAttribute { BuildableFromExisting(Upsert upsert) { super( @@ -274,6 +300,18 @@ public BuildableFromExisting attributes(Map attributes) { return this; } + @Override + public Buildable readTag(String policyName, String readTag) { + super.readTag(policyName, readTag); + return this; + } + + @Override + public Buildable writeTag(String policyName, String writeTag) { + super.writeTag(policyName, writeTag); + return this; + } + @Override public BuildableFromExisting booleanValue(String columnName, boolean value) { super.booleanValue(columnName, value); @@ -393,5 +431,29 @@ public BuildableFromExisting clearAttribute(String name) { attributes.remove(name); return this; } + + @Override + public BuildableFromExisting clearReadTag(String policyName) { + AbacOperationAttributes.clearReadTag(attributes, policyName); + return this; + } + + @Override + public BuildableFromExisting clearReadTags() { + AbacOperationAttributes.clearReadTags(attributes); + return this; + } + + @Override + public BuildableFromExisting clearWriteTag(String policyName) { + AbacOperationAttributes.clearWriteTag(attributes, policyName); + return this; + } + + @Override + public BuildableFromExisting clearWriteTags() { + AbacOperationAttributes.clearWriteTags(attributes); + return this; + } } } diff --git a/core/src/main/java/com/scalar/db/common/DecoratedDistributedTransactionAdmin.java b/core/src/main/java/com/scalar/db/common/DecoratedDistributedTransactionAdmin.java index b27a0092fd..b8062f2fc6 100644 --- a/core/src/main/java/com/scalar/db/common/DecoratedDistributedTransactionAdmin.java +++ b/core/src/main/java/com/scalar/db/common/DecoratedDistributedTransactionAdmin.java @@ -316,6 +316,228 @@ public Set getPrivileges(String username, String namespaceName, Strin return distributedTransactionAdmin.getPrivileges(username, namespaceName, tableName); } + @Override + public void createPolicy(String policyName, @Nullable String dataTagColumnName) + throws ExecutionException { + distributedTransactionAdmin.createPolicy(policyName, dataTagColumnName); + } + + @Override + public void enablePolicy(String policyName) throws ExecutionException { + distributedTransactionAdmin.enablePolicy(policyName); + } + + @Override + public void disablePolicy(String policyName) throws ExecutionException { + distributedTransactionAdmin.disablePolicy(policyName); + } + + @Override + public Optional getPolicy(String policyName) throws ExecutionException { + return distributedTransactionAdmin.getPolicy(policyName); + } + + @Override + public List getPolicies() throws ExecutionException { + return distributedTransactionAdmin.getPolicies(); + } + + @Override + public void createLevel( + String policyName, String levelShortName, String levelLongName, int levelNumber) + throws ExecutionException { + distributedTransactionAdmin.createLevel(policyName, levelShortName, levelLongName, levelNumber); + } + + @Override + public void dropLevel(String policyName, String levelShortName) throws ExecutionException { + distributedTransactionAdmin.dropLevel(policyName, levelShortName); + } + + @Override + public Optional getLevel(String policyName, String levelShortName) + throws ExecutionException { + return distributedTransactionAdmin.getLevel(policyName, levelShortName); + } + + @Override + public List getLevels(String policyName) throws ExecutionException { + return distributedTransactionAdmin.getLevels(policyName); + } + + @Override + public void createCompartment( + String policyName, String compartmentShortName, String compartmentLongName) + throws ExecutionException { + distributedTransactionAdmin.createCompartment( + policyName, compartmentShortName, compartmentLongName); + } + + @Override + public void dropCompartment(String policyName, String compartmentShortName) + throws ExecutionException { + distributedTransactionAdmin.dropCompartment(policyName, compartmentShortName); + } + + @Override + public Optional getCompartment(String policyName, String compartmentShortName) + throws ExecutionException { + return distributedTransactionAdmin.getCompartment(policyName, compartmentShortName); + } + + @Override + public List getCompartments(String policyName) throws ExecutionException { + return distributedTransactionAdmin.getCompartments(policyName); + } + + @Override + public void createGroup( + String policyName, + String groupShortName, + String groupLongName, + @Nullable String parentGroupShortName) + throws ExecutionException { + distributedTransactionAdmin.createGroup( + policyName, groupShortName, groupLongName, parentGroupShortName); + } + + @Override + public void dropGroup(String policyName, String groupShortName) throws ExecutionException { + distributedTransactionAdmin.dropGroup(policyName, groupShortName); + } + + @Override + public Optional getGroup(String policyName, String groupShortName) + throws ExecutionException { + return distributedTransactionAdmin.getGroup(policyName, groupShortName); + } + + @Override + public List getGroups(String policyName) throws ExecutionException { + return distributedTransactionAdmin.getGroups(policyName); + } + + @Override + public void setLevelsToUser( + String policyName, + String username, + String levelShortName, + @Nullable String defaultLevelShortName, + @Nullable String rowLevelShortName) + throws ExecutionException { + distributedTransactionAdmin.setLevelsToUser( + policyName, username, levelShortName, defaultLevelShortName, rowLevelShortName); + } + + @Override + public void addCompartmentToUser( + String policyName, + String username, + String compartmentShortName, + AccessMode accessMode, + boolean defaultCompartment, + boolean rowCompartment) + throws ExecutionException { + distributedTransactionAdmin.addCompartmentToUser( + policyName, username, compartmentShortName, accessMode, defaultCompartment, rowCompartment); + } + + @Override + public void removeCompartmentFromUser( + String policyName, String username, String compartmentShortName) throws ExecutionException { + distributedTransactionAdmin.removeCompartmentFromUser( + policyName, username, compartmentShortName); + } + + @Override + public void addGroupToUser( + String policyName, + String username, + String groupShortName, + AccessMode accessMode, + boolean defaultGroup, + boolean rowGroup) + throws ExecutionException { + distributedTransactionAdmin.addGroupToUser( + policyName, username, groupShortName, accessMode, defaultGroup, rowGroup); + } + + @Override + public void removeGroupFromUser(String policyName, String username, String groupShortName) + throws ExecutionException { + distributedTransactionAdmin.removeGroupFromUser(policyName, username, groupShortName); + } + + @Override + public void dropUserTagInfoFromUser(String policyName, String username) + throws ExecutionException { + distributedTransactionAdmin.dropUserTagInfoFromUser(policyName, username); + } + + @Override + public Optional getUserTagInfo(String policyName, String username) + throws ExecutionException { + return distributedTransactionAdmin.getUserTagInfo(policyName, username); + } + + @Override + public void applyPolicyToNamespace(String policyName, String namespaceName) + throws ExecutionException { + distributedTransactionAdmin.applyPolicyToNamespace(policyName, namespaceName); + } + + @Override + public void enableNamespacePolicy(String policyName, String namespaceName) + throws ExecutionException { + distributedTransactionAdmin.enableNamespacePolicy(policyName, namespaceName); + } + + @Override + public void disableNamespacePolicy(String policyName, String namespaceName) + throws ExecutionException { + distributedTransactionAdmin.disableNamespacePolicy(policyName, namespaceName); + } + + @Override + public Optional getNamespacePolicy(String policyName, String namespaceName) + throws ExecutionException { + return distributedTransactionAdmin.getNamespacePolicy(policyName, namespaceName); + } + + @Override + public List getNamespacePolicies() throws ExecutionException { + return distributedTransactionAdmin.getNamespacePolicies(); + } + + @Override + public void applyPolicyToTable(String policyName, String namespaceName, String tableName) + throws ExecutionException { + distributedTransactionAdmin.applyPolicyToTable(policyName, namespaceName, tableName); + } + + @Override + public void enableTablePolicy(String policyName, String namespaceName, String tableName) + throws ExecutionException { + distributedTransactionAdmin.enableTablePolicy(policyName, namespaceName, tableName); + } + + @Override + public void disableTablePolicy(String policyName, String namespaceName, String tableName) + throws ExecutionException { + distributedTransactionAdmin.disableTablePolicy(policyName, namespaceName, tableName); + } + + @Override + public Optional getTablePolicy( + String policyName, String namespaceName, String tableName) throws ExecutionException { + return distributedTransactionAdmin.getTablePolicy(policyName, namespaceName, tableName); + } + + @Override + public List getTablePolicies() throws ExecutionException { + return distributedTransactionAdmin.getTablePolicies(); + } + @Override public void close() { distributedTransactionAdmin.close(); diff --git a/core/src/main/java/com/scalar/db/common/error/CoreError.java b/core/src/main/java/com/scalar/db/common/error/CoreError.java index b02b3c45a6..5d2eba33e5 100644 --- a/core/src/main/java/com/scalar/db/common/error/CoreError.java +++ b/core/src/main/java/com/scalar/db/common/error/CoreError.java @@ -690,6 +690,12 @@ public enum CoreError implements ScalarDbError { ""), DATA_LOADER_ERROR_METHOD_NULL_ARGUMENT( Category.USER_ERROR, "0151", "Method null argument not allowed", "", ""), + ABAC_NOT_ENABLED( + Category.USER_ERROR, + "0152", + "The attribute-based access control feature is not enabled. To use this feature, you must enable it. Note that this feature is supported only in the ScalarDB Enterprise edition", + "", + ""), // // Errors for the concurrency error category diff --git a/core/src/test/java/com/scalar/db/api/AbacOperationAttributesTest.java b/core/src/test/java/com/scalar/db/api/AbacOperationAttributesTest.java new file mode 100644 index 0000000000..56ed98af0f --- /dev/null +++ b/core/src/test/java/com/scalar/db/api/AbacOperationAttributesTest.java @@ -0,0 +1,152 @@ +package com.scalar.db.api; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.scalar.db.io.Key; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import org.junit.jupiter.api.Test; + +public class AbacOperationAttributesTest { + + @Test + public void setReadTag_ShouldSetReadTag() { + // Arrange + Map attributes = new HashMap<>(); + String policyName = "policyName"; + String readTag = "readTag"; + + // Act + AbacOperationAttributes.setReadTag(attributes, policyName, readTag); + + // Assert + assertThat(attributes) + .containsEntry(AbacOperationAttributes.READ_TAG_PREFIX + policyName, readTag); + } + + @Test + public void clearReadTag_ShouldClearReadTag() { + // Arrange + Map attributes = new HashMap<>(); + String policyName = "policyName"; + String readTag = "readTag"; + AbacOperationAttributes.setReadTag(attributes, policyName, readTag); + + // Act + AbacOperationAttributes.clearReadTag(attributes, policyName); + + // Assert + assertThat(attributes).doesNotContainKey(AbacOperationAttributes.READ_TAG_PREFIX + policyName); + } + + @Test + public void clearReadTags_ShouldClearReadTags() { + // Arrange + Map attributes = new HashMap<>(); + String policyName1 = "policyName1"; + String policyName2 = "policyName2"; + String readTag1 = "readTag1"; + String readTag2 = "readTag2"; + AbacOperationAttributes.setReadTag(attributes, policyName1, readTag1); + AbacOperationAttributes.setReadTag(attributes, policyName2, readTag2); + + // Act + AbacOperationAttributes.clearReadTags(attributes); + + // Assert + assertThat(attributes).doesNotContainKey(AbacOperationAttributes.READ_TAG_PREFIX + policyName1); + assertThat(attributes).doesNotContainKey(AbacOperationAttributes.READ_TAG_PREFIX + policyName2); + } + + @Test + public void setWriteTag_ShouldSetWriteTag() { + // Arrange + Map attributes = new HashMap<>(); + String policyName = "policyName"; + String writeTag = "writeTag"; + + // Act + AbacOperationAttributes.setWriteTag(attributes, policyName, writeTag); + + // Assert + assertThat(attributes) + .containsEntry(AbacOperationAttributes.WRITE_TAG_PREFIX + policyName, writeTag); + } + + @Test + public void clearWriteTag_ShouldClearWriteTag() { + // Arrange + Map attributes = new HashMap<>(); + String policyName = "policyName"; + String writeTag = "writeTag"; + AbacOperationAttributes.setWriteTag(attributes, policyName, writeTag); + + // Act + AbacOperationAttributes.clearWriteTag(attributes, policyName); + + // Assert + assertThat(attributes).doesNotContainKey(AbacOperationAttributes.WRITE_TAG_PREFIX + policyName); + } + + @Test + public void clearWriteTags_ShouldClearWriteTags() { + // Arrange + Map attributes = new HashMap<>(); + String policyName1 = "policyName1"; + String policyName2 = "policyName2"; + String writeTag1 = "writeTag1"; + String writeTag2 = "writeTag2"; + AbacOperationAttributes.setWriteTag(attributes, policyName1, writeTag1); + AbacOperationAttributes.setWriteTag(attributes, policyName2, writeTag2); + + // Act + AbacOperationAttributes.clearWriteTags(attributes); + + // Assert + assertThat(attributes) + .doesNotContainKey(AbacOperationAttributes.WRITE_TAG_PREFIX + policyName1); + assertThat(attributes) + .doesNotContainKey(AbacOperationAttributes.WRITE_TAG_PREFIX + policyName2); + } + + @Test + public void getReadTag_ShouldReturnReadTag() { + // Arrange + String policyName = "policyName"; + String readTag = "readTag"; + Operation operation = + Get.newBuilder() + .namespace("ns") + .table("tbl") + .partitionKey(Key.ofInt("pk", 0)) + .readTag(policyName, readTag) + .build(); + + // Act + Optional actual = AbacOperationAttributes.getReadTag(operation, policyName); + + // Assert + assertThat(actual).hasValue(readTag); + } + + @Test + public void getWriteTag_ShouldReturnWriteTag() { + // Arrange + String policyName = "policyName"; + String writeTag = "writeTag"; + Operation operation = + Update.newBuilder() + .namespace("ns") + .table("tbl") + .partitionKey(Key.ofInt("pk", 0)) + .writeTag(policyName, writeTag) + .build(); + + // Act + Optional actual = AbacOperationAttributes.getWriteTag(operation, policyName); + + // Assert + assertThat(actual).hasValue(writeTag); + } +} diff --git a/core/src/test/java/com/scalar/db/api/DeleteBuilderTest.java b/core/src/test/java/com/scalar/db/api/DeleteBuilderTest.java index c704c8169e..853a87b356 100644 --- a/core/src/test/java/com/scalar/db/api/DeleteBuilderTest.java +++ b/core/src/test/java/com/scalar/db/api/DeleteBuilderTest.java @@ -66,6 +66,8 @@ public void build_WithAllParameters_ShouldBuildDeleteWithAllParameters() { .condition(condition1) .attribute("a1", "v1") .attributes(ImmutableMap.of("a2", "v2", "a3", "v3")) + .readTag("policyName1", "readTag") + .writeTag("policyName2", "writeTag") .build(); // Assert @@ -77,7 +79,17 @@ public void build_WithAllParameters_ShouldBuildDeleteWithAllParameters() { partitionKey1, clusteringKey1, Consistency.EVENTUAL, - ImmutableMap.of("a1", "v1", "a2", "v2", "a3", "v3"), + ImmutableMap.of( + "a1", + "v1", + "a2", + "v2", + "a3", + "v3", + AbacOperationAttributes.READ_TAG_PREFIX + "policyName1", + "readTag", + AbacOperationAttributes.WRITE_TAG_PREFIX + "policyName2", + "writeTag"), condition1)); } @@ -101,7 +113,7 @@ public void build_FromExistingWithoutChange_ShouldCopy() { @Test public void build_FromExistingAndUpdateAllParameters_ShouldBuildDeleteWithUpdatedParameters() { // Arrange - Delete existingDelete = + Delete existingDelete1 = new Delete( NAMESPACE_1, TABLE_1, @@ -110,10 +122,23 @@ public void build_FromExistingAndUpdateAllParameters_ShouldBuildDeleteWithUpdate Consistency.LINEARIZABLE, ImmutableMap.of("a1", "v1", "a2", "v2", "a3", "v3"), condition1); + Delete existingDelete2 = + new Delete( + NAMESPACE_1, + TABLE_1, + partitionKey1, + clusteringKey1, + Consistency.LINEARIZABLE, + ImmutableMap.of( + AbacOperationAttributes.READ_TAG_PREFIX + "policyName1", + "readTag", + AbacOperationAttributes.WRITE_TAG_PREFIX + "policyName2", + "writeTag"), + condition1); // Act - Delete newDelete = - Delete.newBuilder(existingDelete) + Delete newDelete1 = + Delete.newBuilder(existingDelete1) .partitionKey(partitionKey2) .clusteringKey(clusteringKey2) .namespace(NAMESPACE_2) @@ -124,10 +149,17 @@ public void build_FromExistingAndUpdateAllParameters_ShouldBuildDeleteWithUpdate .attribute("a4", "v4") .attributes(ImmutableMap.of("a5", "v5", "a6", "v6", "a7", "v7")) .clearAttribute("a7") + .readTag("policyName1", "readTag") + .writeTag("policyName2", "writeTag") + .build(); + Delete newDelete2 = + Delete.newBuilder(existingDelete2) + .clearReadTag("policyName1") + .clearWriteTag("policyName2") .build(); // Assert - assertThat(newDelete) + assertThat(newDelete1) .isEqualTo( new Delete( NAMESPACE_2, @@ -135,8 +167,28 @@ public void build_FromExistingAndUpdateAllParameters_ShouldBuildDeleteWithUpdate partitionKey2, clusteringKey2, Consistency.EVENTUAL, - ImmutableMap.of("a4", "v4", "a5", "v5", "a6", "v6"), + ImmutableMap.of( + "a4", + "v4", + "a5", + "v5", + "a6", + "v6", + AbacOperationAttributes.READ_TAG_PREFIX + "policyName1", + "readTag", + AbacOperationAttributes.WRITE_TAG_PREFIX + "policyName2", + "writeTag"), condition2)); + assertThat(newDelete2) + .isEqualTo( + new Delete( + NAMESPACE_1, + TABLE_1, + partitionKey1, + clusteringKey1, + Consistency.LINEARIZABLE, + ImmutableMap.of(), + condition1)); } @Test diff --git a/core/src/test/java/com/scalar/db/api/GetBuilderTest.java b/core/src/test/java/com/scalar/db/api/GetBuilderTest.java index e4a9bbd1a0..d99782ba33 100644 --- a/core/src/test/java/com/scalar/db/api/GetBuilderTest.java +++ b/core/src/test/java/com/scalar/db/api/GetBuilderTest.java @@ -9,6 +9,7 @@ import com.scalar.db.api.Selection.Conjunction; import com.scalar.db.io.Key; import java.util.Arrays; +import java.util.Collections; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; @@ -76,6 +77,7 @@ public void buildGet_WithClusteringKey_ShouldBuildGetWithClusteringKey() { .projections("c5", "c6") .attribute("a1", "v1") .attributes(ImmutableMap.of("a2", "v2", "a3", "v3")) + .readTag("policyName1", "readTag") .build(); // Assert @@ -87,7 +89,15 @@ public void buildGet_WithClusteringKey_ShouldBuildGetWithClusteringKey() { partitionKey1, clusteringKey1, Consistency.EVENTUAL, - ImmutableMap.of("a1", "v1", "a2", "v2", "a3", "v3"), + ImmutableMap.of( + "a1", + "v1", + "a2", + "v2", + "a3", + "v3", + AbacOperationAttributes.READ_TAG_PREFIX + "policyName1", + "readTag"), Arrays.asList("c1", "c2", "c3", "c4", "c5", "c6"), ImmutableSet.of())); } @@ -627,7 +637,7 @@ public void buildGet_FromExistingWithoutChange_ShouldCopy() { public void buildGet_FromExistingAndUpdateAllParametersExceptConjunctions_ShouldBuildGetWithUpdatedParameters() { // Arrange - Get existingGet = + Get existingGet1 = new Get( NAMESPACE_1, TABLE_1, @@ -643,10 +653,20 @@ public void buildGet_FromExistingWithoutChange_ShouldCopy() { Conjunction.of( ConditionBuilder.column("ck4").isGreaterThanInt(10), ConditionBuilder.column("col1").isGreaterThanInt(10)))); + Get existingGet2 = + new Get( + NAMESPACE_1, + TABLE_1, + partitionKey1, + clusteringKey1, + Consistency.LINEARIZABLE, + ImmutableMap.of(AbacOperationAttributes.READ_TAG_PREFIX + "policyName1", "readTag"), + Collections.emptyList(), + ImmutableSet.of()); // Act - Get newGet = - Get.newBuilder(existingGet) + Get newGet1 = + Get.newBuilder(existingGet1) .partitionKey(partitionKey2) .clusteringKey(clusteringKey2) .namespace(NAMESPACE_2) @@ -660,10 +680,12 @@ public void buildGet_FromExistingWithoutChange_ShouldCopy() { .attribute("a4", "v4") .attributes(ImmutableMap.of("a5", "v5", "a6", "v6", "a7", "v7")) .clearAttribute("a7") + .readTag("policyName1", "readTag") .build(); + Get newGet2 = Get.newBuilder(existingGet2).clearReadTag("policyName1").build(); // Assert - assertThat(newGet) + assertThat(newGet1) .isEqualTo( new Get( NAMESPACE_2, @@ -671,7 +693,15 @@ public void buildGet_FromExistingWithoutChange_ShouldCopy() { partitionKey2, clusteringKey2, Consistency.EVENTUAL, - ImmutableMap.of("a4", "v4", "a5", "v5", "a6", "v6"), + ImmutableMap.of( + "a4", + "v4", + "a5", + "v5", + "a6", + "v6", + AbacOperationAttributes.READ_TAG_PREFIX + "policyName1", + "readTag"), Arrays.asList("c3", "c4", "c5", "c6", "c7"), ImmutableSet.of( Conjunction.of( @@ -680,6 +710,17 @@ public void buildGet_FromExistingWithoutChange_ShouldCopy() { Conjunction.of( ConditionBuilder.column("ck4").isGreaterThanInt(10), ConditionBuilder.column("col1").isGreaterThanInt(10))))); + assertThat(newGet2) + .isEqualTo( + new Get( + NAMESPACE_1, + TABLE_1, + partitionKey1, + clusteringKey1, + Consistency.LINEARIZABLE, + ImmutableMap.of(), + Collections.emptyList(), + ImmutableSet.of())); } @Test @@ -1230,6 +1271,7 @@ public void buildGetWithIndex_WithMandatoryParameters_ShouldBuildGetWithMandator .projections("c5", "c6") .attribute("a1", "v1") .attributes(ImmutableMap.of("a2", "v2", "a3", "v3")) + .readTag("policyName1", "readTag") .build(); // Assert @@ -1240,7 +1282,15 @@ public void buildGetWithIndex_WithMandatoryParameters_ShouldBuildGetWithMandator TABLE_1, indexKey1, Consistency.EVENTUAL, - ImmutableMap.of("a1", "v1", "a2", "v2", "a3", "v3"), + ImmutableMap.of( + "a1", + "v1", + "a2", + "v2", + "a3", + "v3", + AbacOperationAttributes.READ_TAG_PREFIX + "policyName1", + "readTag"), Arrays.asList("c1", "c2", "c3", "c4", "c5", "c6"), ImmutableSet.of())); } @@ -1706,7 +1756,7 @@ public void buildGetWithIndex_FromExistingWithoutChange_ShouldCopy() { public void buildGetWithIndex_FromExistingAndUpdateAllParameters_ShouldBuildGetWithUpdatedParameters() { // Arrange - GetWithIndex existingGet = + GetWithIndex existingGet1 = new GetWithIndex( NAMESPACE_1, TABLE_1, @@ -1715,10 +1765,19 @@ public void buildGetWithIndex_FromExistingWithoutChange_ShouldCopy() { ImmutableMap.of("a1", "v1", "a2", "v2", "a3", "v3"), Arrays.asList("c1", "c2"), ImmutableSet.of()); + GetWithIndex existingGet2 = + new GetWithIndex( + NAMESPACE_1, + TABLE_1, + indexKey1, + Consistency.EVENTUAL, + ImmutableMap.of(AbacOperationAttributes.READ_TAG_PREFIX + "policyName1", "readTag"), + Collections.emptyList(), + ImmutableSet.of()); // Act - Get newGet = - Get.newBuilder(existingGet) + Get newGet1 = + Get.newBuilder(existingGet1) .indexKey(indexKey2) .namespace(NAMESPACE_2) .table(TABLE_2) @@ -1731,19 +1790,39 @@ public void buildGetWithIndex_FromExistingWithoutChange_ShouldCopy() { .attribute("a4", "v4") .attributes(ImmutableMap.of("a5", "v5", "a6", "v6", "a7", "v7")) .clearAttribute("a7") + .readTag("policyName1", "readTag") .build(); + Get newGet2 = Get.newBuilder(existingGet2).clearReadTag("policyName1").build(); // Assert - assertThat(newGet) + assertThat(newGet1) .isEqualTo( new GetWithIndex( NAMESPACE_2, TABLE_2, indexKey2, Consistency.EVENTUAL, - ImmutableMap.of("a4", "v4", "a5", "v5", "a6", "v6"), + ImmutableMap.of( + "a4", + "v4", + "a5", + "v5", + "a6", + "v6", + AbacOperationAttributes.READ_TAG_PREFIX + "policyName1", + "readTag"), Arrays.asList("c3", "c4", "c5", "c6", "c7"), ImmutableSet.of())); + assertThat(newGet2) + .isEqualTo( + new GetWithIndex( + NAMESPACE_1, + TABLE_1, + indexKey1, + Consistency.EVENTUAL, + ImmutableMap.of(), + Collections.emptyList(), + ImmutableSet.of())); } @Test diff --git a/core/src/test/java/com/scalar/db/api/ScanBuilderTest.java b/core/src/test/java/com/scalar/db/api/ScanBuilderTest.java index 32b7b159f1..db8d8fe2c6 100644 --- a/core/src/test/java/com/scalar/db/api/ScanBuilderTest.java +++ b/core/src/test/java/com/scalar/db/api/ScanBuilderTest.java @@ -9,6 +9,7 @@ import com.scalar.db.api.Selection.Conjunction; import com.scalar.db.io.Key; import java.util.Arrays; +import java.util.Collections; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; @@ -102,6 +103,7 @@ public void buildScan_ScanWithAllParameters_ShouldBuildScanCorrectly() { .consistency(Consistency.EVENTUAL) .attribute("a1", "v1") .attributes(ImmutableMap.of("a2", "v2", "a3", "v3")) + .readTag("policyName1", "readTag") .build(); // Assert @@ -112,7 +114,15 @@ public void buildScan_ScanWithAllParameters_ShouldBuildScanCorrectly() { TABLE_1, partitionKey1, Consistency.EVENTUAL, - ImmutableMap.of("a1", "v1", "a2", "v2", "a3", "v3"), + ImmutableMap.of( + "a1", + "v1", + "a2", + "v2", + "a3", + "v3", + AbacOperationAttributes.READ_TAG_PREFIX + "policyName1", + "readTag"), Arrays.asList("pk1", "ck1", "ck2", "ck3", "ck4"), ImmutableSet.of(), startClusteringKey1, @@ -201,7 +211,7 @@ public void buildScan_FromExistingWithoutChange_ShouldCopy() { @Test public void buildScan_FromExistingAndUpdateAllParameters_ShouldBuildScanWithUpdatedParameters() { // Arrange - Scan existingScan = + Scan existingScan1 = new Scan( NAMESPACE_1, TABLE_1, @@ -216,10 +226,25 @@ public void buildScan_FromExistingAndUpdateAllParameters_ShouldBuildScanWithUpda true, Arrays.asList(ordering1, ordering2), 10); + Scan existingScan2 = + new Scan( + NAMESPACE_1, + TABLE_1, + partitionKey1, + Consistency.EVENTUAL, + ImmutableMap.of(AbacOperationAttributes.READ_TAG_PREFIX + "policyName1", "readTag"), + Collections.emptyList(), + ImmutableSet.of(), + startClusteringKey1, + false, + endClusteringKey1, + false, + Arrays.asList(ordering1, ordering2), + 10); // Act - Scan newScan = - Scan.newBuilder(existingScan) + Scan newScan1 = + Scan.newBuilder(existingScan1) .namespace(NAMESPACE_2) .table(TABLE_2) .partitionKey(partitionKey2) @@ -239,17 +264,27 @@ public void buildScan_FromExistingAndUpdateAllParameters_ShouldBuildScanWithUpda .attribute("a4", "v4") .attributes(ImmutableMap.of("a5", "v5", "a6", "v6", "a7", "v7")) .clearAttribute("a7") + .readTag("policyName1", "readTag") .build(); + Scan newScan2 = Scan.newBuilder(existingScan2).clearReadTag("policyName1").build(); // Assert - assertThat(newScan) + assertThat(newScan1) .isEqualTo( new Scan( NAMESPACE_2, TABLE_2, partitionKey2, Consistency.LINEARIZABLE, - ImmutableMap.of("a4", "v4", "a5", "v5", "a6", "v6"), + ImmutableMap.of( + "a4", + "v4", + "a5", + "v5", + "a6", + "v6", + AbacOperationAttributes.READ_TAG_PREFIX + "policyName1", + "readTag"), Arrays.asList("pk2", "ck2", "ck3", "ck4", "ck5"), ImmutableSet.of(), startClusteringKey2, @@ -258,6 +293,22 @@ public void buildScan_FromExistingAndUpdateAllParameters_ShouldBuildScanWithUpda false, Arrays.asList(ordering3, ordering4, ordering5, ordering1, ordering2), 5)); + assertThat(newScan2) + .isEqualTo( + new Scan( + NAMESPACE_1, + TABLE_1, + partitionKey1, + Consistency.EVENTUAL, + ImmutableMap.of(), + Collections.emptyList(), + ImmutableSet.of(), + startClusteringKey1, + false, + endClusteringKey1, + false, + Arrays.asList(ordering1, ordering2), + 10)); } @Test @@ -329,6 +380,7 @@ public void buildScanAll_ScanWithAllParameters_ShouldBuildScanCorrectly() { .where(ConditionBuilder.column("ck1").isGreaterThanInt(10)) .attribute("a1", "v1") .attributes(ImmutableMap.of("a2", "v2", "a3", "v3")) + .readTag("policyName1", "readTag") .build(); // Assert @@ -338,7 +390,15 @@ public void buildScanAll_ScanWithAllParameters_ShouldBuildScanCorrectly() { NAMESPACE_1, TABLE_1, Consistency.EVENTUAL, - ImmutableMap.of("a1", "v1", "a2", "v2", "a3", "v3"), + ImmutableMap.of( + "a1", + "v1", + "a2", + "v2", + "a3", + "v3", + AbacOperationAttributes.READ_TAG_PREFIX + "policyName1", + "readTag"), Arrays.asList("pk1", "ck1", "ck2", "ck3", "ck4"), ImmutableSet.of( Conjunction.of(ConditionBuilder.column("ck1").isGreaterThanInt(10))), @@ -368,7 +428,7 @@ public void buildScanAll_FromExistingWithoutChange_ShouldCopy() { public void buildScanAll_FromExistingAndUpdateAllParameters_ShouldBuildScanWithUpdatedParameters() { // Arrange - Scan existingScan = + Scan existingScan1 = new ScanAll( NAMESPACE_1, TABLE_1, @@ -378,10 +438,20 @@ public void buildScanAll_FromExistingWithoutChange_ShouldCopy() { ImmutableSet.of(), ImmutableList.of(ordering1, ordering2), 10); + Scan existingScan2 = + new ScanAll( + NAMESPACE_1, + TABLE_1, + Consistency.EVENTUAL, + ImmutableMap.of(AbacOperationAttributes.READ_TAG_PREFIX + "policyName1", "readTag"), + Collections.emptyList(), + ImmutableSet.of(), + ImmutableList.of(ordering1, ordering2), + 10); // Act - Scan newScan = - Scan.newBuilder(existingScan) + Scan newScan1 = + Scan.newBuilder(existingScan1) .namespace(NAMESPACE_2) .table(TABLE_2) .limit(5) @@ -398,20 +468,41 @@ public void buildScanAll_FromExistingWithoutChange_ShouldCopy() { .attribute("a4", "v4") .attributes(ImmutableMap.of("a5", "v5", "a6", "v6", "a7", "v7")) .clearAttribute("a7") + .readTag("policyName1", "readTag") .build(); + Scan newScan2 = Scan.newBuilder(existingScan2).clearReadTag("policyName1").build(); // Assert - assertThat(newScan) + assertThat(newScan1) .isEqualTo( new ScanAll( NAMESPACE_2, TABLE_2, Consistency.LINEARIZABLE, - ImmutableMap.of("a4", "v4", "a5", "v5", "a6", "v6"), + ImmutableMap.of( + "a4", + "v4", + "a5", + "v5", + "a6", + "v6", + AbacOperationAttributes.READ_TAG_PREFIX + "policyName1", + "readTag"), Arrays.asList("pk2", "ck2", "ck3", "ck4", "ck5"), ImmutableSet.of(), ImmutableList.of(ordering3, ordering4, ordering5, ordering1, ordering2), 5)); + assertThat(newScan2) + .isEqualTo( + new ScanAll( + NAMESPACE_1, + TABLE_1, + Consistency.EVENTUAL, + ImmutableMap.of(), + Collections.emptyList(), + ImmutableSet.of(), + ImmutableList.of(ordering1, ordering2), + 10)); } @Test @@ -475,6 +566,7 @@ public void buildScanWithIndex_ScanWithAllParameters_ShouldBuildScanCorrectly() .consistency(Consistency.EVENTUAL) .attribute("a1", "v1") .attributes(ImmutableMap.of("a2", "v2", "a3", "v3")) + .readTag("policyName1", "readTag") .build(); // Assert @@ -485,7 +577,15 @@ public void buildScanWithIndex_ScanWithAllParameters_ShouldBuildScanCorrectly() TABLE_1, indexKey1, Consistency.EVENTUAL, - ImmutableMap.of("a1", "v1", "a2", "v2", "a3", "v3"), + ImmutableMap.of( + "a1", + "v1", + "a2", + "v2", + "a3", + "v3", + AbacOperationAttributes.READ_TAG_PREFIX + "policyName1", + "readTag"), Arrays.asList("pk1", "ck1", "ck2", "ck3", "ck4"), ImmutableSet.of(), 10)); @@ -513,7 +613,7 @@ public void buildScanWithIndex_FromExistingWithoutChange_ShouldCopy() { public void buildScanWithIndex_FromExistingAndUpdateAllParameters_ShouldBuildScanWithUpdatedParameters() { // Arrange - Scan existingScan = + Scan existingScan1 = new ScanWithIndex( NAMESPACE_1, TABLE_1, @@ -523,10 +623,20 @@ public void buildScanWithIndex_FromExistingWithoutChange_ShouldCopy() { Arrays.asList("pk1", "ck1"), ImmutableSet.of(), 10); + Scan existingScan2 = + new ScanWithIndex( + NAMESPACE_1, + TABLE_1, + indexKey1, + Consistency.EVENTUAL, + ImmutableMap.of(AbacOperationAttributes.READ_TAG_PREFIX + "policyName1", "readTag"), + Collections.emptyList(), + ImmutableSet.of(), + 10); // Act - Scan newScan = - Scan.newBuilder(existingScan) + Scan newScan1 = + Scan.newBuilder(existingScan1) .namespace(NAMESPACE_2) .table(TABLE_2) .indexKey(indexKey2) @@ -540,20 +650,41 @@ public void buildScanWithIndex_FromExistingWithoutChange_ShouldCopy() { .attribute("a4", "v4") .attributes(ImmutableMap.of("a5", "v5", "a6", "v6", "a7", "v7")) .clearAttribute("a7") + .readTag("policyName1", "readTag") .build(); + Scan newScan2 = Scan.newBuilder(existingScan2).clearReadTag("policyName1").build(); // Assert - assertThat(newScan) + assertThat(newScan1) .isEqualTo( new ScanWithIndex( NAMESPACE_2, TABLE_2, indexKey2, Consistency.LINEARIZABLE, - ImmutableMap.of("a4", "v4", "a5", "v5", "a6", "v6"), + ImmutableMap.of( + "a4", + "v4", + "a5", + "v5", + "a6", + "v6", + AbacOperationAttributes.READ_TAG_PREFIX + "policyName1", + "readTag"), Arrays.asList("pk2", "ck2", "ck3", "ck4", "ck5"), ImmutableSet.of(), 5)); + assertThat(newScan2) + .isEqualTo( + new ScanWithIndex( + NAMESPACE_1, + TABLE_1, + indexKey1, + Consistency.EVENTUAL, + ImmutableMap.of(), + Collections.emptyList(), + ImmutableSet.of(), + 10)); } @Test diff --git a/core/src/test/java/com/scalar/db/api/UpdateBuilderTest.java b/core/src/test/java/com/scalar/db/api/UpdateBuilderTest.java index 92712d7b0c..1d2691138b 100644 --- a/core/src/test/java/com/scalar/db/api/UpdateBuilderTest.java +++ b/core/src/test/java/com/scalar/db/api/UpdateBuilderTest.java @@ -92,6 +92,8 @@ public void build_WithAllParameters_ShouldBuildUpdateCorrectly() { .condition(condition1) .attribute("a1", "v1") .attributes(ImmutableMap.of("a2", "v2", "a3", "v3")) + .readTag("policyName1", "readTag") + .writeTag("policyName2", "writeTag") .build(); // Assert @@ -123,7 +125,18 @@ public void build_WithAllParameters_ShouldBuildUpdateCorrectly() { assertThat(actual.getColumns().get("text2").getTextValue()).isEqualTo("another_value"); assertThat(actual.getCondition()).hasValue(condition1); assertThat(actual.getAttributes()) - .isEqualTo(ImmutableMap.of("a1", "v1", "a2", "v2", "a3", "v3")); + .isEqualTo( + ImmutableMap.of( + "a1", + "v1", + "a2", + "v2", + "a3", + "v3", + AbacOperationAttributes.READ_TAG_PREFIX + "policyName1", + "readTag", + AbacOperationAttributes.WRITE_TAG_PREFIX + "policyName2", + "writeTag")); } @Test @@ -219,7 +232,7 @@ public void build_FromExistingWithoutChange_ShouldCopy() { @Test public void build_FromExistingAndUpdateAllParameters_ShouldBuildUpdateWithUpdatedParameters() { // Arrange - Update existingUpdate = + Update existingUpdate1 = Update.newBuilder() .namespace(NAMESPACE_1) .table(TABLE_1) @@ -243,10 +256,20 @@ public void build_FromExistingAndUpdateAllParameters_ShouldBuildUpdateWithUpdate .attribute("a1", "v1") .attributes(ImmutableMap.of("a2", "v2", "a3", "v3")) .build(); + Update existingUpdate2 = + Update.newBuilder() + .namespace(NAMESPACE_1) + .table(TABLE_1) + .partitionKey(partitionKey1) + .clusteringKey(clusteringKey1) + .bigIntValue("bigint1", BigIntColumn.MIN_VALUE) + .readTag("policyName1", "readTag") + .writeTag("policyName2", "writeTag") + .build(); // Act - Update newUpdate = - Update.newBuilder(existingUpdate) + Update newUpdate1 = + Update.newBuilder(existingUpdate1) .namespace(NAMESPACE_2) .table(TABLE_2) .partitionKey(partitionKey2) @@ -271,38 +294,65 @@ public void build_FromExistingAndUpdateAllParameters_ShouldBuildUpdateWithUpdate .attribute("a4", "v4") .attributes(ImmutableMap.of("a5", "v5", "a6", "v6", "a7", "v7")) .clearAttribute("a7") + .readTag("policyName1", "readTag") + .writeTag("policyName2", "writeTag") + .build(); + Update newUpdate2 = + Update.newBuilder(existingUpdate2) + .clearReadTag("policyName1") + .clearWriteTag("policyName2") .build(); // Assert - assertThat(newUpdate.forNamespace()).hasValue(NAMESPACE_2); - assertThat(newUpdate.forTable()).hasValue(TABLE_2); - Assertions.assertThat(newUpdate.getPartitionKey()).isEqualTo(partitionKey2); - assertThat(newUpdate.getClusteringKey()).hasValue(clusteringKey2); - assertThat(newUpdate.getColumns().size()).isEqualTo(14); - assertThat(newUpdate.getColumns().get("bigint1").getBigIntValue()) + assertThat(newUpdate1.forNamespace()).hasValue(NAMESPACE_2); + assertThat(newUpdate1.forTable()).hasValue(TABLE_2); + Assertions.assertThat(newUpdate1.getPartitionKey()).isEqualTo(partitionKey2); + assertThat(newUpdate1.getClusteringKey()).hasValue(clusteringKey2); + assertThat(newUpdate1.getColumns().size()).isEqualTo(14); + assertThat(newUpdate1.getColumns().get("bigint1").getBigIntValue()) .isEqualTo(BigIntColumn.MIN_VALUE); - assertThat(newUpdate.getColumns().get("bigint2").getBigIntValue()) + assertThat(newUpdate1.getColumns().get("bigint2").getBigIntValue()) .isEqualTo(Long.valueOf(BigIntColumn.MIN_VALUE)); - assertThat(newUpdate.getColumns().get("blob1").getBlobValueAsBytes()) + assertThat(newUpdate1.getColumns().get("blob1").getBlobValueAsBytes()) .isEqualTo("foo".getBytes(StandardCharsets.UTF_8)); - assertThat(newUpdate.getColumns().get("blob2").getBlobValueAsByteBuffer()) + assertThat(newUpdate1.getColumns().get("blob2").getBlobValueAsByteBuffer()) .isEqualTo(ByteBuffer.allocate(2)); - assertThat(newUpdate.getColumns().get("bool1").getBooleanValue()).isFalse(); - assertThat(newUpdate.getColumns().get("bool2").getBooleanValue()).isFalse(); - assertThat(newUpdate.getColumns().get("double1").getDoubleValue()).isEqualTo(Double.MIN_VALUE); - assertThat(newUpdate.getColumns().get("double2").getDoubleValue()) + assertThat(newUpdate1.getColumns().get("bool1").getBooleanValue()).isFalse(); + assertThat(newUpdate1.getColumns().get("bool2").getBooleanValue()).isFalse(); + assertThat(newUpdate1.getColumns().get("double1").getDoubleValue()).isEqualTo(Double.MIN_VALUE); + assertThat(newUpdate1.getColumns().get("double2").getDoubleValue()) .isEqualTo(Double.valueOf(Double.MIN_VALUE)); - assertThat(newUpdate.getColumns().get("float1").getFloatValue()).isEqualTo(Float.MIN_VALUE); - assertThat(newUpdate.getColumns().get("float2").getFloatValue()) + assertThat(newUpdate1.getColumns().get("float1").getFloatValue()).isEqualTo(Float.MIN_VALUE); + assertThat(newUpdate1.getColumns().get("float2").getFloatValue()) .isEqualTo(Float.valueOf(Float.MIN_VALUE)); - assertThat(newUpdate.getColumns().get("int1").getIntValue()).isEqualTo(Integer.MIN_VALUE); - assertThat(newUpdate.getColumns().get("int2").getIntValue()) + assertThat(newUpdate1.getColumns().get("int1").getIntValue()).isEqualTo(Integer.MIN_VALUE); + assertThat(newUpdate1.getColumns().get("int2").getIntValue()) .isEqualTo(Integer.valueOf(Integer.MIN_VALUE)); - assertThat(newUpdate.getColumns().get("text").getTextValue()).isEqualTo("another_value"); - assertThat(newUpdate.getColumns().get("text2").getTextValue()).isEqualTo("foo"); - assertThat(newUpdate.getCondition()).hasValue(condition2); - assertThat(newUpdate.getAttributes()) - .isEqualTo(ImmutableMap.of("a4", "v4", "a5", "v5", "a6", "v6")); + assertThat(newUpdate1.getColumns().get("text").getTextValue()).isEqualTo("another_value"); + assertThat(newUpdate1.getColumns().get("text2").getTextValue()).isEqualTo("foo"); + assertThat(newUpdate1.getCondition()).hasValue(condition2); + assertThat(newUpdate1.getAttributes()) + .isEqualTo( + ImmutableMap.of( + "a4", + "v4", + "a5", + "v5", + "a6", + "v6", + AbacOperationAttributes.READ_TAG_PREFIX + "policyName1", + "readTag", + AbacOperationAttributes.WRITE_TAG_PREFIX + "policyName2", + "writeTag")); + + assertThat(newUpdate2.forNamespace()).hasValue(NAMESPACE_1); + assertThat(newUpdate2.forTable()).hasValue(TABLE_1); + Assertions.assertThat(newUpdate2.getPartitionKey()).isEqualTo(partitionKey1); + assertThat(newUpdate2.getClusteringKey()).hasValue(clusteringKey1); + assertThat(newUpdate2.getColumns().size()).isEqualTo(1); + assertThat(newUpdate2.getColumns().get("bigint1").getBigIntValue()) + .isEqualTo(BigIntColumn.MIN_VALUE); + assertThat(newUpdate2.getAttributes()).isEmpty(); } @Test diff --git a/core/src/test/java/com/scalar/db/api/UpsertBuilderTest.java b/core/src/test/java/com/scalar/db/api/UpsertBuilderTest.java index 6fd92fb120..7d43a9c878 100644 --- a/core/src/test/java/com/scalar/db/api/UpsertBuilderTest.java +++ b/core/src/test/java/com/scalar/db/api/UpsertBuilderTest.java @@ -87,6 +87,8 @@ public void build_WithAllParameters_ShouldBuildUpsertCorrectly() { .value(TextColumn.of("text2", "another_value")) .attribute("a1", "v1") .attributes(ImmutableMap.of("a2", "v2", "a3", "v3")) + .readTag("policyName1", "readTag") + .writeTag("policyName2", "writeTag") .build(); // Assert @@ -117,7 +119,18 @@ public void build_WithAllParameters_ShouldBuildUpsertCorrectly() { assertThat(actual.getColumns().get("text").getTextValue()).isEqualTo("a_value"); assertThat(actual.getColumns().get("text2").getTextValue()).isEqualTo("another_value"); assertThat(actual.getAttributes()) - .isEqualTo(ImmutableMap.of("a1", "v1", "a2", "v2", "a3", "v3")); + .isEqualTo( + ImmutableMap.of( + "a1", + "v1", + "a2", + "v2", + "a3", + "v3", + AbacOperationAttributes.READ_TAG_PREFIX + "policyName1", + "readTag", + AbacOperationAttributes.WRITE_TAG_PREFIX + "policyName2", + "writeTag")); } @Test @@ -210,7 +223,7 @@ public void build_FromExistingWithoutChange_ShouldCopy() { @Test public void build_FromExistingAndUpdateAllParameters_ShouldBuildUpsertWithUpdatedParameters() { // Arrange - Upsert existingUpsert = + Upsert existingUpsert1 = Upsert.newBuilder() .namespace(NAMESPACE_1) .table(TABLE_1) @@ -233,10 +246,20 @@ public void build_FromExistingAndUpdateAllParameters_ShouldBuildUpsertWithUpdate .attribute("a1", "v1") .attributes(ImmutableMap.of("a2", "v2", "a3", "v3")) .build(); + Upsert existingUpsert2 = + Upsert.newBuilder() + .namespace(NAMESPACE_1) + .table(TABLE_1) + .partitionKey(partitionKey1) + .clusteringKey(clusteringKey1) + .bigIntValue("bigint1", BigIntColumn.MAX_VALUE) + .readTag("policyName1", "readTag") + .writeTag("policyName2", "writeTag") + .build(); // Act - Upsert newUpsert = - Upsert.newBuilder(existingUpsert) + Upsert newUpsert1 = + Upsert.newBuilder(existingUpsert1) .namespace(NAMESPACE_2) .table(TABLE_2) .partitionKey(partitionKey2) @@ -260,37 +283,64 @@ public void build_FromExistingAndUpdateAllParameters_ShouldBuildUpsertWithUpdate .attribute("a4", "v4") .attributes(ImmutableMap.of("a5", "v5", "a6", "v6", "a7", "v7")) .clearAttribute("a7") + .readTag("policyName1", "readTag") + .writeTag("policyName2", "writeTag") + .build(); + Upsert newUpsert2 = + Upsert.newBuilder(existingUpsert2) + .clearReadTag("policyName1") + .clearWriteTag("policyName2") .build(); // Assert - assertThat(newUpsert.forNamespace()).hasValue(NAMESPACE_2); - assertThat(newUpsert.forTable()).hasValue(TABLE_2); - Assertions.assertThat(newUpsert.getPartitionKey()).isEqualTo(partitionKey2); - assertThat(newUpsert.getClusteringKey()).hasValue(clusteringKey2); - assertThat(newUpsert.getColumns().size()).isEqualTo(14); - assertThat(newUpsert.getColumns().get("bigint1").getBigIntValue()) + assertThat(newUpsert1.forNamespace()).hasValue(NAMESPACE_2); + assertThat(newUpsert1.forTable()).hasValue(TABLE_2); + Assertions.assertThat(newUpsert1.getPartitionKey()).isEqualTo(partitionKey2); + assertThat(newUpsert1.getClusteringKey()).hasValue(clusteringKey2); + assertThat(newUpsert1.getColumns().size()).isEqualTo(14); + assertThat(newUpsert1.getColumns().get("bigint1").getBigIntValue()) .isEqualTo(BigIntColumn.MIN_VALUE); - assertThat(newUpsert.getColumns().get("bigint2").getBigIntValue()) + assertThat(newUpsert1.getColumns().get("bigint2").getBigIntValue()) .isEqualTo(Long.valueOf(BigIntColumn.MIN_VALUE)); - assertThat(newUpsert.getColumns().get("blob1").getBlobValueAsBytes()) + assertThat(newUpsert1.getColumns().get("blob1").getBlobValueAsBytes()) .isEqualTo("foo".getBytes(StandardCharsets.UTF_8)); - assertThat(newUpsert.getColumns().get("blob2").getBlobValueAsByteBuffer()) + assertThat(newUpsert1.getColumns().get("blob2").getBlobValueAsByteBuffer()) .isEqualTo(ByteBuffer.allocate(2)); - assertThat(newUpsert.getColumns().get("bool1").getBooleanValue()).isFalse(); - assertThat(newUpsert.getColumns().get("bool2").getBooleanValue()).isFalse(); - assertThat(newUpsert.getColumns().get("double1").getDoubleValue()).isEqualTo(Double.MIN_VALUE); - assertThat(newUpsert.getColumns().get("double2").getDoubleValue()) + assertThat(newUpsert1.getColumns().get("bool1").getBooleanValue()).isFalse(); + assertThat(newUpsert1.getColumns().get("bool2").getBooleanValue()).isFalse(); + assertThat(newUpsert1.getColumns().get("double1").getDoubleValue()).isEqualTo(Double.MIN_VALUE); + assertThat(newUpsert1.getColumns().get("double2").getDoubleValue()) .isEqualTo(Double.valueOf(Double.MIN_VALUE)); - assertThat(newUpsert.getColumns().get("float1").getFloatValue()).isEqualTo(Float.MIN_VALUE); - assertThat(newUpsert.getColumns().get("float2").getFloatValue()) + assertThat(newUpsert1.getColumns().get("float1").getFloatValue()).isEqualTo(Float.MIN_VALUE); + assertThat(newUpsert1.getColumns().get("float2").getFloatValue()) .isEqualTo(Float.valueOf(Float.MIN_VALUE)); - assertThat(newUpsert.getColumns().get("int1").getIntValue()).isEqualTo(Integer.MIN_VALUE); - assertThat(newUpsert.getColumns().get("int2").getIntValue()) + assertThat(newUpsert1.getColumns().get("int1").getIntValue()).isEqualTo(Integer.MIN_VALUE); + assertThat(newUpsert1.getColumns().get("int2").getIntValue()) .isEqualTo(Integer.valueOf(Integer.MIN_VALUE)); - assertThat(newUpsert.getColumns().get("text").getTextValue()).isEqualTo("another_value"); - assertThat(newUpsert.getColumns().get("text2").getTextValue()).isEqualTo("foo"); - assertThat(newUpsert.getAttributes()) - .isEqualTo(ImmutableMap.of("a4", "v4", "a5", "v5", "a6", "v6")); + assertThat(newUpsert1.getColumns().get("text").getTextValue()).isEqualTo("another_value"); + assertThat(newUpsert1.getColumns().get("text2").getTextValue()).isEqualTo("foo"); + assertThat(newUpsert1.getAttributes()) + .isEqualTo( + ImmutableMap.of( + "a4", + "v4", + "a5", + "v5", + "a6", + "v6", + AbacOperationAttributes.READ_TAG_PREFIX + "policyName1", + "readTag", + AbacOperationAttributes.WRITE_TAG_PREFIX + "policyName2", + "writeTag")); + + assertThat(newUpsert2.forNamespace()).hasValue(NAMESPACE_1); + assertThat(newUpsert2.forTable()).hasValue(TABLE_1); + Assertions.assertThat(newUpsert2.getPartitionKey()).isEqualTo(partitionKey1); + assertThat(newUpsert2.getClusteringKey()).hasValue(clusteringKey1); + assertThat(newUpsert2.getColumns().size()).isEqualTo(1); + assertThat(newUpsert2.getColumns().get("bigint1").getBigIntValue()) + .isEqualTo(BigIntColumn.MAX_VALUE); + assertThat(newUpsert2.getAttributes()).isEmpty(); } @Test