Skip to content

Commit 3ffb5d8

Browse files
committed
Adding @Security documentation
1 parent 6b012e6 commit 3ffb5d8

File tree

6 files changed

+239
-59
lines changed

6 files changed

+239
-59
lines changed

docs/authentication_authorization.md

Lines changed: 7 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@ sidebar_label: Authentication and authorization
77
You might not want to expose your GraphQL API to anyone. Or you might want to keep some queries/mutations or fields
88
reserved to some users.
99

10-
GraphQLite offers some control over what a user can do with your API based on authentication (whether the user
11-
is logged or not) or authorization (what rights the user have).
10+
GraphQLite offers some control over what a user can do with your API. You can restrict access to resources:
11+
12+
- based on authentication using the [`@Logged` annotation](#logged-and-right-annotations) (restrict access to logged users)
13+
- based on authorization using the [`@Right` annotation](#logged-and-right-annotations) (restrict access to logged users with certain rights).
14+
- based on fine-grained authorization using the [`@Security` annotation](fine-grained-security.md) (restrict access for some given resources to some users).
1215

1316
<div class="alert alert-info">
1417
GraphQLite does not have its own security mechanism.
15-
Unless you're using our Symfony Bundle, it is up to you to connect this feature to your framework's security mechanism.<br>
16-
See <a href="#connecting-graphqlite-to-your-framework-s-security-module">Connecting GraphQLite to your framework's security module</a>.
18+
Unless you're using our Symfony Bundle or our Laravel package, it is up to you to connect this feature to your framework's security mechanism.<br>
19+
See <a href="implementing-security.md">Connecting GraphQLite to your framework's security module</a>.
1720
</div>
1821

1922
## `@Logged` and `@Right` annotations
@@ -112,55 +115,3 @@ While this is the most secured mode, it can have drawbacks when working with dev
112115
(you need to be logged as admin to fetch the complete schema).
113116

114117
<div class="alert alert-info">The "HideIfUnauthorized" mode was the default mode in GraphQLite 3 and is optionnal from GraphQLite 4+.</div>
115-
116-
## Connecting GraphQLite to your framework's security module
117-
118-
<div class="alert alert-info">
119-
This step is NOT necessary for user using GraphQLite through the Symfony Bundle
120-
</div>
121-
122-
GraphQLite needs to know if a user is logged or not, and what rights it has.
123-
But this is specific of the framework you use.
124-
125-
To plug GraphQLite to your framework's security mechanism, you will have to provide two classes implementing:
126-
127-
* `TheCodingMachine\GraphQLite\Security\AuthenticationServiceInterface`
128-
* `TheCodingMachine\GraphQLite\Security\AuthorizationServiceInterface`
129-
130-
Those two interfaces act as adapters between GraphQLite and your framework:
131-
132-
```php
133-
interface AuthenticationServiceInterface
134-
{
135-
/**
136-
* Returns true if the "current" user is logged.
137-
*
138-
* @return bool
139-
*/
140-
public function isLogged(): bool;
141-
}
142-
```
143-
144-
```php
145-
interface AuthorizationServiceInterface
146-
{
147-
/**
148-
* Returns true if the "current" user has access to the right "$right".
149-
*
150-
* @param string $right
151-
* @return bool
152-
*/
153-
public function isAllowed(string $right): bool;
154-
}
155-
```
156-
157-
You need to write classes that implement these interfaces. Then, you must register those classes with GraphQLite.
158-
It you are [using the `SchemaFactory`](other-frameworks.md), you can register your classes using:
159-
160-
```php
161-
// Configure an authentication service (to resolve the @Logged annotations).
162-
$schemaFactory->setAuthenticationService($myAuthenticationService);
163-
// Configure an authorization service (to resolve the @Right annotations).
164-
$schemaFactory->setAuthorizationService($myAuthorizationService);
165-
```
166-

docs/fine-grained-security.md

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
---
2+
id: fine-grained-security
3+
title: Fine grained security
4+
sidebar_label: Fine grained security
5+
---
6+
7+
If the [`@Logged` and `@Right` annotations](authentication_authorization.md#logged-and-right-annotations) are not
8+
granular enough for your needs, you can use the advanced `@Security` annotation.
9+
10+
Using the `@Security` annotation, you can write an *expression* that can contain custom logic. For instance:
11+
12+
- Check that a user can access a given resource
13+
- Check that a user has one right or another right
14+
- ...
15+
16+
## Using the @Security annotation
17+
18+
The `@Security` annotation is very flexible: it allows you to pass an expression that can contains custom logic:
19+
20+
```php
21+
use TheCodingMachine\GraphQLite\Annotations\Security;
22+
23+
// ...
24+
25+
/**
26+
* @Query
27+
* @Security("is_granted('ROLE_ADMIN') or is_granted('POST_SHOW', post)")
28+
*/
29+
public function getPost(Post $post): array
30+
{
31+
// ...
32+
}
33+
```
34+
35+
The *expression* defined in the `@Security` annotation must conform to [Symfony's Expression Language syntax](https://symfony.com/doc/4.4/components/expression_language/syntax.html)
36+
37+
<div class="alert alert-info">
38+
If you are a Symfony user, you might already be used to the <code>@Security</code> annotation. Most of the inspiration
39+
of this annotation comes from Symfony. Warning though! GraphQLite's <code>@Security</code> annotation and
40+
Symfony's <code>@Security</code> annotation are slightly different. Especially, the two annotations do not live
41+
in the same namespace!
42+
</div>
43+
44+
## The `is_granted` function
45+
46+
Use the `is_granted` function to check if a user has a special right.
47+
48+
```php
49+
@Security("is_granted('ROLE_ADMIN')")
50+
```
51+
52+
is similar to
53+
54+
```php
55+
@Right("ROLE_ADMIN")
56+
```
57+
58+
In addition, the `is_granted` function accepts a second optional parameter: the "scope" of the right.
59+
60+
```php
61+
/**
62+
* @Query
63+
* @Security("is_granted('POST_SHOW', post)")
64+
*/
65+
public function getPost(Post $post): array
66+
{
67+
// ...
68+
}
69+
```
70+
71+
In the example above, the `getPost` method can be called only if the logged user has the 'POST_SHOW' permission on the
72+
`$post` object. You can notice that the `$post` object comes from the parameters.
73+
74+
## Accessing method parameters
75+
76+
All parameters passed to the method can be accessed in the `@Security` expression.
77+
78+
```php
79+
/**
80+
* @Query
81+
* @Security("startDate < endDate", statusCode=400, message="End date must be after start date")
82+
*/
83+
public function getPosts(DateTimeImmutable $startDate, DateTimeImmutable $endDate): array
84+
{
85+
// ...
86+
}
87+
```
88+
89+
In the example above, we tweak a bit the Security annotation purpose to do simple input validation.
90+
91+
## Setting HTTP code and error message
92+
93+
You can use the `statusCode` and `message` attributes to set the HTTP code and GraphQL error message.
94+
95+
```php
96+
/**
97+
* @Query
98+
* @Security("is_granted('POST_SHOW', post)", statusCode=404, message="Post not found (let's pretend the post does not exists!)")
99+
*/
100+
public function getPost(Post $post): array
101+
{
102+
// ...
103+
}
104+
```
105+
106+
Note: since a single GraphQL call contain many errors, 2 errors might have conflicting HTTP status code.
107+
The resulting status code is up to the GraphQL middleware you use. Most of the time, the status code with the
108+
higher error code will be returned.
109+
110+
## Accessing the user
111+
112+
You can use the `user` variable to access the currently logged user.
113+
You can use the `is_logged()` function to check if a user is logged or not.
114+
115+
```php
116+
/**
117+
* @Query
118+
* @Security("is_logged() && user.age > 18")
119+
*/
120+
public function getNSFWImages(): array
121+
{
122+
// ...
123+
}
124+
```
125+
126+
## Accessing the current object
127+
128+
You can use the `this` variable to access any (public) property / method of the current class.
129+
130+
```php
131+
class Post {
132+
/**
133+
* @Query
134+
* @Security("this.canAccess(post, user)")
135+
*/
136+
public function getPost(Post $post): array
137+
{
138+
// ...
139+
}
140+
141+
public function canAccess(Post $post, User $user): bool
142+
{
143+
// Some custom logic here
144+
}
145+
}
146+
```
147+
148+
## Available scope
149+
150+
The `@Security` annotation can be used in any query, mutation or field, so anywhere you have a `@Query`, `@Mutation`
151+
or `@Field` annotation.
152+
153+
## How to restrict a given resource
154+
155+
The `is_granted` method can be used to restrict access to a specific resource.
156+
157+
```php
158+
@Security("is_granted('POST_SHOW', post)")
159+
```
160+
161+
If you are wondering how to configure these fine-grained permissions, this is not something that GraphQLite handles
162+
itself. Instead, this depends on the framework you are using.
163+
164+
If you are using Symfony, you will [create a custom voter](https://symfony.com/doc/current/security/voters.html).
165+
166+
If you are using Laravel, you will [create a Gate or a Policy](https://laravel.com/docs/6.x/authorization).
167+
168+
If you are using another framework, you need to know that the `is_granted` function simply forwards the call to
169+
the `isAllowed` method of the configured `AuthorizationSerice`. See [Connecting GraphQLite to your framework's security module
170+
](implementing-security.md) for more details

docs/implementing-security.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
---
2+
id: implementing-security
3+
title: Connecting GraphQLite to your framework's security module
4+
sidebar_label: Connecting security to your framework
5+
---
6+
7+
<div class="alert alert-info">
8+
This step is NOT necessary for users using GraphQLite through the Symfony Bundle or the Laravel package
9+
</div>
10+
11+
GraphQLite needs to know if a user is logged or not, and what rights it has.
12+
But this is specific of the framework you use.
13+
14+
To plug GraphQLite to your framework's security mechanism, you will have to provide two classes implementing:
15+
16+
* `TheCodingMachine\GraphQLite\Security\AuthenticationServiceInterface`
17+
* `TheCodingMachine\GraphQLite\Security\AuthorizationServiceInterface`
18+
19+
Those two interfaces act as adapters between GraphQLite and your framework:
20+
21+
```php
22+
interface AuthenticationServiceInterface
23+
{
24+
/**
25+
* Returns true if the "current" user is logged
26+
*/
27+
public function isLogged(): bool;
28+
29+
/**
30+
* Returns an object representing the current logged user.
31+
* Can return null if the user is not logged.
32+
*/
33+
public function getUser(): ?object;
34+
}
35+
```
36+
37+
```php
38+
interface AuthorizationServiceInterface
39+
{
40+
/**
41+
* Returns true if the "current" user has access to the right "$right"
42+
*
43+
* @param mixed $subject The scope this right applies on. $subject is typically an object or a FQCN. Set $subject to "null" if the right is global.
44+
*/
45+
public function isAllowed(string $right, $subject = null): bool;
46+
}
47+
```
48+
49+
You need to write classes that implement these interfaces. Then, you must register those classes with GraphQLite.
50+
It you are [using the `SchemaFactory`](other_frameworks.md), you can register your classes using:
51+
52+
```php
53+
// Configure an authentication service (to resolve the @Logged annotations).
54+
$schemaFactory->setAuthenticationService($myAuthenticationService);
55+
// Configure an authorization service (to resolve the @Right annotations).
56+
$schemaFactory->setAuthorizationService($myAuthorizationService);
57+
```
58+

docs/other_frameworks.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,4 +281,4 @@ return new Picotainer([
281281

282282
And we are done! You can now test your query using your favorite GraphQL client.
283283

284-
![](../img/query1.png)
284+
![](assets/query1.png)

docs/queries.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ The easiest way to test a GraphQL endpoint is to use [GraphiQL](https://github.c
5656

5757
Here a query using our simple *Hello World* example:
5858

59-
![](../img/query1.png)
59+
![](/img/query1.png)
6060

6161
## Query with a type
6262

website/sidebars.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
"docs": {
33
"Introduction": ["features"],
44
"Installation": ["getting-started", "symfony-bundle", "laravel-package", "universal_service_providers", "other-frameworks"],
5-
"Usage": ["queries", "mutations", "type_mapping", "autowiring", "extend_type", "authentication_authorization", "external_type_declaration", "input-types", "inheritance-interfaces"],
5+
"Usage": ["queries", "mutations", "type_mapping", "autowiring", "extend_type", "external_type_declaration", "input-types", "inheritance-interfaces"],
6+
"Security": ["authentication_authorization", "fine-grained-security", "implementing-security"],
67
"Performance": ["query-plan", "prefetch-method"],
78
"Advanced": ["file-uploads", "pagination", "custom-types", "field-middlewares", "extend_input_type", "multiple_output_types", "symfony-bundle-advanced", "internals", "troubleshooting", "migrating"],
89
"Reference": ["annotations_reference"]

0 commit comments

Comments
 (0)