happn-3
Version:
pub/sub api as a service using primus and mongo & redis or nedb, can work as cluster, single process or embedded using nedb
232 lines (210 loc) • 7.69 kB
JavaScript
const commons = require('happn-commons'),
_ = commons._,
nodeUtil = require('util'),
CONSTANTS = commons.constants,
utils = commons.utils;
module.exports = class LookupTables {
constructor() {
this.authorizeCallback = nodeUtil.callbackify(this.authorize);
this.wildcardMatch = utils.wildcardMatch.bind(utils);
this.stripLeadingSlashes = utils.stripLeadingSlashes;
}
static create() {
return new LookupTables();
}
initialize(happn, config) {
this.config = config || {};
this.happn = happn;
this.dataService = this.happn.services.data;
this.securityService = this.happn.services.security;
this.utils = this.happn.services.utils;
this.permissionsTemplate = require('./permissions-template').create(this.utils);
}
async upsertLookupTable(table) {
for (let path of table.paths) await this.insertPath(table.name, path, false);
let affectedGroups = await this.__getGroupsByTable(table.name);
return this.securityService.dataChanged(
CONSTANTS.SECURITY_DIRECTORY_EVENTS.LOOKUP_TABLE_CHANGED,
{
groups: affectedGroups,
table: table.name,
},
null
);
}
async deleteLookupTable(name) {
let table = await this.fetchLookupTable(name);
for (let path of table.paths) {
await this.removePath(name, path, false);
}
let affectedGroups = await this.__getGroupsByTable(name);
return this.securityService.dataChanged(
CONSTANTS.SECURITY_DIRECTORY_EVENTS.LOOKUP_TABLE_CHANGED,
{
groups: affectedGroups,
table: name,
},
null
);
}
async insertPath(tableName, path, callDataChanged = true) {
path = this.stripLeadingSlashes(path);
await this.dataService.upsert(`/_SYSTEM/_SECURITY/_LOOKUP/${tableName}/${path}`, {
authorized: true,
});
if (callDataChanged) {
let affectedGroups = await this.__getGroupsByTable(tableName);
return this.securityService.dataChanged(
CONSTANTS.SECURITY_DIRECTORY_EVENTS.LOOKUP_TABLE_CHANGED,
{
groups: affectedGroups,
table: tableName,
},
null
);
}
}
async removePath(tableName, path, callDataChanged = true) {
path = this.stripLeadingSlashes(path);
await this.dataService.remove(`/_SYSTEM/_SECURITY/_LOOKUP/${tableName}/${path}`);
if (callDataChanged) {
let affectedGroups = await this.__getGroupsByTable(tableName);
return this.securityService.dataChanged(
CONSTANTS.SECURITY_DIRECTORY_EVENTS.LOOKUP_TABLE_CHANGED,
{
groups: affectedGroups,
table: tableName,
},
null
);
}
}
async __getGroupsByTable(tableName) {
let lookupPath = `/_SYSTEM/_SECURITY/_PERMISSIONS/_LOOKUP/${tableName}/*`;
let storedGroups = await this.dataService.get(lookupPath);
let groups = storedGroups.map((data) => data._meta.path.split('/').pop());
return groups;
}
async fetchLookupTable(name) {
let tablePaths = await this.dataService.get(`/_SYSTEM/_SECURITY/_LOOKUP/${name}/*`);
let paths = tablePaths
.filter((tp) => tp.data.authorized)
.map((tp) => this.__extractPath(tp._meta.path, name));
return { name, paths };
}
__extractPath(path, tableName) {
return path
.split(tableName + '/')
.slice(1)
.join(tableName + '/');
}
async upsertLookupPermission(groupName, permission) {
let storedPermissions = await this.fetchLookupPermissions(groupName);
if (storedPermissions.find((current) => _.isEqual(current, permission))) return; //permission already stored
storedPermissions.push(permission);
await this.__storePermissions(groupName, storedPermissions, permission.table);
return this.securityService.dataChanged(
CONSTANTS.SECURITY_DIRECTORY_EVENTS.LOOKUP_PERMISSION_CHANGED,
{
group: groupName,
table: permission.table,
},
null
);
}
async __storePermissions(groupName, permissions, tableName) {
let relevantPermissions = permissions.filter((perm) => perm.table === tableName);
let dataPathGxT = `/_SYSTEM/_SECURITY/_PERMISSIONS/_LOOKUP/${tableName}/${groupName}`;
let dataPathTxG = `/_SYSTEM/_SECURITY/_PERMISSIONS/_LOOKUP/${groupName}/${tableName}`;
if (relevantPermissions.length === 0) {
await this.dataService.remove(dataPathGxT);
return this.dataService.remove(dataPathTxG);
} else {
await this.dataService.upsert(dataPathGxT, {});
return this.dataService.upsert(dataPathTxG, {
permissions: relevantPermissions,
});
}
}
async fetchLookupPermissions(groupName) {
let storedPermissions = await this.dataService.get(
`/_SYSTEM/_SECURITY/_PERMISSIONS/_LOOKUP/${groupName}/*`
);
return this.dataService
.extractData(storedPermissions)
.reduce((perms, table) => perms.concat(table.permissions), []);
}
async removeLookupPermission(groupName, permission) {
let storedPermissions = await this.fetchLookupPermissions(groupName);
let index = storedPermissions.findIndex((current) => _.isEqual(current, permission));
if (index > -1) {
storedPermissions.splice(index, 1);
await this.__storePermissions(groupName, storedPermissions, permission.table);
return this.securityService.dataChanged(
CONSTANTS.SECURITY_DIRECTORY_EVENTS.LOOKUP_PERMISSION_CHANGED,
{
group: groupName,
table: permission.table,
},
null
);
}
}
async removeAllTablePermission(groupName, tableName) {
let storedPermissions = await this.fetchLookupPermissions(groupName);
storedPermissions = storedPermissions.filter((permission) => permission.table !== tableName);
await this.__storePermissions(groupName, storedPermissions, tableName);
return this.securityService.dataChanged(
CONSTANTS.SECURITY_DIRECTORY_EVENTS.LOOKUP_PERMISSION_CHANGED,
{
group: groupName,
table: tableName,
},
null
);
}
async authorize(session, path, action) {
if (!session.user) {
session.user = await this.securityService.users.getUser(session.username);
session.user.name = session.username;
}
let groups = Object.keys(session.user.groups);
let authorized = false;
for (let group of groups) {
let ok = await this.authorizeGroup(session, group, path, action);
if (ok) {
authorized = true;
break;
}
}
return authorized;
}
async authorizeGroup(session, groupName, path, action) {
let permissions = await this.fetchLookupPermissions(groupName);
if (!permissions || permissions.length === 0) return false; //Nothing to test
for (const permission of permissions) {
if (await this.__authorizeLookupPermission(session, permission, path, action)) {
return true;
}
}
return false;
}
async __authorizeLookupPermission(session, permission, path, action) {
if (!permission.actions.includes(action)) return false;
let matches = path.match(permission.regex);
if (!matches) return false;
let permissionPaths = this.__buildPermissionPaths(session, permission.path, matches).map(
(path) => this.stripLeadingSlashes(path)
);
let lookupPaths = (await this.fetchLookupTable(permission.table)).paths;
return permissionPaths.some((permPath) =>
lookupPaths.some((luPath) => this.wildcardMatch(permPath, luPath))
);
}
__buildPermissionPaths(session, path, matches) {
return this.permissionsTemplate.parsePermissions(
path.replace(/\{\{\$([0-9]*)\}\}/g, (_m, num) => matches[num]),
session
);
}
};