Skip to content

Commit 3842026

Browse files
authored
Merge pull request #46 from Naccomy/feat/support-third-party-blocks
feat: add third-party block support
2 parents 309dd82 + 9cf7f25 commit 3842026

File tree

2 files changed

+115
-3
lines changed

2 files changed

+115
-3
lines changed

biscuit_test.py

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ def choose_key(kid):
217217
elif kid == 1:
218218
return root.public_key
219219
else:
220-
raise Exception("Unknown key identifier")
220+
raise Exception("Unknown key identifier")
221221

222222
biscuit_builder0 = BiscuitBuilder("user({id})", { 'id': "1234" })
223223
token0 = biscuit_builder0.build(other_root.private_key).to_base64()
@@ -386,10 +386,10 @@ def test_biscuit_inspection():
386386
builder.set_root_key_id(42)
387387
token2 = builder.build(kp.private_key).append(BlockBuilder('test(false);'))
388388
print(token2.to_base64())
389-
389+
390390
utoken1 = UnverifiedBiscuit.from_base64(token1.to_base64())
391391
utoken2 = UnverifiedBiscuit.from_base64(token2.to_base64())
392-
392+
393393
assert utoken1.root_key_id() is None
394394
assert utoken2.root_key_id() == 42
395395

@@ -463,3 +463,33 @@ def test(left, right):
463463
policy = authorizer.build_unauthenticated().authorize()
464464
assert policy == 0
465465

466+
def test_append_third_party():
467+
root_kp = KeyPair()
468+
biscuit_builder = BiscuitBuilder("user({id})", { 'id': "1234" })
469+
biscuit = biscuit_builder.build(root_kp.private_key)
470+
471+
third_party_kp = KeyPair()
472+
new_block = BlockBuilder("external_fact({fact})", { 'fact': "56" })
473+
third_party_request = biscuit.third_party_request()
474+
third_party_block = third_party_request.create_block(third_party_kp.private_key, new_block)
475+
biscuit_with_third_party_block = biscuit.append_third_party(third_party_kp.public_key, third_party_block)
476+
477+
assert repr(biscuit_with_third_party_block.block_external_key(1)) == repr(third_party_kp.public_key)
478+
479+
def test_read_third_party_facts():
480+
root_kp = KeyPair()
481+
biscuit_builder = BiscuitBuilder("user({id})", { 'id': "1234" })
482+
biscuit = biscuit_builder.build(root_kp.private_key)
483+
484+
third_party_kp = KeyPair()
485+
new_block = BlockBuilder("external_fact({fact})", { 'fact': "56" })
486+
third_party_request = biscuit.third_party_request()
487+
third_party_block = third_party_request.create_block(third_party_kp.private_key, new_block)
488+
biscuit_with_third_party_block = biscuit.append_third_party(third_party_kp.public_key, third_party_block)
489+
490+
491+
authorizer = AuthorizerBuilder("allow if true").build(biscuit_with_third_party_block)
492+
rule = Rule(f"ext_fact($fact) <- external_fact($fact) trusting {third_party_kp.public_key}")
493+
facts = authorizer.query(rule)
494+
495+
assert facts[0].terms[0] == "56"

src/lib.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ use ::biscuit_auth::builder::MapKey;
1111
use ::biscuit_auth::datalog::ExternFunc;
1212
use ::biscuit_auth::AuthorizerBuilder;
1313
use ::biscuit_auth::RootKeyProvider;
14+
use ::biscuit_auth::ThirdPartyBlock;
15+
use ::biscuit_auth::ThirdPartyRequest;
1416
use ::biscuit_auth::UnverifiedBiscuit;
1517
use chrono::DateTime;
1618
use chrono::Duration;
@@ -397,6 +399,36 @@ impl PyBiscuit {
397399
.map(PyBiscuit)
398400
}
399401

402+
/// Create a new `Biscuit` by appending a third-party attenuation block
403+
///
404+
/// :param external_key: the public key of the third-party that signed the block.
405+
/// :type external_key: PublicKey
406+
/// :param block: the third party block to append
407+
/// :type block: ThirdPartyBlock
408+
/// :return: the attenuated biscuit
409+
/// :rtype: Biscuit
410+
pub fn append_third_party(
411+
&self,
412+
external_key: &PyPublicKey,
413+
block: &PyThirdPartyBlock,
414+
) -> PyResult<PyBiscuit> {
415+
self.0
416+
.append_third_party(external_key.0, block.0.clone())
417+
.map_err(|e| BiscuitBuildError::new_err(e.to_string()))
418+
.map(PyBiscuit)
419+
}
420+
421+
/// Create a third-party request for generating third-party blocks.
422+
///
423+
/// :return: the third-party request
424+
/// :rtype: ThirdPartyRequest
425+
pub fn third_party_request(&self) -> PyResult<PyThirdPartyRequest> {
426+
self.0
427+
.third_party_request()
428+
.map_err(|e| BiscuitBuildError::new_err(e.to_string()))
429+
.map(|request| PyThirdPartyRequest(Some(request)))
430+
}
431+
400432
/// The revocation ids of the token, encoded as hexadecimal strings
401433
#[getter]
402434
pub fn revocation_ids(&self) -> Vec<String> {
@@ -407,6 +439,21 @@ impl PyBiscuit {
407439
.collect()
408440
}
409441

442+
/// Get the external key of a block if it exists
443+
///
444+
/// :param index: the block index
445+
/// :type index: int
446+
/// :return: the public key if it exists
447+
/// :rtype: str | None
448+
pub fn block_external_key(&self, index: usize) -> PyResult<Option<PyPublicKey>> {
449+
let opt_key = self
450+
.0
451+
.block_external_key(index)
452+
.map_err(|e| BiscuitBlockError::new_err(e.to_string()))?;
453+
454+
Ok(opt_key.map(PyPublicKey))
455+
}
456+
410457
fn __repr__(&self) -> String {
411458
self.0.print()
412459
}
@@ -1018,6 +1065,41 @@ impl PyBlockBuilder {
10181065
}
10191066
}
10201067

1068+
#[pyclass(name = "ThirdPartyRequest")]
1069+
pub struct PyThirdPartyRequest(Option<ThirdPartyRequest>);
1070+
1071+
#[pymethods]
1072+
impl PyThirdPartyRequest {
1073+
/// Create a third-party block
1074+
///
1075+
/// :param private_key: the third-party's private key used to sign the block
1076+
/// :type external_key: PrivateKey
1077+
/// :param block: the block builder to be signed
1078+
/// :type block: BlockBuilder
1079+
/// :return: a signed block that can be appended to a Biscuit
1080+
/// :rtype: ThirdPartyBlock
1081+
///
1082+
/// :note: this method consumes the `ThirdPartyRequest` object.
1083+
pub fn create_block(
1084+
&mut self,
1085+
private_key: &PyPrivateKey,
1086+
block: &PyBlockBuilder,
1087+
) -> PyResult<PyThirdPartyBlock> {
1088+
self.0
1089+
.take()
1090+
.expect("third party request already consumed")
1091+
.create_block(
1092+
&private_key.0,
1093+
block.0.clone().expect("builder already consumed"),
1094+
)
1095+
.map_err(|e| BiscuitBuildError::new_err(e.to_string()))
1096+
.map(PyThirdPartyBlock)
1097+
}
1098+
}
1099+
1100+
#[pyclass(name = "ThirdPartyBlock")]
1101+
pub struct PyThirdPartyBlock(ThirdPartyBlock);
1102+
10211103
/// ed25519 keypair
10221104
#[pyclass(name = "KeyPair")]
10231105
pub struct PyKeyPair(KeyPair);

0 commit comments

Comments
 (0)