diff --git a/README.md b/README.md index df5b40f..318fbd7 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ PHP bindings for [Biscuit](https://www.biscuitsec.org), a bearer token supportin - [Biscuit Website](https://www.biscuitsec.org) - Documentation and examples - [Biscuit Specification](https://github.com/biscuit-auth/biscuit) - [Biscuit Rust](https://github.com/biscuit-auth/biscuit-rust) - Technical details +- [Upgrading Guide](./UPGRADING.md) - Migration guide for breaking changes ## Requirements @@ -94,24 +95,23 @@ composer require ptondereau/ext-biscuit-php ```php addCode('user("alice"); resource("file1")'); -$biscuit = $builder->build($root->private()); +// Create a biscuit token (can pass code directly to constructor) +$builder = new BiscuitBuilder('user("alice"); resource("file1")'); +$biscuit = $builder->build($root->getPrivateKey()); // Serialize to base64 $token = $biscuit->toBase64(); // Parse and authorize -$parsed = Biscuit::fromBase64($token, $root->public()); +$parsed = Biscuit::fromBase64($token, $root->getPublicKey()); -$authBuilder = new AuthorizerBuilder(); -$authBuilder->addCode('allow if user("alice"), resource("file1")'); +// Authorizer with inline code +$authBuilder = new AuthorizerBuilder('allow if user("alice"), resource("file1")'); $authorizer = $authBuilder->build($parsed); // Check authorization @@ -141,12 +141,34 @@ $biscuitWithAttestation = $biscuit->appendThirdParty( ); ``` +### Parameterized Primitives + +```php +use Biscuit\Auth\{Fact, Rule, Check, Policy}; + +// Fact with constructor params +$fact = new Fact('user({id})', ['id' => 'alice']); + +// Rule with params +$rule = new Rule('can_read($u, {res}) <- user($u)', ['res' => 'file1']); + +// Check with params +$check = new Check('check if user({name})', ['name' => 'alice']); + +// Policy with params +$policy = new Policy('allow if user({name})', ['name' => 'alice']); + +// Or use set() method for dynamic values +$fact = new Fact('user({id})'); +$fact->set('id', $userId); +``` + ### Authorizer Queries ```php $authorizer = $authBuilder->build($biscuit); -$rule = new Rule('users($id) <- user($id);'); +$rule = new Rule('users($id) <- user($id)'); $facts = $authorizer->query($rule); foreach ($facts as $fact) { @@ -180,10 +202,9 @@ use Biscuit\Auth\Algorithm; // Ed25519 is the default algorithm (recommended) $keypair1 = new KeyPair(); // Uses Ed25519 -$keypair2 = KeyPair::newWithAlgorithm(); // Uses Ed25519 by default // Explicitly use Secp256r1 -$keypair3 = KeyPair::newWithAlgorithm(Algorithm::Secp256r1); +$keypair2 = new KeyPair(Algorithm::Secp256r1); // Key import defaults to Ed25519 $publicKey = PublicKey::fromBytes($bytes); // Defaults to Ed25519 diff --git a/UPGRADING.md b/UPGRADING.md new file mode 100644 index 0000000..3d9ecf8 --- /dev/null +++ b/UPGRADING.md @@ -0,0 +1,155 @@ +# Upgrading Guide + +## Upgrading from v0.2.x to v0.3.0 + +v0.3.0 introduces API simplification changes that consolidate redundant methods and add optional parameters to constructors. This is a **breaking change** release. + +### KeyPair Constructor + +**Before (v0.2.x):** +```php +use Biscuit\Auth\KeyPair; +use Biscuit\Auth\Algorithm; + +// Default Ed25519 +$kp = new KeyPair(); + +// Explicit algorithm required separate method +$kp = KeyPair::newWithAlgorithm(Algorithm::Secp256r1); +``` + +**After (v0.3.0):** +```php +use Biscuit\Auth\KeyPair; +use Biscuit\Auth\Algorithm; + +// Default Ed25519 (unchanged) +$kp = new KeyPair(); + +// Explicit algorithm now via constructor +$kp = new KeyPair(Algorithm::Secp256r1); +``` + +**Migration:** Replace all `KeyPair::newWithAlgorithm($alg)` calls with `new KeyPair($alg)`. + +--- + +### Builder addCode Methods + +The `addCodeWithParams()` method has been removed. Use `addCode()` with optional parameters instead. + +**Before (v0.2.x):** +```php +$builder = new BiscuitBuilder(); +$builder->addCode('user("alice")'); +$builder->addCodeWithParams('resource({res})', ['res' => 'file1']); +``` + +**After (v0.3.0):** +```php +$builder = new BiscuitBuilder(); +$builder->addCode('user("alice")'); +$builder->addCode('resource({res})', ['res' => 'file1']); +``` + +**Affected classes:** +- `BiscuitBuilder` +- `BlockBuilder` +- `AuthorizerBuilder` + +**Migration:** Replace all `addCodeWithParams($source, $params)` calls with `addCode($source, $params)`. + +--- + +### Builder Constructors with Optional Source + +All builder constructors now accept optional source code and parameters. + +**Before (v0.2.x):** +```php +$builder = new BiscuitBuilder(); +$builder->addCode('user({id})', ['id' => 'alice']); +``` + +**After (v0.3.0):** +```php +// Can still use the old pattern +$builder = new BiscuitBuilder(); +$builder->addCode('user({id})', ['id' => 'alice']); + +// Or use constructor directly +$builder = new BiscuitBuilder('user({id})', ['id' => 'alice']); +``` + +**Affected classes:** +- `BiscuitBuilder::__construct(?string $source = null, ?array $params = null, ?array $scope_params = null)` +- `BlockBuilder::__construct(?string $source = null, ?array $params = null, ?array $scope_params = null)` +- `AuthorizerBuilder::__construct(?string $source = null, ?array $params = null, ?array $scope_params = null)` + +**Migration:** No changes required. This is backward compatible. + +--- + +### Primitive Constructors with Optional Params + +`Fact`, `Rule`, `Check`, and `Policy` constructors now accept optional parameters. + +**Before (v0.2.x):** +```php +$fact = new Fact('user({id})'); +$fact->set('id', 'alice'); + +$rule = new Rule('can_read($u, {res}) <- user($u)'); +$rule->set('res', 'file1'); + +$check = new Check('check if user({name})'); +$check->set('name', 'alice'); + +$policy = new Policy('allow if user({name})'); +$policy->set('name', 'alice'); +``` + +**After (v0.3.0):** +```php +// Can still use set() method +$fact = new Fact('user({id})'); +$fact->set('id', 'alice'); + +// Or use constructor params directly +$fact = new Fact('user({id})', ['id' => 'alice']); + +$rule = new Rule('can_read($u, {res}) <- user($u)', ['res' => 'file1']); + +$check = new Check('check if user({name})', ['name' => 'alice']); + +$policy = new Policy('allow if user({name})', ['name' => 'alice']); +``` + +**Constructor signatures:** +- `Fact::__construct(string $source, ?array $params = null)` +- `Rule::__construct(string $source, ?array $params = null, ?array $scope_params = null)` +- `Check::__construct(string $source, ?array $params = null, ?array $scope_params = null)` +- `Policy::__construct(string $source, ?array $params = null, ?array $scope_params = null)` + +**Migration:** No changes required. The `set()` method is still available. This is backward compatible. + +--- + +### Quick Migration Checklist + +1. **Search and replace:** + - `KeyPair::newWithAlgorithm(` → `new KeyPair(` + - `->addCodeWithParams(` → `->addCode(` + +2. **Optional improvements (not required):** + - Use constructor params for builders instead of separate `addCode()` calls + - Use constructor params for primitives instead of separate `set()` calls + +### Removed Methods + +| Class | Removed Method | Replacement | +|-------|---------------|-------------| +| `KeyPair` | `newWithAlgorithm(Algorithm $alg)` | `new KeyPair($alg)` | +| `BiscuitBuilder` | `addCodeWithParams()` | `addCode($source, $params)` | +| `BlockBuilder` | `addCodeWithParams()` | `addCode($source, $params)` | +| `AuthorizerBuilder` | `addCodeWithParams()` | `addCode($source, $params)` | diff --git a/biscuit-php.stubs.php b/biscuit-php.stubs.php index a607939..c071473 100644 --- a/biscuit-php.stubs.php +++ b/biscuit-php.stubs.php @@ -70,11 +70,9 @@ public function __construct() {} } class AuthorizerBuilder { - public function addCode(string $source): mixed {} + public function addCode(string $source, ?array $params = null, ?array $scope_params = null): void {} - public function addCodeWithParams(string $source, array $params, array $scope_params): mixed {} - - public function addFact(\Biscuit\Auth\Fact $fact): mixed {} + public function addFact(\Biscuit\Auth\Fact $fact): void {} public function addRule(\Biscuit\Auth\Rule $rule): mixed {} @@ -102,17 +100,15 @@ public function buildUnauthenticated(): \Biscuit\Auth\Authorizer {} public function __toString(): string {} - public function __construct() {} + public function __construct(?string $source = null, ?array $params = null, ?array $scope_params = null) {} } class BiscuitBuilder { public function build(\Biscuit\Auth\PrivateKey $root): \Biscuit\Auth\Biscuit {} - public function addCode(string $source): mixed {} + public function addCode(string $source, ?array $params = null, ?array $scope_params = null): void {} - public function addCodeWithParams(string $source, array $params, array $scope_params): mixed {} - - public function merge(\Biscuit\Auth\BlockBuilder $other) {} + public function merge(\Biscuit\Auth\BlockBuilder $other): void {} public function addFact(\Biscuit\Auth\Fact $fact): mixed {} @@ -124,25 +120,23 @@ public function setRootKeyId(int $root_key_id) {} public function __toString(): string {} - public function __construct() {} + public function __construct(?string $source = null, ?array $params = null, ?array $scope_params = null) {} } class BlockBuilder { - public function addFact(\Biscuit\Auth\Fact $fact): mixed {} + public function addFact(\Biscuit\Auth\Fact $fact): void {} - public function addRule(\Biscuit\Auth\Rule $rule): mixed {} - - public function addCheck(\Biscuit\Auth\Check $check): mixed {} + public function addRule(\Biscuit\Auth\Rule $rule): void {} - public function addCode(string $source): mixed {} + public function addCheck(\Biscuit\Auth\Check $check): void {} - public function addCodeWithParams(string $source, array $params, array $scope_params): mixed {} + public function addCode(string $source, ?array $params = null, ?array $scope_params = null): void {} - public function merge(\Biscuit\Auth\BlockBuilder $other) {} + public function merge(\Biscuit\Auth\BlockBuilder $other): void {} public function __toString(): string {} - public function __construct() {} + public function __construct(?string $source = null, ?array $params = null, ?array $scope_params = null) {} } class ThirdPartyRequest { @@ -165,7 +159,7 @@ public function setScope(string $name, \Biscuit\Auth\PublicKey $key): mixed {} public function __toString(): string {} - public function __construct(string $source) {} + public function __construct(string $source, ?array $params = null, ?array $scope_params = null) {} } class Fact { @@ -178,7 +172,7 @@ public function name(): string {} public function __toString(): string {} - public function __construct(string $source) {} + public function __construct(string $source, ?array $params = null) {} } class Check { @@ -191,7 +185,7 @@ public function setScope(string $name, \Biscuit\Auth\PublicKey $key): mixed {} public function __toString(): string {} - public function __construct(string $source) {} + public function __construct(string $source, ?array $params = null, ?array $scope_params = null) {} } class Policy { @@ -204,19 +198,17 @@ public function setScope(string $name, \Biscuit\Auth\PublicKey $key): mixed {} public function __toString(): string {} - public function __construct(string $source) {} + public function __construct(string $source, ?array $params = null, ?array $scope_params = null) {} } class KeyPair { - public static function newWithAlgorithm(?\Biscuit\Auth\Algorithm $alg = null): \Biscuit\Auth\KeyPair {} - public static function fromPrivateKey(\Biscuit\Auth\PrivateKey $private_key): \Biscuit\Auth\KeyPair {} public function getPublicKey(): \Biscuit\Auth\PublicKey {} public function getPrivateKey(): \Biscuit\Auth\PrivateKey {} - public function __construct() {} + public function __construct(?\Biscuit\Auth\Algorithm $alg = null) {} } class PublicKey { diff --git a/src/lib.rs b/src/lib.rs index 773ac4a..f633df1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -261,44 +261,41 @@ pub struct AuthorizerBuilder(biscuit_auth::AuthorizerBuilder); #[php_impl] impl AuthorizerBuilder { - pub fn __construct() -> Self { - Self(biscuit_auth::AuthorizerBuilder::new()) - } - - pub fn add_code(&mut self, source: &str) -> PhpResult<()> { - self.0 = self - .0 - .clone() - .code(source) - .map_err(|e| PhpException::from_class::(e.to_string()))?; - Ok(()) + pub fn __construct( + source: Option, + params: Option>, + scope_params: Option>, + ) -> PhpResult { + let mut builder = Self(biscuit_auth::AuthorizerBuilder::new()); + if let Some(src) = source { + builder.add_code(&src, params, scope_params)?; + } + Ok(builder) } - pub fn add_code_with_params( + pub fn add_code( &mut self, source: &str, - params: HashMap, - scope_params: HashMap, + params: Option>, + scope_params: Option>, ) -> PhpResult<()> { - let mut term_params: HashMap = - HashMap::with_capacity(params.len()); - - for (key, p) in params.iter() { - let term_value = mixed_value_to_term(p)?; - term_params.insert(key.clone(), term_value); - } - - let mut scope_params_cloned: HashMap = - HashMap::with_capacity(scope_params.len()); - - for (key, scope_param) in scope_params.iter() { - scope_params_cloned.insert(key.clone(), scope_param.0); - } + let term_params: HashMap = match params { + Some(p) => p + .iter() + .map(|(k, v)| mixed_value_to_term(v).map(|term| (k.clone(), term))) + .collect::, PhpException>>()?, + None => HashMap::new(), + }; + + let scope: HashMap = match scope_params { + Some(sp) => sp.iter().map(|(k, v)| (k.clone(), v.0)).collect(), + None => HashMap::new(), + }; self.0 = self .0 .clone() - .code_with_params(source, term_params, scope_params_cloned) + .code_with_params(source, term_params, scope) .map_err(|e| PhpException::from_class::(e.to_string()))?; Ok(()) } @@ -412,8 +409,16 @@ pub struct BiscuitBuilder(biscuit_auth::builder::BiscuitBuilder); #[php_impl] impl BiscuitBuilder { - pub fn __construct() -> Self { - Self(biscuit_auth::builder::BiscuitBuilder::new()) + pub fn __construct( + source: Option, + params: Option>, + scope_params: Option>, + ) -> PhpResult { + let mut builder = Self(biscuit_auth::builder::BiscuitBuilder::new()); + if let Some(src) = source { + builder.add_code(&src, params, scope_params)?; + } + Ok(builder) } // Build the biscuit with a private key @@ -426,40 +431,29 @@ impl BiscuitBuilder { .map_err(|e| PhpException::default(format!("Build error: {}", e))) } - pub fn add_code(&mut self, source: &str) -> PhpResult<()> { - self.0 = self - .0 - .clone() - .code(source) - .map_err(|e| PhpException::from_class::(e.to_string()))?; - Ok(()) - } - - pub fn add_code_with_params( + pub fn add_code( &mut self, source: &str, - params: HashMap, - scope_params: HashMap, + params: Option>, + scope_params: Option>, ) -> PhpResult<()> { - let mut term_params: HashMap = - HashMap::with_capacity(params.len()); - - for (key, p) in params.iter() { - let term_value = mixed_value_to_term(p)?; - term_params.insert(key.clone(), term_value); - } - - let mut scope_params_cloned: HashMap = - HashMap::with_capacity(scope_params.len()); - - for (key, scope_param) in scope_params.iter() { - scope_params_cloned.insert(key.clone(), scope_param.0); - } + let term_params: HashMap = match params { + Some(p) => p + .iter() + .map(|(k, v)| mixed_value_to_term(v).map(|term| (k.clone(), term))) + .collect::, PhpException>>()?, + None => HashMap::new(), + }; + + let scope: HashMap = match scope_params { + Some(sp) => sp.iter().map(|(k, v)| (k.clone(), v.0)).collect(), + None => HashMap::new(), + }; self.0 = self .0 .clone() - .code_with_params(source, term_params, scope_params_cloned) + .code_with_params(source, term_params, scope) .map_err(|e| PhpException::from_class::(e.to_string()))?; Ok(()) } @@ -511,8 +505,16 @@ pub struct BlockBuilder(biscuit_auth::builder::BlockBuilder); #[php_impl] impl BlockBuilder { - pub fn __construct() -> Self { - Self(biscuit_auth::builder::BlockBuilder::default()) + pub fn __construct( + source: Option, + params: Option>, + scope_params: Option>, + ) -> PhpResult { + let mut builder = Self(biscuit_auth::builder::BlockBuilder::default()); + if let Some(src) = source { + builder.add_code(&src, params, scope_params)?; + } + Ok(builder) } pub fn add_fact(&mut self, fact: &Fact) -> PhpResult<()> { @@ -542,40 +544,29 @@ impl BlockBuilder { Ok(()) } - pub fn add_code(&mut self, source: &str) -> PhpResult<()> { - self.0 = self - .0 - .clone() - .code(source) - .map_err(|e| PhpException::from_class::(e.to_string()))?; - Ok(()) - } - - pub fn add_code_with_params( + pub fn add_code( &mut self, source: &str, - params: HashMap, - scope_params: HashMap, + params: Option>, + scope_params: Option>, ) -> PhpResult<()> { - let mut term_params: HashMap = - HashMap::with_capacity(params.len()); - - for (key, p) in params.iter() { - let term_value = mixed_value_to_term(p)?; - term_params.insert(key.clone(), term_value); - } - - let mut scope_params_cloned: HashMap = - HashMap::with_capacity(scope_params.len()); - - for (key, scope_param) in scope_params.iter() { - scope_params_cloned.insert(key.clone(), scope_param.0); - } + let term_params: HashMap = match params { + Some(p) => p + .iter() + .map(|(k, v)| mixed_value_to_term(v).map(|term| (k.clone(), term))) + .collect::, PhpException>>()?, + None => HashMap::new(), + }; + + let scope: HashMap = match scope_params { + Some(sp) => sp.iter().map(|(k, v)| (k.clone(), v.0)).collect(), + None => HashMap::new(), + }; self.0 = self .0 .clone() - .code_with_params(source, term_params, scope_params_cloned) + .code_with_params(source, term_params, scope) .map_err(|e| PhpException::from_class::(e.to_string()))?; Ok(()) } @@ -626,11 +617,30 @@ pub struct Rule(biscuit_auth::builder::Rule); #[php_impl] impl Rule { - pub fn __construct(source: &str) -> PhpResult { - source + pub fn __construct( + source: &str, + params: Option>, + scope_params: Option>, + ) -> PhpResult { + let mut rule: biscuit_auth::builder::Rule = source .try_into() - .map(Self) - .map_err(|e| PhpException::from_class::(e.to_string())) + .map_err(|e| PhpException::from_class::(format!("{}", e)))?; + + if let Some(p) = params { + p.iter().try_for_each(|(key, value)| { + rule.set(key, mixed_value_to_term(value)?) + .map_err(|e| PhpException::from_class::(e.to_string())) + })?; + } + + if let Some(sp) = scope_params { + sp.iter().try_for_each(|(key, pk)| { + rule.set_scope(key, pk.0) + .map_err(|e| PhpException::from_class::(e.to_string())) + })?; + } + + Ok(Self(rule)) } /// @param int|string|bool|null $value @@ -660,11 +670,22 @@ pub struct Fact(biscuit_auth::builder::Fact); #[php_impl] impl Fact { - pub fn __construct(source: &str) -> PhpResult { - source + pub fn __construct( + source: &str, + params: Option>, + ) -> PhpResult { + let mut fact: biscuit_auth::builder::Fact = source .try_into() - .map(Self) - .map_err(|e| PhpException::from_class::(e.to_string())) + .map_err(|e| PhpException::from_class::(format!("{}", e)))?; + + if let Some(p) = params { + p.iter().try_for_each(|(key, value)| { + fact.set(key, mixed_value_to_term(value)?) + .map_err(|e| PhpException::from_class::(e.to_string())) + })?; + } + + Ok(Self(fact)) } /// @param int|string|bool|null $value @@ -693,11 +714,32 @@ pub struct Check(biscuit_auth::builder::Check); #[php_impl] impl Check { - pub fn __construct(source: &str) -> PhpResult { - source + pub fn __construct( + source: &str, + params: Option>, + scope_params: Option>, + ) -> PhpResult { + let mut check: biscuit_auth::builder::Check = source .try_into() - .map(Self) - .map_err(|e| PhpException::from_class::(e.to_string())) + .map_err(|e| PhpException::from_class::(format!("{}", e)))?; + + if let Some(p) = params { + p.iter().try_for_each(|(key, value)| { + check + .set(key, mixed_value_to_term(value)?) + .map_err(|e| PhpException::from_class::(e.to_string())) + })?; + } + + if let Some(sp) = scope_params { + sp.iter().try_for_each(|(key, pk)| { + check + .set_scope(key, pk.0) + .map_err(|e| PhpException::from_class::(e.to_string())) + })?; + } + + Ok(Self(check)) } /// @param int|string|bool|null $value @@ -727,11 +769,32 @@ pub struct Policy(biscuit_auth::builder::Policy); #[php_impl] impl Policy { - pub fn __construct(source: &str) -> PhpResult { - source + pub fn __construct( + source: &str, + params: Option>, + scope_params: Option>, + ) -> PhpResult { + let mut policy: biscuit_auth::builder::Policy = source .try_into() - .map(Self) - .map_err(|e| PhpException::from_class::(e.to_string())) + .map_err(|e| PhpException::from_class::(format!("{}", e)))?; + + if let Some(p) = params { + p.iter().try_for_each(|(key, value)| { + policy + .set(key, mixed_value_to_term(value)?) + .map_err(|e| PhpException::from_class::(e.to_string())) + })?; + } + + if let Some(sp) = scope_params { + sp.iter().try_for_each(|(key, pk)| { + policy + .set_scope(key, pk.0) + .map_err(|e| PhpException::from_class::(e.to_string())) + })?; + } + + Ok(Self(policy)) } /// @param int|string|bool|null $value @@ -761,12 +824,7 @@ pub struct KeyPair(BiscuitKeyPair); #[php_impl] impl KeyPair { - pub fn __construct() -> Self { - Self(BiscuitKeyPair::new()) - } - - #[php(name = "newWithAlgorithm")] - pub fn new_with_algorithm(alg: Option) -> Self { + pub fn __construct(alg: Option) -> Self { let algorithm = alg.unwrap_or(Algorithm::Ed25519).into(); Self(BiscuitKeyPair::new_with_algorithm(algorithm)) } diff --git a/tests/BiscuitTest.php b/tests/BiscuitTest.php index c0311c6..31921b0 100644 --- a/tests/BiscuitTest.php +++ b/tests/BiscuitTest.php @@ -103,16 +103,61 @@ public function testBiscuitBuilderWithParameters(): void $kp = new KeyPair(); $builder = new BiscuitBuilder(); - $builder->addCodeWithParams( - 'user({username}); resource({res});', - ['username' => 'alice', 'res' => 'file1'], - [], - ); + $builder->addCode('user({username}); resource({res});', ['username' => 'alice', 'res' => 'file1']); $biscuit = $builder->build($kp->getPrivateKey()); static::assertInstanceOf(Biscuit::class, $biscuit); } + public function testBiscuitBuilderAddCodeWithOptionalParams(): void + { + $kp = new KeyPair(); + $builder = new BiscuitBuilder(); + + // Without params (existing behavior) + $builder->addCode('user("alice")'); + + // With params (new unified API) + $builder->addCode('resource({res})', ['res' => 'file1']); + + $biscuit = $builder->build($kp->getPrivateKey()); + static::assertInstanceOf(Biscuit::class, $biscuit); + static::assertStringContainsString('user("alice")', $biscuit->blockSource(0)); + static::assertStringContainsString('resource("file1")', $biscuit->blockSource(0)); + } + + public function testBiscuitBuilderConstructorWithCode(): void + { + $kp = new KeyPair(); + + // Constructor with code and params + $builder = new BiscuitBuilder('user({id})', ['id' => 'alice']); + $biscuit = $builder->build($kp->getPrivateKey()); + + static::assertInstanceOf(Biscuit::class, $biscuit); + static::assertStringContainsString('user("alice")', $biscuit->blockSource(0)); + } + + public function testBiscuitBuilderConstructorEmpty(): void + { + $builder = new BiscuitBuilder(); + static::assertInstanceOf(BiscuitBuilder::class, $builder); + } + + public function testBlockBuilderConstructorWithCode(): void + { + $builder = new BlockBuilder('resource({res})', ['res' => 'file1']); + static::assertStringContainsString('resource("file1")', (string) $builder); + } + + public function testAuthorizerBuilderConstructorWithCode(): void + { + $authBuilder = new AuthorizerBuilder('allow if true'); + $authorizer = $authBuilder->buildUnauthenticated(); + $policy = $authorizer->authorize(); + static::assertSame(0, $policy); + } + public function testBiscuitSerialization(): void { $privateKeyHex = 'ed25519-private/473b5189232f3f597b5c2f3f9b0d5e28b1ee4e7cce67ec6b7fbf5984157a6b97'; @@ -167,7 +212,7 @@ public function testBlockBuilder(): void public function testBlockBuilderWithParameters(): void { $builder = new BlockBuilder(); - $builder->addCodeWithParams('resource({res}); permission({perm});', ['res' => 'file1', 'perm' => 'read'], []); + $builder->addCode('resource({res}); permission({perm});', ['res' => 'file1', 'perm' => 'read']); static::assertInstanceOf(BlockBuilder::class, $builder); } @@ -198,7 +243,7 @@ public function testAuthorizerBuilderWithParameters(): void $biscuit = $biscuitBuilder->build($kp->getPrivateKey()); $authBuilder = new AuthorizerBuilder(); - $authBuilder->addCodeWithParams('allow if user({username})', ['username' => 'alice'], []); + $authBuilder->addCode('allow if user({username})', ['username' => 'alice']); $authorizer = $authBuilder->build($biscuit); static::assertInstanceOf(Authorizer::class, $authorizer); @@ -223,7 +268,7 @@ public function testCompleteLifecycle(): void $kp = KeyPair::fromPrivateKey($privateKey); $biscuitBuilder = new BiscuitBuilder(); - $biscuitBuilder->addCodeWithParams('user({id})', ['id' => '1234'], []); + $biscuitBuilder->addCode('user({id})', ['id' => '1234']); foreach (['read', 'write'] as $right) { $biscuitBuilder->addFact(new Fact("right(\"{$right}\")")); @@ -239,7 +284,7 @@ public function testCompleteLifecycle(): void $parsedToken = Biscuit::fromBase64($token, $kp->getPublicKey()); $authBuilder = new AuthorizerBuilder(); - $authBuilder->addCodeWithParams('allow if user({id})', ['id' => '1234'], []); + $authBuilder->addCode('allow if user({id})', ['id' => '1234']); $authorizer = $authBuilder->build($parsedToken); $policy = $authorizer->authorize(); @@ -251,7 +296,7 @@ public function testAuthorizerQuery(): void $kp = new KeyPair(); $biscuitBuilder = new BiscuitBuilder(); - $biscuitBuilder->addCodeWithParams('user({id})', ['id' => '1234'], []); + $biscuitBuilder->addCode('user({id})', ['id' => '1234']); $biscuit = $biscuitBuilder->build($kp->getPrivateKey()); $authBuilder = new AuthorizerBuilder(); @@ -273,11 +318,11 @@ public function testAuthorizerSnapshot(): void $kp = KeyPair::fromPrivateKey($privateKey); $biscuitBuilder = new BiscuitBuilder(); - $biscuitBuilder->addCodeWithParams('user({id})', ['id' => '1234'], []); + $biscuitBuilder->addCode('user({id})', ['id' => '1234']); $biscuit = $biscuitBuilder->build($privateKey); $authBuilder = new AuthorizerBuilder(); - $authBuilder->addCodeWithParams('allow if user({id})', ['id' => '1234'], []); + $authBuilder->addCode('allow if user({id})', ['id' => '1234']); $authorizer = $authBuilder->build($biscuit); $snapshot = $authorizer->base64Snapshot(); @@ -302,7 +347,7 @@ public function testAuthorizerSnapshot(): void public function testAuthorizerBuilderSnapshot(): void { $authBuilder = new AuthorizerBuilder(); - $authBuilder->addCodeWithParams('allow if user({id})', ['id' => '1234'], []); + $authBuilder->addCode('allow if user({id})', ['id' => '1234']); $snapshot = $authBuilder->base64Snapshot(); static::assertIsString($snapshot); @@ -402,12 +447,12 @@ public function testThirdPartyBlocks(): void { $rootKp = new KeyPair(); $biscuitBuilder = new BiscuitBuilder(); - $biscuitBuilder->addCodeWithParams('user({id})', ['id' => '1234'], []); + $biscuitBuilder->addCode('user({id})', ['id' => '1234']); $biscuit = $biscuitBuilder->build($rootKp->getPrivateKey()); $thirdPartyKp = new KeyPair(); $newBlock = new BlockBuilder(); - $newBlock->addCodeWithParams('external_fact({fact})', ['fact' => '56'], []); + $newBlock->addCode('external_fact({fact})', ['fact' => '56']); $thirdPartyRequest = $biscuit->thirdPartyRequest(); static::assertInstanceOf(ThirdPartyRequest::class, $thirdPartyRequest); @@ -497,6 +542,24 @@ public function testPolicyWithSet(): void static::assertInstanceOf(Policy::class, $policy); } + public function testRuleConstructorWithParams(): void + { + $rule = new Rule('can_read($u, {res}) <- user($u)', ['res' => 'file1']); + static::assertStringContainsString('can_read($u, "file1")', (string) $rule); + } + + public function testCheckConstructorWithParams(): void + { + $check = new Check('check if user({username})', ['username' => 'alice']); + static::assertStringContainsString('user("alice")', (string) $check); + } + + public function testPolicyConstructorWithParams(): void + { + $policy = new Policy('allow if user({username})', ['username' => 'alice']); + static::assertStringContainsString('user("alice")', (string) $policy); + } + public function testBlockMerge(): void { $builder1 = new BlockBuilder(); diff --git a/tests/FactTest.php b/tests/FactTest.php index b1aa34a..18dc0c4 100644 --- a/tests/FactTest.php +++ b/tests/FactTest.php @@ -18,6 +18,18 @@ public function testGoodTermConversion(): void static::assertSame('user(15)', (string) $fact); } + public function testConstructorWithParams(): void + { + $fact = new Fact('user({id})', ['id' => 'alice']); + static::assertSame('user("alice")', (string) $fact); + } + + public function testConstructorWithoutParams(): void + { + $fact = new Fact('user("bob")'); + static::assertSame('user("bob")', (string) $fact); + } + public function testExcpetionWhenBadFact(): void { $this->expectException(InvalidFact::class); diff --git a/tests/KeyPairTest.php b/tests/KeyPairTest.php index 04c4e9e..5b47491 100644 --- a/tests/KeyPairTest.php +++ b/tests/KeyPairTest.php @@ -22,25 +22,25 @@ public function testKeyPairGeneration(): void static::assertIsString($keyPair->getPublicKey()->toHex()); } - public function testNewWithAlgorithmDefault(): void + public function testConstructorDefault(): void { - $keyPair = KeyPair::newWithAlgorithm(); + $keyPair = new KeyPair(); static::assertInstanceOf(KeyPair::class, $keyPair); static::assertStringStartsWith('ed25519/', $keyPair->getPublicKey()->toHex()); } - public function testNewWithAlgorithmEd25519(): void + public function testConstructorEd25519(): void { - $keyPair = KeyPair::newWithAlgorithm(Algorithm::Ed25519); + $keyPair = new KeyPair(Algorithm::Ed25519); static::assertInstanceOf(KeyPair::class, $keyPair); static::assertStringStartsWith('ed25519/', $keyPair->getPublicKey()->toHex()); } - public function testNewWithAlgorithmSecp256r1(): void + public function testConstructorSecp256r1(): void { - $keyPair = KeyPair::newWithAlgorithm(Algorithm::Secp256r1); + $keyPair = new KeyPair(Algorithm::Secp256r1); static::assertInstanceOf(KeyPair::class, $keyPair); static::assertStringStartsWith('secp256r1/', $keyPair->getPublicKey()->toHex()); diff --git a/tests/RbacExampleTest.php b/tests/RbacExampleTest.php index 9ecddcb..f89ee04 100644 --- a/tests/RbacExampleTest.php +++ b/tests/RbacExampleTest.php @@ -41,16 +41,12 @@ public function testAdminServiceCanPerformCriticalOperations(): void // 1. CREATE TOKEN: payment-api has service-admin role for critical priority $tokenBuilder = new BiscuitBuilder(); - $tokenBuilder->addCodeWithParams('user({id})', ['id' => 'payment-api'], []); - $tokenBuilder->addCodeWithParams( - 'user_roles({id}, "api", {priority}, {roles})', - [ - 'id' => 'payment-api', - 'priority' => 'critical', - 'roles' => ['service-admin'], - ], - [], - ); + $tokenBuilder->addCode('user({id})', ['id' => 'payment-api']); + $tokenBuilder->addCode('user_roles({id}, "api", {priority}, {roles})', [ + 'id' => 'payment-api', + 'priority' => 'critical', + 'roles' => ['service-admin'], + ]); $token = $tokenBuilder->build($keyPair->getPrivateKey()); @@ -58,15 +54,11 @@ public function testAdminServiceCanPerformCriticalOperations(): void $authBuilder = new AuthorizerBuilder(); // Define what "service-admin" role can do at "critical" priority level - $authBuilder->addCodeWithParams( - 'role({priority}, {role}, {permissions})', - [ - 'priority' => 'critical', - 'role' => 'service-admin', - 'permissions' => ['api:read', 'api:write', 'api:delete', 'api:admin'], - ], - [], - ); + $authBuilder->addCode('role({priority}, {role}, {permissions})', [ + 'priority' => 'critical', + 'role' => 'service-admin', + 'permissions' => ['api:read', 'api:write', 'api:delete', 'api:admin'], + ]); // RBAC Logic: Derive rights from user_roles + role definitions $authBuilder->addCode(' @@ -100,16 +92,12 @@ public function testWriterServiceCannotPerformCriticalOperations(): void // 1. CREATE TOKEN: notification-api has service-writer role for normal priority only $tokenBuilder = new BiscuitBuilder(); - $tokenBuilder->addCodeWithParams('user({id})', ['id' => 'notification-api'], []); - $tokenBuilder->addCodeWithParams( - 'user_roles({id}, "api", {priority}, {roles})', - [ - 'id' => 'notification-api', - 'priority' => 'normal', - 'roles' => ['service-writer'], - ], - [], - ); + $tokenBuilder->addCode('user({id})', ['id' => 'notification-api']); + $tokenBuilder->addCode('user_roles({id}, "api", {priority}, {roles})', [ + 'id' => 'notification-api', + 'priority' => 'normal', + 'roles' => ['service-writer'], + ]); $token = $tokenBuilder->build($keyPair->getPrivateKey()); @@ -117,15 +105,11 @@ public function testWriterServiceCannotPerformCriticalOperations(): void $authBuilder = new AuthorizerBuilder(); // Define what "service-writer" role can do at "normal" priority (no critical!) - $authBuilder->addCodeWithParams( - 'role({priority}, {role}, {permissions})', - [ - 'priority' => 'normal', - 'role' => 'service-writer', - 'permissions' => ['api:read', 'api:write'], - ], - [], - ); + $authBuilder->addCode('role({priority}, {role}, {permissions})', [ + 'priority' => 'normal', + 'role' => 'service-writer', + 'permissions' => ['api:read', 'api:write'], + ]); // Same RBAC logic $authBuilder->addCode(' @@ -158,39 +142,32 @@ public function testPriorityScopedRoles(): void // CREATE TOKEN: payment-api is admin for critical, writer for normal $tokenBuilder = new BiscuitBuilder(); - $tokenBuilder->addCodeWithParams('user({id})', ['id' => 'payment-api'], []); + $tokenBuilder->addCode('user({id})', ['id' => 'payment-api']); // Admin for critical priority - $tokenBuilder->addCodeWithParams( - 'user_roles({id}, "api", {priority}, {roles})', - [ - 'id' => 'payment-api', - 'priority' => 'critical', - 'roles' => ['service-admin'], - ], - [], - ); + $tokenBuilder->addCode('user_roles({id}, "api", {priority}, {roles})', [ + 'id' => 'payment-api', + 'priority' => 'critical', + 'roles' => ['service-admin'], + ]); // Writer for normal priority - $tokenBuilder->addCodeWithParams( - 'user_roles({id}, "api", {priority}, {roles})', - [ - 'id' => 'payment-api', - 'priority' => 'normal', - 'roles' => ['service-writer'], - ], - [], - ); + $tokenBuilder->addCode('user_roles({id}, "api", {priority}, {roles})', [ + 'id' => 'payment-api', + 'priority' => 'normal', + 'roles' => ['service-writer'], + ]); $token = $tokenBuilder->build($keyPair->getPrivateKey()); // TEST 1: payment-api CAN perform api:delete at critical priority (admin role) $authBuilder1 = new AuthorizerBuilder(); - $authBuilder1->addCodeWithParams( - 'role("critical", "service-admin", {perms})', - ['perms' => ['api:read', 'api:write', 'api:delete', 'api:admin']], - [], - ); + $authBuilder1->addCode('role("critical", "service-admin", {perms})', ['perms' => [ + 'api:read', + 'api:write', + 'api:delete', + 'api:admin', + ]]); $authBuilder1->addCode(' right($id, $p, $op, $priority) <- user($id), operation($op), resource($priority), @@ -206,11 +183,7 @@ public function testPriorityScopedRoles(): void // TEST 2: payment-api CANNOT perform api:delete at normal priority (only writer role) $authBuilder2 = new AuthorizerBuilder(); - $authBuilder2->addCodeWithParams( - 'role("normal", "service-writer", {perms})', - ['perms' => ['api:read', 'api:write']], - [], - ); + $authBuilder2->addCode('role("normal", "service-writer", {perms})', ['perms' => ['api:read', 'api:write']]); $authBuilder2->addCode(' right($id, $p, $op, $priority) <- user($id), operation($op), resource($priority),