Skip to content
This repository has been archived by the owner on Dec 15, 2020. It is now read-only.

Commit

Permalink
Merge 8bcfb3f into 78a8bc6
Browse files Browse the repository at this point in the history
  • Loading branch information
andrew-aladev committed Jul 21, 2016
2 parents 78a8bc6 + 8bcfb3f commit ec5b6b7
Show file tree
Hide file tree
Showing 14 changed files with 389 additions and 228 deletions.
26 changes: 14 additions & 12 deletions lib/acl-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,32 @@
"id": "/",
"type": "array",
"items": {
"id": "1",
"id": "items",
"type": "object",
"properties": {
"path": {
"id": "path",
"type": "string"
"role": {
"id": "role",
"type": "string",
"default": "public"
},
"roles": {
"id": "roles",
"paths": {
"id": "paths",
"type": "array",
"items": {
"id": "1",
"id": "items",
"type": "object",
"properties": {
"role": {
"id": "role",
"type": "string"
"path": {
"id": "path",
"type": "string",
"default": "/"
},
"verbs": {
"id": "verbs",
"type": "array",
"items": {
"id": "0",
"type": "string"
"type": "string",
"default": "GET"
}
}
}
Expand Down
74 changes: 48 additions & 26 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
/*jshint -W121 */
'use strict';
var assert = require('assert');
var objectAssign = require('object-assign');

var debug = require('debug')('thalisalti:acl');
var fast = require('./indexOf');

var NOTFOUND = -1;
var IDTOKEN = '{:id}';

var defaults = {
stripTrailingSlash: true
};


function stripTrailingSlash(path) {
// single slash '/' is a valid path, it should be ignored
var lastIndex = path.length - 1;
if (lastIndex > 0 && path[lastIndex] === '/') {
debug('ignoring trailing slash for %s', path);
return path.substring(0, lastIndex);
}
return path;
}

/** Gets the req object, collection of paths, and path to validate */
function lookupPathVerb(req, paths, lookupPath) {
function lookupPathVerb(req, paths, path, lookupPath) {
var pathExists = fast.indexOf(paths, lookupPath, 'path');

if (NOTFOUND === pathExists) {
Expand All @@ -21,7 +36,7 @@ function lookupPathVerb(req, paths, lookupPath) {
var verbExists = fast.indexOf(verbs, req.method);

if (NOTFOUND === verbExists) {
debug('verb and path not found: req.Path: %s lookupPath: %s', req.path, lookupPath);
debug('verb and path not found: req.Path: %s lookupPath: %s', path, lookupPath);
return false;
}

Expand Down Expand Up @@ -50,7 +65,7 @@ if (!String.prototype.startsWith) {



module.exports = function(dbname, inAcl, callback) {
module.exports = function(dbname, inAcl, callback, options) {

if (!dbname || typeof dbname !== 'string') {
throw new Error('invalid configuration - missing dbname');
Expand All @@ -59,13 +74,15 @@ module.exports = function(dbname, inAcl, callback) {
if (!inAcl || !(inAcl instanceof Array)) {
throw new Error('invalid configuration - inAcl is not an array');
}

if(!callback || typeof callback !== 'function'){
throw new Error('invalid configuration - callback is NOT a function');
}

options = objectAssign({}, defaults, options);

var thaliPrefix = '/' + dbname + '/_local/thali_';

var acl = JSON.parse(JSON.stringify(inAcl).replace(/{:db}/g, dbname));

//path.role.verb
Expand All @@ -75,8 +92,13 @@ module.exports = function(dbname, inAcl, callback) {
var okPathVerb = false;
debug('method: %s url: %s path: %s', req.method, req.url, req.path);

var path = req.path;
if (options.stripTrailingSlash) {
path = stripTrailingSlash(req.path);
}

if (!req.connection.pskRole) {
debug('unauthorized0: no role: %s ', req.path);
debug('unauthorized0: no role: %s ', path);
return res.status(401).send(msg401);
}

Expand All @@ -86,22 +108,22 @@ module.exports = function(dbname, inAcl, callback) {
var roleExists = fast.indexOf(acl, pskRole, 'role');

if (NOTFOUND === roleExists) {
debug('unauthorized1: %s : %s', pskRole, req.path);
debug('unauthorized1: %s : %s', pskRole, path);
return res.status(401).send(msg401);
}

var paths = acl[roleExists].paths;

//here we're doing a strict simple RAW path lookup on ACLs
var rawExists = fast.indexOf(paths, req.path, 'path');
var rawExists = fast.indexOf(paths, path, 'path');

if (NOTFOUND !== rawExists) {
//verbs are just an array of strings
var verbs = paths[rawExists].verbs;
var verbExists = fast.indexOf(verbs, req.method);

if (NOTFOUND === verbExists) {
debug('unauthorized3: %s : %s', pskRole, req.path);
debug('unauthorized3: %s : %s', pskRole, path);
return res.status(401).send(msg401);
}
else {
Expand All @@ -112,21 +134,21 @@ module.exports = function(dbname, inAcl, callback) {

else {
//this section deals with /db/id, /db/_local/id, and /db/id/attachment stuff.
var pathParts = req.path.split('/');
var pathParts = path.split('/');

if (pathParts[0] !== '') {
//sanity check.. the first element should always be empty.
debug('unauthorized4.0: %s : %s', pskRole, req.path);
debug('unauthorized4.0: %s : %s', pskRole, path);
return res.status(401).send(msg401);
}

var lookupPath = req.path.substring(0, req.path.lastIndexOf('/') + 1) + IDTOKEN;
var lookupPath = path.substring(0, path.lastIndexOf('/') + 1) + IDTOKEN;

if (pathParts.length === 3) {
//we have just a path and ID
//do a search on /db/id and bail if NO GOOD
if (!lookupPathVerb(req, paths, lookupPath)) {
debug('unauthorized4.1: req.Path: %s req.path: %s', req.path, lookupPath);
if (!lookupPathVerb(req, paths, path, lookupPath)) {
debug('unauthorized4.1: req.Path: %s req.path: %s', path, lookupPath);
return res.status(401).send(msg401);
}
else {
Expand All @@ -138,19 +160,19 @@ module.exports = function(dbname, inAcl, callback) {
//is this a /thali_ one?

//TODO:
var isThaliPrefix = req.path.startsWith(thaliPrefix);
if (isThaliPrefix && ! lookupPathVerb(req, paths, thaliPrefix + IDTOKEN)){
debug('unauthorized4.thaliPrefix: req.Path: %s path: %s', req.path, thaliPrefix);
return res.status(401).send(msg401);
var isThaliPrefix = path.startsWith(thaliPrefix);
if (isThaliPrefix && ! lookupPathVerb(req, paths, path, thaliPrefix + IDTOKEN)){
debug('unauthorized4.thaliPrefix: req.Path: %s path: %s', path, thaliPrefix);
return res.status(401).send(msg401);
}
if (isThaliPrefix && !getThaliId(req.path, dbname, callback)) {
debug('unauthorized4.thaliCallback: req.Path: %s path: %s', req.path, thaliPrefix);

if (isThaliPrefix && !getThaliId(path, dbname, callback)) {
debug('unauthorized4.thaliCallback: req.Path: %s path: %s', path, thaliPrefix);
return res.status(401).send(msg401);
}

if (!lookupPathVerb(req, paths, lookupPath)) {
debug('unauthorized4.2: req.Path: %s req.path: %s', req.path, lookupPath);
if (!lookupPathVerb(req, paths, path, lookupPath)) {
debug('unauthorized4.2: req.Path: %s req.path: %s', path, lookupPath);
return res.status(401).send(msg401);
}
else {
Expand All @@ -161,16 +183,16 @@ module.exports = function(dbname, inAcl, callback) {
else if (pathParts.length === 4 && pathParts[3] === 'attachment') {
var attachmentPath = '/' + pathParts[1] + '/' + IDTOKEN + '/attachment';

if (!lookupPathVerb(req, paths, attachmentPath)) {
debug('unauthorized4.3: req.Path: %s req.path: %s', req.path, attachmentPath);
if (!lookupPathVerb(req, paths, path, attachmentPath)) {
debug('unauthorized4.3: req.Path: %s req.path: %s', path, attachmentPath);
return res.status(401).send(msg401);
}
else {
okPathVerb = true;
}
}
else {
debug('unauthorized5: %s : %s', pskRole, req.path);
debug('unauthorized5: %s : %s', pskRole, path);
return res.status(401).send(msg401);
}
}
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
},
"homepage": "https://github.com/thaliproject/salti#readme",
"dependencies": {
"object-assign": "^4.1.0",
"debug": "^2.2.0"
},
"devDependencies": {
Expand Down
68 changes: 36 additions & 32 deletions sample/acl-example.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,40 @@

//go here with the schema and build away...
//http://jeremydorn.com/json-editor/
//http://bit.ly/1Qs3l66

//ideally - 1 less check if path has a trailing /..
// ie. /foo/ is faster than /bar

module.exports = [{
"path": "/",
"roles": [{
"role": "public",
"verbs": ["GET"]
}, {
"role": "user",
"verbs": ["GET"]
}]
}, {
"path": "/foo/",
"roles": [{
"role": "public",
"verbs": ["GET", "POST"]
}, {
"role": "user",
"verbs": ["GET", "PUR", "POST"]
}]
}, {
"path": "/bar",
"roles": [{
"role": "public",
"verbs": ["GET"]
}, {
"role": "user",
"verbs": ["GET"]
}]
}];
module.exports = [
{
'role': 'public',
'paths': [
{
'path': '/',
'verbs': ['GET']
},
{
'path': '/foo',
'verbs': ['GET', 'POST']
},
{
'path': '/bar',
'verbs': ['GET']
}
]
},
{
'role': 'user',
'paths': [
{
'path': '/',
'verbs': ['GET']
},
{
'path': '/foo',
'verbs': ['GET', 'PUT', 'POST']
},
{
'path': '/bar',
'verbs': ['GET']
}
]
}
];
9 changes: 8 additions & 1 deletion sample/app/guid.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

module.exports = function guid() {
function guid() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
Expand All @@ -9,3 +9,10 @@ module.exports = function guid() {
return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
s4() + '-' + s4() + s4() + s4();
}

if (typeof module != 'undefined') {
module.exports = guid;
}
if (typeof angular != 'undefined') {
angular.module('myApp').constant('$guid', guid);
}
10 changes: 4 additions & 6 deletions sample/app/validate.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
var myApp = angular.module('myApp', []);

myApp.controller('ValidateController', ['$scope', '$http', function ($scope, $http) {
myApp.controller('ValidateController', ['$scope', '$http', '$guid', function ($scope, $http, $guid) {
$scope.greeting = 'Welcome to SALTI!';
$scope.errors = 'no errors';
$scope.messages = 'no messages';
Expand All @@ -26,10 +26,8 @@ myApp.controller('ValidateController', ['$scope', '$http', function ($scope, $ht
});
}

function genDoc(){
return JSON.stringify({ _id: $guid(), 'type': 'foobar' })
}
}]);


function genDoc(){
return JSON.stringify({ _id: guid(), "type": "foobar" })
}

0 comments on commit ec5b6b7

Please sign in to comment.