Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 33 additions & 3 deletions biscuit_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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"
82 changes: 82 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<PyBiscuit> {
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<PyThirdPartyRequest> {
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<String> {
Expand All @@ -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<Option<PyPublicKey>> {
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()
}
Expand Down Expand Up @@ -1018,6 +1065,41 @@ impl PyBlockBuilder {
}
}

#[pyclass(name = "ThirdPartyRequest")]
pub struct PyThirdPartyRequest(Option<ThirdPartyRequest>);

#[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<PyThirdPartyBlock> {
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);
Expand Down