diff --git a/packages/xo-acl-resolver/.babelrc b/packages/xo-acl-resolver/.babelrc new file mode 100644 index 000000000..0a89ba36d --- /dev/null +++ b/packages/xo-acl-resolver/.babelrc @@ -0,0 +1,8 @@ +{ + "comments": false, + "compact": true, + "presets": [ + "stage-0", + "es2015" + ] +} diff --git a/packages/xo-acl-resolver/.editorconfig b/packages/xo-acl-resolver/.editorconfig new file mode 100644 index 000000000..da21ef4c5 --- /dev/null +++ b/packages/xo-acl-resolver/.editorconfig @@ -0,0 +1,65 @@ +# http://EditorConfig.org +# +# Julien Fontanet's configuration +# https://gist.github.com/julien-f/8096213 + +# Top-most EditorConfig file. +root = true + +# Common config. +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespaces = true + +# CoffeeScript +# +# https://github.com/polarmobile/coffeescript-style-guide/blob/master/README.md +[*.{,lit}coffee] +indent_size = 2 +indent_style = space + +# Markdown +[*.{md,mdwn,mdown,markdown}] +indent_size = 4 +indent_style = space + +# Package.json +# +# This indentation style is the one used by npm. +[/package.json] +indent_size = 2 +indent_style = space + +# Jade +[*.jade] +indent_size = 2 +indent_style = space + +# JavaScript +# +# Two spaces seems to be the standard most common style, at least in +# Node.js (http://nodeguide.com/style.html#tabs-vs-spaces). +[*.js] +indent_size = 2 +indent_style = space + +# Less +[*.less] +indent_size = 2 +indent_style = space + +# Sass +# +# Style used for http://libsass.com +[*.s[ac]ss] +indent_size = 2 +indent_style = space + +# YAML +# +# Only spaces are allowed. +[*.yaml] +indent_size = 2 +indent_style = space diff --git a/packages/xo-acl-resolver/.gitignore b/packages/xo-acl-resolver/.gitignore new file mode 100644 index 000000000..827e4e420 --- /dev/null +++ b/packages/xo-acl-resolver/.gitignore @@ -0,0 +1,7 @@ +/dist/ +/node_modules/ + +npm-debug.log +npm-debug.log.* +pnpm-debug.log +pnpm-debug.log.* diff --git a/packages/xo-acl-resolver/.npmignore b/packages/xo-acl-resolver/.npmignore new file mode 100644 index 000000000..c31ee82cb --- /dev/null +++ b/packages/xo-acl-resolver/.npmignore @@ -0,0 +1,10 @@ +/examples/ +example.js +example.js.map +*.example.js +*.example.js.map + +/test/ +/tests/ +*.spec.js +*.spec.js.map diff --git a/packages/xo-acl-resolver/.travis.yml b/packages/xo-acl-resolver/.travis.yml new file mode 100644 index 000000000..a9b136ea6 --- /dev/null +++ b/packages/xo-acl-resolver/.travis.yml @@ -0,0 +1,9 @@ +language: node_js +node_js: + - 'stable' + - '4' + - '0.12' + +# Use containers. +# http://docs.travis-ci.com/user/workers/container-based-infrastructure/ +sudo: false diff --git a/packages/xo-acl-resolver/README.md b/packages/xo-acl-resolver/README.md new file mode 100644 index 000000000..371fa5483 --- /dev/null +++ b/packages/xo-acl-resolver/README.md @@ -0,0 +1,74 @@ +# xo-acl-resolver [![Build Status](https://travis-ci.org/vatesfr/xo-acl-resolver.png?branch=master)](https://travis-ci.org/vatesfr/xo-acl-resolver) + +> [Xen-Orchestra](http://xen-orchestra.com/) internal: do ACLs resolution. + +## Install + +Installation of the [npm package](https://npmjs.org/package/xo-acl-resolver): + +``` +> npm install --save xo-acl-resolver +``` + +## Usage + +```js +import check from 'xo-acl-resolver' + +// This object contains a list of permissions returned from +// xo-server's acl.getCurrentPermissions. +const permissions = { /* ... */ } + +// This function should returns synchronously an object from an id. +const getObject = id => { /* ... */ } + +// For a single object: +if (check(permissions, getObject, objectId, permission)) { + console.log(`${permission} set for object ${objectId}`) +} + +// For multiple objects/permissions: +if (check(permissions, getObject, [ + [ object1Id, permission1 ], + [ object12d, permission2 ], +])) { + console.log('all permissions checked') +} +``` + +## Development + +### Installing dependencies + +``` +> npm install +``` + +### Compilation + +The sources files are watched and automatically recompiled on changes. + +``` +> npm run dev +``` + +### Tests + +``` +> npm run test-dev +``` + +## Contributions + +Contributions are *very* welcomed, either on the documentation or on +the code. + +You may: + +- report any [issue](https://github.com/vatesfr/xo-acl-resolver/issues) + you've encountered; +- fork and create a pull request. + +## License + +ISC © [Vates SAS](https://vates.fr) diff --git a/packages/xo-acl-resolver/package.json b/packages/xo-acl-resolver/package.json new file mode 100644 index 000000000..cd7360322 --- /dev/null +++ b/packages/xo-acl-resolver/package.json @@ -0,0 +1,48 @@ +{ + "name": "xo-acl-resolver", + "version": "0.2.3", + "license": "ISC", + "description": "Xen-Orchestra internal: do ACLs resolution", + "keywords": [], + "homepage": "https://github.com/vatesfr/xo-acl-resolver", + "bugs": "https://github.com/vatesfr/xo-acl-resolver/issues", + "repository": { + "type": "git", + "url": "https://github.com/vatesfr/xo-acl-resolver" + }, + "author": { + "name": "Julien Fontanet", + "email": "julien.fontanet@vates.fr" + }, + "preferGlobal": false, + "main": "dist/", + "bin": {}, + "files": [ + "dist/" + ], + "engines": { + "node": ">=0.12" + }, + "devDependencies": { + "babel-cli": "^6.4.5", + "babel-eslint": "^7.0.0", + "babel-preset-es2015": "^6.3.13", + "babel-preset-stage-0": "^6.3.13", + "dependency-check": "^2.5.1", + "standard": "^8.0.0" + }, + "scripts": { + "build": "babel --source-maps --out-dir=dist/ src/", + "depcheck": "dependency-check ./package.json", + "dev": "babel --watch --source-maps --out-dir=dist/ src/", + "lint": "standard", + "posttest": "npm run lint && npm run depcheck", + "prepublish": "npm run build" + }, + "standard": { + "ignore": [ + "dist/**" + ], + "parser": "babel-eslint" + } +} diff --git a/packages/xo-acl-resolver/src/index.js b/packages/xo-acl-resolver/src/index.js new file mode 100644 index 000000000..4e08db43a --- /dev/null +++ b/packages/xo-acl-resolver/src/index.js @@ -0,0 +1,131 @@ +// These global variables are not a problem because the algorithm is +// synchronous. +let permissionsByObject +let getObject + +// ------------------------------------------------------------------- + +const authorized = () => true // eslint-disable-line no-unused-vars +const forbiddden = () => false // eslint-disable-line no-unused-vars + +const and = (...checkers) => (object, permission) => { // eslint-disable-line no-unused-vars + for (const checker of checkers) { + if (!checker(object, permission)) { + return false + } + } + return true +} + +const or = (...checkers) => (object, permission) => { // eslint-disable-line no-unused-vars + for (const checker of checkers) { + if (checker(object, permission)) { + return true + } + } + return false +} + +// ------------------------------------------------------------------- + +const checkMember = (memberName) => (object, permission) => { + const member = object[memberName] + return member !== object.id && checkAuthorization(member, permission) +} + +const checkSelf = ({ id }, permission) => { + const permissionsForObject = permissionsByObject[id] + + return ( + permissionsForObject && + permissionsForObject[permission] + ) +} + +// =================================================================== + +const checkAuthorizationByTypes = { + host: or(checkSelf, checkMember('$pool')), + + message: checkMember('$object'), + + network: or(checkSelf, checkMember('$pool')), + + SR: or(checkSelf, checkMember('$pool')), + + task: checkMember('$host'), + + VBD: checkMember('VDI'), + + // Access to a VDI is granted if the user has access to the + // containing SR or to a linked VM. + VDI (vdi, permission) { + // Check authorization for the containing SR. + if (checkAuthorization(vdi.$SR, permission)) { + return true + } + + // Check authorization for each of the connected VMs. + for (const vbdId of vdi.$VBDs) { + if (checkAuthorization(getObject(vbdId).VM, permission)) { + return true + } + } + + return false + }, + + 'VDI-snapshot': checkMember('$snapshot_of'), + + VIF: or(checkMember('$network'), checkMember('$VM')), + + VM: or(checkSelf, checkMember('$container')), + + 'VM-controller': checkMember('$container'), + + 'VM-snapshot': checkMember('$snapshot_of'), + + 'VM-template': or(checkSelf, checkMember('$pool')) +} + +// Hoisting is important for this function. +function checkAuthorization (objectId, permission) { + const object = getObject(objectId) + if (!object) { + return false + } + + const checker = checkAuthorizationByTypes[object.type] || checkSelf + + return checker(object, permission) +} + +// ------------------------------------------------------------------- + +export default ( + permissionsByObject_, + getObject_, + permissions, + permission +) => { + // Assign global variables. + permissionsByObject = permissionsByObject_ + getObject = getObject_ + + try { + if (permission) { + return checkAuthorization(permissions, permission) + } else { + for (const [objectId, permission] of permissions) { + if (!checkAuthorization(objectId, permission)) { + return false + } + } + } + + return true + } finally { + // Free the global variables. + permissionsByObject = getObject = null + } +}