Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 121 additions & 3 deletions modules/ROOT/pages/security/authorization.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ For instance, to only require filtering for the reading and aggregating posts:
[source, graphql, indent=0]
----
type Post @authorization(filter: [
{ operations: [READ, AGGREGATE] where: { node: { author: { id: "$jwt.sub" } } } }
{ operations: [READ, AGGREGATE], where: { node: { author: { id: "$jwt.sub" } } } }
]) {
title: String!
content: String!
Expand Down Expand Up @@ -106,11 +106,10 @@ Validation can be configured to only be performed on certain operations:

For instance, to only require validation for the update or deletion of a post:


[source, graphql, indent=0]
----
type Post @authorization(validate: [
{ operations: [UPDATE, DELETE] where: { node: { author: { id: "$jwt.sub" } } } }
{ operations: [UPDATE, DELETE], where: { node: { author: { id: "$jwt.sub" } } } }
]) {
title: String!
content: String!
Expand All @@ -123,6 +122,94 @@ type Post @authorization(validate: [
In case there is no `operations` argument with a list of operations, the GraphQL Library treats the authorization configuration as if the full list of operations had been provided.
====

=== When

Validation can be configured to only be performed before or after an operation is executed.
This is done using the `when` argument which accepts an array of the following values:

* `BEFORE`
* `AFTER`

Additionally, some operations only support validation either before or after them, which is summarised in this table:

[cols="2,5"]
|===
| `operation` | `when`

| `READ`
| `BEFORE`

| `AGGREGATE`
| `BEFORE`

| `CREATE`
| `AFTER`

| `UPDATE`
| `BEFORE`, `AFTER`

| `DELETE`
| `BEFORE`

| `CREATE_RELATIONSHIP`
| `BEFORE`, `AFTER`

| `DELETE_RELATIONSHIP`
| `BEFORE`, `AFTER`

|===

As a brief example, if you wanted anybody to be able to attempt to update any post, but wanted to check that after the update, the author it is connected to is still the current user, you could do the following:

[source, graphql, indent=0]
----
type Post @authorization(validate: [
{ operations: [UPDATE], when: [AFTER], where: { node: { author: { id: "$jwt.sub" } } } }
]) {
title: String!
content: String!
author: User! @relationship(type: "AUTHORED", direction: IN)
}
----

== Authorization on fields

The `@authorization` directive can be used either on either object types or their fields, with the former being used in examples for the most part on this page. When applied to a field, the authorization rules are only evaluated if the matching operations are performed on that field. For example, consider a `User` type with a `password` field:

[source, graphql, indent=0]
----
type User {
id: ID!
username: String!
password: String! @authorization(where: [{ operations: [READ, UPDATE], where: { node: { id: "$jwt.sub" } } }])
}
----

When executing the following query, a valid identity is not needed:

[source, graphql, indent=0]
----
{
users {
username
}
}
----

However, consider the following query:

[source, graphql, indent=0]
----
{
users {
username
password
}
}
----

This will require a valid JWT to have been provided with the request, and the matching users will be filtered down according to the JWT subject. The same will apply for attempting to update the `password` field, the update will only apply to the user matching the JWT.


== Authorization without authentication

Expand All @@ -147,3 +234,34 @@ type Post @authorization(filter: [
author: User! @relationship(type: "AUTHORED", direction: IN)
}
----

== Ordering of rules

In each ruleset (`filter` and `validate`), rules are joined with an `OR`. The two rulesets are joined with an `AND`.

An example pseudo-logic would be `(filterRule1 OR filterRule2) AND (validateRule1 OR validateRule2)`.

If ever there are two rules which you would like to be combined with an `AND`, these should be combined into a single rule. Take for instance the following example:

[source, graphql, indent=0]
----
type User @authorization(validate: [
{ operations: [UPDATE], where: { jwt: { roles_INCLUDES: "admin" } } }
{ operations: [UPDATE], where: { node: { locked: false } } }
]) {
id: ID!
locked: Boolean!
}
----

Say in this example we wanted it to be that a user needs to be an admin _and_ the `locked` property must be `false` in order to update a `User` node. We would need to combine these predicates into a single rule:

[source, graphql, indent=0]
----
type User @authorization(validate: [
{ operations: [UPDATE], where: { AND: [{ jwt: { roles_INCLUDES: "admin" } }, { node: { locked: false } }] } }
]) {
id: ID!
locked: Boolean!
}
----
62 changes: 62 additions & 0 deletions modules/ROOT/pages/security/operations.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,66 @@ query {
}
----

=== Connection

For a connection query, rules with `READ` in the operations are evaluated for any type being read:

[source, graphql, indent=0]
----
query {
moviesConnection {
edges {
node { # READ ON OBJECT Movie
title # READ ON FIELD_DEFINITION Movie.title
actorsConnection {
edges {
node { # READ ON OBJECT Actor
name # READ ON FIELD_DEFINITION Actor.name
}
}
}
}
}
}
}
----

=== Aggregation

For an aggregation query, rules with `AGGREGATE` in the operations are evaluated for any type being aggregated:

[source, graphql, indent=0]
----
query {
moviesAggregate { # AGGREGATE ON OBJECT Movie
title { # AGGREGATE ON FIELD_DEFINITION Movie.title
longest
}
}
}
----

The same logic applies to aggregations of nested nodes:

[source, graphql, indent=0]
----
query {
movies {
actorsAggregate { # AGGREGATE ON OBJECT Actor
node {
name { # AGGREGATE ON FIELD_DEFINITION Actor.name
longest
}
}
}
}
}
----

== Mutation

=== Create

For `create` mutations, `CREATE` rules on the object are evaluated for each node created, as well as field definition rules:

[source, graphql, indent=0]
Expand All @@ -56,6 +114,8 @@ mutation {
}
----

=== Delete

For single `delete` mutations, rules with `DELETE` on the object are evaluated:

[source, graphql, indent=0]
Expand All @@ -81,6 +141,8 @@ mutation {
}
----

=== Update

For a complex `update` mutation with many effects, a variety of rules is evaluated, as well as `READ` rules for the selection set:

[source, graphql, indent=0]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
= Subscriptions authorization

Subscriptions require their own authorization rules, which are configured with the `@subscriptionsAuthorization` directive.
These rules are different to authorization rules for queries and mutations because they use filtering rules available for subscriptions events.
These rules are different to authorization rules for queries and mutations because they use filtering rules available for subscriptions events. These filtering rules can only be used to filter against the properties of the nodes impacted by the events - they cannot be used to arbitarily filter across related nodes as in the schema generated by the Neo4j GraphQL Library normally.

All subscriptions authorization rules have an implied requirement for authentication, given that the rules are normally evaluated against values in the JWT payload.

Expand Down
2 changes: 2 additions & 0 deletions modules/ROOT/pages/types/relationships.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ You can add relationship properties to the example in two steps:
. Add a type definition decorated with the `@relationshipProperties` directive, containing the desired relationship properties.
. Add a `properties` argument to both "sides" (or just one side, if you prefer) of the `@relationship` directive which points to the newly defined interface.

Relationship properties fields can only be primitive types or their list variants. You cannot map complex types such as object types into the types modelling relationship properties.

For example, suppose you want to distinguish which roles an actor played in a movie:

[source, graphql, indent=0]
Expand Down
Loading