-
Notifications
You must be signed in to change notification settings - Fork 9
[PP-3772] Add patron blocking rules per library #3090
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
Open
dbernstein
wants to merge
31
commits into
main
Choose a base branch
from
feature/PP-3772-Add-patron-blocking-rules-per-library
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
de7daab
Add per-library patron blocking rules to SIP2 LibrarySettings (PP-3772)
dbernstein 1544416
Refactor to push patron blocks up the inheritance stack.
dbernstein f01559d
Add rules engine
dbernstein 4ca159a
Tie patron blocking rules data into the rules engine.
dbernstein bff70e0
* rule_engine.py — Richer error messages:
dbernstein 81f9b16
* Deprecated code removed
dbernstein 5bd417f
Add int() as an allowable function to the simpleeval rules engine.
dbernstein 1758f4a
Add documentation for functions available for building patron blockin…
dbernstein b020ca7
Fix syntactic error introduced accidentally during the manual merge p…
dbernstein 5c17c72
Fix linting errors.
dbernstein ece3259
Update lock file.
dbernstein 07d7a87
Adds backend support for front end rule validations before saving:
dbernstein b4d641c
Fix mypy
dbernstein 0f87fc9
Add test coverage to fetch_live_rule_validation_values
dbernstein 560dcb7
Remove inline imports.
dbernstein 73f6944
Fix lock file.
dbernstein f4170ba
Remove in-method imports.
dbernstein f0abc14
Add simpleeval to toml.
dbernstein bdfb62f
Remove divider comment blocks from rules_engine.py.
dbernstein 797aa78
Convert docstrings to reST from google.
dbernstein 5b9fc20
Replace _sip2_thread_local with explicit RemoteAuthResult API
dbernstein 69a86d2
Remove stale comment.
dbernstein b3639d3
Wire validate_message into patron blocking rules validation
dbernstein 598840f
Add validation when supports_patron_blocking_rules is False
dbernstein 30ee26b
Replace SIP2-specific checks with supports_patron_blocking_rules (OCP)
dbernstein c91a030
Unify evaluator lifecycle between admin validation and runtime (#9)
dbernstein a8ceca5
Rename tests to accord with CLAUDE.md recommentations.
dbernstein 26b5503
README.md link for available functions for patron blocking rules.
dbernstein 49d27dc
Rename BLOCKED_CREDENTIALS to SUSPENDED_CREDENTIALS and add BLOCKED_B…
dbernstein 478b37e
Fix poetry lock and mypy.
dbernstein 53981e1
Patron blocking: fail-open, CloudWatch logging, remove live validation
dbernstein File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| # Patron Blocking Rules — Allowed Functions | ||
|
|
||
| Patron blocking rule expressions are evaluated by a locked-down | ||
| [simpleeval](https://github.com/danthedeckie/simpleeval) sandbox. | ||
| Only the functions listed below may be called inside a rule expression. | ||
| Any reference to an unlisted function causes the rule to **fail closed** | ||
| (the patron is blocked at runtime; the rule is rejected at admin-save time). | ||
|
|
||
| --- | ||
|
|
||
| ## `age_in_years` | ||
|
|
||
| Calculates the age of a person in **whole years** from a date string. | ||
| Use this to write rules that gate access by age (e.g. block minors or | ||
| enforce senior-only services). | ||
|
|
||
| ### Signature | ||
|
|
||
| ```text | ||
| age_in_years(date_str, fmt=None) -> int | ||
| ``` | ||
|
|
||
| ### Parameters | ||
|
|
||
| | Parameter | Type | Required | | ||
| |------------|-----------------|----------| | ||
| | `date_str` | `str` | Yes | | ||
| | `fmt` | `str` or `None` | No | | ||
|
|
||
| - **`date_str`** — A date string representing the person's date of birth. | ||
| ISO 8601 format (`YYYY-MM-DD`) is tried first; if that fails, | ||
| `dateutil.parser` is used as a fallback, accepting most common | ||
| human-readable formats (e.g. `"Jan 1, 1990"`, `"01/01/1990"`). | ||
| - **`fmt`** — An explicit | ||
| [`strptime`](https://docs.python.org/3/library/datetime.html#datetime.datetime.strptime) | ||
| format string (e.g. `"%d/%m/%Y"`). When supplied, no automatic parsing | ||
| is attempted. | ||
|
|
||
| ### Returns | ||
|
|
||
| `int` — The person's age in complete years (fractional years are truncated, | ||
| not rounded). | ||
|
|
||
| ### Raises | ||
|
|
||
| `ValueError` — If `date_str` cannot be parsed (either by ISO 8601, the | ||
| supplied `fmt`, or `dateutil`). At runtime this causes the rule to | ||
| **fail closed**. | ||
|
|
||
| ### Examples | ||
|
|
||
| ```python | ||
| # Block patrons under 18 (field returned verbatim from the SIP2 server) | ||
| age_in_years({polaris_patron_birthdate}) < 18 | ||
|
|
||
| # Block patrons under 18 using an explicit strptime format | ||
| age_in_years({dob_field}, "%d/%m/%Y") < 18 | ||
|
|
||
| # Block patrons aged 65 or over (e.g. senior-only restriction) | ||
| age_in_years({polaris_patron_birthdate}) >= 65 | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## `int` | ||
|
|
||
| Converts a value to a Python `int`. Useful when the SIP2 server returns | ||
| a numeric field as a string (a common occurrence) and you need to compare | ||
| it numerically rather than lexicographically. | ||
|
|
||
| ### Signature | ||
|
|
||
| ```text | ||
| int(value) -> int | ||
| ``` | ||
|
|
||
| ### Parameters | ||
|
|
||
| | Parameter | Type | Required | | ||
| |-----------|-------|----------| | ||
| | `value` | `Any` | Yes | | ||
|
|
||
| - **`value`** — The value to convert. Typically a string such as `"3"` or | ||
| a float such as `2.9`. Any value accepted by Python's built-in `int()` is | ||
| valid. Passing a non-numeric string (e.g. `"adult"`) raises a `ValueError` | ||
| and causes the rule to **fail closed**. | ||
|
|
||
| ### Returns | ||
|
|
||
| `int` — The integer representation of `value`. Floating-point values are | ||
| **truncated** toward zero (e.g. `int("2.9")` raises `ValueError`; pass a | ||
| float literal or cast via `{field} * 1` first if you need truncation of | ||
| floats). | ||
|
|
||
| ### Raises | ||
|
|
||
| `ValueError` — If `value` cannot be converted to an integer. At runtime | ||
| this causes the rule to **fail closed**. | ||
|
|
||
| ### Examples | ||
|
|
||
| ```python | ||
| # Block patron class codes above 2 (SIP2 returns the code as a string) | ||
| int({sipserver_patron_class}) > 2 | ||
|
|
||
| # Block if a numeric expiry-year field indicates an expired account | ||
| int({expire_year}) < 2025 | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## Notes | ||
|
|
||
| - **String methods are available** — methods on Python `str` values can be | ||
| called directly on string-valued placeholders. For example, to check | ||
| whether a patron identifier starts with a certain prefix: | ||
|
|
||
| ```python | ||
| {patron_identifier}.startswith("1234") | ||
| ``` | ||
|
|
||
| - **Fail-closed behaviour** — any function call that raises an exception | ||
| (e.g. an unparseable date or a non-numeric string passed to `int()`) | ||
| causes the patron to be **blocked** at runtime and the rule to be | ||
| **rejected** at admin-save time. Write test rules carefully using | ||
| representative patron data before enabling them in production. | ||
| - **No other builtins** — Python builtins such as `len`, `str`, `float`, | ||
| `abs`, and `round` are **not** available. If you need additional | ||
| functions, request them via the standard feature-request process so they | ||
| can be reviewed and added to `DEFAULT_ALLOWED_FUNCTIONS` in | ||
| `rule_engine.py`. | ||
| - **Placeholder syntax** — field values from the SIP2 response are | ||
| referenced as `{field_name}`. All fields returned by the SIP2 | ||
| `patron_information` command are available, plus the normalised `{fines}` | ||
| key (a `float` derived from `fee_amount`). | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
This doc is created, but its not referenced anywhere. At the very least this should be referenced in the README so people can find this documentation.