diff --git a/biscuit_test.py b/biscuit_test.py index 8d49186..ac308fa 100644 --- a/biscuit_test.py +++ b/biscuit_test.py @@ -217,7 +217,7 @@ def choose_key(kid): elif kid == 1: return root.public_key else: - raise Exception("Unknown key identifier") + raise Exception("Unknown key identifier") biscuit_builder0 = BiscuitBuilder("user({id})", { 'id': "1234" }) token0 = biscuit_builder0.build(other_root.private_key).to_base64() @@ -386,10 +386,10 @@ def test_biscuit_inspection(): builder.set_root_key_id(42) token2 = builder.build(kp.private_key).append(BlockBuilder('test(false);')) print(token2.to_base64()) - + utoken1 = UnverifiedBiscuit.from_base64(token1.to_base64()) utoken2 = UnverifiedBiscuit.from_base64(token2.to_base64()) - + assert utoken1.root_key_id() is None assert utoken2.root_key_id() == 42 @@ -463,3 +463,33 @@ def test(left, right): policy = authorizer.build_unauthenticated().authorize() assert policy == 0 +def test_append_third_party(): + root_kp = KeyPair() + biscuit_builder = BiscuitBuilder("user({id})", { 'id': "1234" }) + biscuit = biscuit_builder.build(root_kp.private_key) + + third_party_kp = KeyPair() + new_block = BlockBuilder("external_fact({fact})", { 'fact': "56" }) + third_party_request = biscuit.third_party_request() + third_party_block = third_party_request.create_block(third_party_kp.private_key, new_block) + biscuit_with_third_party_block = biscuit.append_third_party(third_party_kp.public_key, third_party_block) + + assert repr(biscuit_with_third_party_block.block_external_key(1)) == repr(third_party_kp.public_key) + +def test_read_third_party_facts(): + root_kp = KeyPair() + biscuit_builder = BiscuitBuilder("user({id})", { 'id': "1234" }) + biscuit = biscuit_builder.build(root_kp.private_key) + + third_party_kp = KeyPair() + new_block = BlockBuilder("external_fact({fact})", { 'fact': "56" }) + third_party_request = biscuit.third_party_request() + third_party_block = third_party_request.create_block(third_party_kp.private_key, new_block) + biscuit_with_third_party_block = biscuit.append_third_party(third_party_kp.public_key, third_party_block) + + + authorizer = AuthorizerBuilder("allow if true").build(biscuit_with_third_party_block) + rule = Rule(f"ext_fact($fact) <- external_fact($fact) trusting {third_party_kp.public_key}") + facts = authorizer.query(rule) + + assert facts[0].terms[0] == "56" diff --git a/src/lib.rs b/src/lib.rs index cf049dd..2bdb8a4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,8 @@ use ::biscuit_auth::builder::MapKey; use ::biscuit_auth::datalog::ExternFunc; use ::biscuit_auth::AuthorizerBuilder; use ::biscuit_auth::RootKeyProvider; +use ::biscuit_auth::ThirdPartyBlock; +use ::biscuit_auth::ThirdPartyRequest; use ::biscuit_auth::UnverifiedBiscuit; use chrono::DateTime; use chrono::Duration; @@ -397,6 +399,36 @@ impl PyBiscuit { .map(PyBiscuit) } + /// Create a new `Biscuit` by appending a third-party attenuation block + /// + /// :param external_key: the public key of the third-party that signed the block. + /// :type external_key: PublicKey + /// :param block: the third party block to append + /// :type block: ThirdPartyBlock + /// :return: the attenuated biscuit + /// :rtype: Biscuit + pub fn append_third_party( + &self, + external_key: &PyPublicKey, + block: &PyThirdPartyBlock, + ) -> PyResult { + self.0 + .append_third_party(external_key.0, block.0.clone()) + .map_err(|e| BiscuitBuildError::new_err(e.to_string())) + .map(PyBiscuit) + } + + /// Create a third-party request for generating third-party blocks. + /// + /// :return: the third-party request + /// :rtype: ThirdPartyRequest + pub fn third_party_request(&self) -> PyResult { + self.0 + .third_party_request() + .map_err(|e| BiscuitBuildError::new_err(e.to_string())) + .map(|request| PyThirdPartyRequest(Some(request))) + } + /// The revocation ids of the token, encoded as hexadecimal strings #[getter] pub fn revocation_ids(&self) -> Vec { @@ -407,6 +439,21 @@ impl PyBiscuit { .collect() } + /// Get the external key of a block if it exists + /// + /// :param index: the block index + /// :type index: int + /// :return: the public key if it exists + /// :rtype: str | None + pub fn block_external_key(&self, index: usize) -> PyResult> { + let opt_key = self + .0 + .block_external_key(index) + .map_err(|e| BiscuitBlockError::new_err(e.to_string()))?; + + Ok(opt_key.map(PyPublicKey)) + } + fn __repr__(&self) -> String { self.0.print() } @@ -1018,6 +1065,41 @@ impl PyBlockBuilder { } } +#[pyclass(name = "ThirdPartyRequest")] +pub struct PyThirdPartyRequest(Option); + +#[pymethods] +impl PyThirdPartyRequest { + /// Create a third-party block + /// + /// :param private_key: the third-party's private key used to sign the block + /// :type external_key: PrivateKey + /// :param block: the block builder to be signed + /// :type block: BlockBuilder + /// :return: a signed block that can be appended to a Biscuit + /// :rtype: ThirdPartyBlock + /// + /// :note: this method consumes the `ThirdPartyRequest` object. + pub fn create_block( + &mut self, + private_key: &PyPrivateKey, + block: &PyBlockBuilder, + ) -> PyResult { + self.0 + .take() + .expect("third party request already consumed") + .create_block( + &private_key.0, + block.0.clone().expect("builder already consumed"), + ) + .map_err(|e| BiscuitBuildError::new_err(e.to_string())) + .map(PyThirdPartyBlock) + } +} + +#[pyclass(name = "ThirdPartyBlock")] +pub struct PyThirdPartyBlock(ThirdPartyBlock); + /// ed25519 keypair #[pyclass(name = "KeyPair")] pub struct PyKeyPair(KeyPair);