Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 157 additions & 11 deletions service/integration/subject_mappings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1482,9 +1482,129 @@ func (s *SubjectMappingsSuite) Test_ListSubjectConditionSets_NoNamespaceFilter_R
s.True(foundUnnamespaced)
}

func (s *SubjectMappingsSuite) Test_ListSubjectConditionSets_SearchByMetadataLabelValues_PaginationSort_Succeeds() {
suffix := time.Now().UnixNano()
searchToken := fmt.Sprintf("dspx-scs-search-%d", suffix)

multiLabel := map[string]string{
"name": "another",
"display": searchToken,
}
labelValueMatch := s.newSearchTestSubjectConditionSet(fmt.Sprintf(".dspx_scs_selector_%d", suffix), []string{"engineering"}, multiLabel, "")
anotherLabelValueMatch := s.newSearchTestSubjectConditionSet(fmt.Sprintf(".dspx_scs_other_%d", suffix), []string{"finance"}, map[string]string{"owner": "group-" + searchToken}, "")
labelKeyOnlyNonMatch := s.newSearchTestSubjectConditionSet(fmt.Sprintf(".dspx_scs_key_only_%d", suffix), []string{"legal"}, map[string]string{searchToken: "not-a-matching-value"}, "")
conditionOnlyNonMatch := s.newSearchTestSubjectConditionSet("."+searchToken+".department", []string{"value-" + searchToken}, map[string]string{"team": "finance"}, "")
defer s.deleteTestSCSs([]string{
labelValueMatch.GetId(),
anotherLabelValueMatch.GetId(),
labelKeyOnlyNonMatch.GetId(),
conditionOnlyNonMatch.GetId(),
})

listRsp, err := s.db.PolicyClient.ListSubjectConditionSets(s.ctx, &subjectmapping.ListSubjectConditionSetsRequest{
Search: &policy.Search{Term: strings.ToUpper(searchToken)},
Sort: []*subjectmapping.SubjectConditionSetsSort{
{Field: subjectmapping.SortSubjectConditionSetsType_SORT_SUBJECT_CONDITION_SETS_TYPE_CREATED_AT, Direction: policy.SortDirection_SORT_DIRECTION_ASC},
},
})
s.Require().NoError(err)
s.Require().NotNil(listRsp)
s.Require().Len(listRsp.GetSubjectConditionSets(), 2)
s.Equal(labelValueMatch.GetId(), listRsp.GetSubjectConditionSets()[0].GetId())
s.Equal(anotherLabelValueMatch.GetId(), listRsp.GetSubjectConditionSets()[1].GetId())
s.Equal(int32(2), listRsp.GetPagination().GetTotal())

firstPage, err := s.db.PolicyClient.ListSubjectConditionSets(s.ctx, &subjectmapping.ListSubjectConditionSetsRequest{
Search: &policy.Search{Term: searchToken},
Pagination: &policy.PageRequest{Limit: 1},
Sort: []*subjectmapping.SubjectConditionSetsSort{
{Field: subjectmapping.SortSubjectConditionSetsType_SORT_SUBJECT_CONDITION_SETS_TYPE_CREATED_AT, Direction: policy.SortDirection_SORT_DIRECTION_ASC},
},
})
s.Require().NoError(err)
s.Len(firstPage.GetSubjectConditionSets(), 1)
s.Equal(labelValueMatch.GetId(), firstPage.GetSubjectConditionSets()[0].GetId())
s.Equal(int32(2), firstPage.GetPagination().GetTotal())
s.Equal(int32(1), firstPage.GetPagination().GetNextOffset())
}
Comment thread
c-r33d marked this conversation as resolved.

func (s *SubjectMappingsSuite) Test_ListSubjectConditionSets_SearchEscapesLikeWildcardLiterals_Succeeds() {
suffix := time.Now().UnixNano()
conditionSetIDs := []string{
s.newSearchTestSubjectConditionSet(
fmt.Sprintf(".dspx_scs_like_a_%d", suffix),
[]string{"engineering"},
map[string]string{"team": fmt.Sprintf("wildcarda-%d", suffix)},
"",
).GetId(),
s.newSearchTestSubjectConditionSet(
fmt.Sprintf(".dspx_scs_like_b_%d", suffix),
[]string{"finance"},
map[string]string{"team": fmt.Sprintf("wildcardb-%d", suffix)},
"",
).GetId(),
}
defer s.deleteTestSCSs(conditionSetIDs)

for _, query := range []string{
fmt.Sprintf("wildcard_-%d", suffix),
fmt.Sprintf("wildcard%%-%d", suffix),
} {
listRsp, err := s.db.PolicyClient.ListSubjectConditionSets(s.ctx, &subjectmapping.ListSubjectConditionSetsRequest{
Search: &policy.Search{Term: query},
})
s.Require().NoError(err)
s.Empty(listRsp.GetSubjectConditionSets())
s.Equal(int32(0), listRsp.GetPagination().GetTotal())
}
}

func (s *SubjectMappingsSuite) Test_ListSubjectConditionSets_SearchCombinesWithNamespace_Succeeds() {
comNsID := s.exampleComNsID()
netNsID := s.exampleNetNsID()
searchToken := fmt.Sprintf("dspx-scs-namespace-search-%d", time.Now().UnixNano())

comSCS := s.newSearchTestSubjectConditionSet(fmt.Sprintf(".dspx_scs_com_%d", time.Now().UnixNano()), []string{"com-value"}, map[string]string{"team": "com-" + searchToken}, comNsID)
netSCS := s.newSearchTestSubjectConditionSet(fmt.Sprintf(".dspx_scs_net_%d", time.Now().UnixNano()), []string{"net-value"}, map[string]string{"team": "net-" + searchToken}, netNsID)
unnamespacedSCS := s.newSearchTestSubjectConditionSet(fmt.Sprintf(".dspx_scs_global_%d", time.Now().UnixNano()), []string{"unnamespaced-value"}, map[string]string{"team": "global-" + searchToken}, "")
defer s.deleteTestSCSs([]string{comSCS.GetId(), netSCS.GetId(), unnamespacedSCS.GetId()})

listRsp, err := s.db.PolicyClient.ListSubjectConditionSets(s.ctx, &subjectmapping.ListSubjectConditionSetsRequest{
NamespaceId: comNsID,
Search: &policy.Search{Term: "com-" + searchToken},
})
s.Require().NoError(err)
s.Require().Len(listRsp.GetSubjectConditionSets(), 1)
s.Equal(comSCS.GetId(), listRsp.GetSubjectConditionSets()[0].GetId())
s.Equal(comNsID, listRsp.GetSubjectConditionSets()[0].GetNamespace().GetId())
s.Equal(int32(1), listRsp.GetPagination().GetTotal())
}

func (s *SubjectMappingsSuite) Test_ListSubjectConditionSets_SearchEmptyAndWhitespace_Succeeds() {
searchToken := fmt.Sprintf("dspx-scs-trim-%d", time.Now().UnixNano())
created := s.newSearchTestSubjectConditionSet(fmt.Sprintf(".dspx_scs_trim_%d", time.Now().UnixNano()), []string{"trim-value"}, map[string]string{"team": searchToken}, "")
defer s.deleteTestSCSs([]string{created.GetId()})

noSearch, err := s.db.PolicyClient.ListSubjectConditionSets(s.ctx, &subjectmapping.ListSubjectConditionSetsRequest{})
s.Require().NoError(err)
emptySearch, err := s.db.PolicyClient.ListSubjectConditionSets(s.ctx, &subjectmapping.ListSubjectConditionSetsRequest{
Search: &policy.Search{Term: ""},
})
s.Require().NoError(err)
s.Equal(noSearch.GetPagination().GetTotal(), emptySearch.GetPagination().GetTotal())

leadingSpaceSearch, err := s.db.PolicyClient.ListSubjectConditionSets(s.ctx, &subjectmapping.ListSubjectConditionSetsRequest{
Search: &policy.Search{Term: " " + searchToken},
})
s.Require().NoError(err)
s.Require().Len(leadingSpaceSearch.GetSubjectConditionSets(), 1)
s.Equal(created.GetId(), leadingSpaceSearch.GetSubjectConditionSets()[0].GetId())
s.Equal(int32(1), leadingSpaceSearch.GetPagination().GetTotal())
}

func (s *SubjectMappingsSuite) Test_ListSubjectConditionSets_SortByCreatedAt_ASC() {
ids := s.createSortTestSubjectConditionSets([]string{"sort-scs-created-asc-0", "sort-scs-created-asc-1", "sort-scs-created-asc-2"})
defer s.deleteSortTestSubjectConditionSets(ids)
defer s.deleteTestSCSs(ids)

listRsp, err := s.db.PolicyClient.ListSubjectConditionSets(s.ctx, &subjectmapping.ListSubjectConditionSetsRequest{
Sort: []*subjectmapping.SubjectConditionSetsSort{
Expand All @@ -1500,7 +1620,7 @@ func (s *SubjectMappingsSuite) Test_ListSubjectConditionSets_SortByCreatedAt_ASC

func (s *SubjectMappingsSuite) Test_ListSubjectConditionSets_SortByCreatedAt_DESC() {
ids := s.createSortTestSubjectConditionSets([]string{"sort-scs-created-desc-0", "sort-scs-created-desc-1", "sort-scs-created-desc-2"})
defer s.deleteSortTestSubjectConditionSets(ids)
defer s.deleteTestSCSs(ids)

listRsp, err := s.db.PolicyClient.ListSubjectConditionSets(s.ctx, &subjectmapping.ListSubjectConditionSetsRequest{
Sort: []*subjectmapping.SubjectConditionSetsSort{
Expand All @@ -1516,7 +1636,7 @@ func (s *SubjectMappingsSuite) Test_ListSubjectConditionSets_SortByCreatedAt_DES

func (s *SubjectMappingsSuite) Test_ListSubjectConditionSets_SortByUpdatedAt_DESC() {
ids := s.createSortTestSubjectConditionSets([]string{"sort-scs-updated-desc-0", "sort-scs-updated-desc-1", "sort-scs-updated-desc-2"})
defer s.deleteSortTestSubjectConditionSets(ids)
defer s.deleteTestSCSs(ids)

// Update the first SCS so its updated_at is the most recent
time.Sleep(5 * time.Millisecond)
Expand All @@ -1543,7 +1663,7 @@ func (s *SubjectMappingsSuite) Test_ListSubjectConditionSets_SortByUpdatedAt_DES

func (s *SubjectMappingsSuite) Test_ListSubjectConditionSets_SortByUpdatedAt_ASC() {
ids := s.createSortTestSubjectConditionSets([]string{"sort-scs-updated-asc-0", "sort-scs-updated-asc-1", "sort-scs-updated-asc-2"})
defer s.deleteSortTestSubjectConditionSets(ids)
defer s.deleteTestSCSs(ids)

// Update the last SCS so its updated_at is the most recent
time.Sleep(5 * time.Millisecond)
Expand Down Expand Up @@ -1594,7 +1714,7 @@ func (s *SubjectMappingsSuite) Test_ListSubjectConditionSets_SortTieBreaker_Crea
s.Require().NoError(err)
ids[i] = created.GetId()
}
defer s.deleteSortTestSubjectConditionSets(ids)
defer s.deleteTestSCSs(ids)

s.Require().NoError(forceCreatedAtTie(s.ctx, s.db, "subject_condition_set", ids))

Expand All @@ -1613,7 +1733,7 @@ func (s *SubjectMappingsSuite) Test_ListSubjectConditionSets_SortTieBreaker_Crea

func (s *SubjectMappingsSuite) Test_ListSubjectConditionSets_SortByUnspecifiedField_DefaultsToCreatedAt() {
ids := s.createSortTestSubjectConditionSets([]string{"unspecified-field-scs-0", "unspecified-field-scs-1", "unspecified-field-scs-2"})
defer s.deleteSortTestSubjectConditionSets(ids)
defer s.deleteTestSCSs(ids)

listRsp, err := s.db.PolicyClient.ListSubjectConditionSets(s.ctx, &subjectmapping.ListSubjectConditionSetsRequest{
Sort: []*subjectmapping.SubjectConditionSetsSort{
Expand All @@ -1629,7 +1749,7 @@ func (s *SubjectMappingsSuite) Test_ListSubjectConditionSets_SortByUnspecifiedFi

func (s *SubjectMappingsSuite) Test_ListSubjectConditionSets_SortByUnspecifiedDirection_DefaultsToDESC() {
ids := s.createSortTestSubjectConditionSets([]string{"unspecified-dir-scs-0", "unspecified-dir-scs-1", "unspecified-dir-scs-2"})
defer s.deleteSortTestSubjectConditionSets(ids)
defer s.deleteTestSCSs(ids)

listRsp, err := s.db.PolicyClient.ListSubjectConditionSets(s.ctx, &subjectmapping.ListSubjectConditionSetsRequest{
Sort: []*subjectmapping.SubjectConditionSetsSort{
Expand All @@ -1645,7 +1765,7 @@ func (s *SubjectMappingsSuite) Test_ListSubjectConditionSets_SortByUnspecifiedDi

func (s *SubjectMappingsSuite) Test_ListSubjectConditionSets_SortByBothUnspecified_DefaultsToCreatedAtDESC() {
ids := s.createSortTestSubjectConditionSets([]string{"both-unspecified-scs-0", "both-unspecified-scs-1", "both-unspecified-scs-2"})
defer s.deleteSortTestSubjectConditionSets(ids)
defer s.deleteTestSCSs(ids)

listRsp, err := s.db.PolicyClient.ListSubjectConditionSets(s.ctx, &subjectmapping.ListSubjectConditionSetsRequest{
Sort: []*subjectmapping.SubjectConditionSetsSort{
Expand All @@ -1661,7 +1781,7 @@ func (s *SubjectMappingsSuite) Test_ListSubjectConditionSets_SortByBothUnspecifi

func (s *SubjectMappingsSuite) Test_ListSubjectConditionSets_SortOmitted() {
ids := s.createSortTestSubjectConditionSets([]string{"sort-omitted-scs-0", "sort-omitted-scs-1", "sort-omitted-scs-2"})
defer s.deleteSortTestSubjectConditionSets(ids)
defer s.deleteTestSCSs(ids)

listRsp, err := s.db.PolicyClient.ListSubjectConditionSets(s.ctx, &subjectmapping.ListSubjectConditionSetsRequest{})
s.Require().NoError(err)
Expand Down Expand Up @@ -2859,6 +2979,32 @@ func (s *SubjectMappingsSuite) newSCSInNamespace(nsID string) *policy.SubjectCon
return scs
}

func (s *SubjectMappingsSuite) newSearchTestSubjectConditionSet(selector string, values []string, labels map[string]string, namespaceID string) *policy.SubjectConditionSet {
scs, err := s.db.PolicyClient.CreateSubjectConditionSet(s.ctx, &subjectmapping.SubjectConditionSetCreate{
SubjectSets: []*policy.SubjectSet{
{
ConditionGroups: []*policy.ConditionGroup{
{
BooleanOperator: policy.ConditionBooleanTypeEnum_CONDITION_BOOLEAN_TYPE_ENUM_AND,
Conditions: []*policy.Condition{
{
SubjectExternalSelectorValue: selector,
Operator: policy.SubjectMappingOperatorEnum_SUBJECT_MAPPING_OPERATOR_ENUM_IN,
SubjectExternalValues: values,
},
},
},
},
},
},
Metadata: &common.MetadataMutable{
Labels: labels,
},
}, namespaceID, "")
s.Require().NoError(err)
return scs
}

func (s *SubjectMappingsSuite) createSortTestSubjectMappings(prefixes []string) []string {
fixtureAttrValID := s.f.GetAttributeValueKey("example.net/attr/attr1/value/value2").ID
actionRead := s.f.GetStandardAction(policydb.ActionRead.String())
Expand Down Expand Up @@ -2937,8 +3083,8 @@ func (s *SubjectMappingsSuite) deleteSortTestSubjectMappings(ids []string) {
}
}

// deleteSortTestSubjectConditionSets cleans up subject condition sets created by sort tests.
func (s *SubjectMappingsSuite) deleteSortTestSubjectConditionSets(ids []string) {
// deleteTestSCSs cleans up subject condition sets created by sort tests.
func (s *SubjectMappingsSuite) deleteTestSCSs(ids []string) {
for _, id := range ids {
_, err := s.db.PolicyClient.DeleteSubjectConditionSet(s.ctx, id)
s.Require().NoError(err)
Expand Down
16 changes: 13 additions & 3 deletions service/policy/db/queries/subject_mappings.sql
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,19 @@ LEFT JOIN attribute_namespaces n ON n.id = scs.namespace_id
LEFT JOIN attribute_fqns ns_fqns ON ns_fqns.namespace_id = n.id AND ns_fqns.attribute_id IS NULL AND ns_fqns.value_id IS NULL
CROSS JOIN params p
WHERE
(sqlc.narg('namespace_id')::uuid IS NULL AND sqlc.narg('namespace_fqn')::text IS NULL)
OR scs.namespace_id = sqlc.narg('namespace_id')::uuid
OR ns_fqns.fqn = sqlc.narg('namespace_fqn')::text
(
(sqlc.narg('namespace_id')::uuid IS NULL AND sqlc.narg('namespace_fqn')::text IS NULL)
OR scs.namespace_id = sqlc.narg('namespace_id')::uuid
OR ns_fqns.fqn = sqlc.narg('namespace_fqn')::text
)
AND CASE
WHEN sqlc.narg('search')::TEXT IS NULL THEN TRUE
ELSE EXISTS (
SELECT 1
FROM JSONB_EACH_TEXT(COALESCE(scs.metadata -> 'labels', '{}'::JSONB)) AS label(key, value)
Comment thread
c-r33d marked this conversation as resolved.
WHERE label.value ILIKE sqlc.narg('search')::TEXT ESCAPE '\'
)
END
ORDER BY
CASE WHEN p.resolved_field = 'created_at' AND p.resolved_direction = 'ASC' THEN scs.created_at END ASC,
CASE WHEN p.resolved_field = 'created_at' AND p.resolved_direction = 'DESC' THEN scs.created_at END DESC,
Expand Down
2 changes: 2 additions & 0 deletions service/policy/db/subject_mappings.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,12 @@ func (c PolicyDBClient) ListSubjectConditionSets(ctx context.Context, r *subject
}

sortField, sortDirection := GetSubjectConditionSetsSortParams(r.GetSort())
search := pgtypeSubstringSearchPattern(r.GetSearch().GetTerm())

list, err := c.queries.listSubjectConditionSets(ctx, listSubjectConditionSetsParams{
NamespaceID: pgtypeUUID(r.GetNamespaceId()),
NamespaceFqn: pgtypeText(r.GetNamespaceFqn()),
Search: search,
Limit: limit,
Offset: offset,
SortField: sortField,
Expand Down
50 changes: 36 additions & 14 deletions service/policy/db/subject_mappings.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion service/policy/db/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,8 @@ func UUIDToString(uuid pgtype.UUID) string {
return ""
}

return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x",
return fmt.Sprintf(
"%08x-%04x-%04x-%04x-%012x",
uuid.Bytes[0:4],
uuid.Bytes[4:6],
uuid.Bytes[6:8],
Expand Down
Loading