Skip to content
This repository was archived by the owner on Jan 30, 2020. It is now read-only.

Commit c9568f4

Browse files
committed
Merging master to develop in preparation for 2.7.0 release.
2 parents e73fbfd + e10a01c commit c9568f4

28 files changed

+1715
-225
lines changed

CHANGELOG.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,46 @@
22

33
All notable changes to this project will be documented in this file, in reverse chronological order by release.
44

5+
## 2.7.0 - 2018-05-01
6+
7+
### Added
8+
9+
- [#23](https://github.com/zendframework/zend-permissions-acl/pull/23) adds a new assertion, `ExpressionAssertion`, to allow programatically or
10+
automatically (from configuration) building standard comparison assertions
11+
using a variety of operators, including `=` (`==`), `!=`, `<`, `<=`, `>`,
12+
`>=`, `===`, `!==`, `in` (`in_array`), `!in` (`! in_array`), `regex`
13+
(`preg_match`), and `!regex` (`! preg_match`). See https://docs.zendframework.com/zend-permissions-acl/expression/
14+
for details on usage.
15+
16+
- [#3](https://github.com/zendframework/zend-permissions-acl/pull/3) adds two new interfaces designed to allow creation of ownership-based assertions
17+
easier:
18+
19+
- `Zend\Permissions\Acl\ProprietaryInterface` is applicable to both roles and
20+
resources, and provides the method `getOwnerId()` for retrieving the owner
21+
role of an object.
22+
23+
- `Zend\Permissions\Acl\Assertion\OwnershipAssertion` ensures that the owner
24+
of a proprietary resource matches that of the role.
25+
26+
See https://docs.zendframework.com/zend-permissions-acl/ownership/ for details
27+
on usage.
28+
29+
### Changed
30+
31+
- Nothing.
32+
33+
### Deprecated
34+
35+
- Nothing.
36+
37+
### Removed
38+
39+
- Nothing.
40+
41+
### Fixed
42+
43+
- Nothing.
44+
545
## 2.6.1 - 2018-05-01
646

747
### Added

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@
4141
},
4242
"extra": {
4343
"branch-alias": {
44-
"dev-master": "2.6.x-dev",
45-
"dev-develop": "2.7.x-dev"
44+
"dev-master": "2.7.x-dev",
45+
"dev-develop": "2.8.x-dev"
4646
}
4747
},
4848
"scripts": {

composer.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/book/expression.md

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
# Expression Assertions
2+
3+
- Since 2.7.0
4+
5+
Many custom assertions are doing basic comparisons:
6+
7+
- Equality of a role property to a value or property of the resource.
8+
- Other comparisons (`>`, `<`, `in_array`, etc.) of a role property to a value
9+
or values (potentially a property of the resource).
10+
- Regular expressions.
11+
12+
While these can be easily accommodated by the `CallbackAssertion`, such
13+
assertions have one notable problem: they cannot be easily serialized.
14+
15+
To facilitate such assertions, we now provide
16+
`Zend\Permissions\Acl\Assertion\ExpressionAssertion`. This class provides two
17+
static factory methods for creating an instance, each expecting the following:
18+
19+
- The left operand
20+
- An operator
21+
- The right operand
22+
23+
When the assertion is executed, it uses the operator to determine how to compare
24+
the two operands, and thus answer the assertion.
25+
26+
## Operands
27+
28+
The operands can be any PHP value.
29+
30+
Additionally, they can be an associative array containing the key
31+
`ExpressionAssertion::OPERAND_CONTEXT_PROPERTY` (`__context`), with a string
32+
value.
33+
34+
That value can be one of the following:
35+
36+
- A string matching the values "acl", "privilege", "role", or "resource", with
37+
the latter two being most common. When one of these is provided, the
38+
corresponding argument to the `assert()` method will be used.
39+
40+
- A dot-separated string with the first segment being one of the above values,
41+
and the second being a property or field of that object. The
42+
`ExpressionAssertion` will test for:
43+
44+
- a method matching `get<field>()`
45+
- a method matching `is<field>()`
46+
- a public property named `<field>`
47+
48+
in that specific order. In the first two cases, `<field>` will be normalized
49+
to WordCase when creating the method name to test.
50+
51+
## Operators
52+
53+
`ExpressionAssertion` supports the following operators:
54+
55+
```php
56+
const OPERATOR_EQ = '=';
57+
const OPERATOR_NEQ = '!=';
58+
const OPERATOR_LT = '<';
59+
const OPERATOR_LTE = '<=';
60+
const OPERATOR_GT = '>';
61+
const OPERATOR_GTE = '>=';
62+
const OPERATOR_IN = 'in';
63+
const OPERATOR_NIN = '!in';
64+
const OPERATOR_REGEX = 'regex';
65+
const OPERATOR_NREGEX = '!regex';
66+
const OPERATOR_SAME = '===';
67+
const OPERATOR_NSAME = '!==';
68+
```
69+
70+
In most cases, these will operate using the operators as listed above, with the
71+
following exceptions:
72+
73+
- `OPERATOR_EQ` will use `==` as the comparison operator; `OPERATOR_NEQ` will
74+
likewise use `!=`.
75+
- `OPERATOR_IN` and `OPERATOR_NIN` use `in_array()` (with the latter negating
76+
the result), both doing strict comparisons. The right hand operand is expected
77+
to be the array in which to look for results, and the left hand operand is
78+
expected to be the needle to look for.
79+
- `OPERATOR_REGEX` and `OPERATOR_NREGEX` will perform a `preg_match()`
80+
operation, using the right hand operand as the regular expression, and the
81+
left hand operand as the value to compare.
82+
83+
## Constructors
84+
85+
The constructor of `ExpressionAssertion` is private. Instead, you will use one
86+
of two static methods in order to create instances:
87+
88+
- `fromProperties($left, $operator, $right)`
89+
- `fromArray(array $expression)` (expects keys for "left", "operator", and "right")
90+
91+
When creating expressions manually, the first is generally the best choice. When
92+
storing expressions in configuration or a database, the latter is useful, as you
93+
can pass a row of data at a time to the method to get expression instances.
94+
95+
## Examples
96+
97+
First, we'll define both a role and a resource:
98+
99+
```php
100+
namespace Blog\Entity;
101+
102+
use Zend\Permissions\Acl\Resource\ResourceInterface;
103+
use Zend\Permissions\Acl\Role\RoleInterface;
104+
105+
class BlogPost implements ResourceInterface
106+
{
107+
public $title;
108+
109+
public $shortDescription;
110+
111+
public $content;
112+
113+
public $author;
114+
115+
public function __construct(array $data = [])
116+
{
117+
foreach ($data as $property => $value) {
118+
$this->$property = $value;
119+
}
120+
}
121+
122+
public function getResourceId()
123+
{
124+
return 'blogPost';
125+
}
126+
127+
public function getShortDescription()
128+
{
129+
return $this->shortDescription;
130+
}
131+
132+
public function getAuthorName()
133+
{
134+
return $this->author ? $this->author->username : '';
135+
}
136+
}
137+
138+
class User implements RoleInterface
139+
{
140+
public $username;
141+
142+
public $role = 'guest';
143+
144+
public $age;
145+
146+
public function __construct(array $data = [])
147+
{
148+
foreach ($data as $property => $value) {
149+
$this->$property = $value;
150+
}
151+
}
152+
153+
public function getRoleId()
154+
{
155+
return $this->role;
156+
}
157+
158+
public function isAdult()
159+
{
160+
return $this->age >= 18;
161+
}
162+
}
163+
```
164+
165+
Next, let's define some assertions.
166+
167+
```php
168+
use Zend\Permissions\Acl\Assertion\ExpressionAssertion;
169+
170+
// Username of role must be "test":
171+
// Will access $username property on the role instance.
172+
$isTestUser = ExpressionAssertion::fromProperties(
173+
[ExpressionAssertion::OPERAND_CONTEXT_PROPERTY => 'role.username'],
174+
'===',
175+
'test'
176+
);
177+
178+
179+
// Role must be at least 18 years old:
180+
// Will execute `isAdult()` on the role instance.
181+
$isOfLegalAge = ExpressionAssertion::fromProperties(
182+
[ExpressionAssertion::OPERAND_CONTEXT_PROPERTY => 'role.adult'],
183+
'===',
184+
true
185+
);
186+
187+
// Must have edited text:
188+
// Will do a regex comparison on the shortDescription of the blog post
189+
// to ensure we do not have filler text.
190+
$isEditedDescription = ExpressionAssertion::fromArray([
191+
'left' => [ExpressionAssertion::OPERAND_CONTEXT_PROPERTY => 'resource.shortDescription'],
192+
'operator' => '!regex',
193+
'right' => '/lorem ipsum/i',
194+
]);
195+
```

docs/book/ownership.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# Ownership Assertions
2+
3+
- Since 2.7.0
4+
5+
When setting up permissions for an application, site owners common will want to
6+
allow roles to manipulate resources owned by the user with that role. For
7+
example, a blog author should have permission to _write_ new posts, and also to
8+
_modify_ his or her **own** posts, but **not** posts of other authors.
9+
10+
To accomodate this use case, we provide two interfaces:
11+
12+
- **`Zend\Acl\ProprietaryInterface`** is applicable to _resources_ and _roles_.
13+
It provides information about the _owner_ of an object. Objects implementing
14+
this interface are used in conjunction with the `OwnershipAssertion`.
15+
16+
- **`Zend\Acl\Assertion\OwnershipAssertion`** ensures that a resource is owned
17+
by a specific role by comparing it to owners provided by
18+
`ProprietaryInterface` implementations.
19+
20+
### Example
21+
22+
Consider the following entities:
23+
24+
```php
25+
namespace MyApp\Entity;
26+
27+
use Zend\Permissions\Acl\ProprietaryInterface;
28+
use Zend\Permissions\Acl\Resource\ResourceInterface;
29+
use Zend\Permissions\Acl\Role\RoleInterface;
30+
31+
class User implements RoleInterface, ProprietaryInterface
32+
{
33+
protected $id;
34+
35+
protected $role = 'guest';
36+
37+
public function __construct($id, $role)
38+
{
39+
$this->id = $id;
40+
$this->role = $role;
41+
}
42+
43+
public function getRoleId()
44+
{
45+
return $this->role;
46+
}
47+
48+
public function getOwnerId()
49+
{
50+
return $this->id;
51+
}
52+
}
53+
54+
class BlogPost implements ResourceInterface, ProprietaryInterface
55+
{
56+
public $author = null;
57+
58+
public function getResourceId()
59+
{
60+
return 'blogPost';
61+
}
62+
63+
public function getOwnerId()
64+
{
65+
if ($this->author === null) {
66+
return null;
67+
}
68+
69+
return $this->author->getOwnerId();
70+
}
71+
}
72+
```
73+
74+
The `User` marks itself as an _owner_ by implementing `ProprietaryInterface`;
75+
its `getOwnerId()` method will return the user identifier provided during
76+
instantiation.
77+
78+
A `BlogPost` marks itself as a resource and an _owner_ by also implementing
79+
`ProprietaryInterface`; in its case, it returns the author identifier, if
80+
present, but `null` otherwise.
81+
82+
Now let's wire these up into an ACL:
83+
84+
```php
85+
namespace MyApp;
86+
87+
use MyApp\Entity;
88+
use Zend\Permissions\Acl\Acl;
89+
use Zend\Permissions\Acl\Assertion\OwnershipAssertion;
90+
91+
$acl = new Acl();
92+
$acl->addRole('guest');
93+
$acl->addRole('member', 'guest');
94+
$acl->addRole('author', 'member');
95+
$acl->addRole('admin');
96+
97+
$acl->addResource('blogPost');
98+
$acl->addResource('comment');
99+
100+
$acl->allow('guest', 'blogPost', 'view');
101+
$acl->allow('guest', 'comment', array('view', 'submit'));
102+
$acl->allow('author', 'blogPost', 'write');
103+
$acl->allow('author', 'blogPost', 'edit', new OwnershipAssertion());
104+
$acl->allow('admin');
105+
106+
$author1 = new User(1, 'author');
107+
$author2 = new User(2, 'author');
108+
109+
$blogPost = new BlogPost();
110+
$blogPost->author = $author1;
111+
```
112+
113+
The takeaways from the above should be:
114+
115+
- An `author` can _write_ blog posts, and _edit_ posts it owns.
116+
- `$author1` and `$author2` are both authors.
117+
- `$author1` is the author of `$blogPost`.
118+
119+
Knowing these facts, we can expect the following assertion results:
120+
121+
```php
122+
$acl->isAllowed($author1, 'blogPost', 'write'); // true
123+
$acl->isAllowed($author1, $blogPost, 'edit'); // true
124+
$acl->isAllowed($author2, 'blogPost', 'write'); // true
125+
$acl->isAllowed($author2, $blogPost, 'edit'); // false
126+
```

0 commit comments

Comments
 (0)