diff --git a/Cargo.lock b/Cargo.lock index 422238b..d5967e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aho-corasick" @@ -38,6 +38,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.13.1" @@ -52,22 +58,27 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "biscuit-auth" -version = "5.0.0" +version = "6.0.0-beta.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95490f2c91dc452247d00a2fb4779bcedb7693e669354fa1fe2a96679f4950cc" +checksum = "336ffc076d302968607866acb85378ed77066289ce1868aa7770c0f3f6e83e5c" dependencies = [ "base64", "biscuit-parser", "biscuit-quote", + "ecdsa", "ed25519-dalek", - "getrandom 0.1.16", + "elliptic-curve", + "getrandom", "hex", "nom", + "p256", + "pkcs8 0.9.0", "prost", "prost-types", "rand", "rand_core", "regex", + "serde_json", "sha2 0.9.9", "thiserror", "time", @@ -76,9 +87,9 @@ dependencies = [ [[package]] name = "biscuit-parser" -version = "0.1.2" +version = "0.2.0-beta.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9fd6da963e73f7e6db729c3bd76784863ba405b15acbbb5cb08ebc2adbd3bf" +checksum = "0a6f2eb85dcc822200621ce077b9382453d28ddd6a55e7a97bdb592370d2d440" dependencies = [ "hex", "nom", @@ -101,12 +112,12 @@ dependencies = [ [[package]] name = "biscuit-quote" -version = "0.2.2" +version = "0.3.0-beta.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0071fe3634b644a8df1434e3e14841e480c4238059c66a94e479b7eff98c2bd3" +checksum = "12c624b1cf44110708a62b63341b17c37c33fcdd3cce89b501a115af3454dcf8" dependencies = [ "biscuit-parser", - "proc-macro-error", + "proc-macro-error2", "proc-macro2", "quote", "syn 1.0.109", @@ -198,6 +209,18 @@ dependencies = [ "libc", ] +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -235,6 +258,15 @@ dependencies = [ "syn 2.0.85", ] +[[package]] +name = "der" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +dependencies = [ + "const-oid", +] + [[package]] name = "der" version = "0.7.9" @@ -271,7 +303,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", + "const-oid", "crypto-common", + "subtle", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der 0.7.9", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "serdect", + "signature", + "spki 0.7.3", ] [[package]] @@ -280,7 +329,7 @@ version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ - "pkcs8", + "pkcs8 0.10.2", "signature", ] @@ -305,6 +354,37 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pem-rfc7468", + "pkcs8 0.10.2", + "rand_core", + "sec1", + "serdect", + "subtle", + "zeroize", +] + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -319,28 +399,29 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] name = "getrandom" -version = "0.1.16" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", - "wasi 0.9.0+wasi-snapshot-preview1", + "wasi", ] [[package]] -name = "getrandom" -version = "0.2.15" +name = "group" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ - "cfg-if", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "ff", + "rand_core", + "subtle", ] [[package]] @@ -355,6 +436,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "iana-time-zone" version = "0.1.61" @@ -478,6 +568,18 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2 0.10.8", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -487,14 +589,24 @@ dependencies = [ "base64ct", ] +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der 0.6.1", + "spki 0.6.0", +] + [[package]] name = "pkcs8" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der", - "spki", + "der 0.7.9", + "spki 0.7.3", ] [[package]] @@ -519,27 +631,34 @@ dependencies = [ ] [[package]] -name = "proc-macro-error" -version = "1.0.4" +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" dependencies = [ - "proc-macro-error-attr", "proc-macro2", "quote", - "syn 1.0.109", - "version_check", ] [[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "proc-macro-error2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" dependencies = [ + "proc-macro-error-attr2", "proc-macro2", "quote", - "version_check", + "syn 2.0.85", ] [[package]] @@ -684,7 +803,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom", ] [[package]] @@ -716,6 +835,16 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "rustc_version" version = "0.4.1" @@ -725,6 +854,27 @@ dependencies = [ "semver", ] +[[package]] +name = "ryu" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der 0.7.9", + "generic-array", + "pkcs8 0.10.2", + "serdect", + "subtle", + "zeroize", +] + [[package]] name = "semver" version = "1.0.23" @@ -751,6 +901,28 @@ dependencies = [ "syn 2.0.85", ] +[[package]] +name = "serde_json" +version = "1.0.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct", + "serde", +] + [[package]] name = "sha2" version = "0.9.9" @@ -787,9 +959,19 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ + "digest 0.10.7", "rand_core", ] +[[package]] +name = "spki" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +dependencies = [ + "der 0.6.1", +] + [[package]] name = "spki" version = "0.7.3" @@ -797,7 +979,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", - "der", + "der 0.7.9", ] [[package]] @@ -909,12 +1091,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index f7a1808..c04b9d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ name = "biscuit_auth" crate-type = ["cdylib"] [dependencies] -biscuit-auth = { version = "5.0.0", features = ["pem"] } +biscuit-auth = { version = "6.0.0-beta.3", features = ["pem"] } pyo3 = { version = "0.22.6", features = ["extension-module", "chrono"] } hex = "0.4" base64 = "0.13.0" diff --git a/biscuit_auth.pyi b/biscuit_auth.pyi index 20d97e4..22a73db 100644 --- a/biscuit_auth.pyi +++ b/biscuit_auth.pyi @@ -33,6 +33,9 @@ PublicKeyProvider: TypeAlias = Union[ Callable[[], PublicKey], Callable[[int], PublicKey] ] +class Algorithm: + pass + class BiscuitBuilder: # Create a builder from a datalog snippet and optional parameter values # @@ -179,7 +182,7 @@ class Biscuit: @property def revocation_ids(self) -> List[str]: ... -class Authorizer: +class AuthorizerBuilder: # Create a new authorizer from a datalog snippet and optional parameter values # # :param source: a datalog snippet @@ -193,7 +196,7 @@ class Authorizer: source: Optional[str] = None, parameters: Parameters = None, scope_parameters: ScopeParameters = None, - ) -> Authorizer: ... + ) -> AuthorizerBuilder: ... # Add code to the builder, using the provided parameters. # @@ -250,12 +253,43 @@ class Authorizer: # :type builder: BlockBuilder def merge_block(self, builder: BlockBuilder) -> None: ... + # Take a snapshot of the authorizer builder and return it, base64-encoded + # + # :return: a snapshot as a base64-encoded string + # :rtype: str + def base64_snapshot(self) -> str: ... + + # Take a snapshot of the authorizer builder and return it, as raw bytes + # + # :return: a snapshot as raw bytes + # :rtype: bytes + def raw_snapshot(self) -> bytes: ... + + # Build an authorizer builder from a base64-encoded snapshot + # + # :param input: base64-encoded snapshot + # :type input: str + # :return: the authorizer builder + # :rtype: AuthorizerBuilder + @classmethod + def from_base64_snapshot(cls, input: str) -> AuthorizerBuilder: ... + + # Build an authorizer builder from a snapshot's raw bytes + # + # :param input: raw snapshot bytes + # :type input: bytes + # :return: the authorizer builder + # :rtype: AuthorizerBuilder + @classmethod + def from_raw_snapshot(cls, input: bytes) -> AuthorizerBuilder: ... + # Add a `Biscuit` to this `Authorizer` # # :param token: the token to authorize # :type token: Biscuit - def add_token(self, token: Biscuit) -> None: ... + def build(self, token: Biscuit) -> Authorizer: ... +class Authorizer: # Runs the authorization checks and policies # # Returns the index of the matching allow policy, or an error containing the matching deny @@ -405,12 +439,6 @@ class PublicKey: # :rtype: list def to_bytes(self) -> bytes: ... - # Serializes a public key to a hexadecimal string - # - # :return: the public key bytes (hex-encoded) - # :rtype: str - def to_hex(self) -> str: ... - # Deserializes a public key from raw bytes # # :param data: the raw bytes @@ -427,7 +455,7 @@ class PublicKey: # :return: the public key # :rtype: PublicKey @classmethod - def from_hex(cls, data: str) -> PublicKey: ... + def __new__(cls, data: str) -> PublicKey: ... # ed25519 private key class PrivateKey: @@ -437,12 +465,6 @@ class PrivateKey: # :rtype: list def to_bytes(self) -> bytes: ... - # Serializes a private key to a hexadecimal string - # - # :return: the private key bytes (hex-encoded) - # :rtype: str - def to_hex(self) -> str: ... - # Deserializes a private key from raw bytes # # :param data: the raw bytes @@ -459,7 +481,7 @@ class PrivateKey: # :return: the private key # :rtype: PrivateKey @classmethod - def from_hex(cls, data: str) -> PrivateKey: ... + def __new__(cls, data: str) -> PrivateKey: ... # A single datalog Fact # diff --git a/biscuit_test.py b/biscuit_test.py index 4c5302b..660a4c3 100644 --- a/biscuit_test.py +++ b/biscuit_test.py @@ -4,7 +4,7 @@ import pytest -from biscuit_auth import KeyPair,Authorizer, Biscuit, BiscuitBuilder, BlockBuilder, Check, Fact, KeyPair, Policy, PrivateKey, PublicKey, Rule, UnverifiedBiscuit +from biscuit_auth import Algorithm, KeyPair, Authorizer, AuthorizerBuilder, Biscuit, BiscuitBuilder, BlockBuilder, Check, Fact, KeyPair, Policy, PrivateKey, PublicKey, Rule, UnverifiedBiscuit def test_fact(): fact = Fact('fact(1, true, "", "Test", hex:aabbcc, 2023-04-29T01:00:00Z)') @@ -13,7 +13,7 @@ def test_fact(): def test_biscuit_builder(): kp = KeyPair() - pubkey = PublicKey.from_hex("acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189") + pubkey = PublicKey("ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189") builder = BiscuitBuilder( """ @@ -23,6 +23,8 @@ def test_biscuit_builder(): bytes({bytes}); datetime({datetime}); set({set}); + array({array}); + map({map}); check if true trusting {pubkey}; """, { 'str': "1234", @@ -31,6 +33,8 @@ def test_biscuit_builder(): 'bytes': [0xaa, 0xbb], 'datetime': datetime(2023, 4, 3, 10, 0, 0, tzinfo = timezone.utc), 'set': {2, True, "Test", datetime(2023, 4, 29, 1, 0, 0, tzinfo = timezone.utc) }, + 'array': [2, True, "Test", ["inner"]], + 'map': { 'key': [{12: "value"}]} }, { 'pubkey': pubkey } ) @@ -57,7 +61,9 @@ def test_biscuit_builder(): bool(true); bytes(hex:aabb); datetime(2023-04-03T10:00:00Z); -set([2, "Test", 2023-04-29T01:00:00Z, true]); +set({2, "Test", 2023-04-29T01:00:00Z, true}); +array([2, true, "Test", ["inner"]]); +map({"key": [{12: "value"}]}); fact(false); fact(true); builder(true); @@ -75,7 +81,7 @@ def test_biscuit_builder(): assert True def test_block_builder(): - pubkey = PublicKey.from_hex("acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189") + pubkey = PublicKey("ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189") builder = BlockBuilder( """ string({str}); @@ -128,8 +134,8 @@ def test_block_builder(): """ def test_authorizer_builder(): - pubkey = PublicKey.from_hex("acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189") - builder = Authorizer( + pubkey = PublicKey("ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189") + builder = AuthorizerBuilder( """ string({str}); int({int}); @@ -162,43 +168,28 @@ def test_authorizer_builder(): builder.add_policy(Policy("allow if fact($var, {f})", { 'f': True})) builder.add_policy(Policy("allow if fact($var, {f}) trusting {pubkey}", { 'f': True}, { 'pubkey': pubkey })) builder.merge_block(BlockBuilder('builder(true);')) - builder.merge(Authorizer('builder(false);')) + builder.merge(AuthorizerBuilder('builder(false);')) builder.add_code("add_code(true);") builder.add_code("add_code(true, {f});", { 'f': True}) builder.add_code("check if add_code(true, {f}) trusting {pubkey};", { 'f': True}, { 'pubkey': pubkey }) - try: - builder.authorize() - except: - pass - - assert repr(builder) == """// Facts: -// origin: authorizer -add_code(true); -add_code(true, true); + assert repr(builder) == """string("1234"); +int(1); bool(true); -builder(false); -builder(true); bytes(hex:aabb); datetime(2023-04-03T10:00:00Z); fact(false); fact(true); -int(1); -string("1234"); - -// Rules: -// origin: authorizer +builder(true); +builder(false); +add_code(true); +add_code(true, true); head($var) <- fact($var, true); head($var) <- fact($var, true) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189; - -// Checks: -// origin: authorizer check if true trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189; check if fact($var, true); check if fact($var, true) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189; check if add_code(true, true) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189; - -// Policies: allow if true; allow if true trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189; allow if fact($var, true); @@ -207,7 +198,7 @@ def test_authorizer_builder(): def test_authorizer_limits(): - auth = Authorizer("") + auth = AuthorizerBuilder() limits = auth.limits() limits.max_time = timedelta(microseconds=2000) auth.set_limits(limits) @@ -216,7 +207,7 @@ def test_authorizer_limits(): def test_key_selection(): - private_key = PrivateKey.from_hex("473b5189232f3f597b5c2f3f9b0d5e28b1ee4e7cce67ec6b7fbf5984157a6b97") + private_key = PrivateKey("ed25519-private/473b5189232f3f597b5c2f3f9b0d5e28b1ee4e7cce67ec6b7fbf5984157a6b97") root = KeyPair.from_private_key(private_key) other_root = KeyPair() @@ -247,7 +238,7 @@ def choose_key(kid): pass def test_complete_lifecycle(): - private_key = PrivateKey.from_hex("473b5189232f3f597b5c2f3f9b0d5e28b1ee4e7cce67ec6b7fbf5984157a6b97") + private_key = PrivateKey("ed25519-private/473b5189232f3f597b5c2f3f9b0d5e28b1ee4e7cce67ec6b7fbf5984157a6b97") root = KeyPair.from_private_key(private_key) biscuit_builder = BiscuitBuilder("user({id})", { 'id': "1234" }) @@ -259,10 +250,10 @@ def test_complete_lifecycle(): parsedToken = Biscuit.from_base64(token, root.public_key) - authorizer = Authorizer("allow if user({id})", { 'id': "1234" }) + authorizer = AuthorizerBuilder("allow if user({id})", { 'id': "1234" }) print(authorizer) - authorizer.add_token(parsedToken) + authorizer = authorizer.build(parsedToken) policy = authorizer.authorize() @@ -276,7 +267,7 @@ def test_complete_lifecycle(): assert facts[0].terms == ["1234"] def test_snapshot(): - private_key = PrivateKey.from_hex("473b5189232f3f597b5c2f3f9b0d5e28b1ee4e7cce67ec6b7fbf5984157a6b97") + private_key = PrivateKey("ed25519-private/473b5189232f3f597b5c2f3f9b0d5e28b1ee4e7cce67ec6b7fbf5984157a6b97") root = KeyPair.from_private_key(private_key) biscuit_builder = BiscuitBuilder("user({id})", { 'id': "1234" }) @@ -288,10 +279,10 @@ def test_snapshot(): parsedToken = Biscuit.from_base64(token, root.public_key) - authorizer = Authorizer("allow if user({id})", { 'id': "1234" }) + authorizer = AuthorizerBuilder("allow if user({id})", { 'id': "1234" }) print(authorizer) - authorizer.add_token(parsedToken) + authorizer = authorizer.build(parsedToken) snapshot = authorizer.base64_snapshot() parsed = Authorizer.from_base64_snapshot(snapshot) @@ -325,48 +316,63 @@ def test_snapshot(): assert raw_facts[0].name == "u" assert raw_facts[0].terms == ["1234"] +def test_builder_snapshot(): + authorizer = AuthorizerBuilder("allow if user({id})", { 'id': "1234" }) + + print(authorizer) + + snapshot = authorizer.base64_snapshot() + parsed = AuthorizerBuilder.from_base64_snapshot(snapshot) + assert repr(authorizer) == repr(parsed) + + # raw_snapshot() returns a list of bytes, not a `bytes` value directly + return + raw_snapshot = authorizer.raw_snapshot() + parsed_from_raw = AuthorizerBuilder.from_raw_snapshot(raw_snapshot) + assert repr(authorizer) == repr(parsed_from_raw) + def test_public_keys(): # Happy path (hex to bytes and back) - public_key_from_hex = PublicKey.from_hex("acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189") + public_key_from_hex = PublicKey("ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189") public_key_bytes = bytes(public_key_from_hex.to_bytes()) - public_key_from_bytes = PublicKey.from_bytes(public_key_bytes) - assert public_key_from_bytes.to_hex() == "acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189" + public_key_from_bytes = PublicKey.from_bytes(public_key_bytes, Algorithm.Ed25519) + assert repr(public_key_from_bytes) == "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189" # Not valid hex with pytest.raises(ValueError): - PublicKey.from_hex("notarealkey") + PublicKey("notarealkey") # Valid hex, but too short with pytest.raises(ValueError): - PublicKey.from_hex("deadbeef1234") + PublicKey("ed25519/deadbeef1234") # Not enough bytes with pytest.raises(ValueError): - PublicKey.from_bytes(b"1230fw9ia3") + PublicKey.from_bytes(b"1230fw9ia3", Algorithm.Ed25519) def test_private_keys(): # Happy path (hex to bytes and back) - private_key_from_hex = PrivateKey.from_hex("12aca40167fbdd1a11037e9fd440e3d510d9d9dea70a6646aa4aaf84d718d75a") + private_key_from_hex = PrivateKey("ed25519-private/12aca40167fbdd1a11037e9fd440e3d510d9d9dea70a6646aa4aaf84d718d75a") private_key_bytes = bytes(private_key_from_hex.to_bytes()) - private_key_from_bytes = PrivateKey.from_bytes(private_key_bytes) - assert private_key_from_bytes.to_hex() == "12aca40167fbdd1a11037e9fd440e3d510d9d9dea70a6646aa4aaf84d718d75a" + private_key_from_bytes = PrivateKey.from_bytes(private_key_bytes, Algorithm.Ed25519) + assert repr(private_key_from_bytes) == "ed25519-private/12aca40167fbdd1a11037e9fd440e3d510d9d9dea70a6646aa4aaf84d718d75a" # Not valid hex with pytest.raises(ValueError): - PrivateKey.from_hex("notarealkey") + PrivateKey("notarealkey") # Valid hex, but too short with pytest.raises(ValueError): - PrivateKey.from_hex("deadbeef1234") + PrivateKey("ed25519-private/deadbeef1234") # Not enough bytes with pytest.raises(ValueError): - PrivateKey.from_bytes(b"1230fw9ia3") + PrivateKey.from_bytes(b"1230fw9ia3", Algorithm.Ed25519) def test_biscuit_inspection(): kp = KeyPair() - pubkey = PublicKey.from_hex("acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189") + pubkey = PublicKey("ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189") builder = BiscuitBuilder( """ @@ -400,7 +406,7 @@ def test_biscuit_inspection(): assert utoken2.block_source(1) == "test(false);\n" def test_biscuit_revocation_ids(): - public_key = PublicKey.from_hex("acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189") + public_key = PublicKey("ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189") token = Biscuit.from_base64("Ep8BCjUKB3VzZXJfaWQKBWFsaWNlCgVmaWxlMRgDIgoKCAiACBIDGIEIIg4KDAgHEgMYgQgSAxiCCBIkCAASINFHns5iUW6aZiXA0GoqXyrpvFXrquiRGBZjPy3VJPoHGkAC0oew5bIngBkvg1FThYPBf30CAOBksyofzweJnmT_sQ5N4yT1xevHLImmPkJDFyJs9VXrQtroGy_UY5z3WREIGsgBCl4KATAKATEYAyouCgsIBBIDCIMIEgIYABIHCAISAwiDCBIICIAIEgMIhAgSDAgHEgMIhAgSAwiDCDIkCiIKAggbEgcIAhIDCIMIEgYIAxICGAASCwgEEgMIgwgSAhgAEiQIABIg2QCord1Cw30xC2Oea3AuAOPZ2Mvm9-EQmV3zN7zXwQEaQCLnXqIAz3srYrOJKY_g3slzt_nH5U52w8QYEdcuqCxoInvJB5t9BZht4X75MBzM3Aj1AjRVOGmH0ebuQ5GxnwYagwEKGQoFZmlsZTIYAyIOCgwIBxIDGIEIEgMYhQgSJAgAEiAMOoeV68xL1RTh_y4VeK3DUDBP_gnlPSsckzo87Pf7ihpAFAo2Mf7K5VC1HlC5uCK5R_tIXIAHCzRIL6EWzepWAUAWSh0KlZtA_tinJ-L2LAtXY1dgxIjIvw7agO5ZFVjECSIiCiBuSznFYC0NJn8VmDlZmiq1GpBSOERAwHjLZoQJG_24NA==", public_key) utoken = UnverifiedBiscuit.from_base64("Ep8BCjUKB3VzZXJfaWQKBWFsaWNlCgVmaWxlMRgDIgoKCAiACBIDGIEIIg4KDAgHEgMYgQgSAxiCCBIkCAASINFHns5iUW6aZiXA0GoqXyrpvFXrquiRGBZjPy3VJPoHGkAC0oew5bIngBkvg1FThYPBf30CAOBksyofzweJnmT_sQ5N4yT1xevHLImmPkJDFyJs9VXrQtroGy_UY5z3WREIGsgBCl4KATAKATEYAyouCgsIBBIDCIMIEgIYABIHCAISAwiDCBIICIAIEgMIhAgSDAgHEgMIhAgSAwiDCDIkCiIKAggbEgcIAhIDCIMIEgYIAxICGAASCwgEEgMIgwgSAhgAEiQIABIg2QCord1Cw30xC2Oea3AuAOPZ2Mvm9-EQmV3zN7zXwQEaQCLnXqIAz3srYrOJKY_g3slzt_nH5U52w8QYEdcuqCxoInvJB5t9BZht4X75MBzM3Aj1AjRVOGmH0ebuQ5GxnwYagwEKGQoFZmlsZTIYAyIOCgwIBxIDGIEIEgMYhQgSJAgAEiAMOoeV68xL1RTh_y4VeK3DUDBP_gnlPSsckzo87Pf7ihpAFAo2Mf7K5VC1HlC5uCK5R_tIXIAHCzRIL6EWzepWAUAWSh0KlZtA_tinJ-L2LAtXY1dgxIjIvw7agO5ZFVjECSIiCiBuSznFYC0NJn8VmDlZmiq1GpBSOERAwHjLZoQJG_24NA==") @@ -417,7 +423,7 @@ def test_biscuit_revocation_ids(): ] def test_unverified_biscuit_signature_check(): - public_key = PublicKey.from_hex("acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189") + public_key = PublicKey("ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189") utoken = UnverifiedBiscuit.from_base64("Ep8BCjUKB3VzZXJfaWQKBWFsaWNlCgVmaWxlMRgDIgoKCAiACBIDGIEIIg4KDAgHEgMYgQgSAxiCCBIkCAASINFHns5iUW6aZiXA0GoqXyrpvFXrquiRGBZjPy3VJPoHGkAC0oew5bIngBkvg1FThYPBf30CAOBksyofzweJnmT_sQ5N4yT1xevHLImmPkJDFyJs9VXrQtroGy_UY5z3WREIGsgBCl4KATAKATEYAyouCgsIBBIDCIMIEgIYABIHCAISAwiDCBIICIAIEgMIhAgSDAgHEgMIhAgSAwiDCDIkCiIKAggbEgcIAhIDCIMIEgYIAxICGAASCwgEEgMIgwgSAhgAEiQIABIg2QCord1Cw30xC2Oea3AuAOPZ2Mvm9-EQmV3zN7zXwQEaQCLnXqIAz3srYrOJKY_g3slzt_nH5U52w8QYEdcuqCxoInvJB5t9BZht4X75MBzM3Aj1AjRVOGmH0ebuQ5GxnwYagwEKGQoFZmlsZTIYAyIOCgwIBxIDGIEIEgMYhQgSJAgAEiAMOoeV68xL1RTh_y4VeK3DUDBP_gnlPSsckzo87Pf7ihpAFAo2Mf7K5VC1HlC5uCK5R_tIXIAHCzRIL6EWzepWAUAWSh0KlZtA_tinJ-L2LAtXY1dgxIjIvw7agO5ZFVjECSIiCiBuSznFYC0NJn8VmDlZmiq1GpBSOERAwHjLZoQJG_24NA==") @@ -432,13 +438,32 @@ def test_append_on_unverified(): def test_keypair_from_private_key_der(): private_key_der = bytes.fromhex("302e020100300506032b6570042204200499694d0da05dcac40052663e71d50c1539465f8926dfe92033cf7aaad53d65") - private_key_hex = "0499694d0da05dcac40052663e71d50c1539465f8926dfe92033cf7aaad53d65" - kp = KeyPair.from_private_key_der(der=private_key_der) - assert kp.private_key.to_hex() == private_key_hex + private_key_hex = "ed25519-private/0499694d0da05dcac40052663e71d50c1539465f8926dfe92033cf7aaad53d65" + kp = KeyPair.from_private_key(PrivateKey.from_der(der=private_key_der)) + assert repr(kp.private_key) == private_key_hex def test_keypair_from_private_key_pem(): private_key_pem = "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEIASZaU0NoF3KxABSZj5x1QwVOUZfiSbf6SAzz3qq1T1l\n-----END PRIVATE KEY-----" - private_key_hex = "0499694d0da05dcac40052663e71d50c1539465f8926dfe92033cf7aaad53d65" - kp = KeyPair.from_private_key_pem(pem=private_key_pem) - assert kp.private_key.to_hex() == private_key_hex + private_key_hex = "ed25519-private/0499694d0da05dcac40052663e71d50c1539465f8926dfe92033cf7aaad53d65" + kp = KeyPair.from_private_key(PrivateKey.from_pem(pem=private_key_pem)) + assert repr(kp.private_key) == private_key_hex + +def test_extern_func(): + def test(left, right): + if left == right: + return True + else: + return False + + authorizer = AuthorizerBuilder(""" + check if 2.extern::other(); + allow if 1.extern::test(1); + """) + authorizer.register_extern_funcs({ + 'test': test, + 'other': lambda x : x == 2, + }) + policy = authorizer.build_unauthenticated().authorize() + assert policy == 0 + diff --git a/docs/basic-use.rst b/docs/basic-use.rst index bb12f1b..abeda7f 100644 --- a/docs/basic-use.rst +++ b/docs/basic-use.rst @@ -2,7 +2,7 @@ Basic use ========= ->>> from biscuit_auth import Authorizer, Biscuit, BiscuitBuilder, BlockBuilder, KeyPair, PrivateKey, PublicKey, Rule, UnverifiedBiscuit +>>> from biscuit_auth import Authorizer, AuthorizerBuilder, Biscuit, BiscuitBuilder, BlockBuilder, KeyPair, PrivateKey, PublicKey, Rule, UnverifiedBiscuit >>> from datetime import datetime, timedelta, timezone Create and manage keypairs @@ -11,23 +11,23 @@ Create and manage keypairs >>> # random keypair >>> keypair = KeyPair() >>> # serialize a keypair to hexadecimal strings ->>> private_key_str = keypair.private_key.to_hex() ->>> public_key_str = keypair.public_key.to_hex() +>>> private_key_str = repr(keypair.private_key) +>>> public_key_str = repr(keypair.public_key) >>> # parse a private key from an hex string ->>> parsed_private_key = PrivateKey.from_hex("23d9d45b32899eefd4cde9a2caecdd41f0449c95ee1e4c6b53ef38cb957dd690") +>>> parsed_private_key = PrivateKey("ed25519-private/23d9d45b32899eefd4cde9a2caecdd41f0449c95ee1e4c6b53ef38cb957dd690") >>> # parse a public key from an hex string ->>> parsed_public_key = PublicKey.from_hex("9e124fbb46ff99a87219aef4b09f4f6c3b7fd96b7bd279e38af3ef429a101c69") +>>> parsed_public_key = PublicKey("ed25519/9e124fbb46ff99a87219aef4b09f4f6c3b7fd96b7bd279e38af3ef429a101c69") >>> # build a keypair from a private key >>> parsed_keypair = KeyPair.from_private_key(parsed_private_key) ->>> parsed_keypair.private_key.to_hex() -'23d9d45b32899eefd4cde9a2caecdd41f0449c95ee1e4c6b53ef38cb957dd690' ->>> parsed_keypair.public_key.to_hex() -'9e124fbb46ff99a87219aef4b09f4f6c3b7fd96b7bd279e38af3ef429a101c69' +>>> parsed_keypair.private_key +ed25519-private/23d9d45b32899eefd4cde9a2caecdd41f0449c95ee1e4c6b53ef38cb957dd690 +>>> parsed_keypair.public_key +ed25519/9e124fbb46ff99a87219aef4b09f4f6c3b7fd96b7bd279e38af3ef429a101c69 Build a biscuit token --------------------- ->>> private_key = PrivateKey.from_hex("23d9d45b32899eefd4cde9a2caecdd41f0449c95ee1e4c6b53ef38cb957dd690") +>>> private_key = PrivateKey("ed25519-private/23d9d45b32899eefd4cde9a2caecdd41f0449c95ee1e4c6b53ef38cb957dd690") >>> token = BiscuitBuilder(""" ... user({user_id}); ... check if time($time), $time < {expiration}; @@ -41,7 +41,7 @@ Build a biscuit token Biscuit tokens can carry a root key identifier, helping the verifying party select the correct public key amongst several valid keys. This is especially useful when performing key rotation, when multiple keys are active at the same time. ->>> private_key = PrivateKey.from_hex("00731a0f129f088e069d8a8b3523a724bc48136bfc22c916cb754adbf503ad5e") +>>> private_key = PrivateKey("ed25519-private/00731a0f129f088e069d8a8b3523a724bc48136bfc22c916cb754adbf503ad5e") >>> builder = BiscuitBuilder(""" ... user({user_id}); ... check if time($time), $time < {expiration}; @@ -70,10 +70,9 @@ Append a block to a biscuit token Parse and authorize a biscuit token ----------------------------------- ->>> public_key = PublicKey.from_hex("9e124fbb46ff99a87219aef4b09f4f6c3b7fd96b7bd279e38af3ef429a101c69") +>>> public_key = PublicKey("ed25519/9e124fbb46ff99a87219aef4b09f4f6c3b7fd96b7bd279e38af3ef429a101c69") >>> token = Biscuit.from_base64("En0KEwoEMTIzNBgDIgkKBwgKEgMYgAgSJAgAEiCp8D9laR_CXmFmiUlo6zi8L63iapXDxX1evELp4HVaBRpAx3Mkwu2f2AcNq48IZwu-pxACq1stL76DSMGEugmiduuTVwMqLmgKZ4VFgzeydCrYY_Id3MkxgTgjXzEHUH4DDSIiCiB55I7ykL9wQXHRDqUnSgZwCdYNdO7c8LZEj0VH5sy3-Q==", public_key) ->>> authorizer = Authorizer( """ time({now}); allow if user($u); """, { 'now': datetime.now(tz = timezone.utc)} ) ->>> authorizer.add_token(token) +>>> authorizer = AuthorizerBuilder( """ time({now}); allow if user($u); """, { 'now': datetime.now(tz = timezone.utc)} ).build(token) >>> authorizer.authorize() 0 @@ -81,14 +80,13 @@ In order to help with key rotation, biscuit tokens can optionally carry a root k >>> def public_key_fn(kid): ... if kid is None: -... return PublicKey.from_hex("9e124fbb46ff99a87219aef4b09f4f6c3b7fd96b7bd279e38af3ef429a101c69") +... return PublicKey("ed25519/9e124fbb46ff99a87219aef4b09f4f6c3b7fd96b7bd279e38af3ef429a101c69") ... elif kid == 1: -... return PublicKey.from_hex("1d211ddaf521cc45b620431817ba4fe0457be467ba4d724ecf514db3070b53cc") +... return PublicKey("ed25519/1d211ddaf521cc45b620431817ba4fe0457be467ba4d724ecf514db3070b53cc") ... else: ... raise Exception("unknown key identifier") >>> token = Biscuit.from_base64("CAESfQoTCgQxMjM0GAMiCQoHCAoSAxiACBIkCAASII5WVsvM52T91C12wnzButmyzmtGSX_rbM6hCSIJihX2GkDwAcVxTnY8aeMLm-i2R_VzTfIMQZya49ogXO2h2Fg2TJsDcG3udIki9il5PA05lKUwrfPNroS7Qg5e04AyLLcHIiIKII5rh75jrCrgE6Rzw6GVYczMn1IOo287uO4Ef5wp7obY", public_key_fn) ->>> authorizer = Authorizer( """ time({now}); allow if user($u); """, { 'now': datetime.now(tz = timezone.utc)} ) ->>> authorizer.add_token(token) +>>> authorizer = AuthorizerBuilder( """ time({now}); allow if user($u); """, { 'now': datetime.now(tz = timezone.utc)} ).build(token) >>> authorizer.authorize() 0 diff --git a/docs/datalog.rst b/docs/datalog.rst index 86de68a..63b2507 100644 --- a/docs/datalog.rst +++ b/docs/datalog.rst @@ -25,7 +25,7 @@ head($u, "abcd") <- body($u), $u == "abcd" ... check if right($r), {rights}.contains($r); ... """, {'rights': {'read', 'write'}}) // no root key id set -check if right($r), ["read", "write"].contains($r); +check if right($r), {"read", "write"}.contains($r); Rules, checks and policies can also contain parameters for public keys. Those are specified in a separate dictionnary: @@ -34,7 +34,7 @@ Rules, checks and policies can also contain parameters for public keys. Those ar ... check if admin({user}) trusting {rights_service_pubkey} ... """, ... {'user': "abcd" }, -... {'rights_service_pubkey': PublicKey.from_hex("9e124fbb46ff99a87219aef4b09f4f6c3b7fd96b7bd279e38af3ef429a101c69") }) +... {'rights_service_pubkey': PublicKey("ed25519/9e124fbb46ff99a87219aef4b09f4f6c3b7fd96b7bd279e38af3ef429a101c69") }) check if admin("abcd") trusting ed25519/9e124fbb46ff99a87219aef4b09f4f6c3b7fd96b7bd279e38af3ef429a101c69; @@ -117,7 +117,7 @@ bytes(hex:aabbff) >>> Fact("bytes({bytes})", {'bytes': [0xaa, 0xbb, 255]}) bytes(hex:aabbff) >>> Fact("set({set})", {'set': {0, True, "ab", b'\xaa'}}) -set([0, "ab", hex:aa, true]) +set({0, "ab", hex:aa, true}) Inspecting datalog values ------------------------- @@ -133,7 +133,7 @@ Terms of a fact can be extracted to python values.o .. warning:: Extracting sets is not supported yet. ->>> Fact("fact([123])").terms +>>> Fact("fact({123})").terms Traceback (most recent call last): ... pyo3_runtime.PanicException: not yet implemented diff --git a/pyproject.toml b/pyproject.toml index f69ca45..9fd8698 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,9 @@ [project] -name="biscuit-python" +name="biscuit_auth" description="Python bindings for the biscuit auth platform" readme="README.md" license= { file = "LICENSE" } +dynamic = ["version"] [project.urls] homepage = "https://biscuitsec.org/usage/python" diff --git a/src/lib.rs b/src/lib.rs index 2540e11..bd2be88 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,10 @@ // There seem to be false positives with pyo3 #![allow(clippy::borrow_deref_ref)] +#![allow(unexpected_cfgs)] +#![allow(clippy::useless_conversion)] +use ::biscuit_auth::builder::MapKey; +use ::biscuit_auth::datalog::ExternFunc; +use ::biscuit_auth::AuthorizerBuilder; use ::biscuit_auth::RootKeyProvider; use ::biscuit_auth::UnverifiedBiscuit; use chrono::DateTime; @@ -8,6 +13,9 @@ use chrono::TimeZone; use chrono::Utc; use std::collections::BTreeSet; use std::collections::HashMap; +use std::ops::Deref; +use std::str::FromStr; +use std::sync::Arc; use ::biscuit_auth::{ builder, error, Authorizer, AuthorizerLimits, Biscuit, KeyPair, PrivateKey, PublicKey, @@ -46,6 +54,30 @@ create_exception!( pyo3::exceptions::PyException ); +#[pyclass(eq, eq_int, name = "Algorithm")] +#[derive(PartialEq, Eq, Clone, Copy)] +pub enum PyAlgorithm { + Ed25519, + Secp256r1, +} + +impl From for PyAlgorithm { + fn from(value: builder::Algorithm) -> Self { + match value { + builder::Algorithm::Ed25519 => Self::Ed25519, + builder::Algorithm::Secp256r1 => Self::Secp256r1, + } + } +} +impl From for builder::Algorithm { + fn from(value: PyAlgorithm) -> Self { + match value { + PyAlgorithm::Ed25519 => Self::Ed25519, + PyAlgorithm::Secp256r1 => Self::Secp256r1, + } + } +} + struct PyKeyProvider { py_value: PyObject, } @@ -81,7 +113,7 @@ impl RootKeyProvider for PyKeyProvider { /// :param scope_parameters: public keys for the public key parameters in the datalog snippet /// :type scope_parameters: dict, optional #[pyclass(name = "BiscuitBuilder")] -pub struct PyBiscuitBuilder(builder::BiscuitBuilder); +pub struct PyBiscuitBuilder(Option); #[pymethods] impl PyBiscuitBuilder { @@ -100,7 +132,7 @@ impl PyBiscuitBuilder { parameters: Option>, scope_parameters: Option>, ) -> PyResult { - let mut builder = PyBiscuitBuilder(builder::BiscuitBuilder::new()); + let mut builder = PyBiscuitBuilder(Some(builder::BiscuitBuilder::new())); if let Some(source) = source { builder.add_code(&source, parameters, scope_parameters)?; } @@ -118,6 +150,7 @@ impl PyBiscuitBuilder { Ok(PyBiscuit( self.0 .clone() + .expect("builder already consumed") .build(&keypair) .map_err(|e| BiscuitBuildError::new_err(e.to_string()))?, )) @@ -157,9 +190,15 @@ impl PyBiscuitBuilder { scope_params = HashMap::new(); } - self.0 - .add_code_with_params(source, params, scope_params) - .map_err(|e| DataLogError::new_err(e.to_string())) + self.0 = Some( + self.0 + .take() + .expect("builder already consumed") + .code_with_params(source, params, scope_params) + .map_err(|e| DataLogError::new_err(e.to_string()))?, + ); + + Ok(()) } /// Add a single fact to the builder. A single fact can be built with @@ -168,9 +207,15 @@ impl PyBiscuitBuilder { /// :param fact: a datalog fact /// :type fact: Fact pub fn add_fact(&mut self, fact: &PyFact) -> PyResult<()> { - self.0 - .add_fact(fact.0.clone()) - .map_err(|e| DataLogError::new_err(e.to_string())) + self.0 = Some( + self.0 + .take() + .expect("builder already consumed") + .fact(fact.0.clone()) + .map_err(|e| DataLogError::new_err(e.to_string()))?, + ); + + Ok(()) } /// Add a single rule to the builder. A single rule can be built with @@ -179,9 +224,15 @@ impl PyBiscuitBuilder { /// :param rule: a datalog rule /// :type rule: Rule pub fn add_rule(&mut self, rule: &PyRule) -> PyResult<()> { - self.0 - .add_rule(rule.0.clone()) - .map_err(|e| DataLogError::new_err(e.to_string())) + self.0 = Some( + self.0 + .take() + .expect("builder already consumed") + .rule(rule.0.clone()) + .map_err(|e| DataLogError::new_err(e.to_string()))?, + ); + + Ok(()) } /// Add a single check to the builder. A single check can be built with @@ -190,29 +241,51 @@ impl PyBiscuitBuilder { /// :param check: a datalog check /// :type check: Check pub fn add_check(&mut self, check: &PyCheck) -> PyResult<()> { - self.0 - .add_check(check.0.clone()) - .map_err(|e| DataLogError::new_err(e.to_string())) + self.0 = Some( + self.0 + .take() + .expect("builder already consumed") + .check(check.0.clone()) + .map_err(|e| DataLogError::new_err(e.to_string()))?, + ); + + Ok(()) } /// Merge a `BlockBuilder` in this `BiscuitBuilder`. The `BlockBuilder` parameter will not be modified /// /// :param builder: a datalog BlockBuilder /// :type builder: BlockBuilder - pub fn merge(&mut self, builder: &PyBlockBuilder) { - self.0.merge(builder.0.clone()) + pub fn merge(&mut self, builder: &PyBlockBuilder) -> PyResult<()> { + self.0 = Some( + self.0 + .take() + .expect("builder already consumed") + .merge(builder.0.clone().expect("builder already consumed")), + ); + + Ok(()) } /// Set the root key identifier for this `BiscuitBuilder` /// /// :param root_key_id: the root key identifier /// :type root_key_id: int - pub fn set_root_key_id(&mut self, root_key_id: u32) { - self.0.set_root_key_id(root_key_id) + pub fn set_root_key_id(&mut self, root_key_id: u32) -> PyResult<()> { + self.0 = Some( + self.0 + .take() + .expect("builder already consumed") + .root_key_id(root_key_id), + ); + Ok(()) } fn __repr__(&self) -> String { - self.0.to_string() + match self.0 { + Some(ref b) => b.to_string(), + None => "_ BiscuitBuilder already consumed_".to_string(), + } } } @@ -314,7 +387,7 @@ impl PyBiscuit { /// :rtype: Biscuit pub fn append(&self, block: &PyBlockBuilder) -> PyResult { self.0 - .append(block.0.clone()) + .append(block.0.clone().expect("builder already consumed")) .map_err(|e| BiscuitBuildError::new_err(e.to_string())) .map(PyBiscuit) } @@ -342,8 +415,8 @@ impl PyBiscuit { /// :type parameters: dict, optional /// :param scope_parameters: public keys for the public key parameters in the datalog snippet /// :type scope_parameters: dict, optional -#[pyclass(name = "Authorizer")] -pub struct PyAuthorizer(Authorizer); +#[pyclass(name = "AuthorizerBuilder")] +pub struct PyAuthorizerBuilder(Option); #[pyclass(name = "AuthorizerLimits")] #[derive(Clone)] @@ -357,7 +430,7 @@ pub struct PyAuthorizerLimits { } #[pymethods] -impl PyAuthorizer { +impl PyAuthorizerBuilder { /// Create a new authorizer from a datalog snippet and optional parameter values /// /// :param source: a datalog snippet @@ -372,8 +445,8 @@ impl PyAuthorizer { source: Option, parameters: Option>, scope_parameters: Option>, - ) -> PyResult { - let mut builder = PyAuthorizer(Authorizer::new()); + ) -> PyResult { + let mut builder = PyAuthorizerBuilder(Some(AuthorizerBuilder::new())); if let Some(source) = source { builder.add_code(&source, parameters, scope_parameters)?; } @@ -414,9 +487,15 @@ impl PyAuthorizer { scope_params = HashMap::new(); } - self.0 - .add_code_with_params(source, params, scope_params) - .map_err(|e| DataLogError::new_err(e.to_string())) + self.0 = Some( + self.0 + .take() + .expect("builder already consumed") + .code_with_params(source, params, scope_params) + .map_err(|e| DataLogError::new_err(e.to_string()))?, + ); + + Ok(()) } /// Add a single fact to the authorizer. A single fact can be built with @@ -425,9 +504,15 @@ impl PyAuthorizer { /// :param fact: a datalog fact /// :type fact: Fact pub fn add_fact(&mut self, fact: &PyFact) -> PyResult<()> { - self.0 - .add_fact(fact.0.clone()) - .map_err(|e| DataLogError::new_err(e.to_string())) + self.0 = Some( + self.0 + .take() + .expect("builder already consumed") + .fact(fact.0.clone()) + .map_err(|e| DataLogError::new_err(e.to_string()))?, + ); + + Ok(()) } /// Add a single rule to the authorizer. A single rule can be built with @@ -436,9 +521,15 @@ impl PyAuthorizer { /// :param rule: a datalog rule /// :type rule: Rule pub fn add_rule(&mut self, rule: &PyRule) -> PyResult<()> { - self.0 - .add_rule(rule.0.clone()) - .map_err(|e| DataLogError::new_err(e.to_string())) + self.0 = Some( + self.0 + .take() + .expect("builder already consumed") + .rule(rule.0.clone()) + .map_err(|e| DataLogError::new_err(e.to_string()))?, + ); + + Ok(()) } /// Add a single check to the authorizer. A single check can be built with @@ -447,9 +538,15 @@ impl PyAuthorizer { /// :param check: a datalog check /// :type check: Check pub fn add_check(&mut self, check: &PyCheck) -> PyResult<()> { - self.0 - .add_check(check.0.clone()) - .map_err(|e| DataLogError::new_err(e.to_string())) + self.0 = Some( + self.0 + .take() + .expect("builder already consumed") + .check(check.0.clone()) + .map_err(|e| DataLogError::new_err(e.to_string()))?, + ); + + Ok(()) } /// Add a single policy to the authorizer. A single policy can be built with @@ -458,65 +555,224 @@ impl PyAuthorizer { /// :param policy: a datalog policy /// :type policy: Policy pub fn add_policy(&mut self, policy: &PyPolicy) -> PyResult<()> { - self.0 - .add_policy(policy.0.clone()) - .map_err(|e| DataLogError::new_err(e.to_string())) + self.0 = Some( + self.0 + .take() + .expect("builder already consumed") + .policy(policy.0.clone()) + .map_err(|e| DataLogError::new_err(e.to_string()))?, + ); + + Ok(()) } - /// Returns the runtime limits of the authorizer - /// - /// Those limits cover all the executions under the `authorize`, `query` and `query_all` methods - pub fn limits(&self) -> PyAuthorizerLimits { - let limits = self.0.limits(); - PyAuthorizerLimits { + pub fn limits(&self) -> PyResult { + let limits = self + .0 + .as_ref() + .expect("builder already consumed") + .limits() + .clone(); + + Ok(PyAuthorizerLimits { max_facts: limits.max_facts, max_iterations: limits.max_iterations, max_time: Duration::from_std(limits.max_time).expect("Duration out of range"), - } + }) } /// Sets the runtime limits of the authorizer /// /// Those limits cover all the executions under the `authorize`, `query` and `query_all` methods - pub fn set_limits(&mut self, limits: &PyAuthorizerLimits) { - self.0.set_limits(AuthorizerLimits { - max_facts: limits.max_facts, - max_iterations: limits.max_iterations, - max_time: Duration::to_std(&limits.max_time).expect("Duration out of range"), - }) + pub fn set_limits(&mut self, limits: &PyAuthorizerLimits) -> PyResult<()> { + self.0 = Some(self.0.take().expect("builder already consumed").set_limits( + AuthorizerLimits { + max_facts: limits.max_facts, + max_iterations: limits.max_iterations, + max_time: Duration::to_std(&limits.max_time).expect("Duration out of range"), + }, + )); + + Ok(()) } /// adds a fact `time($current_time)` with the current time - pub fn set_time(&mut self) { - self.0.set_time(); + pub fn set_time(&mut self) -> PyResult<()> { + self.0 = Some(self.0.take().expect("builder already consumed").time()); + Ok(()) } /// Merge another `Authorizer` in this `Authorizer`. The `Authorizer` argument will not be modified /// /// :param builder: an Authorizer /// :type builder: Authorizer - pub fn merge(&mut self, builder: &PyAuthorizer) { - self.0.merge(builder.0.clone()) + pub fn merge(&mut self, builder: &PyAuthorizerBuilder) -> PyResult<()> { + self.0 = Some( + self.0 + .take() + .expect("builder already consumed") + .merge(builder.0.clone().expect("builder already consumed")), + ); + Ok(()) } /// Merge a `BlockBuilder` in this `Authorizer`. The `BlockBuilder` will not be modified /// /// :param builder: a BlockBuilder /// :type builder: BlockBuilder - pub fn merge_block(&mut self, builder: &PyBlockBuilder) { - self.0.merge_block(builder.0.clone()) + pub fn merge_block(&mut self, builder: &PyBlockBuilder) -> PyResult<()> { + self.0 = Some( + self.0 + .take() + .expect("builder already consumed") + .merge_block(builder.0.clone().expect("builder already consumed")), + ); + Ok(()) + } + + pub fn register_extern_func(&mut self, name: &str, func: PyObject) -> PyResult<()> { + self.0 = Some( + self.0 + .take() + .expect("builder already consumed") + .register_extern_func( + name.to_string(), + ExternFunc::new(Arc::new(move |left, right| { + Python::with_gil(|py| { + let bound = func.bind(py); + if bound.is_callable() { + let left = term_to_py(&left).map_err(|e| e.to_string())?; + let result = match right { + Some(right) => { + let right = + term_to_py(&right).map_err(|e| e.to_string())?; + bound.call1((left, right)).map_err(|e| e.to_string())? + } + None => bound.call1((left,)).map_err(|e| e.to_string())?, + }; + let py_result: PyTerm = + result.extract().map_err(|e| e.to_string())?; + Ok(py_result.to_term().map_err(|e| e.to_string())?) + } else { + Err("expected a function".to_string()) + } + }) + })), + ), + ); + Ok(()) + } + + pub fn register_extern_funcs(&mut self, funcs: HashMap) -> PyResult<()> { + for (name, func) in funcs { + self.register_extern_func(&name, func)?; + } + + Ok(()) + } + + pub fn set_extern_funcs(&mut self, funcs: HashMap) -> PyResult<()> { + self.0 = Some( + self.0 + .take() + .expect("builder already consumed") + .set_extern_funcs(HashMap::new()), + ); + self.register_extern_funcs(funcs) + } + + /// Take a snapshot of the authorizer builder and return it, base64-encoded + /// + /// :return: a snapshot as a base64-encoded string + /// :rtype: str + pub fn base64_snapshot(&self) -> PyResult { + self.0 + .clone() + .expect("builder already consumed") + .to_base64_snapshot() + .map_err(|error| BiscuitSerializationError::new_err(error.to_string())) + } + + /// Take a snapshot of the authorizer and return it, as raw bytes + /// + /// :return: a snapshot as raw bytes + /// :rtype: bytes + pub fn raw_snapshot(&self) -> PyResult> { + self.0 + .clone() + .expect("builder already consumed") + .to_raw_snapshot() + .map_err(|error| BiscuitSerializationError::new_err(error.to_string())) + } + + /// Build an authorizer builder from a base64-encoded snapshot + /// + /// :param input: base64-encoded snapshot + /// :type input: str + /// :return: the authorizer builder + /// :rtype: AuthorizerBuilder + #[classmethod] + pub fn from_base64_snapshot(_: &Bound, input: &str) -> PyResult { + Ok(PyAuthorizerBuilder(Some( + AuthorizerBuilder::from_base64_snapshot(input) + .map_err(|error| BiscuitValidationError::new_err(error.to_string()))?, + ))) } - /// Add a `Biscuit` to this `Authorizer` + /// Build an authorizer builder from a snapshot's raw bytes + /// + /// :param input: raw snapshot bytes + /// :type input: bytes + /// :return: the authorizer builder + /// :rtype: AuthorizerBuilder + #[classmethod] + pub fn from_raw_snapshot(_: &Bound, input: &[u8]) -> PyResult { + Ok(PyAuthorizerBuilder(Some( + AuthorizerBuilder::from_raw_snapshot(input) + .map_err(|error| BiscuitValidationError::new_err(error.to_string()))?, + ))) + } + + /// Build the `AuthorizerBuilder` with the provided `Biscuit` /// /// :param token: the token to authorize /// :type token: Biscuit - pub fn add_token(&mut self, token: &PyBiscuit) -> PyResult<()> { - self.0 - .add_token(&token.0) - .map_err(|e| BiscuitValidationError::new_err(e.to_string())) + /// :return: the authorizer + /// :rtype Authorizer + pub fn build(&self, token: &PyBiscuit) -> PyResult { + Ok(PyAuthorizer( + self.0 + .clone() + .expect("builder already consumed") + .build(&token.0) + .map_err(|e| BiscuitValidationError::new_err(e.to_string()))?, + )) + } + + pub fn build_unauthenticated(&self) -> PyResult { + Ok(PyAuthorizer( + self.0 + .clone() + .expect("builder already consumed") + .build_unauthenticated() + .map_err(|e| BiscuitValidationError::new_err(e.to_string()))?, + )) + } + + fn __repr__(&self) -> String { + match self.0 { + Some(ref x) => x.to_string(), + None => "_ consumed AuthorizerBuilder _".to_string(), + } } +} +/// The Authorizer verifies a request according to its policies and the provided token +#[pyclass(name = "Authorizer")] +pub struct PyAuthorizer(Authorizer); + +#[pymethods] +impl PyAuthorizer { /// Runs the authorization checks and policies /// /// Returns the index of the matching allow policy, or an error containing the matching deny @@ -614,7 +870,7 @@ impl PyAuthorizer { /// :type scope_parameters: dict, optional #[pyclass(name = "BlockBuilder")] #[derive(Clone)] -pub struct PyBlockBuilder(builder::BlockBuilder); +pub struct PyBlockBuilder(Option); #[pymethods] impl PyBlockBuilder { @@ -633,7 +889,7 @@ impl PyBlockBuilder { parameters: Option>, scope_parameters: Option>, ) -> PyResult { - let mut builder = PyBlockBuilder(builder::BlockBuilder::new()); + let mut builder = PyBlockBuilder(Some(builder::BlockBuilder::new())); if let Some(source) = source { builder.add_code(&source, parameters, scope_parameters)?; } @@ -646,9 +902,15 @@ impl PyBlockBuilder { /// :param fact: a datalog fact /// :type fact: Fact pub fn add_fact(&mut self, fact: &PyFact) -> PyResult<()> { - self.0 - .add_fact(fact.0.clone()) - .map_err(|e| DataLogError::new_err(e.to_string())) + self.0 = Some( + self.0 + .take() + .expect("builder already consumed") + .fact(fact.0.clone()) + .map_err(|e| DataLogError::new_err(e.to_string()))?, + ); + + Ok(()) } /// Add a single rule to the builder. A single rule can be built with @@ -657,9 +919,15 @@ impl PyBlockBuilder { /// :param rule: a datalog rule /// :type rule: Rule pub fn add_rule(&mut self, rule: &PyRule) -> PyResult<()> { - self.0 - .add_rule(rule.0.clone()) - .map_err(|e| DataLogError::new_err(e.to_string())) + self.0 = Some( + self.0 + .take() + .expect("builder already consumed") + .rule(rule.0.clone()) + .map_err(|e| DataLogError::new_err(e.to_string()))?, + ); + + Ok(()) } /// Add a single check to the builder. A single check can be built with @@ -668,17 +936,29 @@ impl PyBlockBuilder { /// :param check: a datalog check /// :type check: Check pub fn add_check(&mut self, check: &PyCheck) -> PyResult<()> { - self.0 - .add_check(check.0.clone()) - .map_err(|e| DataLogError::new_err(e.to_string())) + self.0 = Some( + self.0 + .take() + .expect("builder already consumed") + .check(check.0.clone()) + .map_err(|e| DataLogError::new_err(e.to_string()))?, + ); + + Ok(()) } /// Merge a `BlockBuilder` in this `BlockBuilder`. The `BlockBuilder` will not be modified /// /// :param builder: a datalog BlockBuilder /// :type builder: BlockBuilder - pub fn merge(&mut self, builder: &PyBlockBuilder) { - self.0.merge(builder.0.clone()) + pub fn merge(&mut self, builder: &mut PyBlockBuilder) -> PyResult<()> { + self.0 = Some( + self.0 + .take() + .expect("builder already consumed") + .merge(builder.0.take().expect("builder already consumed")), + ); + Ok(()) } /// Add code to the builder, using the provided parameters. @@ -715,13 +995,21 @@ impl PyBlockBuilder { scope_params = HashMap::new(); } - self.0 - .add_code_with_params(source, params, scope_params) - .map_err(|e| DataLogError::new_err(e.to_string())) + self.0 = Some( + self.0 + .take() + .expect("builder already consumed") + .code_with_params(source, params, scope_params) + .map_err(|e| DataLogError::new_err(e.to_string()))?, + ); + Ok(()) } fn __repr__(&self) -> String { - self.0.to_string() + match self.0 { + Some(ref b) => b.to_string(), + None => "_ BlockBuilder already consumed _".to_string(), + } } } @@ -748,32 +1036,6 @@ impl PyKeyPair { PyKeyPair(KeyPair::from(&private_key.0)) } - /// Generate a keypair from a DER buffer - /// - /// :param bytes: private key bytes in DER format - /// :type private_key: PrivateKey - /// :return: the corresponding keypair - /// :rtype: KeyPair - #[classmethod] - pub fn from_private_key_der(_: &Bound, der: &[u8]) -> PyResult { - let kp = KeyPair::from_private_key_der(der) - .map_err(|e: error::Format| pyo3::exceptions::PyValueError::new_err(e.to_string()))?; - Ok(PyKeyPair(kp)) - } - - /// Generate a keypair from a PEM buffer - /// - /// :param bytes: private key bytes in PEM format - /// :type private_key: PrivateKey - /// :return: the corresponding keypair - /// :rtype: KeyPair - #[classmethod] - pub fn from_private_key_pem(_: &Bound, pem: &str) -> PyResult { - let kp = KeyPair::from_private_key_pem(pem) - .map_err(|e: error::Format| pyo3::exceptions::PyValueError::new_err(e.to_string()))?; - Ok(PyKeyPair(kp)) - } - /// The public key part #[getter] pub fn public_key(&self) -> PyPublicKey { @@ -804,7 +1066,7 @@ impl PyPublicKey { /// /// :return: the public key bytes /// :rtype: list - pub fn to_bytes(&self) -> [u8; 32] { + pub fn to_bytes(&self) -> Vec { self.0.to_bytes() } @@ -812,8 +1074,8 @@ impl PyPublicKey { /// /// :return: the public key bytes (hex-encoded) /// :rtype: str - pub fn to_hex(&self) -> String { - hex::encode(self.0.to_bytes()) + fn __repr__(&self) -> String { + self.0.to_string() } /// Deserializes a public key from raw bytes @@ -823,8 +1085,8 @@ impl PyPublicKey { /// :return: the public key /// :rtype: PublicKey #[classmethod] - pub fn from_bytes(_: &Bound, data: &[u8]) -> PyResult { - match PublicKey::from_bytes(data) { + pub fn from_bytes(_: &Bound, data: &[u8], alg: &PyAlgorithm) -> PyResult { + match PublicKey::from_bytes(data, builder::Algorithm::from(*alg)) { Ok(key) => Ok(PyPublicKey(key)), Err(error) => Err(PyValueError::new_err(error.to_string())), } @@ -836,13 +1098,37 @@ impl PyPublicKey { /// :type data: str /// :return: the public key /// :rtype: PublicKey + #[new] + pub fn new(data: &str) -> PyResult { + match PublicKey::from_str(data) { + Ok(key) => Ok(PyPublicKey(key)), + Err(error) => Err(PyValueError::new_err(error.to_string())), + } + } + + /// Deserializes a public key from a der buffer + /// + /// :param der: the der buffer + /// :type der: bytes + /// :return: the public key + /// :rtype: PublicKey + #[classmethod] + pub fn from_der(_: &Bound, der: &[u8]) -> PyResult { + match PublicKey::from_der(der) { + Ok(key) => Ok(PyPublicKey(key)), + Err(error) => Err(PyValueError::new_err(error.to_string())), + } + } + + /// Deserializes a public key from a PEM string + /// + /// :param data: the der buffer + /// :type pem: string + /// :return: the public key + /// :rtype: PublicKey #[classmethod] - pub fn from_hex(_: &Bound, data: &str) -> PyResult { - let data = match hex::decode(data) { - Ok(data) => data, - Err(error) => return Err(PyValueError::new_err(error.to_string())), - }; - match PublicKey::from_bytes(&data) { + pub fn from_pem(_: &Bound, pem: &str) -> PyResult { + match PublicKey::from_pem(pem) { Ok(key) => Ok(PyPublicKey(key)), Err(error) => Err(PyValueError::new_err(error.to_string())), } @@ -860,16 +1146,16 @@ impl PyPrivateKey { /// /// :return: the public key bytes /// :rtype: list - pub fn to_bytes(&self) -> [u8; 32] { - self.0.to_bytes() + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes().deref().clone() } /// Serializes a private key to a hexadecimal string /// /// :return: the private key bytes (hex-encoded) /// :rtype: str - pub fn to_hex(&self) -> String { - hex::encode(self.0.to_bytes()) + fn __repr__(&self) -> String { + self.0.to_prefixed_string() } /// Deserializes a private key from raw bytes @@ -879,8 +1165,8 @@ impl PyPrivateKey { /// :return: the private key /// :rtype: PrivateKey #[classmethod] - pub fn from_bytes(_: &Bound, data: &[u8]) -> PyResult { - match PrivateKey::from_bytes(data) { + pub fn from_bytes(_: &Bound, data: &[u8], alg: &PyAlgorithm) -> PyResult { + match PrivateKey::from_bytes(data, builder::Algorithm::from(*alg)) { Ok(key) => Ok(PyPrivateKey(key)), Err(error) => Err(PyValueError::new_err(error.to_string())), } @@ -892,13 +1178,37 @@ impl PyPrivateKey { /// :type data: str /// :return: the private key /// :rtype: PrivateKey + #[new] + pub fn new(data: &str) -> PyResult { + match PrivateKey::from_str(data) { + Ok(key) => Ok(PyPrivateKey(key)), + Err(error) => Err(PyValueError::new_err(error.to_string())), + } + } + + /// Deserializes a private key from a der buffer + /// + /// :param der: the der buffer + /// :type der: bytes + /// :return: the Private key + /// :rtype: PrivateKey #[classmethod] - pub fn from_hex(_: &Bound, data: &str) -> PyResult { - let data = match hex::decode(data) { - Ok(data) => data, - Err(error) => return Err(PyValueError::new_err(error.to_string())), - }; - match PrivateKey::from_bytes(&data) { + pub fn from_der(_: &Bound, der: &[u8]) -> PyResult { + match PrivateKey::from_der(der) { + Ok(key) => Ok(PyPrivateKey(key)), + Err(error) => Err(PyValueError::new_err(error.to_string())), + } + } + + /// Deserializes a private key from a PEM string + /// + /// :param data: the der buffer + /// :type pem: string + /// :return: the Private key + /// :rtype: PrivateKey + #[classmethod] + pub fn from_pem(_: &Bound, pem: &str) -> PyResult { + match PrivateKey::from_pem(pem) { Ok(key) => Ok(PyPrivateKey(key)), Err(error) => Err(PyValueError::new_err(error.to_string())), } @@ -926,6 +1236,17 @@ fn inner_term_to_py(t: &builder::Term, py: Python<'_>) -> PyResult> { } } +fn term_to_py(t: &builder::Term) -> PyResult> { + Python::with_gil(|py| match t { + builder::Term::Parameter(_) => Err(DataLogError::new_err("Invalid term value".to_string())), + builder::Term::Variable(_) => Err(DataLogError::new_err("Invalid term value".to_string())), + builder::Term::Set(_vs) => todo!(), + builder::Term::Array(_vs) => todo!(), + builder::Term::Map(_vs) => todo!(), + term => inner_term_to_py(term, py), + }) +} + /// Wrapper for a non-naïve python date #[derive(FromPyObject)] pub struct PyDate(Py); @@ -940,7 +1261,7 @@ impl Eq for PyDate {} impl PartialOrd for PyDate { fn partial_cmp(&self, other: &Self) -> Option { - self.0.to_string().partial_cmp(&other.0.to_string()) + Some(self.cmp(other)) } } @@ -955,6 +1276,9 @@ impl Ord for PyDate { pub enum PyTerm { Simple(NestedPyTerm), Set(BTreeSet), + Array(Vec), + StrDict(HashMap), + IntDict(HashMap), } impl NestedPyTerm { @@ -986,6 +1310,21 @@ impl PyTerm { .map(|s| s.to_term()) .collect::>() .map(builder::Term::Set), + PyTerm::Array(vs) => vs + .iter() + .map(|s| s.to_term()) + .collect::>() + .map(builder::Term::Array), + PyTerm::StrDict(vs) => vs + .iter() + .map(|(k, v)| Ok((MapKey::Str(k.to_string()), v.to_term()?))) + .collect::>() + .map(builder::Term::Map), + PyTerm::IntDict(vs) => vs + .iter() + .map(|(k, v)| Ok((MapKey::Integer(*k), v.to_term()?))) + .collect::>() + .map(builder::Term::Map), } } } @@ -1026,23 +1365,7 @@ impl PyFact { /// The fact terms #[getter] pub fn terms(&self) -> PyResult> { - self.0 - .predicate - .terms - .iter() - .map(|t| { - Python::with_gil(|py| match t { - builder::Term::Parameter(_) => { - Err(DataLogError::new_err("Invalid term value".to_string())) - } - builder::Term::Variable(_) => { - Err(DataLogError::new_err("Invalid term value".to_string())) - } - builder::Term::Set(_vs) => todo!(), - term => inner_term_to_py(term, py), - }) - }) - .collect() + self.0.predicate.terms.iter().map(term_to_py).collect() } fn __repr__(&self) -> String { @@ -1247,7 +1570,7 @@ impl PyUnverifiedBiscuit { /// :rtype: Biscuit pub fn append(&self, block: &PyBlockBuilder) -> PyResult { self.0 - .append(block.0.clone()) + .append(block.0.clone().expect("builder already consumed")) .map_err(|e| BiscuitBuildError::new_err(e.to_string())) .map(PyUnverifiedBiscuit) } @@ -1263,16 +1586,12 @@ impl PyUnverifiedBiscuit { } pub fn verify(&self, root: PyObject) -> PyResult { - // TODO replace with UnverifiedBiscuit::check_signature once https://github.com/biscuit-auth/biscuit-rust/pull/189 is merged and released - - let data = self - .0 - .to_vec() - .map_err(|e| BiscuitValidationError::new_err(e.to_string()))?; - match Biscuit::from(data, PyKeyProvider { py_value: root }) { - Ok(biscuit) => Ok(PyBiscuit(biscuit)), - Err(error) => Err(BiscuitValidationError::new_err(error.to_string())), - } + Ok(PyBiscuit( + self.0 + .clone() + .verify(PyKeyProvider { py_value: root }) + .map_err(|e| BiscuitValidationError::new_err(e.to_string()))?, + )) } } @@ -1286,16 +1605,27 @@ pub fn biscuit_auth(py: Python, m: &Bound) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; m.add("DataLogError", py.get_type_bound::())?; - m.add("AuthorizationError", py.get_type_bound::())?; - m.add("BiscuitBuildError", py.get_type_bound::())?; - m.add("BiscuitBlockError", py.get_type_bound::())?; + m.add( + "AuthorizationError", + py.get_type_bound::(), + )?; + m.add( + "BiscuitBuildError", + py.get_type_bound::(), + )?; + m.add( + "BiscuitBlockError", + py.get_type_bound::(), + )?; m.add( "BiscuitValidationError", py.get_type_bound::(),