-
Notifications
You must be signed in to change notification settings - Fork 2
Replace authorization config example with shipped config in v7 docs #164
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,7 +3,8 @@ | |
| ## Summary | ||
|
|
||
| Authorization decides whether an already-authenticated identity may reach a given resource. | ||
| 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. | ||
| 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. | ||
| Inheritance runs from child to parent, so `superuser` inherits every permission granted to `admin` without declaring any of its own. | ||
|
|
||
| ## Details | ||
|
|
||
|
|
@@ -13,7 +14,7 @@ Authorization is the process by which a system takes a validated identity and ch | |
|
|
||
| ## How it works | ||
|
|
||
| In Dotkernel API each authenticatable entity (admin/user) comes with their `roles` table where you can define roles for each entity. | ||
| 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. | ||
| RBAC comes in to ensure that each entity has the appropriate role and permission to access a resource. | ||
|
|
||
| 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 | |
|
|
||
| The configuration file for the role and permission definitions is `config/autoload/authorization.global.php`. | ||
|
|
||
| Roles are the backed enums `Core\Admin\Enum\AdminRoleEnum` (`superuser`, `admin`) and | ||
| `Core\User\Enum\UserRoleEnum` (`user`, `guest`), so the array keys are their `->value` strings. | ||
|
|
||
| ```php | ||
| 'mezzio-authorization-rbac' => [ | ||
| 'roles' => [ | ||
| AdminRole::ROLE_SUPERUSER => [], | ||
| AdminRole::ROLE_ADMIN => [ | ||
| AdminRole::ROLE_SUPERUSER, | ||
| ], | ||
| UserRole::ROLE_GUEST => [ | ||
| UserRole::ROLE_USER, | ||
| ], | ||
| ], | ||
| 'permissions' => [ | ||
| AdminRole::ROLE_SUPERUSER => [], | ||
| AdminRole::ROLE_ADMIN => [ | ||
| 'other.routes' | ||
| 'admin.list', | ||
| 'home' | ||
| ], | ||
| UserRole::ROLE_USER => [ | ||
| 'other.routes', | ||
| 'user.my-account.update', | ||
| 'user.my-account.view', | ||
| use Core\Admin\Enum\AdminRoleEnum; | ||
| use Core\User\Enum\UserRoleEnum; | ||
|
|
||
| return [ | ||
| 'mezzio-authorization-rbac' => [ | ||
| 'roles' => [ | ||
| AdminRoleEnum::Superuser->value => [], | ||
| AdminRoleEnum::Admin->value => [ | ||
| AdminRoleEnum::Superuser->value, | ||
| ], | ||
| UserRoleEnum::Guest->value => [ | ||
| UserRoleEnum::User->value, | ||
| ], | ||
| ], | ||
| UserRole::ROLE_GUEST => [ | ||
| 'other.routes', | ||
| 'security.refresh-token', | ||
| 'error.report', | ||
| 'home', | ||
| 'permissions' => [ | ||
| AdminRoleEnum::Superuser->value => [], | ||
| AdminRoleEnum::Admin->value => [ | ||
| 'admin::list-admin', | ||
| 'admin::create-admin', | ||
| 'admin::delete-admin', | ||
| 'admin::view-admin', | ||
| 'admin::update-admin', | ||
| 'admin::list-role', | ||
| 'admin::view-role', | ||
| 'admin::view-account', | ||
| 'admin::update-account', | ||
| 'user::list-user', | ||
| 'user::create-user', | ||
| 'user::delete-user', | ||
| 'user::view-user', | ||
| 'user::update-user', | ||
| 'user::delete-user-avatar', | ||
| 'user::view-user-avatar', | ||
| 'user::create-user-avatar', | ||
| 'user::list-role', | ||
| 'user::view-role', | ||
| 'user::activate-user', | ||
| 'user::deactivate-user', | ||
| 'app::create-error-report', | ||
| 'app::view-index', | ||
| ], | ||
| UserRoleEnum::User->value => [ | ||
| 'user::delete-account', | ||
| 'user::view-account', | ||
| 'user::update-account', | ||
| 'user::delete-account-avatar', | ||
| 'user::view-account-avatar', | ||
| 'user::create-account-avatar', | ||
| ], | ||
| UserRoleEnum::Guest->value => [ | ||
| 'app::create-error-report', | ||
| 'app::view-index', | ||
| 'user::activate-account', | ||
| 'user::request-activate-account', | ||
| 'user::recover-account', | ||
| 'user::check-account-reset-password', | ||
| 'user::update-account-reset-password', | ||
| 'user::create-account-reset-password', | ||
| 'user::create-account', | ||
| 'security::generate-token', | ||
| 'security::refresh-token', | ||
| ], | ||
| ], | ||
| ], | ||
| ], | ||
| ]; | ||
| ``` | ||
|
|
||
| That is the complete shipped configuration, not an excerpt. | ||
| Between them the three populated roles grant **38 permissions covering all 38 routes**: every route | ||
| the application declares is reachable by at least one role, and no permission names a route that | ||
| does not exist. | ||
| Only `app::view-index` and `app::create-error-report` are granted twice, to both `admin` and `guest`. | ||
|
|
||
| > See [mezzio-authorization-rbac](https://docs.mezzio.dev/mezzio-authorization-rbac/v1/basic-usage/) | ||
| > for more information. | ||
|
|
||
| ## Usage | ||
|
|
||
| Based on the configuration file above, we have two admin roles (`superuser`, `admin`) and two user roles (`user`, `guest`). | ||
| Based on the configuration file above, we have two admin roles (`superuser`, `admin`) and two user | ||
| roles (`user`, `guest`). | ||
|
Comment on lines
+110
to
+111
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sentence splitting... |
||
|
|
||
| A permission in Dotkernel API is a **route name** — the third argument given to the route in a | ||
| module's `RoutesDelegator`. To list the names you can grant, run | ||
| `php ./bin/cli.php route:list`; see [Displaying Dotkernel API endpoints](../commands/display-available-endpoints.md). | ||
|
Comment on lines
+113
to
+115
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sentence splitting... and new sentence starting on the same line with the previous one. |
||
|
|
||
| ### How inheritance works here | ||
|
|
||
| The array under `roles` maps a role to its **parents**, and inheritance runs in the direction that | ||
| often surprises people: a **parent receives the permissions of its children**, because | ||
| `laminas-permissions-rbac` resolves `hasPermission()` by walking down into child roles. | ||
|
Comment on lines
+119
to
+121
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sentence splitting... |
||
|
|
||
| So in the shipped configuration: | ||
|
|
||
| Roles inherit the permissions from their parents: | ||
| | Entry | Meaning | | ||
| | --- | --- | | ||
| | `superuser => []` | `superuser` has no parent | | ||
| | `admin => [superuser]` | `superuser` is the parent of `admin`, so **`superuser` inherits everything granted to `admin`** | | ||
| | `guest => [user]` | `user` is the parent of `guest`, so **`user` inherits everything granted to `guest`** | | ||
|
|
||
| - `superuser` has no parent | ||
| - `admin` has `superuser` as a parent which means `superuser` also has `admin` permissions | ||
| - `user` has no parent | ||
| - `guest` has `user` as a parent which means `user` also has `guest` permissions | ||
| That is why `superuser` needs no permissions of its own: its list is empty, yet it can reach all 23 | ||
| routes granted to `admin`. | ||
|
Comment on lines
+131
to
+132
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sentence splitting... |
||
|
|
||
| For each role we defined an array of permissions. | ||
| A permission in Dotkernel API is basically a route name. | ||
| It is also why `user` ends up with 17 effective permissions — its own 6 plus the 11 granted to | ||
| `guest` — while `guest` keeps only its own 11 and cannot reach the account routes reserved for a | ||
| signed-in user. | ||
|
|
||
|
Comment on lines
+134
to
137
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sentence splitting... |
||
| 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. | ||
| Effective totals, once inheritance is applied: | ||
|
|
||
| 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. | ||
| | Role | Own | Inherited | Effective | | ||
| | --- | --- | --- | --- | | ||
| | `superuser` | 0 | 23 from `admin` | 23 | | ||
| | `admin` | 23 | — | 23 | | ||
| | `user` | 6 | 11 from `guest` | 17 | | ||
| | `guest` | 11 | — | 11 | | ||
|
|
||
| ### How a request is authorized | ||
|
|
||
| `AuthorizationMiddleware` injects `Mezzio\Authorization\AuthorizationInterface` rather than an RBAC | ||
| class directly — the RBAC adapter is bound by `Mezzio\Authorization\Rbac\ConfigProvider`, registered | ||
| in `config/config.php`. | ||
|
|
||
|
Comment on lines
+149
to
+152
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sentence splitting... |
||
| For each request it: | ||
|
|
||
| 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. | ||
| 2. Rejects an account that is inactive, or a user that has been deleted. | ||
| 3. Replaces the identity's roles with the role names read from that record. | ||
| 4. Calls `isGranted()` once per role and allows the request as soon as **any** role grants the route. | ||
|
|
||
| If no role grants it, the response is `403 Forbidden` with | ||
| `You are not allowed to access this resource.` | ||
|
Comment on lines
+160
to
+161
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sentence splitting... |
||
|
|
||
| > Note this middleware returns a plain JSON error body rather than a Problem Details document, so an | ||
| > authorization failure does not look like the errors described in | ||
| > [Problem details](../extended-features/problem-details.md). | ||
|
|
||
| ## FAQ | ||
|
|
||
|
|
@@ -98,15 +184,18 @@ A route with no permission entry is unreachable for that role. | |
| **Q: Which access control model is used?** | ||
|
|
||
| A: RBAC, via `mezzio-authorization-rbac` backed by `laminas-permissions-rbac`. | ||
| `AuthorizationMiddleware` depends only on `Mezzio\Authorization\AuthorizationInterface`, so the adapter is selected by configuration rather than hardcoded. | ||
|
|
||
| **Q: How does role inheritance work here?** | ||
|
|
||
| A: A role listed inside another role's entry is its parent's beneficiary: because `admin` lists `superuser`, `superuser` receives everything granted to `admin`. | ||
| That is why `superuser` needs no explicit permissions of its own. | ||
| 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. | ||
| Because `admin` lists `superuser`, `superuser` receives everything granted to `admin`, which is why `superuser` needs no permissions of its own. | ||
| Likewise `guest` lists `user`, so `user` inherits the guest permissions on top of its own. | ||
|
|
||
| **Q: Where are roles stored?** | ||
|
|
||
| A: Each authenticatable entity — admin or user — has its own `roles` table where its roles are defined. | ||
| A: In `admin_role` and `user_role`, with `admin_roles` and `user_roles` as the join tables that assign them to accounts. | ||
| 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. | ||
|
|
||
| **Q: Which middleware enforces this?** | ||
|
|
||
|
|
@@ -116,3 +205,14 @@ See [Middleware flow](../flow/middleware-flow.md). | |
| **Q: Can I use ACL instead of RBAC?** | ||
|
|
||
| A: The ACL adapter ships with the project, but RBAC is what Dotkernel API is configured for; switching means replacing the authorization configuration. | ||
| Because the middleware only knows `AuthorizationInterface`, no application code needs to change. | ||
|
|
||
| **Q: Do the permissions cover every route?** | ||
|
|
||
| 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. | ||
| `app::view-index` and `app::create-error-report` are the only two granted to two roles. | ||
|
|
||
| **Q: What does a rejected request look like?** | ||
|
|
||
| A: `403 Forbidden` with `You are not allowed to access this resource.` | ||
| 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. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sentence splitting...