Authorization
GraphGlue only provides Authorization features. Authentication must be implemented by the user, for example using OpenID Connect
Declaration
To declare Permissions, GraphGlue uses a declarative approach, meaning authorizations are declared using the @Authorization
annotation on Node
subtypes.
Currently, only node-level permissions are supported, while field-level permissions are not.
However, if you need field-level permissions, checking manually is possible.
Overview
Continuing the sample domain model introduced in Modeling, declaraing a permission looks like this:
@DomainNode
@Authorization(
name = "READ",
allow = [Rule("actorAllowedBean")],
allowFromRelated = ["movies"],
disallow = [Rule("actorDisallowedBean")]
)
class Actor : Node() {
@NodeRelationship("ACTOR", Direction.INCOMING)
val movies by NodeProperty<Movie>()
}
The name
is used to identify the permission to check, it is later referenced in Checking.
The checking algorithm works in two steps:
-
Check if access is allowed. Access is allowed if any
Rule
inallow
evaluates totrue
, OR if the permission withname
is granted to any node referenced inallowFromRelated
, or ifallowAll
istrue
. If neither of these conditions is fulfilled, the permission is NOT granted. Especially, ifallow
andallowFromRelated
are both empty, allow is not granted.cautionIf any
NodeSetProperty
is referenced inallowFromRelated
, allow is granted if the permission is granted to ANY of the related nodes. -
Check if access is disallowed. Access is granted if an only if NO
Rule
indisallow
evaluates totrue
.
In this example, allow is granted if the "actorAllowedBean"
Rule evaluate to true
, OR if "READ"
is granted to any Movie in movies
.
Then, the permission is granted if also "actorDisallowedBean"
evaluates to false
Rule
A Rule
consists of two parts: a String
that references a Spring bean of type AllowRuleGenerator
/DisllowRuleGenerator
, and a list of Strings used as configuration parameters.
Example:
@Bean("startsWithRule")
fun startsWithRuleGenerator(): DisallowRuleGenerator {
return DisallowRuleGenerator { node, rule, permission ->
val requiredPrefix = (permission.context as MyAuthorizationContext).prefix
node.property(rule.options[0]).startsWith(Cypher.anonParameter(requiredPrefix))
}
}
This rule checks if a String property on node
with a name provided as first configuration parameter has NOT a prefix provided in the permission to check.
It can be used like this:
@DomainNode
@Authorization("READ", disallow = [Rule("startsWithRule", "name")])
class NamedNode(val name: String) : Node()
AllowRuleGenerator
works the same, with the difference that it may return an additional RelationshipPattern
that is checked for existance in combination with the returned condition.
Note that the AllowRuleGenerator
/DisllowRuleGenerator
generates a CypherDSL Conditon
, which allows checking complex conditions directly in the database.
Since version 6.0.0, disallow rules affect only the path from the node to the node to check the permission on.
Before, disallow rules affect all nodes in the RelationshipPattern
generated by the allow rule.
Inheritance
The algorithm first searches for all @Authorization
annotations with the specified name
.
This includes annotations from superclasses.
It is even possible to use multiple @Authorization
annotations with the same name
on the same class, although this is not recommended.
Checking
Permission
Generally, it is checked if a Permission
is granted to a Node
A Permission
consists of two parts
- The name of the permission to be checked
- A
AuthorizationContext
instance. It is intended implement this interface in order to provide custom values toAuthorizationRuleGenerator
instances, e.g. the id of the authenticated user, or a role String.
GraphQL
To automatically check permissions, provide a Permission
to the GraphQL Context Map under the key Permission::class
.
Permissions are checked at
- the
node
query - queries generated using the
name
parameter of the@DomainNode
annotation - GraphGlue relationship properties
Any other properties returning Node
types are NOT automatically checked!
This is due to checking is only done directly in the database.
This is espacially the case when returning nodes from functions which use the lazy-loading functionality of GraphGlue relationships, or when returning nodes in mutations.
Consider the following example with the Actor
class from above
data class CreateActorOutput(val actor: Actor)
When returing this, actor
is NOT checked, however, note that the permission is checked for GraphGlue relationships on actor
, in this example the movies
property.
Permissions are only checked if necessary.
Considering the above, if actors
is accessed on Movie
, allow is not checked as Actor
allows from related movies
.
Manually
Permissions can be manually checked by injecting a AuthorizationChecker
bean.
It provides a hasAuthorization
function, which takes a Node
and a Permission
as input, and returns whether the permission is granted on the node.
While it is up to the user to provide the required Permission
, the recommended way is to put it into the GraphQL Context Map as described in GraphQL, and then obtain it using an injected DataFetchingEnvironment.
Example:
@Component
class LeafMutations: Mutation {
fun createLeaf(input: CreateLeafInput, dfe: DataFetchingEnvironment): CreateLeafOutput {
val readPermission = dfe.requiredPermission // gives access to the permission under the key Permission::class
val myPermission = Permission("WRITE", dfe.authorizationContext) // AuthorizationContext from the Permission
// ...
}
}
If automatic permission checking in GraphQL is not wanted, it is also possible to only provide the AuthorizationContext
under the key AuthorizationContext::class
to the GraphQL Context Map