You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Replace authorization config example with shipped config in v7 docs
The example used pre-6.0 AdminRole::ROLE_* constants and invented
dot-notation permissions, and was invalid PHP. Documents the real
enum-keyed config, the child-to-parent inheritance direction, and how
AuthorizationMiddleware resolves a request.
Signed-off-by: arhimede <julian@dotkernel.com>
Authorization decides whether an already-authenticated identity may reach a given resource.
6
-
Dotkernel API implements it with role-based access control through `Mezzio\Authorization\Rbac\LaminasRbac`, applied by `AuthorizationMiddleware` and configured in `config/autoload/authorization.global.php`, where each permission is a route name and roles inherit from their parents.
6
+
Dotkernel API implements it with role-based access control through `Mezzio\Authorization\Rbac\LaminasRbac`, applied by `AuthorizationMiddleware` and configured in `config/autoload/authorization.global.php`, where each permission is a route name.
7
+
Inheritance runs from child to parent, so `superuser` inherits every permission granted to `admin` without declaring any of its own.
7
8
8
9
## Details
9
10
@@ -13,7 +14,7 @@ Authorization is the process by which a system takes a validated identity and ch
13
14
14
15
## How it works
15
16
16
-
In Dotkernel API each authenticatable entity (admin/user) comes with their `roles`table where you can define roles for each entity.
17
+
In Dotkernel API each authenticatable entity (admin/user) has its own role table — `admin_role` and `user_role` — plus a join table, `admin_roles` and `user_roles`, assigning roles to accounts.
17
18
RBAC comes in to ensure that each entity has the appropriate role and permission to access a resource.
18
19
19
20
The authorization happens through the `Api\App\Middleware\AuthorizationMiddleware` middleware.
@@ -24,59 +25,144 @@ Dotkernel API makes use of `mezzio-authorization-rbac` and includes the full con
24
25
25
26
The configuration file for the role and permission definitions is `config/autoload/authorization.global.php`.
26
27
28
+
Roles are the backed enums `Core\Admin\Enum\AdminRoleEnum` (`superuser`, `admin`) and
29
+
`Core\User\Enum\UserRoleEnum` (`user`, `guest`), so the array keys are their `->value` strings.
30
+
27
31
```php
28
-
'mezzio-authorization-rbac' => [
29
-
'roles' => [
30
-
AdminRole::ROLE_SUPERUSER => [],
31
-
AdminRole::ROLE_ADMIN => [
32
-
AdminRole::ROLE_SUPERUSER,
33
-
],
34
-
UserRole::ROLE_GUEST => [
35
-
UserRole::ROLE_USER,
36
-
],
37
-
],
38
-
'permissions' => [
39
-
AdminRole::ROLE_SUPERUSER => [],
40
-
AdminRole::ROLE_ADMIN => [
41
-
'other.routes'
42
-
'admin.list',
43
-
'home'
44
-
],
45
-
UserRole::ROLE_USER => [
46
-
'other.routes',
47
-
'user.my-account.update',
48
-
'user.my-account.view',
32
+
use Core\Admin\Enum\AdminRoleEnum;
33
+
use Core\User\Enum\UserRoleEnum;
34
+
35
+
return [
36
+
'mezzio-authorization-rbac' => [
37
+
'roles' => [
38
+
AdminRoleEnum::Superuser->value => [],
39
+
AdminRoleEnum::Admin->value => [
40
+
AdminRoleEnum::Superuser->value,
41
+
],
42
+
UserRoleEnum::Guest->value => [
43
+
UserRoleEnum::User->value,
44
+
],
49
45
],
50
-
UserRole::ROLE_GUEST => [
51
-
'other.routes',
52
-
'security.refresh-token',
53
-
'error.report',
54
-
'home',
46
+
'permissions' => [
47
+
AdminRoleEnum::Superuser->value => [],
48
+
AdminRoleEnum::Admin->value => [
49
+
'admin::list-admin',
50
+
'admin::create-admin',
51
+
'admin::delete-admin',
52
+
'admin::view-admin',
53
+
'admin::update-admin',
54
+
'admin::list-role',
55
+
'admin::view-role',
56
+
'admin::view-account',
57
+
'admin::update-account',
58
+
'user::list-user',
59
+
'user::create-user',
60
+
'user::delete-user',
61
+
'user::view-user',
62
+
'user::update-user',
63
+
'user::delete-user-avatar',
64
+
'user::view-user-avatar',
65
+
'user::create-user-avatar',
66
+
'user::list-role',
67
+
'user::view-role',
68
+
'user::activate-user',
69
+
'user::deactivate-user',
70
+
'app::create-error-report',
71
+
'app::view-index',
72
+
],
73
+
UserRoleEnum::User->value => [
74
+
'user::delete-account',
75
+
'user::view-account',
76
+
'user::update-account',
77
+
'user::delete-account-avatar',
78
+
'user::view-account-avatar',
79
+
'user::create-account-avatar',
80
+
],
81
+
UserRoleEnum::Guest->value => [
82
+
'app::create-error-report',
83
+
'app::view-index',
84
+
'user::activate-account',
85
+
'user::request-activate-account',
86
+
'user::recover-account',
87
+
'user::check-account-reset-password',
88
+
'user::update-account-reset-password',
89
+
'user::create-account-reset-password',
90
+
'user::create-account',
91
+
'security::generate-token',
92
+
'security::refresh-token',
93
+
],
55
94
],
56
95
],
57
-
],
96
+
];
58
97
```
59
98
99
+
That is the complete shipped configuration, not an excerpt.
100
+
Between them the three populated roles grant **38 permissions covering all 38 routes**: every route
101
+
the application declares is reachable by at least one role, and no permission names a route that
102
+
does not exist.
103
+
Only `app::view-index` and `app::create-error-report` are granted twice, to both `admin` and `guest`.
104
+
60
105
> See [mezzio-authorization-rbac](https://docs.mezzio.dev/mezzio-authorization-rbac/v1/basic-usage/)
61
106
> for more information.
62
107
63
108
## Usage
64
109
65
-
Based on the configuration file above, we have two admin roles (`superuser`, `admin`) and two user roles (`user`, `guest`).
110
+
Based on the configuration file above, we have two admin roles (`superuser`, `admin`) and two user
111
+
roles (`user`, `guest`).
112
+
113
+
A permission in Dotkernel API is a **route name** — the third argument given to the route in a
114
+
module's `RoutesDelegator`. To list the names you can grant, run
115
+
`php ./bin/cli.php route:list`; see [Displaying Dotkernel API endpoints](../commands/display-available-endpoints.md).
116
+
117
+
### How inheritance works here
118
+
119
+
The array under `roles` maps a role to its **parents**, and inheritance runs in the direction that
120
+
often surprises people: a **parent receives the permissions of its children**, because
121
+
`laminas-permissions-rbac` resolves `hasPermission()` by walking down into child roles.
122
+
123
+
So in the shipped configuration:
66
124
67
-
Roles inherit the permissions from their parents:
125
+
| Entry | Meaning |
126
+
| --- | --- |
127
+
|`superuser => []`|`superuser` has no parent |
128
+
|`admin => [superuser]`|`superuser` is the parent of `admin`, so **`superuser` inherits everything granted to `admin`**|
129
+
|`guest => [user]`|`user` is the parent of `guest`, so **`user` inherits everything granted to `guest`**|
68
130
69
-
-`superuser` has no parent
70
-
-`admin` has `superuser` as a parent which means `superuser` also has `admin` permissions
71
-
-`user` has no parent
72
-
-`guest` has `user` as a parent which means `user` also has `guest` permissions
131
+
That is why `superuser` needs no permissions of its own: its list is empty, yet it can reach all 23
132
+
routes granted to `admin`.
73
133
74
-
For each role we defined an array of permissions.
75
-
A permission in Dotkernel API is basically a route name.
134
+
It is also why `user` ends up with 17 effective permissions — its own 6 plus the 11 granted to
135
+
`guest` — while `guest` keeps only its own 11 and cannot reach the account routes reserved for a
136
+
signed-in user.
76
137
77
-
As you can see, the `superuser` does not have its own permissions, because it gains all the permissions from `admin`, no need to define explicit permissions.
138
+
Effective totals, once inheritance is applied:
78
139
79
-
The `user` role, gains all the permission from `guest` so no need to define that `user` can access `home` route, but `guest` cannot access user-specific routes.
140
+
| Role | Own | Inherited | Effective |
141
+
| --- | --- | --- | --- |
142
+
|`superuser`| 0 | 23 from `admin`| 23 |
143
+
|`admin`| 23 | — | 23 |
144
+
|`user`| 6 | 11 from `guest`| 17 |
145
+
|`guest`| 11 | — | 11 |
146
+
147
+
### How a request is authorized
148
+
149
+
`AuthorizationMiddleware` injects `Mezzio\Authorization\AuthorizationInterface` rather than an RBAC
150
+
class directly — the RBAC adapter is bound by `Mezzio\Authorization\Rbac\ConfigProvider`, registered
151
+
in `config/config.php`.
152
+
153
+
For each request it:
154
+
155
+
1. Reads `oauth_client_id` from the authenticated identity and loads the matching record — `admin` from the `admin` table, `frontend` from the `user` table, or a `Guest` instance when the client is `guest`. An unrecognised client is rejected.
156
+
2. Rejects an account that is inactive, or a user that has been deleted.
157
+
3. Replaces the identity's roles with the role names read from that record.
158
+
4. Calls `isGranted()` once per role and allows the request as soon as **any** role grants the route.
159
+
160
+
If no role grants it, the response is `403 Forbidden` with
161
+
`You are not allowed to access this resource.`
162
+
163
+
> Note this middleware returns a plain JSON error body rather than a Problem Details document, so an
164
+
> authorization failure does not look like the errors described in
@@ -98,15 +184,18 @@ A route with no permission entry is unreachable for that role.
98
184
**Q: Which access control model is used?**
99
185
100
186
A: RBAC, via `mezzio-authorization-rbac` backed by `laminas-permissions-rbac`.
187
+
`AuthorizationMiddleware` depends only on `Mezzio\Authorization\AuthorizationInterface`, so the adapter is selected by configuration rather than hardcoded.
101
188
102
189
**Q: How does role inheritance work here?**
103
190
104
-
A: A role listed inside another role's entry is its parent's beneficiary: because `admin` lists `superuser`, `superuser` receives everything granted to `admin`.
105
-
That is why `superuser` needs no explicit permissions of its own.
191
+
A: The values listed against a role are its parents, and a parent inherits from its children — `laminas-permissions-rbac` resolves a permission by walking down into child roles.
192
+
Because `admin` lists `superuser`, `superuser` receives everything granted to `admin`, which is why `superuser` needs no permissions of its own.
193
+
Likewise `guest` lists `user`, so `user` inherits the guest permissions on top of its own.
106
194
107
195
**Q: Where are roles stored?**
108
196
109
-
A: Each authenticatable entity — admin or user — has its own `roles` table where its roles are defined.
197
+
A: In `admin_role` and `user_role`, with `admin_roles` and `user_roles` as the join tables that assign them to accounts.
198
+
The role names themselves come from the `AdminRoleEnum` and `UserRoleEnum` backed enums, so adding a role means adding an enum case as well as a row.
110
199
111
200
**Q: Which middleware enforces this?**
112
201
@@ -116,3 +205,14 @@ See [Middleware flow](../flow/middleware-flow.md).
116
205
**Q: Can I use ACL instead of RBAC?**
117
206
118
207
A: The ACL adapter ships with the project, but RBAC is what Dotkernel API is configured for; switching means replacing the authorization configuration.
208
+
Because the middleware only knows `AuthorizationInterface`, no application code needs to change.
209
+
210
+
**Q: Do the permissions cover every route?**
211
+
212
+
A: Yes, exactly. The three populated roles grant 38 permissions across the 38 declared routes, with no route ungranted and no permission naming a route that does not exist.
213
+
`app::view-index` and `app::create-error-report` are the only two granted to two roles.
214
+
215
+
**Q: What does a rejected request look like?**
216
+
217
+
A: `403 Forbidden` with `You are not allowed to access this resource.`
218
+
The same status is returned when the account is inactive, the user was deleted, or the OAuth client is unrecognised, each with its own message.
0 commit comments