Upgrade to RESTHeart v6
RESTHeart v6 introduces many news features, improvements and changes.
This page summarizes the new features and provide guidance on upgrading from previous versions
GraphQL API
RESTHeart 6 provides a GraphQL API for MongoDB that works side by side with the good old REST API to get a comprehensive Data API to build modern applications.
For more information see GraphQL documentation page that contains the following example (this will make happy those who love GraphQL like us):
POST /graphql/mflix HTTP/1.1
{
MoviesByTomatoesRateRange(min: 3.8, max: 4.5, limit: 3, skip: 20, sort: -1){
title
comments{
text
user{
name
}
}
tomatoesRate
}
}
{
"data": {
"MoviesByTomatoesRateRange": [
{
"title": "The Wages of Fear",
"comments": [
{
"text": "Commodi accusamus totam eaque sunt. Nihil reiciendis commodi molestiae esse ipsam corporis reprehenderit. Non nam similique vel dolor magni quia quis.",
"user": {
"name": "Doreah"
}
}
],
"tomatoesRate": 4.4
},
{
"title": "Chicago Deadline",
"comments": [
{
"text": "Nihil itaque a architecto. Illo veritatis totam at quibusdam. Doloremque hic totam consequuntur omnis molestiae commodi iste. Quis alias commodi nemo eveniet.",
"user": {
"name": "Patricia Good"
}
}
],
"tomatoesRate": 4.4
},
{
"title": "The Passion of Joan of Arc",
"comments": [],
"tomatoesRate": 4.4
}
]
}
}
GraalVM
Polyglot JavaScript Services and Interceptors on GraalVM
Services and Interceptors can be developed in JavaScript and TypeScript when running RESTHeart with GraalVM or using restheart-native
Here is an example
export const options = {
name: "helloWorldService",
description: "just another Hello World",
uri: "/hello"
}
export function handle(request, response) {
response.setContent(`{ "msg": `Hello ${rc.name || 'World'}` } `);
response.setContentTypeAsJson();
}
Just copy the test-js-plugins
directory into the plugins
directory to have the JS plugins automatically deployed.
JS plugins can be added or updated without requiring to restart the server, ie v6 supports JS plugins hot deployment.
Native Image
RESTHeart can be built as native image. See GraalVM documentation page.
The Docker image of restheart-native is available:
$ docker pull softinstigate:restheart:latest-native
Write Mode in MongoDB REST API
Breaking change
Until v5, all write requests have always upsert write mode.
V6 changes this and now by default:
POST
verb has insert write modePUT
andPATCH
verbs have update write mode
The new query parameter ?wm=insert|update|upsert
has been introduced to allow specifying the write mode.
However the use of the qparam ?wm
is forbidden by default; to allow it, the following permission must be defined:
For the FileAclAuthorizer
:
permissions:
- role: canUseWMQParam
predicate: path-prefix('/collection')
priority: 0
mongo:
allowWriteMode: true # default false
And in a similar way for the MongoAclAuthorizer
:
{
"role": "canUseWMQParam",
"predicate": "path-prefix('/collection')",
"priority": 0,
"mongo": {
"allowWriteMode": true
}
}
Extended and Simplified Security
Breaking change
RESTHeart 6 ships with Extended and Simplified Security.
First of all, the new write mode simplifies the definition of permissions, since a POST
is an insert while PUT
and PATCH
are updates by default. This makes easier defining permission to allow creating documents or just updating them.
VETOER Authorizers
In RESTHeart, multiple authorizers can be enabled.
In RESTHeart v6 the new attribute authorizerType
of @RegisterPlugin
is defined: an authorizer can be of type ALLOWER
(default) or VETOER
.
@RegisterPlugin(
name = "globalPredicatesVetoer",
description = "vetoes requests according to global predicates",
enabledByDefault = true,
authorizerType = TYPE.VETOER) // <----- optional, default ALLOWER
public class GlobalPredicatesVetoer implements Authorizer {
// omitted
}
As an in-bound request is received and authenticated the isAllowed()
method is
called on each authorizer. A secured request is allowed when no VETOER
denies
it and at least one ALLOWER
allows it.
secure attribute of @RegisterPlugin
The Service
plugin can be configured to require authentication and authorization.
Prior to v6, the configuration option secured: true
must be specified in restheart.yml
in order to secure a Service
:
plugins-args:
my-plugin:
secured: true
This forced the Service
to have a configuration section.
In RESTHeart v6, a Service
can be programmatically secured (i.e. to require authentication and authorization) via the following attribute of the @RegisterPlugin
annotation:
@RegisterPlugin(name = "myService",
description = ".....",
secure = true) // <----- optional, default false
public class MyService implements JsonService {
// code omitted
}
In JavaScript:
export const options = {
name: "myService",
description: "......",
uri: '/myService',
secured: true, // <----- optional, default false
}
fileAclAuthorizer supports same options than mongoAclAuthorizer
Until v5, mongoAclAuthorizer
offers more options for permissions than the fileAclAuthorizer
. In v6 the two authorizers are functionally equivalent with the only difference that mongoAclAuthorizer
handles the ACL permissions in JSON in a MongoDB collection while fileAclAuthorizer
handles them is a YML file. Here is a example of the same permission defined for the two authorizers:
fileAclAuthorizer
# allow role 'user' GET document from /{userid}
# a read filter apply, so only document with status=public or author=userid are returned <- readFilter
# must use 'page' qparam <- qparams-contain(page)
# cannot use 'filter' and 'sort' qparams <- qparams-blacklist(filter, sort)
# the property 'log' is removed from the response <- projectResponse
- roles: [ user ]
predicate: >
method(GET)
and path-template('/{userid}')
and equals(@user._id, ${userid})
and qparams-contain(page)
and qparams-blacklist(filter, sort)
priority: 100
mongo:
readFilter: >
{ "$or": [
{"status": "public"},
{"author": "@user._id" }
]}
projectResponse: >
{ "log": 0 }
mongoAclAuthorizer
{
"_id": "userCanGetOwnCollection",
"description": [
"**** DESCRIPTION PROPERTY IS NOT REQUIRED, HERE ONLY FOR DOCUMENTATION PURPOSES",
"allow role 'user' GET document from /{userid}",
"a read filter apply, so only document with status=public or author=userid are returned <- readFilter",
"must use 'page' qparam <- qparams-contain(page)",
"cannot use 'filter' and 'sort' qparams <- qparams-blacklist(filter, sort)",
"the property 'log' is removed from the response <- projectResponse"
],
"roles": ["user"],
"predicate": "method(GET) and path-template('/{userid}') and equals(@user._id, ${userid}) and qparams-contain(page) and qparams-blacklist(filter, sort)",
"priority": 100,
"mongo": {
"readFilter": {
"_$or": [{ "status": "public" }, { "author": "@user._id" }]
},
"projectResponse": { "log": 0 }
}
New ACL Permission predicates
The ACL permissions use the undertow predicate language to define the condition a request must met to be authorized.
RESTHeart 6 extends this language introducing the following 7 new predicates that extend and simplify the ACL permission definition:
predicate | description | example |
---|---|---|
qparams-contain |
true if the request query string contains the specified parameter |
qparams-contain(page,pagesize) |
qparams-blacklist |
true if the request query string does not contain the blacklisted parameters |
qparams-contain(filter,sort) |
qparams-whitelist |
true if the request query string contains only whitelisted parameters |
qparams-whitelist(page,pagesize) |
qparams-size |
true if the request query string the specified number of parameters |
qparams-size(3) |
bson-request-contains |
true if the request content is Bson and contains the specified properties |
bson-request-contains(foo,bar.sub) |
bson-request-whitelist |
true if the request content is Bson and only contains whitelisted properties |
bson-request-whitelist(foo,bar.sub) |
bson-request-blacklist |
true if the request bson content does not contain blacklisted properties |
bson-request-contains(foo,bar.sub) |
in |
true if value is in array (available from v6.1.5) |
path-template('/{coll}') and in(value=${coll}, array=@user.tenants) |
Consider the following example:
# allow role 'user' GET document from /{userid}
# a read filter apply, so only document with status=public or author=userid are returned <- readFilter
# must use 'page' qparam <- qparams-contain(page)
# cannot use 'filter' and 'sort' qparams <- qparams-blacklist(filter, sort)
- roles: [ user ]
predicate: >
method(GET)
and path-template('/{userid}')
and equals(@user._id, ${userid})
and qparams-contain(page)
and qparams-blacklist(filter, sort)
More examples can be found here
MongoPermissions
For requests handled by the MongoService
(i.e. the service that implements the REST API for MongoDB) the permission can specify the MongoPermissions
object.
mongo:
allowManagementRequests: false # default false
allowBulkPatch: false # default false
allowBulkDelete: false # default false
allowWriteMode: false # default false
readFilter: >
{"$or": [ {"status": "public"}, {"author": "@user._id"} ] }
writeFilter: >
{"author": "@user._id"}
mergeRequest: >
{"author": "@user._id"}
permission | description |
---|---|
allowManagementRequests |
DB Management Requests are forbidden by default (create/delete/update dbs, collection, file buckets schema stores and schemas, list/create/delete indexes, read db and collection metadata). To allow these requests, allowManagementRequests must be set to true |
allowBulkPatch |
bulk PATCH requests are forbidden by default, to allow these requests, allowBulkPatch must be set to true |
allowBulkDelete |
bulk DELETE requests are forbidden by default, to allow these requests, allowBulkDelete must be set to true |
allowWriteMode |
requests cannot use the query parameter ?wm=insert|update|upsert by default. To allow it, allowWriteMode must be set to true |
Note that, in order to allow those requests, not only the corresponding flag must be set to true
but the permission predicate
must resolve to true
.
Consider the following examples.
The next one won’t allow the role user
to execute a bulk PATCH even if the allowBulkPatch
is true
since the predicate
requires the request verb to be GET
- roles: [ user ]
predicate: path-prefix('coll') and method(GET)
mongo:
allowBulkPatch: true
The next request allows to PATCH the collection coll
and all documents in it, but won’t allow to execute a bulk PATCH (i.e. the request PATCH /coll/*?filter={ "status": "draft" }
since the allowBulkPatch
is false
- roles: [ user ]
predicate: path-prefix('coll') and method(PATCH)
mongo:
allowBulkPatch: false
mongo.readFilter and mongo.writeFilter
Breaking change
readFilter
and writeFilter
are available in RESTHeart v5 as well.
These are optional filters that are added to read and write requests respectively when authorized by an ACL permission that defines them.
Starting from v6, these must be under the mongo
object because they are applicable only to requests handled by the MongoService
mongo.mergeRequest
mergeRequest
allows to merge the specified properties to the request content. In this way, server-side evaluated properties can be enforced.
In the following example:
- roles: [ user ]
predicate: path-prefix('coll') and method(PATCH)
mongo:
mergeRequest: >
{"author": "@user._id"}
the property author
is evaluated to be the _id
of the authenticated client.
@user
is a special variable that allows accessing the properties of the user object. The following variables are available:
variable | description |
---|---|
@user |
the user object (excluding the password), e.g. @user.userid (for users defined in acl.yml by FileRealmAuthenticator ) or @user._id (for users defined in MongoDB by MongoRealmAuthenticator ) |
@request |
the properties of the request, e.g. @request.remoteIp |
@mongoPermissions |
the MongoPermissions object, e.g. @mongoPermissions.writeFilter |
@now |
the current date time |
@filter |
the value of the filter query parameter |
mongo.projectResponse
projectResponse
allows to project the response content, i.e. to remove properties.
It can be used with positive or negative logic.
The following hides the properties secret
and a.nested.secret
(you can use the dot notation!). All other properties are returned.
- roles: [ user ]
predicate: path-prefix('coll') and method(PATCH)
mongo:
projectResponse: >
{"secret": 0, "a.nested.secret": 0 }
The following only returns the property public
(you can use the dot notation!). All other properties are hidden.
- roles: [ user ]
predicate: path-prefix('coll') and method(PATCH)
mongo:
projectResponse: >
{"public": 1 }
OriginVetoer
OriginVetoer
authorizer protects from CSRF attacks by forbidding requests whose Origin header is not whitelisted.
It can configured as follows in the Authorizers section of restheart.yml
:
authorizers:
originVetoer:
enabled: true # <---- default is false
whitelist:
- https://restheart.org
- http://localhost
filterOperatorsBlacklist
A global blacklist for MongoDb query operators in the filter
query parameter.
plugins-args:
filterOperatorsBlacklist:
blacklist: [ "$where" ]
enabled: true
With this configuration, the request GET /coll?filter={"$where": "...."}
is forbidden.
Variables in aggregation and permission
The same variables that can be used in permissions, can be used in aggregation definition plus the variables @page
, @pagesize
, @skip
and @limit
that are computed from the paging query parameters ?page=1&pagesize=4
.
This is a valid aggregation definition where a filter is applied from the readFilter
property of the ACL permission that eventually allowed the request.
{
"uri": "meetingsDate",
"type": "pipeline",
"stages": [
{
"$match": {
"$var": "@mongoPermissions.readFilter"
}
},
{
"$sort": {
"_id": -1
}
}
]
}
Dynamic Change Streams
Change streams definition can be updated without requiring to restart RESTHeart.
When a change stream definition is updated, all clients connected via WebSocket are automatically disconnected from the old change stream.
Java version
Breaking change
RESTHeart 6.0.x and 6.1.x requires Java 16+ or GraalVM 21.1 (JDK 16). RESTHeart 6.2.x requires Java 17+ or GraalVM 22.0.0.2 (JDK 17).