-
Notifications
You must be signed in to change notification settings - Fork 39
fix(policy): narrow authorization attribute lookups #3986
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| package db | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "strings" | ||
|
|
||
| "github.com/opentdf/platform/protocol/go/policy" | ||
| "github.com/opentdf/platform/protocol/go/policy/attributes" | ||
| "github.com/opentdf/platform/service/pkg/db" | ||
| ) | ||
|
|
||
| // resolveEntitleableValueFqns preserves value lookup semantics without hydrating | ||
| // encryption policy or resource mappings that authorization never consumes. | ||
| func (c *PolicyDBClient) resolveEntitleableValueFqns(ctx context.Context, fqns []string) ([]string, map[string]*attributes.GetAttributeValuesByFqnsResponse_AttributeAndValue, error) { | ||
| normalized := make([]string, len(fqns)) | ||
| definitionFqns := make([]string, 0, len(fqns)) | ||
| seenDefinitions := make(map[string]struct{}, len(fqns)) | ||
| requested := make(map[string]struct{}, len(fqns)) | ||
| for i, fqn := range fqns { | ||
| fqn = strings.ToLower(fqn) | ||
| normalized[i] = fqn | ||
| requested[fqn] = struct{}{} | ||
| defFqn := definitionFqnFromValueFqn(fqn) | ||
| if _, seen := seenDefinitions[defFqn]; defFqn != "" && !seen { | ||
| seenDefinitions[defFqn] = struct{}{} | ||
| definitionFqns = append(definitionFqns, defFqn) | ||
| } | ||
| } | ||
| rows, err := c.queries.getEntitleableAttributeValues(ctx, getEntitleableAttributeValuesParams{ | ||
| DefinitionFqns: definitionFqns, | ||
| ValueFqns: normalized, | ||
| }) | ||
| if err != nil { | ||
| return nil, nil, db.WrapIfKnownInvalidQueryErr(err) | ||
| } | ||
| definitions := make(map[string]*policy.Attribute, len(definitionFqns)) | ||
| traversable := make(map[string]bool, len(definitionFqns)) | ||
| pairs := make(map[string]*attributes.GetAttributeValuesByFqnsResponse_AttributeAndValue, len(fqns)) | ||
| for _, row := range rows { | ||
| attr, exists := definitions[row.DefinitionFqn] | ||
| if !exists { | ||
| attr = &policy.Attribute{ | ||
| Id: row.DefinitionID, Fqn: row.DefinitionFqn, | ||
| Rule: attributesRuleTypeEnumTransformOut(string(row.Rule)), | ||
| Namespace: &policy.Namespace{Id: row.NamespaceID, Name: row.NamespaceName, Fqn: row.NamespaceFqn}, | ||
| } | ||
| definitions[row.DefinitionFqn] = attr | ||
| traversable[row.DefinitionFqn] = row.AllowTraversal | ||
| } | ||
| if row.ValueID == "" { | ||
| continue | ||
| } | ||
| _, isRequested := requested[row.ValueFqn] | ||
| if !row.ValueActive { | ||
| if isRequested { | ||
| return nil, nil, fmt.Errorf("value fqn [%s] inactive: %w", row.ValueFqn, db.ErrAttributeValueInactive) | ||
| } | ||
| continue | ||
| } | ||
| value := &policy.Value{Id: row.ValueID, Fqn: row.ValueFqn} | ||
| attr.Values = append(attr.Values, value) | ||
| if isRequested { | ||
| pairs[row.ValueFqn] = &attributes.GetAttributeValuesByFqnsResponse_AttributeAndValue{Attribute: attr, Value: value} | ||
| } | ||
| } | ||
| for _, fqn := range normalized { | ||
| if _, found := pairs[fqn]; found { | ||
| continue | ||
| } | ||
| defFqn := definitionFqnFromValueFqn(fqn) | ||
| if traversable[defFqn] { | ||
| pairs[fqn] = &attributes.GetAttributeValuesByFqnsResponse_AttributeAndValue{Attribute: definitions[defFqn]} | ||
| continue | ||
| } | ||
| return nil, nil, fmt.Errorf("could not find value for FQN [%s]: %w", fqn, db.ErrNotFound) | ||
| } | ||
| return normalized, pairs, nil | ||
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| -- name: getEntitleableAttributeValues :many | ||
| -- Authorization needs value identity and rule context, not grants, keys, or resource | ||
| -- mappings. Only hierarchy definitions need their other active values, in policy order. | ||
| WITH definitions AS ( | ||
| SELECT ad.id, ad.namespace_id, ad.rule, ad.allow_traversal, ad.values_order, | ||
| df.fqn AS definition_fqn | ||
| FROM attribute_definitions ad | ||
| JOIN attribute_fqns df ON df.attribute_id = ad.id AND df.value_id IS NULL | ||
| JOIN attribute_namespaces ns ON ns.id = ad.namespace_id AND ns.active = TRUE | ||
| WHERE df.fqn = ANY(@definition_fqns::text[]) AND ad.active = TRUE | ||
| ), requested_values AS ( | ||
| SELECT av.id, av.attribute_definition_id, av.active, vf.fqn | ||
| FROM attribute_fqns vf | ||
| JOIN attribute_values av ON av.id = vf.value_id | ||
| JOIN definitions d ON d.id = av.attribute_definition_id | ||
| WHERE vf.fqn = ANY(@value_fqns::text[]) | ||
| ), selected_values AS ( | ||
| SELECT * FROM requested_values | ||
| UNION ALL | ||
| SELECT av.id, av.attribute_definition_id, av.active, vf.fqn | ||
| FROM definitions d | ||
| JOIN attribute_values av ON av.attribute_definition_id = d.id AND av.active = TRUE | ||
| JOIN attribute_fqns vf ON vf.value_id = av.id | ||
| WHERE d.rule = 'HIERARCHY' AND NOT EXISTS (SELECT 1 FROM requested_values rv WHERE rv.id = av.id) | ||
| ) | ||
| SELECT d.id AS definition_id, d.definition_fqn, d.rule, d.allow_traversal, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should the state of
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we need an integration test for the behavior with |
||
| ns.id AS namespace_id, ns.name AS namespace_name, nf.fqn AS namespace_fqn, | ||
| COALESCE(v.id::text, '')::text AS value_id, | ||
| COALESCE(v.fqn, '')::text AS value_fqn, | ||
| COALESCE(v.active, FALSE)::boolean AS value_active | ||
| FROM definitions d | ||
| JOIN attribute_namespaces ns ON ns.id = d.namespace_id | ||
| JOIN attribute_fqns nf ON nf.namespace_id = ns.id AND nf.attribute_id IS NULL AND nf.value_id IS NULL | ||
| LEFT JOIN selected_values v ON v.attribute_definition_id = d.id | ||
| ORDER BY d.id, CASE WHEN d.rule = 'HIERARCHY' THEN ARRAY_POSITION(d.values_order, v.id) END, v.id; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need some comments about each subquery's intent