Fine grained security
If the #[Logged]
and #[Right]
attributes are not
granular enough for your needs, you can use the advanced #[Security]
attribute.
Using the #[Security]
attribute, you can write an expression that can contain custom logic. For instance:
- Check that a user can access a given resource
- Check that a user has one right or another right
- ...
Using the #[Security] attribute
The #[Security]
attribute is very flexible: it allows you to pass an expression that can contains custom logic:
use TheCodingMachine\GraphQLite\Annotations\Security;
// ...
#[Query]
#[Security("is_granted('ROLE_ADMIN') or is_granted('POST_SHOW', post)")]
public function getPost(Post $post): array
{
// ...
}
The expression defined in the #[Security]
attribute must conform to Symfony's Expression Language syntax
#[Security]
attribute. Most of the inspiration of this attribute comes from Symfony. Warning though! GraphQLite's #[Security]
attribute and Symfony's #[Security]
attribute are slightly different. Especially, the two attributes do not live in the same namespace!The is_granted
function
Use the is_granted
function to check if a user has a special right.
#[Security("is_granted('ROLE_ADMIN')")]
is similar to
#[Right("ROLE_ADMIN")]
In addition, the is_granted
function accepts a second optional parameter: the "scope" of the right.
#[Query]
#[Security("is_granted('POST_SHOW', post)")]
public function getPost(Post $post): array
{
// ...
}
In the example above, the getPost
method can be called only if the logged user has the 'POST_SHOW' permission on the
$post
object. You can notice that the $post
object comes from the parameters.
Accessing method parameters
All parameters passed to the method can be accessed in the #[Security]
expression.
#[Query]
#[Security(expression: "startDate < endDate", statusCode: 400, message: "End date must be after start date")]
public function getPosts(DateTimeImmutable $startDate, DateTimeImmutable $endDate): array
{
// ...
}
In the example above, we tweak a bit the Security attribute purpose to do simple input validation.
Setting HTTP code and error message
You can use the statusCode
and message
attributes to set the HTTP code and GraphQL error message.
#[Query]
#[Security(expression: "is_granted('POST_SHOW', post)", statusCode: 404, message: "Post not found (let's pretend the post does not exists!)")]
public function getPost(Post $post): array
{
// ...
}
Note: since a single GraphQL call contain many errors, 2 errors might have conflicting HTTP status code. The resulting status code is up to the GraphQL middleware you use. Most of the time, the status code with the higher error code will be returned.
Setting a default value
If you do not want an error to be thrown when the security condition is not met, you can use the failWith
attribute
to set a default value.
#[Query]
#[Security(expression: "is_granted('CAN_SEE_MARGIN', this)", failWith: null)]
public function getMargin(): float
{
// ...
}
The failWith
attribute behaves just like the #[FailWith]
attribute
but for a given #[Security]
attribute.
You cannot use the failWith
attribute along statusCode
or message
attributes.
Accessing the user
You can use the user
variable to access the currently logged user.
You can use the is_logged()
function to check if a user is logged or not.
#[Query]
#[Security("is_logged() && user.age > 18")]
public function getNSFWImages(): array
{
// ...
}
Accessing the current object
You can use the this
variable to access any (public) property / method of the current class.
class Post {
#[Field]
#[Security("this.canAccessBody(user)")]
public function getBody(): array
{
// ...
}
public function canAccessBody(User $user): bool
{
// Some custom logic here
}
}
Available scope
The #[Security]
attribute can be used in any query, mutation or field, so anywhere you have a #[Query]
, #[Mutation]
or #[Field]
attribute.
How to restrict access to a given resource
The is_granted
method can be used to restrict access to a specific resource.
#[Security("is_granted('POST_SHOW', post)")]
If you are wondering how to configure these fine-grained permissions, this is not something that GraphQLite handles itself. Instead, this depends on the framework you are using.
If you are using Symfony, you will create a custom voter.
If you are using Laravel, you will create a Gate or a Policy.
If you are using another framework, you need to know that the is_granted
function simply forwards the call to
the isAllowed
method of the configured AuthorizationSerice
. See Connecting GraphQLite to your framework's security module
for more details