Add the dated public key fetch and the key schedule Python had none of - #4
Merged
Merged
Conversation
The package could verify only with a key the caller had already obtained, so every application had to build the request to a creator's well known end point, choose the key in force on the OWID's date, cache and time out on its own. The other ports carry that inside the library, and the specification (SWAN-community/owid#7) says a verifier sends the OWID's own date every time. public_key_fetch asks /owid/api/v{n}/public-key?date={minutes}&format=pkcs on the domain the OWID carries, at the version the OWID carries, through urllib with a ten second timeout. Keys are held per request URL up to 1024, a domain that is not a host name or a scheme that is not HTTP is refused before anything is opened, and a key that cannot be had or read answers KEY_UNAVAILABLE or INVALID_KEY rather than a forgery. A caller may pass a transport of its own. PublicKeySchedule and DatedPublicKey choose the key in force at a date out of a published schedule, being the latest start at or before the date, with current() the key in force now and last() documented as not that. endpoints.public_key_response_at answers the date parameter for a creator that rotates its key as the specification requires. io.minutes_since_base shares write_date's arithmetic. The core keeps no network access of its own: the fetch is imported only by a caller that asks for it, and a contract test scans the package for any other route to the network. The tests drive the real fetch against a stand in for the end point on the loopback address serving the published 51d.es schedule, and check the choice of key against a genuine identifier the 51Degrees cloud issued on 4 September 2026, the fixtures the Java port uses. 161 tests pass, README examples included.
Automation51D
pushed a commit
to 51Degrees/pipeline-python
that referenced
this pull request
Sep 6, 2026
…WID with the dated key fetch, and report whether a licence key was given' Moves the owid-python submodule onto the 51Degrees fork head that carries the dated public key fetch and the key schedule, which the Python port had none of until SWAN-community/owid-python#4. The build time copy into the package as the private module _owid takes whatever Python files the submodule holds, so public_key_fetch.py and public_key_schedule.py are now carried in a published wheel alongside the rest and need no change to ci/copy-owid-source.ps1. Nothing in DidClient changes as a result. It fetches the whole published schedule from the cloud's own key endpoint with a resource key and selects by the start of a key's period itself, with a fifteen minute boundary tolerance the OWID library does not model, so the library's PublicKeySchedule is not the right thing for it to call. The two are deliberately different and both are correct. Adds has_licence_key, which the Java and .NET clients already have and this one did not. A licence key is needed only by redeem, so a client without one still reads keys and verifies signatures, and a caller can now ask rather than track it separately. Verified with the submodule at 5fe9a7cc: 145 passed, 2 skipped.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
Adds to the Python port the two things every other port now carries and this one did not: fetching a creator's public key from the well known end point for the date an OWID carries, and choosing a key out of a published schedule by the start of its period. The core keeps its promise of no network access of its own, because the fetch lives in
owid.public_key_fetch, is imported only by a caller that asks for it, and a new contract test scans the package for any other route to the network.Why
This port was written with key fetching scoped out ("retrieving a creator public key over HTTP is left to the caller"), so every application had to build the request, choose the key in force on the OWID's date, cache and time out on its own. The point of the library is that this is done once per language. The specification says where the key is and how to ask for it: the OWID's Domain field plus
/owid/api/v{n}/public-key, sending the OWID's own date, with the key in force being the one with the latest start at or before that date (SWAN-community/owid#7). The Java port gained the same two pieces on 5 September 2026 in SWAN-community/owid-java#6, and this change mirrors that shape.What changed
owid/public_key_fetch.py(new):public_key_urlbuilds{scheme}://{domain}/owid/api/v{n}/public-key?date={minutes}&format=pkcsat the version the OWID carries;public_key_pem,signature_status,verifyandclear_cache. Keys are held per request URL up to 1024 before the store is emptied, each request waits at most ten seconds, a domain that is not a host name and a scheme that is not HTTP are refused before anything is opened, a body beyond 64 KiB is not a key, and a key that cannot be had or read answersKEY_UNAVAILABLEorINVALID_KEYrather than a forgery. A caller may pass a transport of its own.owid/public_key_schedule.py(new):DatedPublicKeyandPublicKeySchedulewithkey_in_force,key_for,current,last,signature_statusandverify. Two keys sharing a start settle in favour of the first supplied, as the cloud does. A naive datetime is read as UTC.owid/endpoints.py:public_key_response_atanswers the date parameter for a creator that rotates its key as the specification requires, being the key in force at the date, the key in force now for an undated or future date, 404 when nothing is in force and 400 for a date that is not a count of minutes.owid/io.py:minutes_since_base, sharingwrite_date's arithmetic.owid/error.py:PublicKeyFetchErrorcarrying the status, the domain and the response code.owid/__init__.pyexports the new names without importing the fetch.tests/test_public_key_fetch.pydrives the realurllibpath against a stand in for the end point on the loopback address (tests/key_end_point.py) that answers as the cloud does, with the request moment fixed at 14 September 2026 so an undated request is served a key other than the one that signed the fixture.tests/test_public_key_schedule.pyandtests/test_public_key_response_at.pycover the choice of key, including the batch generated on 1 September that broke the .NET port.tests/test_network_contract.pykeeps the no network promise. The fixtures undertests/dataare the genuine identifier the 51Degrees cloud issued on 4 September 2026 and the thirty published 51d.es keys, the same files the Java port uses.tests/test_readme.pyruns, the interface list, and the testing notes.Verification
python -m unittest discoveron Python 3.14 with cryptography 49: 161 tests, OK. Before this change the suite held 117, so 44 tests are new..invalidname space and answersKEY_UNAVAILABLE; the schedule one verifies the README's own identifier under the key in force on its date.Notes
fiftyone_pipeline_did) fetches the whole schedule fromid/keywith a resource key and selects by start itself, so it is unaffected. The 51Degrees fork and pipeline-python's submodule pin can follow this merge.51d.es, the creator named in a 51Did identifier, the fetch answersKEY_UNAVAILABLEtoday, because that creator requires a resource key or licence key on both mandatory end points and answers 401 without one, which I checked against production on 5 September 2026. The Authentication section of the specification allows a creator to do that and requires a client to be prepared for it, so the creator is conformant and so is this port, whose duty there is to report the key as unavailable rather than the signature as forged. Presenting a credential is a separate matter, and only the JavaScript port has an answer to it today throughowid.fetchHeaders. This port follows the other five in leaving it to a transport the caller supplies. Bringing the six into line with JavaScript needs one decision taken across them, because the specification lets each creator choose its own header and parameter names.Produced with AI assistance and checked against the outputs above.