Skip to main content

Authorization

caution

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:

  1. Check if access is allowed. Access is allowed if any Rule in allow evaluates to true, OR if the permission with name is granted to any node referenced in allowFromRelated, or if allowAll is true. If neither of these conditions is fulfilled, the permission is NOT granted. Especially, if allow and allowFromRelated are both empty, allow is not granted.

    caution

    If any NodeSetProperty is referenced in allowFromRelated, allow is granted if the permission is granted to ANY of the related nodes.

  2. Check if access is disallowed. Access is granted if an only if NO Rule in disallow evaluates to true.

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.

caution

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 to AuthorizationRuleGenerator 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

danger

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.

info

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
// ...
}
}
tip

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