Refactor PATCH operations, add filter parser, and parameterize ID types#39
Refactor PATCH operations, add filter parser, and parameterize ID types#39Paul-E wants to merge 9 commits intoShiftControl-io:mainfrom
Conversation
|
⏳ I'm reviewing this pull request for security vulnerabilities and code quality issues. I'll provide an update when I'm done |
|
✅ I finished the code review, and left comments with the issues I found. |
PR Red Team Report: #39 — Refactor PATCH operations, add filter parser, and parameterize ID typesReviewed by: 4 parallel AI agents (Security, Error Handling, Test Coverage, Code Review) Critical Findings[C-1] Silent path swallowing via
|
| Gap | Location | Severity |
|---|---|---|
| Lenient bool deserializer has zero unit tests | src/utils/serde.rs |
Critical |
| MemberType case-insensitive deser untested | src/models/group.rs |
Critical |
| No tests with non-String ID types (e.g., UUID) | src/models/user.rs, group.rs |
Critical |
| No tests for number/bool/null comparison values in filters | src/filter.rs |
High |
| Filter/PatchPath JSON serde round-trip untested | src/filter.rs |
High |
| PatchOperation serialization untested (only deser) | src/models/others.rs |
High |
Case-insensitive op names ("Add", "ADD") untested |
src/models/others.rs |
High |
| Entra user test has no field-level assertions | src/models/user.rs |
Medium |
| No negative test: Remove without path, invalid op values | src/models/others.rs |
High |
| No negative test: malformed path fallthrough to WithoutPath | src/models/others.rs |
High |
Generated by PRRedTeam -- 4 parallel agents reviewing security, bugs/compliance, error handling, and test coverage.
This allows users of the library to use different types. Eg String, uiid::Uuid, email_address::EmailAddress, etc...
…der tests - Replace #[serde(untagged)] on OperationTarget with a custom Deserialize impl that checks for the "path" key explicitly. Malformed paths now produce an error instead of silently falling through to WithoutPath. - Add optional `value` field to PatchOperation::Remove to support providers (e.g. JumpCloud) that include a value array on remove ops, per RFC 7644 Section 3.5.2.2. - Add Okta test fixtures: deactivate (pathless replace), add members (array value with path), replace members (multi-member array). - Add JumpCloud test fixtures: per-field replace ops, minimal member add, remove-with-value. - Add negative tests: malformed path rejection, invalid op rejection.
Add clippy with -D warnings to catch lint issues early. Add a step that regenerates filter_parser.rs from the LALRPOP grammar and diffs against the committed file, ensuring the generated parser has not been tampered with or fallen out of sync.
Red Team Follow-Up: Findings StatusResolved
Remaining (for contributor or future work)
|
|
Hey @Paul-E, I've run my PR review agent against the PR. Overall I'm ok with it. I addressed the highest ones, if you could commit sign future commits that would be awesome. |
Thanks for reviewing, I'll take a look at the feedback this week |
Refactor
PATCHoperationsRefactor patch operation datastructures to better leverage the sum type nature of Rust enums such that they more closely models the shape of the data in the SCIM spec. The enum representation expresses the relationship between the
opandvalue, andpathandvalue, which helps make invalid states unrepresentable, leading to rejection of invalid data.Add parser for filters and
PATCHoperationsThe RFC 7644 provides a grammar for filters, defined by RFC 7644 §3.4.2.2. This PR adds
Eg a filter
emails[type eq "work"]filter can be both serialized and parsed like soThe current implementation exposes the full AST, which can be unwieldy to work with. Future work may improve the DX by offering tools with a different interface
Building the parser
LALRPOP offers two methods of turning
src/filter_parser.lalrpopintosrc/filter_parser.rs, each described in the guide. One method is to use a build script, detecting changes and re-building the Rust parser each run of the compiler. The other is to use the lalrpop cli to generatesrc/filter_parser.rswithout the use of macros or build scripts.I chose the CLI route so as to not introduce a build script. Build scripts run arbitrary Rust code on each compilation, so some developers and maintainers feel more comfortable using libraries without them. Statically generating
src/filter_parser.rsis also more likely to play nice with IDEs and LSP, as it is "just another Rust file".A drawback of using the CLI is it means a very large
src/filter_parser.rs. Because this file is so large, it is in practice not re-viewable. An attacker could hide an exploit in its large footprint. This can be mitigated with CI job that re-generatesfilter_parser.rsand validates that it matches what is in the MR. As a stopgap, maintainers can perform this action manually.Make id types parametric
Previously some ID types were parsed as
Strings, meaning users of the crate required another parsing pass if their ID UUIDs, emails, etc... With the ID types parameterized, invalid IDs are caught during the initial deserialization pass.Users of the library may still use
Stringas the ID type to get the old behavior, and indeed this is the default type if none is specified.Remove
Defaultimpls on various typesWhile using
Defaultis a common lightweight builder pattern for applications, it is probably not the right approach for libraries, as users might not have a grasp of what they need to "fill in" after the struct is created viaDefault. Objects created viaDeafultshould be ready to use immediately.If a builder is desired, something like
TypedBuilderis more likely to be the correct approach, as it leverages the compiler to guide users of the library towards constructing correct objects.