1.18.0 • Published 9 months ago

@sap/ams v1.18.0

Weekly downloads
-
License
SAP DEVELOPER LIC...
Repository
-
Last release
9 months ago

@sap/ams

This is the Node.Js runtime library used to perform authorization checks in applications which authenticate users via the SAP Cloud Identity Services.

The module @sap/ams-dev provides the corresponding tooling support during application development.

As ADC (Authorization Decision Controller) an in-process engine based on DCN (Data Control Notation) files is used in versions >= 1.17.0. Follow the migration guide in the CHANGELOG of @sap/ams-dev for version 1.0.0 to update your local test setup from OPA to DCN Engine.

:warning: For the time being, the OPA buildpack is still required for deployment to CF in order to push DCL policies to the AMS server. Replacements are currently discussed that will make it obsolete in future releases.

Installation

Via public npmjs repository:

npm install @sap/ams

Usage examples

Basic allow request on resource salesOrders with action read:

const pdp = new PolicyDecisionPoint();
const attr = new Attributes();
attr.setAction("read")
	.setResource("salesOrders")
	.setPolicies(["sales.readAllSalesOrders"])
let isAllowedRead = await this.pdp.allow(attr);

Basic allowFilterClause request with unknown $env.$user:

const pdp = new PolicyDecisionPoint();
const attr = new Attributes();
attr.setAction("read")
	.setResource("salesOrders")
	.setPolicies(["sales.readAllSalesOrders"])
	.addUnknowns(AttributeName.common.APP, AttributeName.common.ENV);
const filterClause = await this.pdp.allowFilterClause(attr);

API Description

Attributes

The Attributes class (see doc/API/Attributes.md) wraps the input data for an authorization check.\ Internally a JSON object is created which could look approximately as follows:

{
	"$dcl": {
		"action":                "read",
		"resource":              "SalesOrder",
		"tenant":                "12345",
		"principal2policies":    ["zone_a","user_a"],
	},
	"$app": {
		"country": "DE"
	},
	"$env": {

	},
	"unknowns": [
		"$app.SalesID"
 	],
	 "ignores": [
		 "input[\"$dcl\"][\"resource\"]"
	]
}

An Attributes object can be defined as follows:

const attr = new Attributes()
	.setAction("read")
	.setPolicies(["ams.readAllSalesOrders"])
	.setResource("salesOrder")
	.setApp({"country": "DE"});

Note: Only principal2policies or policies can be set. The other one will be overridden.

Policy Decision Point

The PolicyDecisionPoint or PDP (see doc/API/PolicyDecisionPoint.md) is the primary API for consumers and is responsible for performing authorization checks using the ADC.

AttributeName

AttributeName (see doc/API/AttributeName.md) is a special type to store unknown and ignore values.

Call

The Call class (see doc/API/Call.md) wraps a condition JSON returned from pdp.allowFilterClause().\ In addition it wraps an enum Call.type:

{
	AND:         "and",
	OR:          "or",
	EQ:          "eq",
	GT:          "gt",
	...
}

The Call API contains functionalities which should make working with big condition results easier.\ First the allowFilterClause condition is transformed into a Call object:

const filterClause = await pdp.allowFilterClause(attributes);
const call = Call.fromCondition(filterClause.condition);

In the most common cases one wants to transform the condition into a sql like statement.\ Therefore a transform function has to be defined:

function transformToSQL(item) {
	if (Call.isCall(item)) {
		switch (item.getType()) {
		case Call.types.EQ: {
			const callChildren = [ "(", item.getArgument(0), " = ", item.getArgument(1), ")" ];
			return callChildren;
		}
		case Call.types.OR: {
			...
		}
		case Call.types.LT: {
			...
		}}
		...
	}
	else if (AttributeName.isAttributeName(item)) {
		return item.toString();
	}
}

This transform function will then be recursively applied on the Call object:

const sqlString = Call.transform(call, transformToSQL);

RolesProvider

An instance of the RolesProvider class can be used to evaluate the roles (or scopes) of a user based on policies of the following form:

GRANT <role> ON $SCOPES;

To get an Array<String> of a user's roles, call getRoles on a previously constructed instance of RolesProvider:

const pdp = new PolicyDecisionPoint();
const rp = new RolesProvider(pdp);
const principle = new Principle(app_tid, scim_id); // app_tid, scim_id taken from SAP Identity Service token

const roles = await rp.getRoles(principle);

Only one RolesProvider instance needs to be constructed and can be used to subsequently get the roles of different users.

RolesCache

A RolesProvider can use an optional RolesCache to improve the performance of subsequent role evaluations for the same user. It has the following configuration parameters:

  • TTL ms: the lifetime of cache entries. Specifies how long the cached roles of a given user are used without evaluating them again via the ADC. If set too high, administrative changes of a user's policy assignments or changes in policies might affect the application's behavior with a high delay which reduces overall security.\ Default: 1 minute
  • limit: maximum number of simultaneous cache entries. The cache follows a FIFO strategy: if necessary, the oldest cache entry is removed to make space for a new entry.\ Default: 10000

A RolesCache can be constructed and used by a RolesProvider as follows:

const rc = new RolesCache(10 * 60 * 1000, 1E6); // 10 min TTL and 100k users max
const rp = new RolesProvider(pdp).withRolesCache(rc);

RolesCache Sizing Guide

The memory consumed by the RolesCache can be estimated with the following formula:

Memory [Byte] = U*R*(2*S) + 84*U

U: #Users
R: #Roles per user
S: String length of role name

It is only an estimate and should be correct up to a factor of 2. Please use it only to get an understanding for the order of magnitude of the memory consumption. Use avg, min or max values of the input parameters to get a memory estimation for the scenario that is most important to you.

The following table gives a quick reference of expected memory consumption: | U (Users) | R (Roles) | S (String length) | Memory | |----------:|----------:|------------------:|-------:| | 1k | 10 | 20 | 1MB | | 1k | 50 | 30 | 3MB | | 10k | 10 | 20 | 5MB | | 100k | 15 | 20 | 70MB | | 100k | 50 | 10 | 100MB | | 100k | 50 | 30 | 300MB | | 1m | 25 | 20 | 1GB |

To compute a suitable user limit for your cache given a fixed amount of memory M, you can estimate it with the following formula:

$$U = \frac{M}{2 R S + 84}$$

Express Middleware

The authorization checks can be performed by the provided express middleware (see doc/API/Middleware.md).

For example, to restrict the /read endpoint to users with read authority:

const { middleware } = require('@sap/ams');

app.get("/read", [requireAuthentication, middleware.hasAuthority("read")], (req, res) => {
  res.send("User is allowed to read");
})

Note that the req object has to be extended with a tokenInfo object from @sap/xssec before calling hasAuthority.\ This can be achieved by registering the middleware after the passport middleware of @sap/xssec.

If a middleware returns false or fails an error will be thrown otherwise the next handler is called.

CAP integration

This module provides a runtime plugin for CAP (Cloud Application Programming Model) applications which is documented in docs/CapIntegration.md.

Logging

The Node AMS library does no logging.\ But theres's a guide on how to log Policy Decision Point results (see doc/Logging/LogPdp.md) and/or perform auditlogging (see doc/Logging/AuditLog.md).

Resources

Reporting Incidents

As registered SAP customers, report your issue in creating an incident for component BC-CP-CF-SEC-LIB on the SAP Support Portal

See also Getting Support in the SAP BTP documentation.

Open Source Legal Notices

SAP Cloud Identity 1.0

1.18.0

9 months ago

1.17.0

9 months ago

1.16.0

1 year ago

1.15.1

1 year ago

1.15.0

1 year ago

1.14.2

1 year ago

1.14.1

1 year ago

1.14.0

2 years ago

1.13.1

2 years ago