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:

When configuring advanced permissions, keep the following in mind:

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.

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:

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

  1. 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:

  2. Click Advanced.

     

    The Advanced Permissions panel for the selected element displays.

     

  3. In the Advanced Permissions panel, click Add.

  4. 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.

  5. In the Advanced Permissions table, click Edit Expression.

     

    The Expression Editor opens.

  6. 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.

  7. Click OK.

  8. 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:

Copy
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:

Copy
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.

Apply 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:

Copy
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.

Apply attribute filter based on groups

Use case: You want to restrict which features of a layer a user can access based on a "PROJECT" attribute of the layer and the user's membership in one or more groups.

 

Example expression:

Copy
if (isUserInRole("Admin Role")) {
    // All projects
    return;
}

const projects = [];
if (isUserInRole("Project Role 1")) {
    projects.push(1);
}
if (isUserInRole("Project Role 2")) {
    projects.push(2);
}

if (projects.length > 0) {
    // Specific projects
    setAttributeFilter(`PROJECT IN (${projects.join(", ")})`);
} else {
    // No projects
    setAttributeFilter("1 = 0");
}

 

Apply attribute filter based on a user lookup that uses a configuration file

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 a JSON configuration file located at C:\ProgramData\Geocortex\Access Control\config\access-control\custom\folder\file1.json.

 

Example expression:

Copy
const lookup = getConfig("folder1/file1.json");
const zone = lookup[user.name];
if (zone) {
    setAttributeFilter(`ZONE = '${zone}'`);
} else {
    setAttributeFilter("1 = 0");
}

 

The getConfig function provides a way to centralize configuration that you want to share between multiple advanced permissions.

 

Apply 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:

Copy
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.

Apply 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:

Copy
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}'`);
});

 

Apply 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:

Copy
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}'`);
});

 

Apply 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:

Copy
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;
    geometry.spatialReference = json.spatialReference;
    setGeometryFilter(geometry);
});

 

The geometry used in a geometry filter must always specify a spatial reference. The geometry provided by an ArcGIS Server query operation does not include a spatial reference. The geometry.spatialReference = json.spatialReference assignment copies the spatial reference from the root of the response onto the geometry.

 

Apply attribute filter to multiple layers at once

Use case: You want to apply a single attribute filter to multiple layers that have common fields. For example, your service contains multiple layers with a "ZONE" field and you only want the user to see features in zone "2". You could apply a single attribute filter to the All Layers and Tables of the service.

 

Example expression:

Copy
const fieldName = "ZONE";
if (layer && layer.fields && layer.fields.some(x => x.name === fieldName)) {
    setAttributeFilter(`${fieldName} = 2`);
}

 

The layer && layer.fields null checks are required because the layer object will not always be defined and have fields when advanced permissions are evaluated at the All Layers and Tables level.

The layer.fields.some(x => x.name === fieldName) test ensures that the attribute filter is only applied to layers that contain the field in question.

The x.name === fieldName test is case sensitive. You could use x.name.toUpperCase() === fieldName to do a case-insensitive match.

 

Apply geometry filter to multiple layers at once

Use case: You want to restrict which features (across multiple layers) that a user can access based on a geometry filter.

 

Example expression:

Copy
if (layer) {
    setGeometryFilter("name-of-your-geofence-geometry");
}

 

The layer null check is required because the layer object will not always be defined when advanced permissions are evaluated at the All Layers and Tables level and there is no need to assign a geometry filter when a layer is not present.

This example applies the geometry filter to all layers in the service. Use caution with this approach because geometry filters can impact performance. You should only apply geometry filters to the specific set of layers that need them.

 

Error handling

Use case: You want to ensure that if there is an error during the execution of your advanced permission JavaScript that you can catch the error and apply appropriate fallback permissions.

 

Example expression:

Copy
try {
    // Do something that could throw an error
} catch (e) {
    // Log the failure
    console.log("An error occurred", e);

    // Apply fallback permissions
    setAttributeFilter("1 = 0");
}

 

Example asynchronous expression:

Copy
run(async () => {
    try {
        const response = await fetch(`https://server/api/?user=${encodeURIComponent(user.name)}`);
        const json = await response.json();
        const zone = json.zone;
        setAttributeFilter(`ZONE = '${zone}'`);
    } catch (e) {
        // Log the failure
        console.log("An error occurred", e);
    
        // Apply fallback permissions
        setAttributeFilter("1 = 0");
    }
});

 

When an advanced permission throws an error, that does not get caught by your JavaScript code, the request will be denied.