Advanced Permissions
You can create custom JavaScript expressions to apply permission rules and attribute filters. This is a powerful feature that lets you apply multiple permissions/filters to multiple users/groups at once.
You can construct an expression that accesses details about the data, the user, and/or the request, and then applies permissions accordingly. For example, you might want to configure an expression that applies permissions according to:
- The user making the request.
- The roles the user belongs to.
- The current request URL.
- The layer being accessed.
You will need a basic understanding of JavaScript.
An invalid expression results in the incoming request being denied.
Errors encountered when evaluating an expression are included in the Access Control logs that are saved on the machine where Access Control is installed. See Logging.
Order of Evaluation and Inheritance
While advanced permissions are powerful and offer many advantages, it is important to note how they differ from regular permissions.
-
Regular permissions:
-
Are evaluated first.
-
Are evaluated from the service level down.
-
Are subject to inheritance rules apply (see Inheritance).
-
-
Advanced permissions:
-
Are evaluated after all regular permissions have been evaluated.
-
Are evaluated in the opposite direction from regular permissions—that is, starting at the most granular and progressing up to the service level.
-
Are not subject to inheritance rules.
-
Because advanced permissions are evaluated in the opposite direction from regular permissions, any advanced permission configured at a higher level overrides more granular permissions. For example, an advanced permission configured at the service level will always override any advanced permission configured at the layer, table, and/or task level.
JavaScript Support
The Access Control Expression Editor supports all JavaScript keywords and functions. You will also find some items that are particularly useful or specific to Access Control.
For example:
- allowLayer, allowField, allowTask, allowLayerEdit
- denyLayer, denyField, denyTask
- user, isUserInRole
- layer
- request
The editor provides code completion suggestions as you type, but you can press Ctrl + Space to display code completion suggestions at any time.
Configure an Expression
-
In the VertiGIS Studio Access Control Designer: select the service, layer, or task on which you want to configure an advanced expression.
For details, refer to the appropriate section:
-
Add Service Permissions, steps 1 - 2
-
Add Layer Permissions, step 1
-
-
Click Advanced.
The Advanced Permissions panel for the selected element displays.
-
In the Advanced Permissions panel, click Add.
-
Select a user or group:
In the Select a User or Group dialog box, in the drop-down list on the right, choose whether to search by Group or by User.
Start typing the name of the group/user into the text box, and then select from the drop-down list that displays.
-
In the Advanced Permissions table, click Edit Expression.
The Expression Editor opens.
-
Type your expression into the editor. As you type, the editor provides JavaScript code completion suggestions.
Press Ctrl + Space to display code completion suggestions at any time.
-
Click OK.
-
Click Save.
Examples
The following sections show how advanced permissions can be used in some common use cases.
These examples are intended to illustrate the APIs and patterns of usage in advanced permissions. They do not represent complete solutions with proper handling of all errors and boundary conditions.
Grant access to specific layers based on user role
Use case: You have multiple layers that you want to allow multiple groups to access.
Using regular permissions, you would need to add one permission to each layer for each group you want to allow.
Using advanced permissions, you can achieve the same result by configuring a single permission at the service level.
Example expression:
if (layer.name === "Cities" || layer.name === "Countries") {
if (isUserInRole("Role 1", "Role 2")) {
allowLayer();
}
}
Notice that we can achieve the same result with a single permission and a small amount of code. Now consider scaling the problem up to 50 layers and 10 groups. The advanced permissions approach is much simpler and easier to maintain.
Deny access to an internal field
Use case: Your service has many layers, and many of them contain an internal field that you do not want your users to access.
You could use regular permissions to achieve this, but you would need to configure a deny permission on every layer containing the field.
Using advanced permissions, you only need to configure one permission at the service level.
Because it is applied at the service level, the following expression evaluates for every layer. If the field does not exist on some of the layers, it is simply ignored.
Example expression:
denyField("INTERNAL_ID");
This is a simple and efficient way to apply a single permission to multiple layers, using a small amount of code.
Deny access to a field based on URL of the request
Use case: You want to prevent users from accessing a particular field if the incoming request is based on a query.
You cannot use regular permissions to achieve this. However, you can configure one advanced permission that checks the URL path of the request. If the path ends with '/query', access to the field is denied.
Example expression:
if(request.path.endsWith("/query")) {
denyField("Field1");
}
You can use this approach to deny access to multiple fields, or to an entire layer, table, or task.
Set an attribute filter based on a user lookup
Use case: You want to restrict which features of a layer a user can access based on a "ZONE" attribute of the layer using a lookup of usernames to zones defined in code.
Example expression:
const lookup = {
bsmith: "Zone 1",
jdoe: "Zone 2",
user1: "Zone 1",
user2: "Zone 3",
};
const zone = lookup[user.name];
if (zone) {
setAttributeFilter(`ZONE = '${zone}'`);
} else {
setAttributeFilter("1 = 0");
}
You can use setAttributeFilter("1 = 0")
to deny access to all features.
Set an attribute filter based on a user lookup that uses a HTTP request
Use case: You want to restrict which features of a layer a user can access based on a "ZONE" attribute of the layer using a lookup of usernames to zones provided by an external service over HTTP.
Example expression:
run(async () => {
const response = await fetch(`https://server/api/?user=${encodeUriComponent(user.name)}`);
const json = await response.json();
const zone = json.zone;
setAttributeFilter(`ZONE = '${zone}'`);
});
The run(async () => {...
pattern is required when you need to perform asynchronous operations like fetch
that use the await
keyword.
Set an attribute filter based on a user lookup that uses a HTTP request with caching
Use case: A cache optimized version of the previous example that caches the result of the HTTP lookup for 30 seconds.
Example expression:
run(async () => {
const cacheKey = `zone-cache-${user.name}`;
let zone = getCachedData(cacheKey);
if (!zone) {
const response = await fetch(`https://server/api/?user=${encodeUriComponent(user.name)}`);
const json = await response.json();
zone = json.zone;
setCachedData(cacheKey, zone, 30000);
}
setAttributeFilter(`ZONE = '${zone}'`);
});
Set an attribute filter based on a user lookup that uses a HTTP request to an ArcGIS Server service
Use case: You want to restrict which features of a layer a user can access based on a "ZONE" attribute of the layer using a lookup of usernames to zones provided by an ArcGIS Server service.
Example expression:
run(async () => {
const where = encodeUriComponent(`USER = '${user.name}'`);
const response = await fetch(`https://server/arcgis/rest/services/MyService/FeatureServer/0/query?f=json&where=${where}&outFields=ZONE`);
const json = await response.json();
const zone = json.features[0].attributes.ZONE;
setAttributeFilter(`ZONE = '${zone}'`);
});
Set a geometry filter based on a user lookup that uses a HTTP request to an ArcGIS Server service
Use case: You want to restrict which features of a layer a user can access based on a geometry using a lookup of usernames to geometries provided by an ArcGIS Server service.
Example expression:
run(async () => {
const where = encodeUriComponent(`USER = '${user.name}'`);
const response = await fetch(`https://server/arcgis/rest/services/MyService/FeatureServer/0/query?f=json&where=${where}&returnGeometry=true`);
const json = await response.json();
const geometry = json.features[0].geometry;
setGeometryFilter(geometry);
});