diff --git a/cmd/migrate.go b/cmd/migrate.go index 45dd4dfce6..7e3e087191 100644 --- a/cmd/migrate.go +++ b/cmd/migrate.go @@ -31,7 +31,6 @@ var ( fmt.Print("migration down applied successfully") }, } - migrateUpCmd = &cobra.Command{ Use: "up", Short: "Run database migrations up to the latest version", diff --git a/docs/grpc/index.html b/docs/grpc/index.html index e50e45f427..5c3348d365 100644 --- a/docs/grpc/index.html +++ b/docs/grpc/index.html @@ -2068,7 +2068,7 @@
list of attribute values that this value is related to (attribute group)
* list of attribute values that this value is related to (attribute group)
*
*
- * repeated string members = 4 [json_name = "members"];
- * @return A list containing the members.
+ * repeated .policy.Value members = 4 [json_name = "members"];
*/
- public com.google.protobuf.ProtocolStringList
- getMembersList() {
+ @java.lang.Override
+ public java.util.List+ * list of attribute values that this value is related to (attribute group) + *+ * + *
repeated .policy.Value members = 4 [json_name = "members"];
+ */
+ @java.lang.Override
+ public java.util.List extends io.opentdf.platform.policy.ValueOrBuilder>
+ getMembersOrBuilderList() {
return members_;
}
/**
@@ -180,9 +189,9 @@ public java.lang.String getValue() {
* list of attribute values that this value is related to (attribute group)
*
*
- * repeated string members = 4 [json_name = "members"];
- * @return The count of members.
+ * repeated .policy.Value members = 4 [json_name = "members"];
*/
+ @java.lang.Override
public int getMembersCount() {
return members_.size();
}
@@ -191,11 +200,10 @@ public int getMembersCount() {
* list of attribute values that this value is related to (attribute group)
*
*
- * repeated string members = 4 [json_name = "members"];
- * @param index The index of the element to return.
- * @return The members at the given index.
+ * repeated .policy.Value members = 4 [json_name = "members"];
*/
- public java.lang.String getMembers(int index) {
+ @java.lang.Override
+ public io.opentdf.platform.policy.Value getMembers(int index) {
return members_.get(index);
}
/**
@@ -203,13 +211,12 @@ public java.lang.String getMembers(int index) {
* list of attribute values that this value is related to (attribute group)
*
*
- * repeated string members = 4 [json_name = "members"];
- * @param index The index of the value to return.
- * @return The bytes of the members at the given index.
+ * repeated .policy.Value members = 4 [json_name = "members"];
*/
- public com.google.protobuf.ByteString
- getMembersBytes(int index) {
- return members_.getByteString(index);
+ @java.lang.Override
+ public io.opentdf.platform.policy.ValueOrBuilder getMembersOrBuilder(
+ int index) {
+ return members_.get(index);
}
public static final int GRANTS_FIELD_NUMBER = 5;
@@ -473,7 +480,7 @@ public void writeTo(com.google.protobuf.CodedOutputStream output)
com.google.protobuf.GeneratedMessageV3.writeString(output, 3, value_);
}
for (int i = 0; i < members_.size(); i++) {
- com.google.protobuf.GeneratedMessageV3.writeString(output, 4, members_.getRaw(i));
+ output.writeMessage(4, members_.get(i));
}
for (int i = 0; i < grants_.size(); i++) {
output.writeMessage(5, grants_.get(i));
@@ -509,13 +516,9 @@ public int getSerializedSize() {
if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(value_)) {
size += com.google.protobuf.GeneratedMessageV3.computeStringSize(3, value_);
}
- {
- int dataSize = 0;
- for (int i = 0; i < members_.size(); i++) {
- dataSize += computeStringSizeNoTag(members_.getRaw(i));
- }
- size += dataSize;
- size += 1 * getMembersList().size();
+ for (int i = 0; i < members_.size(); i++) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeMessageSize(4, members_.get(i));
}
for (int i = 0; i < grants_.size(); i++) {
size += com.google.protobuf.CodedOutputStream
@@ -750,6 +753,7 @@ private void maybeForceBuilderInitialization() {
if (com.google.protobuf.GeneratedMessageV3
.alwaysUseFieldBuilders) {
getAttributeFieldBuilder();
+ getMembersFieldBuilder();
getGrantsFieldBuilder();
getActiveFieldBuilder();
getSubjectMappingsFieldBuilder();
@@ -767,8 +771,13 @@ public Builder clear() {
attributeBuilder_ = null;
}
value_ = "";
- members_ =
- com.google.protobuf.LazyStringArrayList.emptyList();
+ if (membersBuilder_ == null) {
+ members_ = java.util.Collections.emptyList();
+ } else {
+ members_ = null;
+ membersBuilder_.clear();
+ }
+ bitField0_ = (bitField0_ & ~0x00000008);
if (grantsBuilder_ == null) {
grants_ = java.util.Collections.emptyList();
} else {
@@ -827,6 +836,15 @@ public io.opentdf.platform.policy.Value buildPartial() {
}
private void buildPartialRepeatedFields(io.opentdf.platform.policy.Value result) {
+ if (membersBuilder_ == null) {
+ if (((bitField0_ & 0x00000008) != 0)) {
+ members_ = java.util.Collections.unmodifiableList(members_);
+ bitField0_ = (bitField0_ & ~0x00000008);
+ }
+ result.members_ = members_;
+ } else {
+ result.members_ = membersBuilder_.build();
+ }
if (grantsBuilder_ == null) {
if (((bitField0_ & 0x00000010) != 0)) {
grants_ = java.util.Collections.unmodifiableList(grants_);
@@ -862,10 +880,6 @@ private void buildPartial0(io.opentdf.platform.policy.Value result) {
if (((from_bitField0_ & 0x00000004) != 0)) {
result.value_ = value_;
}
- if (((from_bitField0_ & 0x00000008) != 0)) {
- members_.makeImmutable();
- result.members_ = members_;
- }
if (((from_bitField0_ & 0x00000020) != 0)) {
result.fqn_ = fqn_;
}
@@ -941,15 +955,31 @@ public Builder mergeFrom(io.opentdf.platform.policy.Value other) {
bitField0_ |= 0x00000004;
onChanged();
}
- if (!other.members_.isEmpty()) {
- if (members_.isEmpty()) {
- members_ = other.members_;
- bitField0_ |= 0x00000008;
- } else {
- ensureMembersIsMutable();
- members_.addAll(other.members_);
+ if (membersBuilder_ == null) {
+ if (!other.members_.isEmpty()) {
+ if (members_.isEmpty()) {
+ members_ = other.members_;
+ bitField0_ = (bitField0_ & ~0x00000008);
+ } else {
+ ensureMembersIsMutable();
+ members_.addAll(other.members_);
+ }
+ onChanged();
+ }
+ } else {
+ if (!other.members_.isEmpty()) {
+ if (membersBuilder_.isEmpty()) {
+ membersBuilder_.dispose();
+ membersBuilder_ = null;
+ members_ = other.members_;
+ bitField0_ = (bitField0_ & ~0x00000008);
+ membersBuilder_ =
+ com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ?
+ getMembersFieldBuilder() : null;
+ } else {
+ membersBuilder_.addAllMessages(other.members_);
+ }
}
- onChanged();
}
if (grantsBuilder_ == null) {
if (!other.grants_.isEmpty()) {
@@ -1058,9 +1088,16 @@ public Builder mergeFrom(
break;
} // case 26
case 34: {
- java.lang.String s = input.readStringRequireUtf8();
- ensureMembersIsMutable();
- members_.add(s);
+ io.opentdf.platform.policy.Value m =
+ input.readMessage(
+ io.opentdf.platform.policy.Value.parser(),
+ extensionRegistry);
+ if (membersBuilder_ == null) {
+ ensureMembersIsMutable();
+ members_.add(m);
+ } else {
+ membersBuilder_.addMessage(m);
+ }
break;
} // case 34
case 42: {
@@ -1410,80 +1447,97 @@ public Builder setValueBytes(
return this;
}
- private com.google.protobuf.LazyStringArrayList members_ =
- com.google.protobuf.LazyStringArrayList.emptyList();
+ private java.util.List
* list of attribute values that this value is related to (attribute group)
*
*
- * repeated string members = 4 [json_name = "members"];
- * @return A list containing the members.
+ * repeated .policy.Value members = 4 [json_name = "members"];
*/
- public com.google.protobuf.ProtocolStringList
- getMembersList() {
- members_.makeImmutable();
- return members_;
+ public java.util.List
* list of attribute values that this value is related to (attribute group)
*
*
- * repeated string members = 4 [json_name = "members"];
- * @return The count of members.
+ * repeated .policy.Value members = 4 [json_name = "members"];
*/
public int getMembersCount() {
- return members_.size();
+ if (membersBuilder_ == null) {
+ return members_.size();
+ } else {
+ return membersBuilder_.getCount();
+ }
}
/**
*
* list of attribute values that this value is related to (attribute group)
*
*
- * repeated string members = 4 [json_name = "members"];
- * @param index The index of the element to return.
- * @return The members at the given index.
+ * repeated .policy.Value members = 4 [json_name = "members"];
*/
- public java.lang.String getMembers(int index) {
- return members_.get(index);
+ public io.opentdf.platform.policy.Value getMembers(int index) {
+ if (membersBuilder_ == null) {
+ return members_.get(index);
+ } else {
+ return membersBuilder_.getMessage(index);
+ }
}
/**
*
* list of attribute values that this value is related to (attribute group)
*
*
- * repeated string members = 4 [json_name = "members"];
- * @param index The index of the value to return.
- * @return The bytes of the members at the given index.
+ * repeated .policy.Value members = 4 [json_name = "members"];
*/
- public com.google.protobuf.ByteString
- getMembersBytes(int index) {
- return members_.getByteString(index);
+ public Builder setMembers(
+ int index, io.opentdf.platform.policy.Value value) {
+ if (membersBuilder_ == null) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ ensureMembersIsMutable();
+ members_.set(index, value);
+ onChanged();
+ } else {
+ membersBuilder_.setMessage(index, value);
+ }
+ return this;
}
/**
*
* list of attribute values that this value is related to (attribute group)
*
*
- * repeated string members = 4 [json_name = "members"];
- * @param index The index to set the value at.
- * @param value The members to set.
- * @return This builder for chaining.
+ * repeated .policy.Value members = 4 [json_name = "members"];
*/
public Builder setMembers(
- int index, java.lang.String value) {
- if (value == null) { throw new NullPointerException(); }
- ensureMembersIsMutable();
- members_.set(index, value);
- bitField0_ |= 0x00000008;
- onChanged();
+ int index, io.opentdf.platform.policy.Value.Builder builderForValue) {
+ if (membersBuilder_ == null) {
+ ensureMembersIsMutable();
+ members_.set(index, builderForValue.build());
+ onChanged();
+ } else {
+ membersBuilder_.setMessage(index, builderForValue.build());
+ }
return this;
}
/**
@@ -1491,17 +1545,40 @@ public Builder setMembers(
* list of attribute values that this value is related to (attribute group)
*
*
- * repeated string members = 4 [json_name = "members"];
- * @param value The members to add.
- * @return This builder for chaining.
+ * repeated .policy.Value members = 4 [json_name = "members"];
+ */
+ public Builder addMembers(io.opentdf.platform.policy.Value value) {
+ if (membersBuilder_ == null) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ ensureMembersIsMutable();
+ members_.add(value);
+ onChanged();
+ } else {
+ membersBuilder_.addMessage(value);
+ }
+ return this;
+ }
+ /**
+ * + * list of attribute values that this value is related to (attribute group) + *+ * + *
repeated .policy.Value members = 4 [json_name = "members"];
*/
public Builder addMembers(
- java.lang.String value) {
- if (value == null) { throw new NullPointerException(); }
- ensureMembersIsMutable();
- members_.add(value);
- bitField0_ |= 0x00000008;
- onChanged();
+ int index, io.opentdf.platform.policy.Value value) {
+ if (membersBuilder_ == null) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ ensureMembersIsMutable();
+ members_.add(index, value);
+ onChanged();
+ } else {
+ membersBuilder_.addMessage(index, value);
+ }
return this;
}
/**
@@ -1509,17 +1586,54 @@ public Builder addMembers(
* list of attribute values that this value is related to (attribute group)
*
*
- * repeated string members = 4 [json_name = "members"];
- * @param values The members to add.
- * @return This builder for chaining.
+ * repeated .policy.Value members = 4 [json_name = "members"];
+ */
+ public Builder addMembers(
+ io.opentdf.platform.policy.Value.Builder builderForValue) {
+ if (membersBuilder_ == null) {
+ ensureMembersIsMutable();
+ members_.add(builderForValue.build());
+ onChanged();
+ } else {
+ membersBuilder_.addMessage(builderForValue.build());
+ }
+ return this;
+ }
+ /**
+ * + * list of attribute values that this value is related to (attribute group) + *+ * + *
repeated .policy.Value members = 4 [json_name = "members"];
+ */
+ public Builder addMembers(
+ int index, io.opentdf.platform.policy.Value.Builder builderForValue) {
+ if (membersBuilder_ == null) {
+ ensureMembersIsMutable();
+ members_.add(index, builderForValue.build());
+ onChanged();
+ } else {
+ membersBuilder_.addMessage(index, builderForValue.build());
+ }
+ return this;
+ }
+ /**
+ * + * list of attribute values that this value is related to (attribute group) + *+ * + *
repeated .policy.Value members = 4 [json_name = "members"];
*/
public Builder addAllMembers(
- java.lang.Iterablerepeated string members = 4 [json_name = "members"];
- * @return This builder for chaining.
+ * repeated .policy.Value members = 4 [json_name = "members"];
*/
public Builder clearMembers() {
- members_ =
- com.google.protobuf.LazyStringArrayList.emptyList();
- bitField0_ = (bitField0_ & ~0x00000008);;
- onChanged();
+ if (membersBuilder_ == null) {
+ members_ = java.util.Collections.emptyList();
+ bitField0_ = (bitField0_ & ~0x00000008);
+ onChanged();
+ } else {
+ membersBuilder_.clear();
+ }
return this;
}
/**
@@ -1542,20 +1658,106 @@ public Builder clearMembers() {
* list of attribute values that this value is related to (attribute group)
*
*
- * repeated string members = 4 [json_name = "members"];
- * @param value The bytes of the members to add.
- * @return This builder for chaining.
+ * repeated .policy.Value members = 4 [json_name = "members"];
*/
- public Builder addMembersBytes(
- com.google.protobuf.ByteString value) {
- if (value == null) { throw new NullPointerException(); }
- checkByteStringIsUtf8(value);
- ensureMembersIsMutable();
- members_.add(value);
- bitField0_ |= 0x00000008;
- onChanged();
+ public Builder removeMembers(int index) {
+ if (membersBuilder_ == null) {
+ ensureMembersIsMutable();
+ members_.remove(index);
+ onChanged();
+ } else {
+ membersBuilder_.remove(index);
+ }
return this;
}
+ /**
+ * + * list of attribute values that this value is related to (attribute group) + *+ * + *
repeated .policy.Value members = 4 [json_name = "members"];
+ */
+ public io.opentdf.platform.policy.Value.Builder getMembersBuilder(
+ int index) {
+ return getMembersFieldBuilder().getBuilder(index);
+ }
+ /**
+ * + * list of attribute values that this value is related to (attribute group) + *+ * + *
repeated .policy.Value members = 4 [json_name = "members"];
+ */
+ public io.opentdf.platform.policy.ValueOrBuilder getMembersOrBuilder(
+ int index) {
+ if (membersBuilder_ == null) {
+ return members_.get(index); } else {
+ return membersBuilder_.getMessageOrBuilder(index);
+ }
+ }
+ /**
+ * + * list of attribute values that this value is related to (attribute group) + *+ * + *
repeated .policy.Value members = 4 [json_name = "members"];
+ */
+ public java.util.List extends io.opentdf.platform.policy.ValueOrBuilder>
+ getMembersOrBuilderList() {
+ if (membersBuilder_ != null) {
+ return membersBuilder_.getMessageOrBuilderList();
+ } else {
+ return java.util.Collections.unmodifiableList(members_);
+ }
+ }
+ /**
+ * + * list of attribute values that this value is related to (attribute group) + *+ * + *
repeated .policy.Value members = 4 [json_name = "members"];
+ */
+ public io.opentdf.platform.policy.Value.Builder addMembersBuilder() {
+ return getMembersFieldBuilder().addBuilder(
+ io.opentdf.platform.policy.Value.getDefaultInstance());
+ }
+ /**
+ * + * list of attribute values that this value is related to (attribute group) + *+ * + *
repeated .policy.Value members = 4 [json_name = "members"];
+ */
+ public io.opentdf.platform.policy.Value.Builder addMembersBuilder(
+ int index) {
+ return getMembersFieldBuilder().addBuilder(
+ index, io.opentdf.platform.policy.Value.getDefaultInstance());
+ }
+ /**
+ * + * list of attribute values that this value is related to (attribute group) + *+ * + *
repeated .policy.Value members = 4 [json_name = "members"];
+ */
+ public java.util.Listrepeated string members = 4 [json_name = "members"];
- * @return A list containing the members.
+ * repeated .policy.Value members = 4 [json_name = "members"];
*/
- java.util.List
* list of attribute values that this value is related to (attribute group)
*
*
- * repeated string members = 4 [json_name = "members"];
- * @return The count of members.
+ * repeated .policy.Value members = 4 [json_name = "members"];
+ */
+ io.opentdf.platform.policy.Value getMembers(int index);
+ /**
+ * + * list of attribute values that this value is related to (attribute group) + *+ * + *
repeated .policy.Value members = 4 [json_name = "members"];
*/
int getMembersCount();
/**
@@ -79,22 +85,19 @@ public interface ValueOrBuilder extends
* list of attribute values that this value is related to (attribute group)
*
*
- * repeated string members = 4 [json_name = "members"];
- * @param index The index of the element to return.
- * @return The members at the given index.
+ * repeated .policy.Value members = 4 [json_name = "members"];
*/
- java.lang.String getMembers(int index);
+ java.util.List extends io.opentdf.platform.policy.ValueOrBuilder>
+ getMembersOrBuilderList();
/**
*
* list of attribute values that this value is related to (attribute group)
*
*
- * repeated string members = 4 [json_name = "members"];
- * @param index The index of the value to return.
- * @return The bytes of the members at the given index.
+ * repeated .policy.Value members = 4 [json_name = "members"];
*/
- com.google.protobuf.ByteString
- getMembersBytes(int index);
+ io.opentdf.platform.policy.ValueOrBuilder getMembersOrBuilder(
+ int index);
/**
*
diff --git a/services/policy/db/attribute_values.go b/services/policy/db/attribute_values.go
index 843cf24a14..267e848b67 100644
--- a/services/policy/db/attribute_values.go
+++ b/services/policy/db/attribute_values.go
@@ -3,6 +3,7 @@ package db
import (
"context"
"database/sql"
+ "fmt"
"log/slog"
sq "github.com/Masterminds/squirrel"
@@ -32,26 +33,33 @@ func attributeValueHydrateItem(row pgx.Row, opts attributeValueSelectOptions) (*
id string
value string
active bool
- members []string
+ membersJson []byte
metadataJson []byte
attributeId string
fqn sql.NullString
+ members []*policy.Value
)
-
fields := []interface{}{
&id,
&value,
&active,
- &members,
+ &membersJson,
&metadataJson,
&attributeId,
}
+
if opts.withFqn {
fields = append(fields, &fqn)
}
-
if err := row.Scan(fields...); err != nil {
return nil, db.WrapIfKnownInvalidQueryErr(err)
+ } else {
+ if membersJson != nil {
+ members, err = attributesValuesProtojson(membersJson)
+ if err != nil {
+ return nil, err
+ }
+ }
}
m := &common.Metadata{}
@@ -67,7 +75,9 @@ func attributeValueHydrateItem(row pgx.Row, opts attributeValueSelectOptions) (*
Active: &wrapperspb.BoolValue{Value: active},
Members: members,
Metadata: m,
- // TODO: get & hydrate full attribute
+ Attribute: &policy.Attribute{
+ Id: attributeId,
+ },
Fqn: fqn.String,
}
return v, nil
@@ -89,10 +99,37 @@ func attributeValueHydrateItems(rows pgx.Rows, opts attributeValueSelectOptions)
/// CRUD
///
+func addMemberSql(value_id string, member_id string) (string, []interface{}, error) {
+ t := Tables.ValueMembers
+ return db.NewStatementBuilder().
+ Insert(t.Name()).
+ Columns(
+ "value_id",
+ "member_id",
+ ).
+ Values(
+ value_id,
+ member_id,
+ ).
+ Suffix("RETURNING id").
+ ToSql()
+}
+
+func removeMemberSql(value_id string, member_id string) (string, []interface{}, error) {
+ t := Tables.ValueMembers
+ return db.NewStatementBuilder().
+ Delete(t.Name()).
+ Where(sq.Eq{
+ t.Field("value_id"): value_id,
+ t.Field("member_id"): member_id,
+ }).
+ Suffix("RETURNING id").
+ ToSql()
+}
+
func createAttributeValueSql(
attribute_id string,
value string,
- members []string,
metadata []byte,
) (string, []interface{}, error) {
t := Tables.AttributeValues
@@ -101,13 +138,11 @@ func createAttributeValueSql(
Columns(
"attribute_definition_id",
"value",
- "members",
"metadata",
).
Values(
attribute_id,
value,
- members,
metadata,
).
Suffix("RETURNING id").
@@ -123,7 +158,6 @@ func (c PolicyDbClient) CreateAttributeValue(ctx context.Context, attributeId st
sql, args, err := createAttributeValueSql(
attributeId,
v.Value,
- v.Members,
metadataJson,
)
if err != nil {
@@ -137,6 +171,27 @@ func (c PolicyDbClient) CreateAttributeValue(ctx context.Context, attributeId st
return nil, db.WrapIfKnownInvalidQueryErr(err)
}
+ var members []*policy.Value
+
+ // Add members
+ for _, member := range v.Members {
+ var vm_id string
+ sql, args, err := addMemberSql(id, member)
+ if err != nil {
+ return nil, err
+ }
+ if r, err := c.QueryRow(ctx, sql, args, err); err != nil {
+ return nil, err
+ } else if err := r.Scan(&vm_id); err != nil {
+ return nil, db.WrapIfKnownInvalidQueryErr(err)
+ }
+ attr, err := c.GetAttributeValue(ctx, member)
+ if err != nil {
+ return nil, err
+ }
+ members = append(members, attr)
+ }
+
// Update FQN
c.upsertAttrFqn(ctx, attrFqnUpsertOptions{valueId: id})
@@ -144,7 +199,7 @@ func (c PolicyDbClient) CreateAttributeValue(ctx context.Context, attributeId st
Id: id,
Attribute: &policy.Attribute{Id: attributeId},
Value: v.Value,
- Members: v.Members,
+ Members: members,
Metadata: metadata,
Active: &wrapperspb.BoolValue{Value: true},
}
@@ -153,34 +208,60 @@ func (c PolicyDbClient) CreateAttributeValue(ctx context.Context, attributeId st
func getAttributeValueSql(id string, opts attributeValueSelectOptions) (string, []interface{}, error) {
t := Tables.AttributeValues
+ fqnT := Tables.AttrFqn
+ members := "COALESCE(JSON_AGG(JSON_BUILD_OBJECT(" +
+ "'id', vmv.id, " +
+ "'value', vmv.value, " +
+ "'active', vmv.active, " +
+ "'members', vmv.members || ARRAY[]::UUID[], " +
+ "'metadata', vmv.metadata, " +
+ "'attribute', JSON_BUILD_OBJECT(" +
+ "'id', vmv.attribute_definition_id )" // TODO: get the rest of the attribute here from the JOIN?
+ if opts.withFqn {
+ members += ", 'fqn', " + "fqn1.fqn"
+ }
+ members += ")) FILTER (WHERE vmv.id IS NOT NULL ), '[]') AS members"
fields := []string{
- t.Field("id"),
- t.Field("value"),
- t.Field("active"),
- t.Field("members"),
- t.Field("metadata"),
- t.Field("attribute_definition_id"),
+ "av.id",
+ "av.value",
+ "av.active",
+ members,
+ "av.metadata",
+ "av.attribute_definition_id",
}
if opts.withFqn {
- fields = append(fields, Tables.AttrFqn.Field("fqn"))
+ fields = append(fields, "MAX(fqn2.fqn) AS fqn")
}
sb := db.NewStatementBuilder().
Select(fields...).
- From(t.Name())
+ From(t.Name() + " av")
+
+ // join members
+ sb = sb.LeftJoin(Tables.ValueMembers.Name() + " vm ON av.id = vm.value_id")
+
+ // join attribute values
+ sb = sb.LeftJoin(t.Name() + " vmv ON vm.member_id = vmv.id")
if opts.withFqn {
- fqnT := Tables.AttrFqn
- sb = sb.LeftJoin(fqnT.Name() + " ON " + fqnT.Field("value_id") + " = " + t.Field("id"))
+ sb = sb.LeftJoin(fqnT.Name() + " AS fqn1 ON " + "fqn1.value_id" + " = " + "vmv.id")
+ sb = sb.LeftJoin(fqnT.Name() + " AS fqn2 ON " + "fqn2.value_id" + " = " + "av.id")
}
- return sb.Where(sq.Eq{t.Field("id"): id}).
+ return sb.Where(sq.Eq{
+ "av.id": id,
+ }).
+ GroupBy(
+ "av.id",
+ // fqnT.Field("fqn"),
+ ).
ToSql()
}
func (c PolicyDbClient) GetAttributeValue(ctx context.Context, id string) (*policy.Value, error) {
opts := attributeValueSelectOptions{withFqn: true}
sql, args, err := getAttributeValueSql(id, opts)
+ fmt.Println("\nsql: ", sql)
row, err := c.QueryRow(ctx, sql, args, err)
if err != nil {
slog.Error("error getting attribute value", slog.String("id", id), slog.String("sql", sql), slog.String("error", err.Error()))
@@ -197,34 +278,55 @@ func (c PolicyDbClient) GetAttributeValue(ctx context.Context, id string) (*poli
func listAttributeValuesSql(attribute_id string, opts attributeValueSelectOptions) (string, []interface{}, error) {
t := Tables.AttributeValues
+ members := "COALESCE(JSON_AGG(JSON_BUILD_OBJECT(" +
+ "'id', vmv.id, " +
+ "'value', vmv.value, " +
+ "'active', vmv.active, " +
+ "'members', vmv.members || ARRAY[]::UUID[], " +
+ "'metadata', vmv.metadata, " +
+ "'attribute', JSON_BUILD_OBJECT(" +
+ "'id', vmv.attribute_definition_id )"
+ if opts.withFqn {
+ members += ", 'fqn', " + "fqn1.fqn"
+ }
+ members += ")) FILTER (WHERE vmv.id IS NOT NULL ), '[]') AS members"
fields := []string{
- t.Field("id"),
- t.Field("value"),
- t.Field("active"),
- t.Field("members"),
- t.Field("metadata"),
- t.Field("attribute_definition_id"),
+ "av.id",
+ "av.value",
+ "av.active",
+ members,
+ "av.metadata",
+ "av.attribute_definition_id",
}
if opts.withFqn {
- fields = append(fields, "fqn")
+ fields = append(fields, "MAX(fqn2.fqn) AS fqn")
}
sb := db.NewStatementBuilder().
Select(fields...)
+ // join members
+ sb = sb.LeftJoin(Tables.ValueMembers.Name() + " vm ON av.id = vm.value_id")
+
+ // join attribute values
+ sb = sb.LeftJoin(t.Name() + " vmv ON vm.member_id = vmv.id")
+
if opts.withFqn {
fqnT := Tables.AttrFqn
- sb = sb.LeftJoin(fqnT.Name() + " ON " + fqnT.Field("value_id") + " = " + t.Field("id"))
+ sb = sb.LeftJoin(fqnT.Name() + " AS fqn1 ON " + "fqn1.value_id" + " = " + "vmv.id")
+ sb = sb.LeftJoin(fqnT.Name() + " AS fqn2 ON " + "fqn2.value_id" + " = " + "av.id")
}
+ sb = sb.GroupBy("av.id")
+
where := sq.Eq{}
if opts.state != "" && opts.state != StateAny {
- where[t.Field("active")] = opts.state == StateActive
+ where["av.active"] = opts.state == StateActive
}
- where[t.Field("attribute_definition_id")] = attribute_id
+ where["av.attribute_definition_id"] = attribute_id
return sb.
- From(t.Name()).
+ From(t.Name() + " av").
Where(where).
ToSql()
}
@@ -243,27 +345,48 @@ func (c PolicyDbClient) ListAttributeValues(ctx context.Context, attribute_id st
func listAllAttributeValuesSql(opts attributeValueSelectOptions) (string, []interface{}, error) {
t := Tables.AttributeValues
+ members := "COALESCE(JSON_AGG(JSON_BUILD_OBJECT(" +
+ "'id', vmv.id, " +
+ "'value', vmv.value, " +
+ "'active', vmv.active, " +
+ "'members', vmv.members || ARRAY[]::UUID[], " +
+ "'metadata', vmv.metadata, " +
+ "'attribute', JSON_BUILD_OBJECT(" +
+ "'id', vmv.attribute_definition_id )"
+ if opts.withFqn {
+ members += ", 'fqn', " + "fqn1.fqn"
+ }
+ members += ")) FILTER (WHERE vmv.id IS NOT NULL ), '[]') AS members"
fields := []string{
- t.Field("id"),
- t.Field("value"),
- t.Field("active"),
- t.Field("members"),
- t.Field("metadata"),
- t.Field("attribute_definition_id"),
+ "av.id",
+ "av.value",
+ "av.active",
+ members,
+ "av.metadata",
+ "av.attribute_definition_id",
}
if opts.withFqn {
- fields = append(fields, "fqn")
+ fields = append(fields, "MAX(fqn2.fqn) AS fqn")
}
sb := db.NewStatementBuilder().
Select(fields...)
+ // join members
+ sb = sb.LeftJoin(Tables.ValueMembers.Name() + " vm ON av.id = vm.value_id")
+
+ // join attribute values
+ sb = sb.LeftJoin(t.Name() + " vmv ON vm.member_id = vmv.id")
+
if opts.withFqn {
fqnT := Tables.AttrFqn
- sb = sb.LeftJoin(fqnT.Name() + " ON " + fqnT.Field("value_id") + " = " + t.Field("id"))
+ sb = sb.LeftJoin(fqnT.Name() + " AS fqn1 ON " + "fqn1.value_id" + " = " + "vmv.id")
+ sb = sb.LeftJoin(fqnT.Name() + " AS fqn2 ON " + "fqn2.value_id" + " = " + "av.id")
}
+ sb = sb.GroupBy("av.id")
+
return sb.
- From(t.Name()).
+ From(t.Name() + " av").
ToSql()
}
@@ -280,7 +403,6 @@ func (c PolicyDbClient) ListAllAttributeValues(ctx context.Context, state string
func updateAttributeValueSql(
id string,
- members []string,
metadata []byte,
) (string, []interface{}, error) {
t := Tables.AttributeValues
@@ -290,10 +412,6 @@ func updateAttributeValueSql(
sb = sb.Set("metadata", metadata)
}
- if members != nil {
- sb = sb.Set("members", members)
- }
-
return sb.Where(sq.Eq{t.Field("id"): id}).ToSql()
}
@@ -311,7 +429,6 @@ func (c PolicyDbClient) UpdateAttributeValue(ctx context.Context, r *attributes.
sql, args, err := updateAttributeValueSql(
r.Id,
- r.Members,
metadataJson,
)
if db.IsQueryBuilderSetClauseError(err) {
@@ -323,9 +440,60 @@ func (c PolicyDbClient) UpdateAttributeValue(ctx context.Context, r *attributes.
return nil, err
}
+ prev, err := c.GetAttributeValue(ctx, r.Id)
+ if err != nil {
+ return nil, err
+ }
+
if err := c.Exec(ctx, sql, args); err != nil {
return nil, err
}
+ prevMembersSet := map[string]bool{}
+
+ for _, member := range prev.Members {
+ prevMembersSet[member.Id] = true
+ }
+
+ membersSet := map[string]bool{}
+ for _, member := range r.Members {
+ membersSet[member] = true
+ }
+
+ toRemove := map[string]bool{}
+ toAdd := map[string]bool{}
+
+ for member := range prevMembersSet {
+ if _, ok := membersSet[member]; !ok {
+ toRemove[member] = true
+ }
+ }
+
+ for member := range membersSet {
+ if _, ok := prevMembersSet[member]; !ok {
+ toAdd[member] = true
+ }
+ }
+
+ // Remove members
+ for member := range toRemove {
+ sql, args, err := removeMemberSql(r.Id, member)
+ if err != nil {
+ return nil, err
+ }
+ if err := c.Exec(ctx, sql, args); err != nil {
+ return nil, err
+ }
+ }
+
+ for member := range toAdd {
+ sql, args, err := addMemberSql(r.Id, member)
+ if err != nil {
+ return nil, err
+ }
+ if err := c.Exec(ctx, sql, args); err != nil {
+ return nil, err
+ }
+ }
// Update FQN
c.upsertAttrFqn(ctx, attrFqnUpsertOptions{valueId: r.Id})
diff --git a/services/policy/db/attributes.go b/services/policy/db/attributes.go
index 9e744b5750..620b81da16 100644
--- a/services/policy/db/attributes.go
+++ b/services/policy/db/attributes.go
@@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"encoding/json"
+ "fmt"
"log/slog"
"strings"
@@ -34,16 +35,19 @@ func attributesValuesProtojson(valuesJson []byte) ([]*policy.Value, error) {
raw []json.RawMessage
values []*policy.Value
)
+
if err := json.Unmarshal(valuesJson, &raw); err != nil {
return nil, err
}
for _, r := range raw {
- value := policy.Value{}
- if err := protojson.Unmarshal(r, &value); err != nil {
+ value := &policy.Value{}
+ err := protojson.Unmarshal(r, value)
+ if err != nil {
+ fmt.Println("error unmarshaling a value: ", err, string(r))
return nil, err
}
- values = append(values, &value)
+ values = append(values, value)
}
return values, nil
}
@@ -82,10 +86,10 @@ func attributesSelect(opts attributesSelectOptions) sq.SelectBuilder {
if opts.withAttributeValues || opts.withOneValueByFqn != "" {
valueSelect := "JSON_AGG(" +
"JSON_BUILD_OBJECT(" +
- "'id', " + avt.Field("id") + ", " +
- "'value', " + avt.Field("value") + "," +
- "'members', " + avt.Field("members") + "," +
- "'active', " + avt.Field("active")
+ "'id', avt.id," +
+ "'value', avt.value," +
+ "'members', avt.members," +
+ "'active', avt.active"
// include the subject mapping / subject condition set for each value
if opts.withOneValueByFqn != "" {
@@ -123,7 +127,13 @@ func attributesSelect(opts attributesSelectOptions) sq.SelectBuilder {
LeftJoin(nt.Name() + " ON " + nt.Field("id") + " = " + t.Field("namespace_id"))
if opts.withAttributeValues {
- sb = sb.LeftJoin(avt.Name() + " ON " + avt.Field("attribute_definition_id") + " = " + t.Field("id"))
+ sb = sb.LeftJoin("(SELECT av.id, av.value, av.active, COALESCE(JSON_AGG(JSON_BUILD_OBJECT(" +
+ "'id', vmv.id, " +
+ "'value', vmv.value, " +
+ "'active', vmv.active, " +
+ "'members', vmv.members || ARRAY[]::UUID[], " +
+ "'attribute', JSON_BUILD_OBJECT(" +
+ "'id', vmv.attribute_definition_id ))) FILTER (WHERE vmv.id IS NOT NULL ), '[]') AS members, av.attribute_definition_id FROM " + avt.Name() + " av LEFT JOIN " + Tables.ValueMembers.Name() + " vm ON av.id = vm.value_id LEFT JOIN " + avt.Name() + " vmv ON vm.member_id = vmv.id GROUP BY av.id) avt ON avt.attribute_definition_id = " + t.Field("id"))
}
if opts.withKeyAccessGrants {
sb = sb.LeftJoin(Tables.AttributeKeyAccessGrants.Name() + " ON " + Tables.AttributeKeyAccessGrants.WithoutSchema().Name() + ".attribute_definition_id = " + t.Field("id")).
@@ -154,11 +164,10 @@ func attributesSelect(opts attributesSelectOptions) sq.SelectBuilder {
"INNER JOIN " + fqnt.Name() + " AS inner_fqns ON " + avt.Field("id") + " = inner_fqns.value_id " +
"WHERE inner_fqns.fqn = '" + opts.withOneValueByFqn + "' " +
"GROUP BY " + avt.Field("id") + ", inner_fqns.fqn " +
- ") AS val_sm_fqn_join ON " + avt.Field("id") + " = val_sm_fqn_join.av_id " +
- "AND " + avt.Field("id") + " = " + fqnt.Field("value_id"),
+ ") AS val_sm_fqn_join ON " + "avt.id" + " = val_sm_fqn_join.av_id " +
+ "AND " + "avt.id" + " = " + fqnt.Field("value_id"),
)
}
-
g := []string{t.Field("id"), nt.Field("name")}
if opts.withFqn {
@@ -209,7 +218,6 @@ func attributesHydrateItem(row pgx.Row, opts attributesSelectOptions) (*policy.A
return nil, err
}
}
-
var v []*policy.Value
if valuesJson != nil {
v, err = attributesValuesProtojson(valuesJson)
@@ -328,7 +336,8 @@ func getAttributeSql(id string, opts attributesSelectOptions) (string, []interfa
func (c PolicyDbClient) GetAttribute(ctx context.Context, id string) (*policy.Attribute, error) {
opts := attributesSelectOptions{
- withFqn: true,
+ withFqn: true,
+ withAttributeValues: true,
}
sql, args, err := getAttributeSql(id, opts)
row, err := c.QueryRow(ctx, sql, args, err)
diff --git a/services/policy/db/policy.go b/services/policy/db/policy.go
index 9c22c2353d..b56440b8ae 100644
--- a/services/policy/db/policy.go
+++ b/services/policy/db/policy.go
@@ -18,6 +18,7 @@ type PolicyDbClient struct {
var (
TableAttributes = "attribute_definitions"
TableAttributeValues = "attribute_values"
+ TableValueMembers = "attribute_value_members"
TableNamespaces = "attribute_namespaces"
TableAttrFqn = "attribute_fqns"
TableAttributeKeyAccessGrants = "attribute_definition_key_access_grants"
@@ -30,6 +31,7 @@ var (
var Tables struct {
Attributes db.Table
AttributeValues db.Table
+ ValueMembers db.Table
Namespaces db.Table
AttrFqn db.Table
AttributeKeyAccessGrants db.Table
@@ -42,6 +44,7 @@ var Tables struct {
func NewClient(c db.Client) *PolicyDbClient {
Tables.Attributes = db.NewTable(TableAttributes)
Tables.AttributeValues = db.NewTable(TableAttributeValues)
+ Tables.ValueMembers = db.NewTable(TableValueMembers)
Tables.Namespaces = db.NewTable(TableNamespaces)
Tables.AttrFqn = db.NewTable(TableAttrFqn)
Tables.AttributeKeyAccessGrants = db.NewTable(TableAttributeKeyAccessGrants)
diff --git a/services/policy/db/resource_mapping.go b/services/policy/db/resource_mapping.go
index 3f3bff4e62..2bfe853e9d 100644
--- a/services/policy/db/resource_mapping.go
+++ b/services/policy/db/resource_mapping.go
@@ -54,9 +54,11 @@ func resourceMappingHydrateItem(row pgx.Row) (*policy.ResourceMapping, error) {
}
}
- err = protojson.Unmarshal(attributeValueJSON, attributeValue)
- if err != nil {
- return nil, err
+ if attributeValueJSON != nil {
+ if err := protojson.Unmarshal(attributeValueJSON, attributeValue); err != nil {
+ slog.Error("failed to unmarshal attribute value", slog.String("error", err.Error()), slog.String("attribute value JSON", string(attributeValueJSON)))
+ return nil, err
+ }
}
return &policy.ResourceMapping{
@@ -70,19 +72,27 @@ func resourceMappingHydrateItem(row pgx.Row) (*policy.ResourceMapping, error) {
func resourceMappingSelect() sq.SelectBuilder {
t := Tables.ResourceMappings
at := Tables.AttributeValues
+ members := "COALESCE(JSON_AGG(JSON_BUILD_OBJECT(" +
+ "'id', vmv.id, " +
+ "'value', vmv.value, " +
+ "'active', vmv.active, " +
+ "'members', vmv.members || ARRAY[]::UUID[] " +
+ ")) FILTER (WHERE vmv.id IS NOT NULL ), '[]')"
return db.NewStatementBuilder().Select(
t.Field("id"),
t.Field("metadata"),
t.Field("terms"),
"JSON_BUILD_OBJECT("+
- "'id', "+at.Field("id")+", "+
- "'value', "+at.Field("value")+","+
- "'members', "+at.Field("members")+
- ")"+
- " AS attribute_value",
+ "'id', av.id,"+
+ "'value', av.value,"+
+ "'members', "+members+
+ ") AS attribute_value",
).
- LeftJoin(at.Name()+" ON "+at.Field("id")+" = "+t.Field("attribute_value_id")).
- GroupBy(t.Field("id"), at.Field("id"))
+ LeftJoin(at.Name() + " av ON " + t.Field("attribute_value_id") + " = " + "av.id").
+ LeftJoin(Tables.ValueMembers.Name() + " vm ON av.id = vm.value_id").
+ LeftJoin(at.Name() + " vmv ON vm.member_id = vmv.id").
+ GroupBy("av.id").
+ GroupBy(t.Field("id"))
}
/*
diff --git a/services/policy/db/subject_mappings.go b/services/policy/db/subject_mappings.go
index d8054956fa..3270df4724 100644
--- a/services/policy/db/subject_mappings.go
+++ b/services/policy/db/subject_mappings.go
@@ -146,7 +146,12 @@ func subjectMappingSelect() sq.SelectBuilder {
t := Tables.SubjectMappings
avT := Tables.AttributeValues
scsT := Tables.SubjectConditionSet
-
+ members := "COALESCE(JSON_AGG(JSON_BUILD_OBJECT(" +
+ "'id', vmv.id, " +
+ "'value', vmv.value, " +
+ "'active', vmv.active, " +
+ "'members', vmv.members || ARRAY[]::UUID[] " +
+ ")) FILTER (WHERE vmv.id IS NOT NULL ), '[]')"
return db.NewStatementBuilder().Select(
t.Field("id"),
t.Field("actions"),
@@ -157,15 +162,17 @@ func subjectMappingSelect() sq.SelectBuilder {
"'subject_sets', "+scsT.Field("condition")+
") AS subject_condition_set",
"JSON_BUILD_OBJECT("+
- "'id', "+avT.Field("id")+", "+
- "'value', "+avT.Field("value")+", "+
- "'members', "+avT.Field("members")+", "+
- "'active'", avT.Field("active")+
+ "'id', av.id,"+
+ "'value', av.value,"+
+ "'members', "+members+","+
+ "'active', av.active"+
") AS attribute_value",
).
- LeftJoin(avT.Name() + " ON " + t.Field("attribute_value_id") + " = " + avT.Field("id")).
+ LeftJoin(avT.Name() + " av ON " + t.Field("attribute_value_id") + " = " + "av.id").
+ LeftJoin(Tables.ValueMembers.Name() + " vm ON av.id = vm.value_id").
+ LeftJoin(avT.Name() + " vmv ON vm.member_id = vmv.id").
+ GroupBy("av.id").
GroupBy(t.Field("id")).
- GroupBy(avT.Field("id")).
LeftJoin(scsT.Name() + " ON " + scsT.Field("id") + " = " + t.Field("subject_condition_set_id")).
GroupBy(scsT.Field("id"))
}
diff --git a/services/policy/objects.proto b/services/policy/objects.proto
index f21393bd67..33bd99c628 100644
--- a/services/policy/objects.proto
+++ b/services/policy/objects.proto
@@ -67,7 +67,7 @@ message Value {
string value = 3;
// list of attribute values that this value is related to (attribute group)
- repeated string members = 4;
+ repeated Value members = 4;
// list of key access servers
repeated kasregistry.KeyAccessServer grants = 5;