Socket
Socket
Sign inDemoInstall

@opuscapita/bouncer

Package Overview
Dependencies
64
Maintainers
13
Versions
81
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.2.5 to 1.2.6

942

index.js

@@ -10,10 +10,10 @@ const extend = require('extend');

const actionsMap = {
'POST' : 'create',
'GET' : 'view',
'PUT' : 'edit',
'DELETE' : 'delete',
'HEAD' : 'head',
'OPTIONS' : 'options',
'PATCH' : 'patch'
}
'POST': 'create',
'GET': 'view',
'PUT': 'edit',
'DELETE': 'delete',
'HEAD': 'head',
'OPTIONS': 'options',
'PATCH': 'patch'
};

@@ -31,52 +31,45 @@ /**

*/
class Bouncer
{
/**
class Bouncer {
/**
* Initializes a new Bouncer instance and loads all permission configurations.
* @param {object} config - Optional configuration object extending {@link Bouncer.DefaultConfig}.
*/
constructor(config)
{
this.config = extend(true, { }, Bouncer.DefaultConfig, config);
this.cache = new Cache({
driver : 'memory'
});
}
constructor(config) {
this.config = extend(true, { }, Bouncer.DefaultConfig, config);
this.cache = new Cache({
driver: 'memory'
});
}
loadPermissions(src)
{
if(typeof src === 'string')
return this.normalizePermissions(require(src));
else if(Array.isArray(src))
return src.reduce((all, item) => extend(true, all, this.loadPermissions(item)), { });
else
return this.normalizePermissions(extend(true, { }, src));
loadPermissions(src) {
if (typeof src === 'string') {
return this.normalizePermissions(require(src));
} else if (Array.isArray(src)) {
return src.reduce((all, item) => extend(true, all, this.loadPermissions(item)), { });
} else {
return this.normalizePermissions(extend(true, { }, src));
}
}
normalizePermissions(permissions)
{
for(const key in permissions)
{
const resources = permissions[key].resources;
normalizePermissions(permissions) {
for (const key in permissions) {
const resources = permissions[key].resources;
if(resources && Array.isArray(resources))
{
resources.forEach(resource =>
{
if(resource.fields)
{
resource.requestFields = { allow : resource.fields || null, remove : null };
delete resource.fields;
}
if (resources && Array.isArray(resources)) {
resources.forEach(resource => {
if (resource.fields) {
resource.requestFields = {allow: resource.fields || null, remove: null};
delete resource.fields;
}
resource.requestFields = extend(true, { allow : null, remove : null }, resource.requestFields);
resource.responseFields = extend(true, { allow : null, remove : null }, resource.responseFields);
});
}
}
return permissions;
resource.requestFields = extend(true, {allow: null, remove: null}, resource.requestFields);
resource.responseFields = extend(true, {allow: null, remove: null}, resource.responseFields);
});
}
}
/**
return permissions;
}
/**
* Calling this method triggers the **bouncer.permissionsReady** event using RabbitMQ for

@@ -89,39 +82,34 @@ * publishing the permissions loaded by the current Bouncer instance.

*/
async registerPermissions({ retryTimeout = 1000, retryCount = 30 } = { })
{
const eventClient = new EventClient({ exchangeName : 'bouncer', logger : this.config.logger });
const permissions = this.loadPermissions(this.config.permissions);
async registerPermissions({retryTimeout = 1000, retryCount = 30} = { }) {
const eventClient = new EventClient({exchangeName: 'bouncer', logger: this.config.logger});
const permissions = this.loadPermissions(this.config.permissions);
let retryCounter = 0;
let retryCounter = 0;
do
{
try
{
if(await eventClient.queueExists('acl/bouncer.permissionsReady'))
{
await eventClient.emit('bouncer.permissionsReady', { serviceName : this.config.serviceName, permissions : permissions });
break;
}
else
{
throw new Error('Required queue "acl/bouncer.permissionsReady" does not exist.');
}
}
catch(e)
{
retryCounter++;
do {
try {
if (await eventClient.queueExists('acl/bouncer.permissionsReady')) {
await eventClient.emit(
'bouncer.permissionsReady', {serviceName: this.config.serviceName, permissions: permissions}
);
break;
} else {
throw new Error('Required queue "acl/bouncer.permissionsReady" does not exist.');
}
} catch (e) {
retryCounter++;
if(retryCounter < retryCount)
await new Promise(resolve => setTimeout(resolve, retryTimeout));
else
throw e;
}
if (retryCounter < retryCount) {
await new Promise(resolve => setTimeout(resolve, retryTimeout));
} else {
throw e;
}
while(retryCounter < retryCount);
await eventClient.dispose();
}
}
while (retryCounter < retryCount);
/**
await eventClient.dispose();
}
/**
* Tries to find a resource matching a certain URL based on the request's HTTP verb and the user requesting it.

@@ -135,89 +123,83 @@ * @param {string} url - Path of the requested URL.

*/
async findResources(url, method, userData, serviceClient = null, serviceName = null)
{
const roles = (userData && userData.roles) || [ ];
async findResources(url, method, userData, serviceClient = null, serviceName = null) {
const roles = (userData && userData.roles) || [];
const _serviceName = serviceName || this.config.serviceName;
const _serviceName = serviceName || this.config.serviceName;
const userId = userData && userData.id;
const cacheKey = `${_serviceName}${url}:${method}:${userId}`;
const userId = userData && userData.id;
const cacheKey = `${_serviceName}${url}:${method}:${userId}`;
let foundResources = await this.cache.get(cacheKey);
let foundResources = await this.cache.get(cacheKey);
if(!foundResources)
{
foundResources = [ ];
if (!foundResources) {
foundResources = [];
const prefixLength = _serviceName.length + 1;
const action = actionsMap[method.toUpperCase()];
const prefixLength = _serviceName.length + 1;
const action = actionsMap[method.toUpperCase()];
if(!serviceClient)
serviceClient = new ServiceClient({ logger : this.config.logger });
if (!serviceClient) {
serviceClient = new ServiceClient({logger: this.config.logger});
}
const [ permissions, resourceGroups ] = await Promise.all([
this.getPermissions(roles, serviceClient, _serviceName),
this.getResourceGroups(serviceClient, _serviceName)
]);
const [permissions, resourceGroups] = await Promise.all([
this.getPermissions(roles, serviceClient, _serviceName),
this.getResourceGroups(serviceClient, _serviceName)
]);
for(const permission of permissions)
{
const resourceGroupName = permission.resourceGroupId.substring(prefixLength);
for (const permission of permissions) {
const resourceGroupName = permission.resourceGroupId.substring(prefixLength);
if(resourceGroupName === '*' || this.checkAlwaysAllow([ permission.role ]))
{
foundResources.push({
type: [ "rest", "ui" ],
resourceId: '^/',
actions: [ 'create', 'view', 'edit', 'delete', 'head', 'options', 'patch' ],
requestFields: { allow: null, remove: null },
responseFields: { allow: null, remove: null },
roleIds : [ permission.role ]
});
}
else
{
const foundResourceGroup = resourceGroups[resourceGroupName];
const resources = foundResourceGroup && foundResourceGroup.resources;
if (resourceGroupName === '*' || this.checkAlwaysAllow([permission.role])) {
foundResources.push({
type: ['rest', 'ui'],
resourceId: '^/',
actions: ['create', 'view', 'edit', 'delete', 'head', 'options', 'patch'],
requestFields: {allow: null, remove: null},
responseFields: {allow: null, remove: null},
roleIds: [permission.role]
});
} else {
const foundResourceGroup = resourceGroups[resourceGroupName];
const resources = foundResourceGroup && foundResourceGroup.resources;
if(resources)
{
const filtered = resources.filter(r => url.match(new RegExp(this.replacePlaceholders(r.resourceId, userData), 'i')) && r.actions.indexOf(action) > -1)
.map(r => ({ ...r, roleIds : [ permission.role ] }));
if (resources) {
const filtered = resources.
filter(r =>
url.match(new RegExp(this.replacePlaceholders(r.resourceId, userData), 'i')) &&
r.actions.indexOf(action) > -1).
map(r => ({...r, roleIds: [permission.role]}));
foundResources = foundResources.concat(filtered);
}
}
}
foundResources.length && this.cache.put(cacheKey, foundResources, 600);
foundResources = foundResources.concat(filtered);
}
}
}
return foundResources || [ ];
foundResources.length && this.cache.put(cacheKey, foundResources, 600);
}
async getResourceGroups(serviceClient, serviceName)
{
const url = `/api/resourceGroups/${serviceName}?type=rest`;
const cacheKey = this.config.aclServiceName + ':' + url;
const cached = await this.cache.get(cacheKey);
return foundResources || [];
}
return cached || serviceClient.get(this.config.aclServiceName, url).then(([ results ]) =>
{
const resourceGroups = { };
results.forEach(result => resourceGroups[result.resourceGroupId] = result);
async getResourceGroups(serviceClient, serviceName) {
const url = `/api/resourceGroups/${serviceName}?type=rest`;
const cacheKey = `${this.config.aclServiceName}:${url}`;
const cached = await this.cache.get(cacheKey);
this.cache.put(cacheKey, resourceGroups, 600);
return cached || serviceClient.get(this.config.aclServiceName, url).then(([results]) => {
const resourceGroups = { };
results.forEach(result => resourceGroups[result.resourceGroupId] = result);
return resourceGroups;
})
.catch(e =>
{
const message = (e.response && e.response.result && e.response.result.message) || e.message;
const statusCode = (e.response && e.response.statusCode) || '-';
this.cache.put(cacheKey, resourceGroups, 600);
throw new Error(`Could not get resource groups from acl service: "${message}" (${statusCode})`);
});
}
return resourceGroups;
}).
catch(e => {
const message = (e.response && e.response.result && e.response.result.message) || e.message;
const statusCode = (e.response && e.response.statusCode) || '-';
/**
throw new Error(`Could not get resource groups from acl service: "${message}" (${statusCode})`);
});
}
/**
* Gets a list of all business partner identifiers a user is assigned to for a given REST resource.

@@ -238,16 +220,14 @@ *

*/
async getUserBusinessPartnerIdsByUrl(url, userData, serviceClient = null, method = 'GET', serviceName = null)
{
const resources = await this.findResources(url, method, userData, serviceClient, serviceName);
async getUserBusinessPartnerIdsByUrl(url, userData, serviceClient = null, method = 'GET', serviceName = null) {
const resources = await this.findResources(url, method, userData, serviceClient, serviceName);
if(resources.length > 0)
{
const resource = await this.mergeResources(resources);
return (resource && this.getUserBusinessPartnerIds(userData, resource.roleIds)) || [ ];
}
return [ ];
if (resources.length > 0) {
const resource = await this.mergeResources(resources);
return (resource && this.getUserBusinessPartnerIds(userData, resource.roleIds)) || [];
}
/**
return [];
}
/**
* Gets a list of all business partner identifiers a user role is assigned to.

@@ -265,28 +245,31 @@ *

*/
async getUserBusinessPartnerIds(userData, roleIds)
{
let result;
async getUserBusinessPartnerIds(userData, roleIds) {
let result;
if(userData)
{
const roleConstraints = roleIds && Array.isArray(userData.xroles) && userData.xroles.filter(r => roleIds.includes(r.role)).map(r => r.businessPartners);
if (userData) {
const roleConstraints = roleIds &&
Array.isArray(userData.xroles) &&
userData.xroles.filter(r => roleIds.includes(r.role)).map(r => r.businessPartners);
if(userData.roles && userData.roles.indexOf('admin') > -1)
result = [ '*' ];
else if(userData.id && userData.id.match(/^svc_[^@]*$/))
result = [ '*' ];
else if(Array.isArray(roleConstraints) && roleConstraints.length > 0)
result = this.mergeRoleConstraints(roleConstraints);
else if(userData.supplierid) // TODO: Remove this after full migration to Business Partner
result = [ userData.supplierid ];
else if(userData.customerid) // TODO: Remove this after full migration to Business Partner
result = [ userData.customerid ];
else if(userData.businesspartner && userData.businesspartner.id)
result = [ userData.businesspartner.id ];
}
return result || [ ];
if (userData.roles && userData.roles.indexOf('admin') > -1) {
result = ['*'];
} else if (userData.id && userData.id.match(/^svc_[^@]*$/)) {
result = ['*'];
} else if (Array.isArray(roleConstraints) && roleConstraints.length > 0) {
result = this.mergeRoleConstraints(roleConstraints);
} else if (userData.supplierid) {
// TODO: Remove this after full migration to Business Partner
result = [userData.supplierid];
} else if (userData.customerid) {
// TODO: Remove this after full migration to Business Partner
result = [userData.customerid];
} else if (userData.businesspartner && userData.businesspartner.id) {
result = [userData.businesspartner.id];
}
}
/**
return result || [];
}
/**
*

@@ -299,15 +282,15 @@ * @param {string} serviceName - Service name for identifying a resource group identifier.

*/
async getUsersByPermissionAndBusinessPartner(serviceName, resourceGroupId, businessPartnerId, serviceClient = null)
{
if(!serviceClient)
serviceClient = new ServiceClient({ logger : this.config.logger });
async getUsersByPermissionAndBusinessPartner(serviceName, resourceGroupId, businessPartnerId, serviceClient = null) {
if (!serviceClient) {
serviceClient = new ServiceClient({logger: this.config.logger});
}
const [ permissions ] = await serviceClient.get('acl', `/api/resourcePermissions/${serviceName}/${resourceGroupId}`, true);
const roles = permissions.map(p => p.role);
const [permissions] = await serviceClient.get('acl', `/api/resourcePermissions/${serviceName}/${resourceGroupId}`, true);
const roles = permissions.map(p => p.role);
const businessPartners = new Set([ businessPartnerId ]);
const businessPartners = new Set([businessPartnerId]);
const [ businessPartner ] = await serviceClient.get('business-partner', `/api/business-partners/${businessPartnerId}`, true);
const [businessPartner] = await serviceClient.get('business-partner', `/api/business-partners/${businessPartnerId}`, true);
/**
/**
* !Attention: Adding the hierarchy notation with "customerId/*" instead of the actual tenantId

@@ -318,11 +301,12 @@ * to the list of tenants hacks the query in the user service to not return exact tenant

*/
if(businessPartner && businessPartner.hierarchyId)
businessPartner.hierarchyId.split('|').forEach(bp => businessPartners.add(`${bp}/*`));
if (businessPartner && businessPartner.hierarchyId) {
businessPartner.hierarchyId.split('|').forEach(bp => businessPartners.add(`${bp}/*`));
}
const [ users ] = await serviceClient.get('user', `/api/users?include=profile&roles=${roles.join(',')}&businessPartners=${Array.from(businessPartners).join(',')}`, true);
const [users] = await serviceClient.get('user', `/api/users?include=profile&roles=${roles.join(',')}&businessPartners=${Array.from(businessPartners).join(',')}`, true);
return users;
}
return users;
}
/**
/**
*

@@ -336,25 +320,24 @@ * @param {string} serviceName - Service name for identifying a resource group identifier.

*/
async getUsersByPermissionAndTenant(serviceName, resourceGroupId, tenantId, serviceClient = null)
{
if(!serviceClient)
serviceClient = new ServiceClient({ logger : this.config.logger });
// TODO: use default logger (it should inherit context from parent call)
async getUsersByPermissionAndTenant(serviceName, resourceGroupId, tenantId, serviceClient = null) {
if (!serviceClient) {
serviceClient = new ServiceClient({logger: this.config.logger});
}
// TODO: use default logger (it should inherit context from parent call)
const [ permissions ] = await serviceClient.get('acl', `/api/resourcePermissions/${serviceName}/${resourceGroupId}`, true);
const roles = permissions.map(p => p.role);
const [permissions] = await serviceClient.get('acl', `/api/resourcePermissions/${serviceName}/${resourceGroupId}`, true);
const roles = permissions.map(p => p.role);
const splitTentant = {
customerId : (tenantId && tenantId.startsWith('c_') && tenantId.substr(2)) || null,
supplierId : (tenantId && tenantId.startsWith('s_') && tenantId.substr(2)) || null
};
const splitTentant = {
customerId: (tenantId && tenantId.startsWith('c_') && tenantId.substr(2)) || null,
supplierId: (tenantId && tenantId.startsWith('s_') && tenantId.substr(2)) || null
};
const tenants = new Set();
const tenants = new Set();
tenants.add(tenantId);
tenants.add(tenantId);
if(splitTentant.customerId)
{
const [ customer ] = await serviceClient.get('business-partner', `/api/customers/${splitTentant.customerId}`, true);
if (splitTentant.customerId) {
const [customer] = await serviceClient.get('business-partner', `/api/customers/${splitTentant.customerId}`, true);
/**
/**
* !Attention: Adding the hierarchy notation with "customerId/*" instead of the actual tenantId

@@ -365,10 +348,9 @@ * to the list of tenants hacks the query in the user service to not return exact tenant

*/
if(customer && customer.hierarchyId)
customer.hierarchyId.split('|').forEach((c) => tenants.add(`c_${c}/*`));
}
else if(splitTentant.supplierId)
{
const [ supplier ] = await serviceClient.get('business-partner', `/api/suppliers/${splitTentant.supplierId}`, true);
if (customer && customer.hierarchyId) {
customer.hierarchyId.split('|').forEach((c) => tenants.add(`c_${c}/*`));
}
} else if (splitTentant.supplierId) {
const [supplier] = await serviceClient.get('business-partner', `/api/suppliers/${splitTentant.supplierId}`, true);
/**
/**
* !Attention: Adding the hierarchy notation with "suplierId/*" instead of the actual tenantId

@@ -379,16 +361,15 @@ * to the list of tenants hacks the query in the user service to not return exact tenant

*/
if(supplier && supplier.hierarchyId)
supplier.hierarchyId.split('|').forEach((s) => tenants.add(`s_${s}/*`));
}
else
{
throw new Error('The passed tenant identifier is not a valid tenant.');
}
if (supplier && supplier.hierarchyId) {
supplier.hierarchyId.split('|').forEach((s) => tenants.add(`s_${s}/*`));
}
} else {
throw new Error('The passed tenant identifier is not a valid tenant.');
}
const [ users ] = await serviceClient.get('user', `/api/users?include=profile&roles=${roles.join(',')}&tenants=${Array.from(tenants).join(',')}`, true);
const [users] = await serviceClient.get('user', `/api/users?include=profile&roles=${roles.join(',')}&tenants=${Array.from(tenants).join(',')}`, true);
return users;
}
return users;
}
/**
/**
* Gets a list of all permissions granted to a set of roles.

@@ -399,28 +380,24 @@ * @param {array} roles - List of roles to check.

*/
async getPermissions(roles, serviceClient, serviceName)
{
const all = await Promise.all(roles.map(async role =>
{
const url = `/api/permissions/${role}/${serviceName}`;
const cacheKey = this.config.aclServiceName + ':' + url;
const cached = await this.cache.get(cacheKey);
async getPermissions(roles, serviceClient, serviceName) {
const all = await Promise.all(roles.map(async role => {
const url = `/api/permissions/${role}/${serviceName}`;
const cacheKey = `${this.config.aclServiceName}:${url}`;
const cached = await this.cache.get(cacheKey);
return cached || serviceClient.get(this.config.aclServiceName, url).then(([ permissions ]) =>
{
this.cache.put(cacheKey, permissions, 600);
return permissions;
})
.catch(e =>
{
const message = (e.response && e.response.result && e.response.result.message) || e.message;
const statusCode = (e.response && e.response.statusCode) || '-';
return cached || serviceClient.get(this.config.aclServiceName, url).then(([permissions]) => {
this.cache.put(cacheKey, permissions, 600);
return permissions;
}).
catch(e => {
const message = (e.response && e.response.result && e.response.result.message) || e.message;
const statusCode = (e.response && e.response.statusCode) || '-';
throw new Error(`Could not get service-role permissions: "${message}" (${statusCode})`);
});
}));
throw new Error(`Could not get service-role permissions: "${message}" (${statusCode})`);
});
}));
return all.reduce((all, more) => all.concat(more), [ ]);
}
return all.reduce((all, more) => all.concat(more), []);
}
/**
/**
* Gets a list of all tenants a user role is assigned to.

@@ -439,46 +416,42 @@ *

*/
async getUserTenants(userData, roleIds)
{
let result;
async getUserTenants(userData, roleIds) {
let result;
if(userData)
{
const roleConstraints = roleIds && Array.isArray(userData.xroles) && userData.xroles.filter(r => roleIds.includes(r.role)).map(r => r.tenants);
if (userData) {
const roleConstraints = roleIds &&
Array.isArray(userData.xroles) &&
userData.xroles.filter(r => roleIds.includes(r.role)).map(r => r.tenants);
if(userData.roles && userData.roles.indexOf('admin') > -1)
result = [ '*' ];
else if(userData.id && userData.id.match(/^svc_[^@]*$/))
result = [ '*' ];
else if(Array.isArray(roleConstraints) && roleConstraints.length > 0)
result = this.mergeRoleConstraints(roleConstraints);
else if(userData.supplierid)
result = [ `s_${userData.supplierid}` ];
else if(userData.customerid)
result = [ `c_${userData.customerid}` ];
}
return result || [ ];
if (userData.roles && userData.roles.indexOf('admin') > -1) {
result = ['*'];
} else if (userData.id && userData.id.match(/^svc_[^@]*$/)) {
result = ['*'];
} else if (Array.isArray(roleConstraints) && roleConstraints.length > 0) {
result = this.mergeRoleConstraints(roleConstraints);
} else if (userData.supplierid) {
result = [`s_${userData.supplierid}`];
} else if (userData.customerid) {
result = [`c_${userData.customerid}`];
}
}
mergeRoleConstraints(roleConstraints)
{
let resultArray = [ ];
return result || [];
}
for(const roles of roleConstraints)
{
if(roles.includes( '*' ))
{
resultArray = [ '*' ];
break;
}
else
{
resultArray = resultArray.concat(roles);
}
}
mergeRoleConstraints(roleConstraints) {
let resultArray = [];
return [...new Set(resultArray)]
for (const roles of roleConstraints) {
if (roles.includes('*')) {
resultArray = ['*'];
break;
} else {
resultArray = resultArray.concat(roles);
}
}
/**
return [...new Set(resultArray)];
}
/**
* Gets a list of all tenants a user is assigned to for a given REST resource.

@@ -500,16 +473,14 @@ *

*/
async getUserTenantsByUrl(url, userData, serviceClient = null, method = 'GET', serviceName = null)
{
const resources = await this.findResources(url, method, userData, serviceClient, serviceName);
async getUserTenantsByUrl(url, userData, serviceClient = null, method = 'GET', serviceName = null) {
const resources = await this.findResources(url, method, userData, serviceClient, serviceName);
if(resources.length > 0)
{
const resource = await this.mergeResources(resources);
return (resource && this.getUserTenants(userData, resource.roleIds)) || [ ];
}
return [ ];
if (resources.length > 0) {
const resource = await this.mergeResources(resources);
return (resource && this.getUserTenants(userData, resource.roleIds)) || [];
}
/**
return [];
}
/**
* Splits an array of tenant IDs into an array of objects containing a supplierId and a customerId. Whenever a

@@ -522,97 +493,93 @@ * tenant is a supplier or a customer, the corresponding field is set.

*/
splitUserTenants(tenants)
{
if(!Array.isArray(tenants))
return [ ];
return tenants.map(tenantId => ({
supplierId : tenantId.startsWith('s_') ? tenantId.substr(2) : null,
customerId : tenantId.startsWith('c_') ? tenantId.substr(2) : null
}));
splitUserTenants(tenants) {
if (!Array.isArray(tenants)) {
return [];
}
/**
return tenants.map(tenantId => ({
supplierId: tenantId.startsWith('s_') ? tenantId.substr(2) : null,
customerId: tenantId.startsWith('c_') ? tenantId.substr(2) : null
}));
}
/**
* Returns a boolean telling whenever a URL is considered a public resource.
* @returns {boolean} Returns true or false.
*/
isPublicResource(url)
{
const { publicPaths } = this.config;
isPublicResource(url) {
const {publicPaths} = this.config;
for(const i in publicPaths)
{
if(url.match(new RegExp(publicPaths[i])))
return true;
}
return false;
for (const i in publicPaths) {
if (url.match(new RegExp(publicPaths[i]))) {
return true;
}
}
escapeRegExp(value)
{
return value && value.replace(/[.*+?^${}()|[\]\\]/g, '$&');
}
return false;
}
replacePlaceholders(resourceId, userData)
{
const tenantId = (userData.supplierid && 's_' + userData.supplierid)
|| (userData.customerid && 'c_' + userData.customerid);
escapeRegExp(value) {
return value && value.replace(/[.*+?^${}()|[\]\\]/g, '$&');
}
return resourceId.replace(/\${_current_tenant_id}/g, this.escapeRegExp(tenantId)) // TODO: Remove this after full migration to Business Partner
.replace(/\${_current_user_id}/g, this.escapeRegExp(userData.id))
.replace(/\${_current_customer_id}/g, this.escapeRegExp(userData.customerid)) // TODO: Remove this after full migration to Business Partner
.replace(/\${_current_supplier_id}/g, this.escapeRegExp(userData.supplierid)) // TODO: Remove this after full migration to Business Partner
.replace(/\${_current_business_partner_id}/g, this.escapeRegExp(userData.businesspartner && userData.businesspartner.id));
}
replacePlaceholders(resourceId, userData) {
const tenantId = (userData.supplierid && `s_${userData.supplierid}`) ||
(userData.customerid && `c_${userData.customerid}`);
buildRecusiveResult(keys, values)
{
if(keys && keys.length > 0)
{
keys = [ ].concat(keys);
const key = keys.shift();
return resourceId.replace(/\${_current_tenant_id}/g, this.escapeRegExp(tenantId)). // TODO: Remove this after full migration to Business Partner
replace(/\${_current_user_id}/g, this.escapeRegExp(userData.id)).
replace(/\${_current_customer_id}/g, this.escapeRegExp(userData.customerid)). // TODO: Remove this after full migration to Business Partner
replace(/\${_current_supplier_id}/g, this.escapeRegExp(userData.supplierid)). // TODO: Remove this after full migration to Business Partner
replace(/\${_current_business_partner_id}/g,
this.escapeRegExp(userData.businesspartner && userData.businesspartner.id)
);
}
if(typeof values[key] !== 'undefined')
return { [key]: this.buildRecusiveResult(keys, values[key]) };
buildRecusiveResult(keys, values) {
if (keys && keys.length > 0) {
keys = [].concat(keys);
const key = keys.shift();
return { };
}
if (typeof values[key] !== 'undefined') {
return {[key]: this.buildRecusiveResult(keys, values[key])};
}
return keys && values;
return { };
}
deleteRecursiveResult(keys, values)
{
if(keys)
{
keys = [ ].concat(keys);
const key = keys.shift();
return keys && values;
}
if(keys.length > 0)
this.deleteRecursiveResult(keys, values[key]);
else if(key && typeof(values) === 'object')
delete values[key];
}
deleteRecursiveResult(keys, values) {
if (keys) {
keys = [].concat(keys);
const key = keys.shift();
return values;
if (keys.length > 0) {
this.deleteRecursiveResult(keys, values[key]);
} else if (key && typeof(values) === 'object') {
delete values[key];
}
}
applyStructureFilter(keyList, values)
{
return keyList.reduce((all, key) => extend(true, all, this.buildRecusiveResult(key && key.split('.'), values)), { });
}
return values;
}
applyBlackFilter(keyList, values)
{
keyList.forEach(key => this.deleteRecursiveResult(key && key.split('.'), values));
return values;
}
applyStructureFilter(keyList, values) {
return keyList.
reduce((all, key) => extend(true, all, this.buildRecusiveResult(key && key.split('.'), values)), { });
}
checkAlwaysAllow(roles)
{
const { alwaysAllow, alwaysDeny } = this.config.roles;
return roles.findIndex(r => alwaysAllow.findIndex(a => r.match(a)) >= 0 && alwaysDeny.findIndex(a => r.match(a)) === -1) >= 0;
}
applyBlackFilter(keyList, values) {
keyList.forEach(key => this.deleteRecursiveResult(key && key.split('.'), values));
return values;
}
/**
checkAlwaysAllow(roles) {
const {alwaysAllow, alwaysDeny} = this.config.roles;
return roles.
findIndex(r => alwaysAllow.findIndex(a => r.match(a)) >= 0 && alwaysDeny.findIndex(a => r.match(a)) === -1) >= 0;
}
/**
* Filters an object or array of objects recursively and returns a copy.

@@ -627,47 +594,50 @@ * Passing a whiteKeys array carries only keys from there to the result.

*/
filterObject(obj, whiteKeys, blackKeys)
{
if(Array.isArray(obj))
return obj.map(o => this.filterObject(o, whiteKeys, blackKeys)).filter(o => o && Object.keys(o).length > 0);
filterObject(obj, whiteKeys, blackKeys) {
if (Array.isArray(obj)) {
return obj.map(o => this.filterObject(o, whiteKeys, blackKeys)).filter(o => o && Object.keys(o).length > 0);
}
let result = obj && typeof obj === 'object' && extend(true, { }, obj.dataValues || obj);
if(Array.isArray(whiteKeys) && result)
result = this.applyStructureFilter(whiteKeys, result);
if(Array.isArray(blackKeys) && result)
result = this.applyBlackFilter(blackKeys, result);
let result = obj && typeof obj === 'object' && extend(true, { }, obj.dataValues || obj);
return result || obj;
if (Array.isArray(whiteKeys) && result) {
result = this.applyStructureFilter(whiteKeys, result);
}
wrapCallback(callback, whiteKeys, blackKeys)
{
return (obj) => callback(this.filterObject(obj, whiteKeys, blackKeys));
if (Array.isArray(blackKeys) && result) {
result = this.applyBlackFilter(blackKeys, result);
}
mergeResources(resources)
{
const result = extend(true, { }, ...resources);
result.roleIds = [ ];
return result || obj;
}
for(const res of resources)
{
if(!res.requestFields || !res.requestFields.allow)
delete result.requestFields.allow;
if(!res.requestFields || !res.requestFields.remove)
delete result.requestFields.remove;
if(!res.responseFields || !res.responseFields.allow)
delete result.responseFields.allow;
if(!res.responseFields || !res.responseFields.remove)
delete result.responseFields.remove;
wrapCallback(callback, whiteKeys, blackKeys) {
return (obj) => callback(this.filterObject(obj, whiteKeys, blackKeys));
}
result.roleIds = result.roleIds.concat(res.roleIds);
}
mergeResources(resources) {
const result = extend(true, { }, ...resources);
result.roleIds = [];
result.roleIds = [ ...new Set(result.roleIds) ];
for (const res of resources) {
if (!res.requestFields || !res.requestFields.allow) {
delete result.requestFields.allow;
}
if (!res.requestFields || !res.requestFields.remove) {
delete result.requestFields.remove;
}
if (!res.responseFields || !res.responseFields.allow) {
delete result.responseFields.allow;
}
if (!res.responseFields || !res.responseFields.remove) {
delete result.responseFields.remove;
}
return result;
result.roleIds = result.roleIds.concat(res.roleIds);
}
/**
result.roleIds = [...new Set(result.roleIds)];
return result;
}
/**
* Returns a middleware to be used with express.

@@ -678,95 +648,85 @@ * The middleware returned by this method will automatically provide endpoint security, input

*/
middleware()
{
return (req, res, next) =>
{
const url = req.originalUrl.split('?')[0];
middleware() {
return (req, res, next) => {
const url = req.originalUrl.split('?')[0];
if(this.isPublicResource(url))
{
/** TODO: Remove this after full migration to Business Partner --> */
// Normally you should NOT use util.deprecate - see discussions on the node forum for details
/**
if (this.isPublicResource(url)) {
/** TODO: Remove this after full migration to Business Partner --> */
// Normally you should NOT use util.deprecate - see discussions on the node forum for details
/**
* @deprecated Use getUserBusinessPartnerIds instead
*/
req.opuscapita.getUserTenants = async () => util.deprecate(() => [ ], 'getUserTenants() will be removed in upcomming version 2.x - returning empty array')();
req.opuscapita.getUserTenants = async () => util.deprecate(() => [], 'getUserTenants() will be removed in upcomming version 2.x - returning empty array')();
/**
/**
* @deprecated Use getUserBusinessPartnerIdsByUrl instead
*/
req.opuscapita.getUserTenantsByUrl = async () => util.deprecate(() => [ ], 'getUserTenantsByUrl() will be removed in upcomming version 2.x - returning empty array')();
req.opuscapita.getUserTenantsByUrl = async () => util.deprecate(() => [], 'getUserTenantsByUrl() will be removed in upcomming version 2.x - returning empty array')();
/**
/**
* @deprecated New business partners are allowed to be customer and supplier at the same time
*/
req.opuscapita.splitUserTenants = (tenants) => this.splitUserTenants(tenants);
req.opuscapita.splitUserTenants = (tenants) => this.splitUserTenants(tenants);
/**
/**
* @deprecated Use getUsersByPermissionAndBusinessPartner instead
*/
req.opuscapita.getUsersByPermissionAndTenant = async (serviceName, resourceGroupId, tenantId) => this.getUsersByPermissionAndTenant(serviceName, resourceGroupId, tenantId, req.opuscapita.serviceClient);
/** <-- TODO: Remove this after full migration to Business Partner */
req.opuscapita.getUserBusinessPartnerIds = async () => [ ];
req.opuscapita.getUserBusinessPartnerIdsByUrl = async () => [ ];
req.opuscapita.getUsersByPermissionAndBusinessPartner = async (serviceName, resourceGroupId, businessPartnerId) => this.getUsersByPermissionAndTenant(serviceName, resourceGroupId, businessPartnerId, req.opuscapita.serviceClient);
req.opuscapita.getUsersByPermissionAndTenant = async (serviceName, resourceGroupId, tenantId) => this.getUsersByPermissionAndTenant(serviceName, resourceGroupId, tenantId, req.opuscapita.serviceClient);
/** <-- TODO: Remove this after full migration to Business Partner */
next();
}
else
{
const method = req.method;
const userData = req.opuscapita.userData();
const serviceClient = req.opuscapita.serviceClient;
req.opuscapita.getUserBusinessPartnerIds = async () => [];
req.opuscapita.getUserBusinessPartnerIdsByUrl = async () => [];
req.opuscapita.getUsersByPermissionAndBusinessPartner = async (serviceName, resourceGroupId, businessPartnerId) => this.getUsersByPermissionAndTenant(serviceName, resourceGroupId, businessPartnerId, req.opuscapita.serviceClient);
this.findResources(url, method, userData, serviceClient).then(resources =>
{
if(resources.length)
{
const resource = this.mergeResources(resources);
next();
} else {
const method = req.method;
const userData = req.opuscapita.userData();
const serviceClient = req.opuscapita.serviceClient;
{
const { allow, remove } = resource.requestFields || { };
this.findResources(url, method, userData, serviceClient).then(resources => {
if (resources.length) {
const resource = this.mergeResources(resources);
/** TODO: Remove this after full migration to Business Partner --> */
req.opuscapita.getUserTenants = async () => this.getUserTenants(req.opuscapita.userData(), resource.roleIds);
req.opuscapita.getUserTenantsByUrl = async (url, serviceName = null) => this.getUserTenantsByUrl(url, req.opuscapita.userData(), serviceClient, method, serviceName);
req.opuscapita.splitUserTenants = (tenants) => this.splitUserTenants(tenants);
req.opuscapita.getUsersByPermissionAndTenant = async (serviceName, resourceGroupId, tenantId) => this.getUsersByPermissionAndTenant(serviceName, resourceGroupId, tenantId, req.opuscapita.serviceClient);
/** <-- TODO: Remove this after full migration to Business Partner */
{
const {allow, remove} = resource.requestFields || { };
req.opuscapita.getUserBusinessPartnerIds = async () => this.getUserBusinessPartnerIds(req.opuscapita.userData(), resource.roleIds);
req.opuscapita.getUserBusinessPartnerIdsByUrl = async (url, serviceName = null) => this.getUserBusinessPartnerIdsByUrl(url, req.opuscapita.userData(), serviceClient, method, serviceName);
req.opuscapita.getUsersByPermissionAndBusinessPartner = async (serviceName, resourceGroupId, businessPartnerId) => this.getUsersByPermissionAndTenant(serviceName, resourceGroupId, businessPartnerId, req.opuscapita.serviceClient);
/** TODO: Remove this after full migration to Business Partner --> */
req.opuscapita.getUserTenants = async () => this.getUserTenants(req.opuscapita.userData(), resource.roleIds);
req.opuscapita.getUserTenantsByUrl = async (url, serviceName = null) => this.getUserTenantsByUrl(url, req.opuscapita.userData(), serviceClient, method, serviceName);
req.opuscapita.splitUserTenants = (tenants) => this.splitUserTenants(tenants);
req.opuscapita.getUsersByPermissionAndTenant = async (serviceName, resourceGroupId, tenantId) => this.getUsersByPermissionAndTenant(serviceName, resourceGroupId, tenantId, req.opuscapita.serviceClient);
/** <-- TODO: Remove this after full migration to Business Partner */
req.body = req.body && this.filterObject(req.body, allow, remove);
}
req.opuscapita.getUserBusinessPartnerIds = async () => this.getUserBusinessPartnerIds(req.opuscapita.userData(), resource.roleIds);
req.opuscapita.getUserBusinessPartnerIdsByUrl = async (url, serviceName = null) => this.getUserBusinessPartnerIdsByUrl(url, req.opuscapita.userData(), serviceClient, method, serviceName);
req.opuscapita.getUsersByPermissionAndBusinessPartner = async (serviceName, resourceGroupId, businessPartnerId) => this.getUsersByPermissionAndTenant(serviceName, resourceGroupId, businessPartnerId, req.opuscapita.serviceClient);
{
const { allow, remove } = resource.responseFields || { };
req.body = req.body && this.filterObject(req.body, allow, remove);
}
res.json = this.wrapCallback(res.json.bind(res), allow, remove);
res.jsonp = this.wrapCallback(res.jsonp.bind(res), allow, remove);
}
{
const {allow, remove} = resource.responseFields || { };
next();
}
else
{
const message = `You do not have permissions to access the requested resource: ${url}`;
res.json = this.wrapCallback(res.json.bind(res), allow, remove);
res.jsonp = this.wrapCallback(res.jsonp.bind(res), allow, remove);
}
req.opuscapita.logger.warn('No permissions to access resource', {url, method, userData});
res.status(403).json({ message });
}
})
.catch(error =>
{
const message = 'Access was denied due to an internal authentication error.';
next();
} else {
const message = `You do not have permissions to access the requested resource: ${url}`;
req.opuscapita.logger.error(message, {error, url, method, userData });
res.status(403).json({ message });
});
}
}
}
req.opuscapita.logger.warn('No permissions to access resource', {url, method, userData});
res.status(403).json({message});
}
}).
catch(error => {
const message = 'Access was denied due to an internal authentication error.';
req.opuscapita.logger.error(message, {error, url, method, userData});
res.status(403).json({message});
});
}
};
}
}

@@ -789,20 +749,20 @@

Bouncer.DefaultConfig = {
serviceName : config.serviceName,
permissions : process.cwd() + '/src/server/acl.json',
aclServiceName : 'acl',
logger : new Logger({context:{name:"bouncer"}}),
roles : {
alwaysAllow : [ ],
alwaysDeny : [ ]
},
publicPaths : [
'^/public',
'^/static',
'^/api/health/stats$',
'^/api/health/check$',
'^/api/health/metrics$',
'^/api/list/apis$'
]
}
serviceName: config.serviceName,
permissions: `${process.cwd()}/src/server/acl.json`,
aclServiceName: 'acl',
logger: new Logger({context: {name: 'bouncer'}}),
roles: {
alwaysAllow: [],
alwaysDeny: []
},
publicPaths: [
'^/public',
'^/static',
'^/api/health/stats$',
'^/api/health/check$',
'^/api/health/metrics$',
'^/api/list/apis$'
]
};
module.exports = Bouncer;
{
"name": "@opuscapita/bouncer",
"version": "1.2.5",
"version": "1.2.6",
"description": "API and express middleware for OpusCapita ACl service based access security.",

@@ -36,2 +36,3 @@ "main": "index.js",

"@opuscapita/db-init": "^3.0.18",
"@opuscapita/eslint-config-opuscapita-bnapp": "^1.3.5",
"@opuscapita/web-init": "^4.2.0",

@@ -38,0 +39,0 @@ "@types/node": "^18.0.6",

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc