Bind database policy claims as typed OData constants - #3756
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
There is a confirmed null-claim handling bug risk in type promotion (potential null TypeReference) and a shared-mutable-state concern in ResolvedDatabasePolicy.Empty that should be addressed before merging.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Pull request overview
This PR refactors database authorization policy processing to keep JWT claim values out of policy URI text by replacing claim references with inert OData parameter aliases, then injecting the claim values as typed OData AST constants (and parameterizing Cosmos DB constants) to prevent decoding/escaping issues and injection.
Changes:
- Introduces
ResolvedDatabasePolicyto carry policy text (with aliases) plus a separate alias→typed-value map, and updatesProcessDBPolicyto return it. - Extends OData parsing to accept pre-bound parameter alias nodes and resolves aliases to typed constants before operand type promotion.
- Updates Cosmos policy predicate generation to emit constants as bound query parameters instead of inlining.
File summaries
| File | Description |
|---|---|
| src/Service.Tests/UnitTests/DwSqlQueryBuilderUpsertTests.cs | Updates mock policy resolution to return ResolvedDatabasePolicy.Empty. |
| src/Service.Tests/UnitTests/DatabasePolicyClaimBindingUnitTests.cs | Adds regression tests ensuring claim values stay out of policy text and become Cosmos parameters. |
| src/Service.Tests/Authorization/REST/RestAuthorizationHandlerUnitTests.cs | Adjusts wildcard policy test to assert on .Policy. |
| src/Service.Tests/Authorization/AuthorizationResolverUnitTests.cs | Updates policy parsing expectations to use aliases and validates typed claim values/fail-closed behavior. |
| src/Core/Resolvers/AuthorizationPolicyHelpers.cs | Parses resolved policies with parameter alias nodes and injects claim constants into the OData parser. |
| src/Core/Parsers/ODataASTCosmosVisitor.cs | Parameterizes Cosmos constant emission via a provided parameter factory delegate. |
| src/Core/Parsers/FilterParser.cs | Adds optional parameterAliasNodes support and wires them into ODataUriParser. |
| src/Core/Parsers/ClaimsTypeDataUriResolver.cs | Resolves aliases to claim constant nodes before type promotion. |
| src/Core/Authorization/AuthorizationResolver.cs | Returns ResolvedDatabasePolicy, replaces claim substitution with alias binding, and adds invariant typed parsing of primitives. |
| src/Auth/ResolvedDatabasePolicy.cs | Adds the new record type for policy+claim binding. |
| src/Auth/IAuthorizationResolver.cs | Updates interface contract to return ResolvedDatabasePolicy. |
Review details
- Files reviewed: 11/11 changed files
- Comments generated: 3
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
RubenCerna2079
left a comment
There was a problem hiding this comment.
Review still in progress, just want to leave the current comments before I continue.
|
Copilot can you do another review please? Note I have intentionally removed ProcessDBPolicy, despite it being public because we are not concerned that external callers are taking dependency on this. |
Souvik Ghosh (souvikghosh04)
left a comment
There was a problem hiding this comment.
please check on the feasibility for adding at least one E2E test for running in CI for all backends.
| [DataRow("@item.rating eq @claims.emprating)", "rating eq 4.2)", DisplayName = "Valid policy parsing test for double claimvaluetype.")] | ||
| "(rating gt @dabClaim0) and (@dabClaim1 eq true)", DisplayName = "Valid policy parsing test for double and boolean claimvaluetypes.")] | ||
| [DataRow("@item.rating eq @claims.emprating)", "rating eq @dabClaim0)", DisplayName = "Valid policy parsing test for double claimvaluetype.")] | ||
| public void ParseValidDbPolicy(string policy, string expectedParsedPolicy) |
There was a problem hiding this comment.
while most unit tests cover the changes, would it be possible to introduce an E2E test which runs in the CI for all the backends- MSSQL, MySQL, PostgreSQL etc.? like write for one and run for all..
Why make this change?
Closes #3755
What is this change?
ResolvedDatabasePolicy.ConstantNodevalues before operand type promotion.Relevant specification:
flowchart TD A["Trusted configured policy<br/>@item.ownerId eq @claims.userId"] B["Untrusted authenticated claim<br/>alice%27 or 1 eq 1 or %27"] A --> C["AuthorizationResolver"] B --> C C --> D["ResolvedDatabasePolicy"] D --> E["Policy:<br/>ownerId eq @dabClaim0"] D --> F["ClaimValues:<br/>@dabClaim0 maps to raw CLR string"] E --> G["ODataParser"] F --> H["ConstantNode map"] H --> G G --> I["ClaimsTypeDataUriResolver<br/>Resolves aliases before type promotion"] I --> J["ParameterAliasRewriter<br/>Resolves remaining aliases and Boolean contexts"] J --> K["Typed FilterClause AST"] K --> L["ODataASTVisitor"] K --> M["ODataASTCosmosVisitor"] L --> N["SQL predicate and provider parameters"] M --> O["Cosmos SQL predicate and provider parameters"]How was this tested?
Focused unit-test coverage includes:
Sample Request(s)
No client-facing request contract changes are introduced.
Example database policy:
Example request:
A legitimate claim such as
O'Brienor50% completeis preserved exactly and bound as a database parameter. It is never inserted into or reinterpreted as OData policy syntax.