@sapl/sapl-nodejs
v0.1.6
Published
SAPL Node.js library
Downloads
100
Readme
sapl-nodejs
This project provides a client that enables the integration of SAPL in NodeJs.
What is SAPL
SAPL is a further development of ABAC that uses a publish/subscribe model in addition to the classic request-response model.
When a client sends a authorization subscription to the Policy Decision Point (PDP), it receives either a simple response or a data stream of decisions. If the result of the authorization decision changes, this is communicated immediately via the data stream.
What SAPL looks like in Node.js
The SAPL-Node.js package handles the enforcement of policies and related decisions in the background, allowing developers to focus on policy creation. Comprehensive SAPL documentation can be found here, including a playground for writing policies.
Installation preparations
Install an appropriate NodeJs version on your device.
Installation
# install SAPL for NodeJs
npm i @sapl/sapl-nodejs
Usage
Prerequisite for using SAPL in NodeJs
A prerequisite for using SAPL in NodeJs is one of the SAPL servers listed:
Decorators
The SAPL client provides 5 decorators that can be used in the coding.
PreEnforce
The @PreEnforce annotation establishes a policy enforcement point (PEP) before the invocation of the annotated method.
PostEnforce
The @PostEnforce annotation establishes a policy enforcement point (PEP) after the invocation of the annotated method, and alters the return value if indicated by the policy decision point (PDP), if possible based on the structure of the returned JSON object.
EnforceTillDenied, EnforceDropWhileDenied and EnforceRecoverableIfDenied
The @EnforceTillDenied, @EnforceDropWhileDenied and @EnforceRecoverableIfDenied annotations establishes a policy enforcement point (PEP) for data streams. The PEP is only applicable to methods returning a
For a more detailed explanation of the decorators, please refer to the SAPL documentation.
Use of decorators
The decorators supplied are method decorators. In order for the decorators to work properly, the PDP must be instantiated in the form of a remote PDP.
public pdp: RemotePdp;
this.pdp = RemotePdp
.create()
.host('https://localhost')
.port(8443)
.setAuthorizationManager("http://localhost:3000/user");
After the instantiation of the PDP, it can be used for specific credentials or users.
// simple example of a login routine
if (user) {
const userAuthorizationData = require("../db/user_auth.json");
const userAuth = userAuthorizationData.users.find(
(u) => u.username === user.username
);
serviceHandler
.getRemotePdp()
.bearerToken(userAuth.sapl_token) // token for SAPL server authorization
.setUsername(user.username); // username for name of the subject
// alternative variant if not accessed via token
serviceHandler
.getRemotePdp()
.basicAuth("username", "password")
}
Authorization Manager
As already shown earlier the PDP can also be provided with the user's authorizations. The Authorization Manager must be set for this.
this.pdp.setAuthorizationManager('http://localhost:3000/user');
The result of the service should be an array with the authorizations.
HTTP/1.1 200 OK
X-Powered-By: Express
Vary: Origin
Access-Control-Allow-Credentials: true
Content-Type: application/json; charset=utf-8
Content-Length: 33
ETag: W/"21-5BRwB+FgCzXfa3iTJJj0hquEbIo"
Set-Cookie: connect.sid=s%3ALUXSqk0u2pubkx_uvKX-Keqsqax_hjJ9.%2BgPAazqp0k6BsBDgNX8QCTw15stFRaHL4jAeJrnQ1%2B8; Path=/; HttpOnly
Date: Fri, 28 Jun 2024 12:50:08 GMT
Connection: close
[
"admin",
"god",
"editor",
"viewer"
]
Using the PDP outside the decorators
The PDP can also be used outside the decorators. This can be referred to as "manual" use.
// decide example
this.pdp
.decide(AuthorizationSubscription.create('user1', 'read', 'resource1'))
.then((response) => {
//do something
console.log(response);
});
Various API paths can be addressed via the corresponding methods:
- decide
- decideOnce
- multiDecide
For a more detailed explanation of the different API endpoints, please refer to the SAPL documentation.
Constraint Handling
Upon receipt of the decision from the PDP, it can be linked to constraints that can be categorized either as obligations or as advice. obligations must be fulfilled, otherwise an PERMIT will be a DENY. Advices should be fulfilled but have no influence on the decision made, even if they are not fulfilled.
To handle these constraints, the library provides a class called ConstraintEnforcementService that can manage them. This class is used to register the implemented constraint handler.
this.pdp
.getConstraintEnforcementService()
.addOnNextConstraintHandler(new LogConstraintHandlerProvider())
.addOnErrorConstraintHandler(new EmailConstraintHandlerProvider())
.addMappingConstraintHandler(new CustomReplaceHandlerProvider());
Depending on the point at which you want to hook in, the corresponding interface must be used. Here are a few example implementations.
import { ConsumerConstraintHandlerProvider } from '@sapl/sapl-nodejs';
export class LogConstraintHandlerProvider
implements ConsumerConstraintHandlerProvider
{
isResponsible(constraint: any): boolean {
if (constraint['type'] === 'logAccess') {
return true;
} else {
return false;
}
}
getHandler(constraint: object) {
return () => {
this.log(constraint['message']);
};
}
log(message: string) {
console.log(message);
}
}
import { ErrorHandlerProvider } from '@sapl/sapl-nodejs';
export class EmailConstraintHandlerProvider implements ErrorHandlerProvider {
isResponsible(constraint: any): boolean {
if (constraint['type'] === 'sendMail') {
return true;
} else {
return false;
}
}
getHandler(constraint: object) {
return () => {
this.sendEmail(
constraint['recipient'],
constraint['subject'],
constraint['message'],
);
};
}
sendEmail(recipient: string, subject: string, message: string) {
console.log(
'Sending email to ' +
recipient +
' with subject ' +
subject +
' and message ' +
message,
);
}
}
import { HasPriority } from '@sapl/sapl-nodejs';
import { MappingConstraintHandlerProvider } from '@sapl/sapl-nodejs';
export class CustomReplaceHandlerProvider
implements MappingConstraintHandlerProvider
{
// eslint-disable-next-line @typescript-eslint/no-unused-vars
getHandler(constraint: object) {
const customReplaceConfig = constraint;
return (content) => {
return this.customReplaceHandler(content, customReplaceConfig);
};
}
isResponsible(constraint: object): boolean {
if (constraint['type'] === 'customReplace') {
return true;
} else {
return false;
}
}
getPriority(): number {
return 0;
}
compareTo(other: HasPriority): number {
return other.getPriority() < this.getPriority()
? -1
: other.getPriority() === this.getPriority()
? 0
: 1;
}
customReplaceHandler(content: any, constraint: object) {
content.data.diagnosis = constraint['replacement'];
return content;
}
}
For a more detailed explanation of how constraints work, please refer to the SAPL documentation.