Skip to content

Commit

Permalink
Merge 840a114 into aa8b035
Browse files Browse the repository at this point in the history
  • Loading branch information
franciscogouveia committed Mar 4, 2017
2 parents aa8b035 + 840a114 commit 2b7d21f
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 139 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Expand Up @@ -2,6 +2,7 @@ language: node_js
sudo: false

node_js:
- "6"
- "5"
- "4"

Expand Down
21 changes: 16 additions & 5 deletions README.md
Expand Up @@ -46,7 +46,7 @@ const dataRetrieverRouter = new DataRetrievalRouter();
dataRetrieverRouter.register('credentials', (source, key, context) => {

// Obtain your value (e.g. from the context)
const value = context[key];
const value = context.user[key];

return value;
});
Expand Down Expand Up @@ -77,21 +77,32 @@ Evaluate your policies against a certain context
const context = {
user: {
username: 'francisco',
group: ['admin', 'developer'],
validated: true
group: ['articles:admin', 'articles:developer'],
validated: true,
exampleField1: "test-value"
},
connection: {
remoteip: '192.168.0.123',
remoteport: 90,
localip: '192.168.0.2'
localport: 80
localport: 80,
exampleField2: "test-value"
}
};

dataRetrieverRouter.setContext(context);

const policy = {
target: [{ 'credentials:username': 'francisco' }, { 'credentials:group': /^articles\:.*$/ }], // if username is 'francisco' OR group matches 'articles:*' (using native javascript RegExp)
target: [
// if username matches 'francisco' OR (exampleField1 matches exampleField2 AND user group matches 'articles:*')
{ 'credentials:username': 'francisco' },
// OR
{
'credentials:exampleField1': { field: 'connection:exampleField2' }
// AND
'credentials:group': /^articles\:.*$/ //(using native javascript RegExp)
}
],
apply: 'deny-overrides', // permit, unless one denies
rules: [
{
Expand Down
99 changes: 50 additions & 49 deletions lib/DataRetrievalRouter.js
Expand Up @@ -44,9 +44,9 @@ internals.DataRetrievalRouter.prototype.createChild = function (context) {
/**
* Register a data retriever.
*
* * handles - A string or array of strings specifying what this component retrieves (source of data, e.g. 'credentials')
* * retriever - A function which returns data, according to a key. Function signature is (source:string, key:string, context:object) => String
* * options - (optional) A JSON with the following options:
* @param handles - A string or array of strings specifying what this component retrieves (source of data, e.g. 'credentials')
* @param retriever - A function which returns data, according to a key. Function signature is (source:string, key:string, context:object) => String
* @param options - (optional) A JSON with the following options:
* * override - When true, overrides existent handler if exists. When false, throws an error when a repeated handler is used. (default: false)
**/
internals.DataRetrievalRouter.prototype.register = function (handles, retriever, options) {
Expand Down Expand Up @@ -91,71 +91,72 @@ internals.DataRetrievalRouter.prototype._register = function (handles, retriever
/**
* Obtain data from a retriever.
*
* * key - Key value from the source (e.g. 'credentials:username')
* * context - (optional) Context object. Contains the request object.
* * callback - Function with the signature (err, result)
* @param key - Key value from the source (e.g. 'credentials:username')
* @param context - (optional) Context object. Contains the request object.
* @returns Promise
**/
internals.DataRetrievalRouter.prototype.get = function (key, context, callback) {
internals.DataRetrievalRouter.prototype.get = function (key, context) {

if (!callback) {
if (context && context instanceof Function) {
return new Promise((resolve, reject) => {

callback = context;
context = null;
} else {
Joi.assert(key, schemas.DataRetrievalRouter_get_key);
let source;
let subkey;

throw new Error('Callback not given');
if (key.indexOf(':') === -1) {
source = 'credentials'; // keep it backwards compatible
subkey = key;
}
else {
const split_key = key.split(':');
source = split_key[0];
subkey = split_key[1];
}
}

Joi.assert(key, schemas.DataRetrievalRouter_get_key);
let source;
let subkey;

if (key.indexOf(':') === -1) {
source = 'credentials'; // keep it backwards compatible
subkey = key;
}
else {
const split_key = key.split(':');
source = split_key[0];
subkey = split_key[1];
}
Joi.assert(subkey, schemas.DataRetrievalRouter_get_key);
Joi.assert(source, schemas.DataRetrievalRouter_get_source);

Joi.assert(subkey, schemas.DataRetrievalRouter_get_key);
Joi.assert(source, schemas.DataRetrievalRouter_get_source);
const fn = this.retrievers[source];

const fn = this.retrievers[source];
if (!fn) {

if (!fn) {
if (!this.parent) {

if (!this.parent) {
return resolve(null);
}

return callback(null, null);
return this.parent.get(key, context || this.context)
.then((result) => resolve(result))
.catch((err) => reject(err));
}

return this.parent.get(key, context || this.context, callback);
}
if (fn.length > 3) {

if (fn.length > 3) {
// has callback
try {
return fn(source, subkey, context || this.context, (err, value) => {

// has callback
try {
return fn(source, subkey, context || this.context, callback);
} catch(e) {
return callback(e);
if (err) {
return reject(err);
}

resolve(value);
});
} catch(e) {
return reject(e);
}
}
}

let value;
let value;

try {
value = fn(source, subkey, context || this.context);
} catch(e) {
return callback(e);
}
try {
value = fn(source, subkey, context || this.context);
} catch(e) {
return reject(e);
}

callback(null, value);
resolve(value);
});
};
schemas.DataRetrievalRouter_get_source = Joi.string().min(1);
schemas.DataRetrievalRouter_get_key = Joi.string().min(1);
55 changes: 35 additions & 20 deletions lib/index.js
Expand Up @@ -175,38 +175,53 @@ internals.evaluateTargetElement = (dataRetriever, element) => {

return (callback) => {

const tasks = [];
const promises = Object.keys(element).map((key) => internals.evaluateTargetElementKey(dataRetriever, element, key));

for (const key in element) {

tasks.push(internals.evaluateTargetElementKey(dataRetriever, element, key));
}

Async.parallel(tasks, (err, result) => {

if (err) {
return callback(err);
}
Promise.all(promises)
.then((results) => {

// Should all apply (AND)
const nonApplicable = result.filter((value) => !value);
const nonApplicable = results.filter((value) => !value);

callback(null, nonApplicable.length === 0);
});
})
.catch((err) => callback(err))
};
};

internals.evaluateTargetElementKey = (dataRetriever, element, key) => {
/**
* If target is defined as:
* { field: "credentials:user" }
* then this definition should be replaced by
* a value from dataRetriever for matching.
*
* @param dataRetriever
* @param definedValue
* @returns Promise
**/
internals.getTargetValue = (dataRetriever, definedValue) => {

return (callback) => {
if(typeof definedValue === "object") {
if (definedValue.field) {
return dataRetriever.get(definedValue.field);
}
}

dataRetriever.get(key, null, (err, value) => {
return Promise.resolve(definedValue);
};

const result = internals._targetApplies(element[key], value);
internals.evaluateTargetElementKey = (dataRetriever, element, key) => {

callback(null, !!result);
});
};
return Promise.all([
internals.getTargetValue(dataRetriever, element[key]),
dataRetriever.get(key)
])
.then((results) => {

const targetValue = results[0];
const value = results[1];
return internals._targetApplies(targetValue, value);
});
};

/**
Expand Down

0 comments on commit 2b7d21f

Please sign in to comment.