diff --git a/service/integration/attribute_values_test.go b/service/integration/attribute_values_test.go index 31fbe1320a..3183fe3688 100644 --- a/service/integration/attribute_values_test.go +++ b/service/integration/attribute_values_test.go @@ -81,6 +81,11 @@ func (s *AttributeValuesSuite) Test_GetAttributeValue() { s.Equal(len(f.Members), len(v.GetMembers())) // s.Equal(f.AttributeDefinitionId, v.AttributeId) s.Equal("https://example.com/attr/attr1/value/value1", v.GetFqn()) + metadata := v.GetMetadata() + createdAt := metadata.GetCreatedAt() + updatedAt := metadata.GetUpdatedAt() + s.True(createdAt.IsValid() && createdAt.AsTime().Unix() > 0) + s.True(updatedAt.IsValid() && updatedAt.AsTime().Unix() > 0) } func (s *AttributeValuesSuite) Test_GetAttributeValue_NotFound() { @@ -286,6 +291,8 @@ func (s *AttributeValuesSuite) Test_UpdateAttributeValue() { Labels: labels, }, }) + metadata := created.GetMetadata() + updatedAt := metadata.GetUpdatedAt() s.NoError(err) s.NotNil(created) @@ -315,6 +322,7 @@ func (s *AttributeValuesSuite) Test_UpdateAttributeValue() { s.NotNil(got) s.Equal(created.GetId(), got.GetId()) s.EqualValues(expectedLabels, got.GetMetadata().GetLabels()) + s.True(got.GetMetadata().GetUpdatedAt().AsTime().After(updatedAt.AsTime())) } func (s *AttributeValuesSuite) Test_UpdateAttributeValue_WithInvalidId_Fails() { diff --git a/service/integration/attributes_test.go b/service/integration/attributes_test.go index 5b120dae79..04ce1cf6d9 100644 --- a/service/integration/attributes_test.go +++ b/service/integration/attributes_test.go @@ -300,6 +300,11 @@ func (s *AttributesSuite) Test_GetAttribute() { s.Equal(f.Name, gotAttr.GetName()) s.Equal(fmt.Sprintf("%s%s", policydb.AttributeRuleTypeEnumPrefix, f.Rule), gotAttr.GetRule().Enum().String()) s.Equal(f.NamespaceId, gotAttr.GetNamespace().GetId()) + metadata := gotAttr.GetMetadata() + createdAt := metadata.GetCreatedAt() + updatedAt := metadata.GetUpdatedAt() + s.True(createdAt.IsValid() && createdAt.AsTime().Unix() > 0) + s.True(updatedAt.IsValid() && updatedAt.AsTime().Unix() > 0) } } @@ -400,6 +405,8 @@ func (s *AttributesSuite) Test_UpdateAttribute() { }, } created, err := s.db.PolicyClient.CreateAttribute(s.ctx, attr) + metadata := created.GetMetadata() + updatedAt := metadata.GetUpdatedAt() s.NoError(err) s.NotNil(created) @@ -425,6 +432,7 @@ func (s *AttributesSuite) Test_UpdateAttribute() { s.NotNil(got) s.Equal(created.GetId(), got.GetId()) s.EqualValues(expectedLabels, got.GetMetadata().GetLabels()) + s.True(got.GetMetadata().GetUpdatedAt().AsTime().After(updatedAt.AsTime())) } func (s *AttributesSuite) Test_UpdateAttribute_WithInvalidIdFails() { diff --git a/service/integration/namespaces_test.go b/service/integration/namespaces_test.go index 065eb52e57..ad0ddbfffc 100644 --- a/service/integration/namespaces_test.go +++ b/service/integration/namespaces_test.go @@ -99,6 +99,11 @@ func (s *NamespacesSuite) Test_GetNamespace() { s.NotNil(gotNamespace) // name retrieved by ID equal to name used to create s.Equal(test.Name, gotNamespace.GetName()) + metadata := gotNamespace.GetMetadata() + createdAt := metadata.GetCreatedAt() + updatedAt := metadata.GetUpdatedAt() + s.True(createdAt.IsValid() && createdAt.AsTime().Unix() > 0) + s.True(updatedAt.IsValid() && updatedAt.AsTime().Unix() > 0) } // Getting a namespace with an nonExistent id should fail @@ -153,13 +158,15 @@ func (s *NamespacesSuite) Test_UpdateNamespace() { "update": updatedLabel, "new": newLabel, } - created, err := s.db.PolicyClient.CreateNamespace(s.ctx, &namespaces.CreateNamespaceRequest{ Name: "updating-namespace.com", Metadata: &common.MetadataMutable{ Labels: labels, }, }) + metadata := created.GetMetadata() + updatedAt := metadata.GetUpdatedAt() + s.NoError(err) s.NotNil(created) @@ -183,6 +190,7 @@ func (s *NamespacesSuite) Test_UpdateNamespace() { s.NotNil(got) s.Equal(created.GetId(), got.GetId()) s.EqualValues(expectedLabels, got.GetMetadata().GetLabels()) + s.True(got.GetMetadata().GetUpdatedAt().AsTime().After(updatedAt.AsTime())) } func (s *NamespacesSuite) Test_UpdateNamespace_DoesNotExist_ShouldFail() { diff --git a/service/integration/resource_mappings_test.go b/service/integration/resource_mappings_test.go index 16b00f26a2..d0aee91b65 100644 --- a/service/integration/resource_mappings_test.go +++ b/service/integration/resource_mappings_test.go @@ -132,6 +132,11 @@ func (s *ResourceMappingsSuite) Test_GetResourceMapping() { s.True(testedMembers, "expected to test at least one attribute value member") } equalMembers(s.T(), av, mapping.GetAttributeValue(), false) + metadata := mapping.GetMetadata() + createdAt := metadata.GetCreatedAt() + updatedAt := metadata.GetUpdatedAt() + s.True(createdAt.IsValid() && createdAt.AsTime().Unix() > 0) + s.True(updatedAt.IsValid() && updatedAt.AsTime().Unix() > 0) } } @@ -194,6 +199,8 @@ func (s *ResourceMappingsSuite) Test_UpdateResourceMapping() { }, Terms: terms, }) + metadata := createdMapping.GetMetadata() + updatedAt := metadata.GetUpdatedAt() s.NoError(err) s.NotNil(createdMapping) @@ -232,6 +239,7 @@ func (s *ResourceMappingsSuite) Test_UpdateResourceMapping() { s.Equal(createdMapping.GetAttributeValue().GetId(), got.GetAttributeValue().GetId()) s.Equal(updateTerms, got.GetTerms()) s.EqualValues(expectedLabels, got.GetMetadata().GetLabels()) + s.True(got.GetMetadata().GetUpdatedAt().AsTime().After(updatedAt.AsTime())) } func (s *ResourceMappingsSuite) Test_UpdateResourceMappingWithUnknownIdFails() { diff --git a/service/integration/subject_mappings_test.go b/service/integration/subject_mappings_test.go index 16af593c84..c4b6e2f565 100644 --- a/service/integration/subject_mappings_test.go +++ b/service/integration/subject_mappings_test.go @@ -205,6 +205,8 @@ func (s *SubjectMappingsSuite) TestUpdateSubjectMapping_Actions() { } created, err := s.db.PolicyClient.CreateSubjectMapping(context.Background(), newSubjectMapping) + metadata := created.GetMetadata() + updatedAt := metadata.GetUpdatedAt() s.Require().NoError(err) s.NotNil(created) @@ -228,6 +230,7 @@ func (s *SubjectMappingsSuite) TestUpdateSubjectMapping_Actions() { s.Equal(got.GetActions(), newActions) s.Equal(newSubjectMapping.GetAttributeValueId(), got.GetAttributeValue().GetId()) s.Equal(newSubjectMapping.GetExistingSubjectConditionSetId(), got.GetSubjectConditionSet().GetId()) + s.True(got.GetMetadata().GetUpdatedAt().AsTime().After(updatedAt.AsTime())) } func (s *SubjectMappingsSuite) TestUpdateSubjectMapping_SubjectConditionSetId() { @@ -370,6 +373,11 @@ func (s *SubjectMappingsSuite) TestGetSubjectMapping() { s.Equal(fixture.AttributeValueId, got.GetId()) s.NotEmpty(got.GetMembers()) equalMembers(s.T(), got, sm.GetAttributeValue(), false) + metadata := sm.GetMetadata() + createdAt := metadata.GetCreatedAt() + updatedAt := metadata.GetUpdatedAt() + s.True(createdAt.IsValid() && createdAt.AsTime().Unix() > 0) + s.True(updatedAt.IsValid() && updatedAt.AsTime().Unix() > 0) } func (s *SubjectMappingsSuite) TestGetSubjectMapping_NonExistentId_Fails() { @@ -533,6 +541,11 @@ func (s *SubjectMappingsSuite) TestGetSubjectConditionSet_ById() { s.Require().NoError(err) s.NotNil(scs) s.Equal(fixture.Id, scs.GetId()) + metadata := scs.GetMetadata() + createdAt := metadata.GetCreatedAt() + updatedAt := metadata.GetUpdatedAt() + s.True(createdAt.IsValid() && createdAt.AsTime().Unix() > 0) + s.True(updatedAt.IsValid() && updatedAt.AsTime().Unix() > 0) } func (s *SubjectMappingsSuite) TestGetSubjectConditionSet_WithNoId_Fails() { @@ -619,6 +632,8 @@ func (s *SubjectMappingsSuite) TestUpdateSubjectConditionSet_NewSubjectSets() { } created, err := s.db.PolicyClient.CreateSubjectConditionSet(context.Background(), newConditionSet) + metadata := created.GetMetadata() + updatedAt := metadata.GetUpdatedAt() s.Require().NoError(err) s.NotNil(created) @@ -658,6 +673,7 @@ func (s *SubjectMappingsSuite) TestUpdateSubjectConditionSet_NewSubjectSets() { s.Equal(created.GetId(), got.GetId()) s.Equal(len(ss), len(got.GetSubjectSets())) s.Equal(ss[0].GetConditionGroups()[0].GetConditions()[0].GetSubjectExternalSelectorValue(), got.GetSubjectSets()[0].GetConditionGroups()[0].GetConditions()[0].GetSubjectExternalSelectorValue()) + s.True(got.GetMetadata().GetUpdatedAt().AsTime().After(updatedAt.AsTime())) } func (s *SubjectMappingsSuite) TestUpdateSubjectConditionSet_AllAllowedFields() { diff --git a/service/policy/db/attribute_values.go b/service/policy/db/attribute_values.go index ff35537031..0765847891 100644 --- a/service/policy/db/attribute_values.go +++ b/service/policy/db/attribute_values.go @@ -216,7 +216,7 @@ func getAttributeValueSql(id string, opts attributeValueSelectOptions) (string, "'value', vmv.value, " + "'active', vmv.active, " + "'members', vmv.members || ARRAY[]::UUID[], " + - "'metadata', vmv.metadata, " + + getMetadataField("vmv", true) + "'attribute', JSON_BUILD_OBJECT(" + "'id', vmv.attribute_definition_id )" if opts.withFqn { @@ -228,7 +228,7 @@ func getAttributeValueSql(id string, opts attributeValueSelectOptions) (string, "av.value", "av.active", members, - "av.metadata", + getMetadataField("av", false), "av.attribute_definition_id", } if opts.withFqn { @@ -281,7 +281,7 @@ func listAttributeValuesSql(attribute_id string, opts attributeValueSelectOption "'value', vmv.value, " + "'active', vmv.active, " + "'members', vmv.members || ARRAY[]::UUID[], " + - "'metadata', vmv.metadata, " + + getMetadataField("vmv", true) + "'attribute', JSON_BUILD_OBJECT(" + "'id', vmv.attribute_definition_id )" if opts.withFqn { @@ -293,7 +293,7 @@ func listAttributeValuesSql(attribute_id string, opts attributeValueSelectOption "av.value", "av.active", members, - "av.metadata", + getMetadataField("av", false), "av.attribute_definition_id", } if opts.withFqn { @@ -352,7 +352,7 @@ func listAllAttributeValuesSql(opts attributeValueSelectOptions) (string, []inte "'value', vmv.value, " + "'active', vmv.active, " + "'members', vmv.members || ARRAY[]::UUID[], " + - "'metadata', vmv.metadata, " + + getMetadataField("vmv", true) + "'attribute', JSON_BUILD_OBJECT(" + "'id', vmv.attribute_definition_id )" if opts.withFqn { @@ -364,7 +364,7 @@ func listAllAttributeValuesSql(opts attributeValueSelectOptions) (string, []inte "av.value", "av.active", members, - "av.metadata", + getMetadataField("av", false), "av.attribute_definition_id", } if opts.withFqn { diff --git a/service/policy/db/attributes.go b/service/policy/db/attributes.go index fa2c2b648e..fe88a862cb 100644 --- a/service/policy/db/attributes.go +++ b/service/policy/db/attributes.go @@ -80,7 +80,7 @@ func attributesSelect(opts attributesSelectOptions) sq.SelectBuilder { t.Field("id"), t.Field("name"), t.Field("rule"), - t.Field("metadata"), + getMetadataField(t.Name(), false), t.Field("namespace_id"), t.Field("active"), nt.Field("name"), @@ -152,10 +152,10 @@ func attributesSelect(opts attributesSelectOptions) sq.SelectBuilder { "JSON_AGG(JSON_BUILD_OBJECT(" + "'id', " + smT.Field("id") + "," + "'actions', " + smT.Field("actions") + "," + - "'metadata', " + smT.Field("metadata") + "," + + getMetadataField(smT.Name(), true) + "'subject_condition_set', JSON_BUILD_OBJECT(" + "'id', " + scsT.Field("id") + "," + - "'metadata', " + scsT.Field("metadata") + "," + + getMetadataField(scsT.Name(), true) + "'subject_sets', " + scsT.Field("condition") + ")" + ")) AS sub_maps_arr " + diff --git a/service/policy/db/namespaces.go b/service/policy/db/namespaces.go index 2d8f39e6e1..4e399b7aba 100644 --- a/service/policy/db/namespaces.go +++ b/service/policy/db/namespaces.go @@ -77,7 +77,7 @@ func getNamespaceSql(id string, opts namespaceSelectOptions) (string, []interfac t.Field("id"), t.Field("name"), t.Field("active"), - t.Field("metadata"), + getMetadataField("", false), } if opts.withFqn { @@ -126,7 +126,7 @@ func listNamespacesSql(opts namespaceSelectOptions) (string, []interface{}, erro t.Field("id"), t.Field("name"), t.Field("active"), - t.Field("metadata"), + getMetadataField("", false), } if opts.withFqn { diff --git a/service/policy/db/resource_mapping.go b/service/policy/db/resource_mapping.go index c12671b262..02659fd3fc 100644 --- a/service/policy/db/resource_mapping.go +++ b/service/policy/db/resource_mapping.go @@ -79,7 +79,7 @@ func resourceMappingSelect() sq.SelectBuilder { ")) FILTER (WHERE vmv.id IS NOT NULL ), '[]')" return db.NewStatementBuilder().Select( t.Field("id"), - t.Field("metadata"), + getMetadataField(t.Name(), false), t.Field("terms"), "JSON_BUILD_OBJECT("+ "'id', av.id,"+ diff --git a/service/policy/db/subject_mappings.go b/service/policy/db/subject_mappings.go index 5e2bc9a0e4..54adca8b79 100644 --- a/service/policy/db/subject_mappings.go +++ b/service/policy/db/subject_mappings.go @@ -105,7 +105,7 @@ func subjectConditionSetSelect() sq.SelectBuilder { t := Tables.SubjectConditionSet return db.NewStatementBuilder().Select( t.Field("id"), - t.Field("metadata"), + getMetadataField("", false), t.Field("condition"), ) } @@ -171,10 +171,10 @@ func subjectMappingSelect() sq.SelectBuilder { return db.NewStatementBuilder().Select( t.Field("id"), t.Field("actions"), - t.Field("metadata"), + getMetadataField(t.Name(), false), "JSON_BUILD_OBJECT("+ "'id', "+scsT.Field("id")+", "+ - "'metadata', "+scsT.Field("metadata")+", "+ + getMetadataField(scsT.Name(), true)+ "'subject_sets', "+scsT.Field("condition")+ ") AS subject_condition_set", "JSON_BUILD_OBJECT("+ diff --git a/service/policy/db/utils.go b/service/policy/db/utils.go new file mode 100644 index 0000000000..8da7f8544e --- /dev/null +++ b/service/policy/db/utils.go @@ -0,0 +1,15 @@ +package db + +func getMetadataField(table string, isJSON bool) string { + if table != "" { + table += "." + } + metadata := "JSON_STRIP_NULLS(JSON_BUILD_OBJECT('labels', " + table + "metadata->'labels', 'created_at', " + table + "created_at, 'updated_at', " + table + "updated_at))" + + if isJSON { + metadata = "'metadata', " + metadata + ", " + } else { + metadata += " AS metadata" + } + return metadata +}