feat(auth): add the authentication and authorization skeleton
Introduces Johncms\Auth: the layer the new sign-in and permission system is
built on. Nothing calls it yet, so behaviour is unchanged.
Authentication is a chain: every AuthenticatorInterface tagged
johncms.auth.authenticator is asked in turn and the first one to recognise the
request wins; a request nobody recognises belongs to a guest. CurrentUser
resolves that once per request, lazily, and is reset between requests.
Authorization is a chain of voters: any Deny refuses, a single Allow is enough,
and a check nobody voted on is refused. The asymmetry is what later lets a ban,
an impersonation limit or the abilities of an API token take away what a role
grants. Refusing by default means a permission whose voter was forgotten closes
the door instead of opening it.
Identity carries ids, role slugs and permission keys and nothing that needs a
query, so authorization is testable without a container or a schema —
tests/Support/IdentityFactory is what that buys.
The can() helper and its Twig function are not here: with no voters registered
they would answer false to everything, and a silent denial is the worst kind of
placeholder. They arrive with the role voter.
built on. Nothing calls it yet, so behaviour is unchanged.
Authentication is a chain: every AuthenticatorInterface tagged
johncms.auth.authenticator is asked in turn and the first one to recognise the
request wins; a request nobody recognises belongs to a guest. CurrentUser
resolves that once per request, lazily, and is reset between requests.
Authorization is a chain of voters: any Deny refuses, a single Allow is enough,
and a check nobody voted on is refused. The asymmetry is what later lets a ban,
an impersonation limit or the abilities of an API token take away what a role
grants. Refusing by default means a permission whose voter was forgotten closes
the door instead of opening it.
Identity carries ids, role slugs and permission keys and nothing that needs a
query, so authorization is testable without a container or a schema —
tests/Support/IdentityFactory is what that buys.
The can() helper and its Twig function are not here: with no voters registered
they would answer false to everything, and a silent denial is the worst kind of
placeholder. They arrive with the role voter.