spel2js
Advanced tools
Comparing version 0.2.3 to 0.2.4
{ | ||
"name": "spel2js", | ||
"version": "0.2.3", | ||
"version": "0.2.4", | ||
"description": "Parse Spring Expression Language in JavaScript", | ||
@@ -27,3 +27,7 @@ "author": { | ||
"scripts": { | ||
"test": "grunt" | ||
"build": "webpack --progress", | ||
"build:dev": "webpack -d --progress --devtool cheap-module-source-map", | ||
"test": "karma start test/karma.conf.js", | ||
"lint": "eslint src/ test/", | ||
"release": "npm test && npm run build && release-it" | ||
}, | ||
@@ -42,24 +46,36 @@ "keywords": [ | ||
"devDependencies": { | ||
"babelify": "^6.1.2", | ||
"grunt": "^0.4.5", | ||
"grunt-browserify": "^3.8.0", | ||
"grunt-complexity": "^0.3.0", | ||
"grunt-contrib-concat": "^0.5.1", | ||
"grunt-contrib-jshint": "^0.11.2", | ||
"grunt-contrib-uglify": "^0.9.1", | ||
"grunt-karma": "^0.11.0", | ||
"grunt-release": "^0.13.0", | ||
"babel": "6.23.0", | ||
"babel-core": "6.26.0", | ||
"babel-eslint": "8.0.1", | ||
"babel-istanbul-loader": "0.1.0", | ||
"babel-loader": "7.1.2", | ||
"babel-preset-es2015": "6.24.1", | ||
"babel-preset-es2017": "6.24.1", | ||
"babel-register": "6.26.0", | ||
"eslint": "4.7.2", | ||
"eslint-config-airbnb-base": "12.0.0", | ||
"eslint-config-prettier": "2.6.0", | ||
"eslint-import-resolver-webpack": "0.8.3", | ||
"eslint-loader": "1.9.0", | ||
"eslint-plugin-import": "2.7.0", | ||
"eslint-plugin-prettier": "2.3.1", | ||
"jasmine-core": "2.8.0", | ||
"karma": "1.7.1", | ||
"karma-browserify": "^4.2.1", | ||
"karma-coverage": "^0.4.2", | ||
"karma-jasmine": "^0.3.5", | ||
"karma-phantomjs-launcher": "^0.2.0", | ||
"load-grunt-tasks": "^3.2.0", | ||
"karma-browserify": "4.2.1", | ||
"karma-coverage": "1.1.1", | ||
"karma-jasmine": "0.3.5", | ||
"karma-phantomjs-launcher": "1.0.4", | ||
"karma-source-map-support": "1.2.0", | ||
"karma-sourcemap-loader": "0.3.7", | ||
"karma-webpack": "2.0.4", | ||
"phantomjs": "2.1.7", | ||
"time-grunt": "^1.2.1" | ||
"prettier": "1.7.0", | ||
"release-it": "3.0.0", | ||
"webpack": "3.6.0", | ||
"webpack-bower-externals": "1.5.4", | ||
"webpack-node-externals": "1.6.0" | ||
}, | ||
"engines": { | ||
"node": ">=0.10.0" | ||
"node": ">=8" | ||
} | ||
} |
162
README.md
@@ -20,2 +20,91 @@ # spel2js | ||
## Getting Started | ||
Install SpEL2JS: | ||
```sh | ||
$ npm i -S spel2js | ||
# or | ||
$ bower i -S spel2js | ||
``` | ||
Or [download the zip](https://github.com/benmarch/spel2js/archive/master.zip) | ||
Include the dependency using a module loader or script tag. | ||
## Usage | ||
SpEL2JS exports a singleton with two members: | ||
```js | ||
import spel2js from 'spel2js'; | ||
console.log(spel2js); | ||
/* | ||
{ | ||
StandardContext, | ||
SpelExpressionEvaluator | ||
} | ||
*/ | ||
``` | ||
### `StandardContext` | ||
The `StandardContext` is a factory that creates a evaluation context for an expression. | ||
**NOTE:** This is not the same as the Java `EvaluationContext` class, though it serves a similar purpose. | ||
```js | ||
let spelContext = spel2js.StandardContext.create(authentication, principal); | ||
``` | ||
The `create()` method takes two arguments: `authentication` and `principal` | ||
`authentication` is an instance of Spring's [`Authentication`](https://docs.spring.io/spring-security/site/docs/current/apidocs/org/springframework/security/core/Authentication.html) class from Spring Security. | ||
`principal` is any object representing the user (this is just used for reference, and can be any value or structure) | ||
### `SpelExpressionEvaluator` | ||
The heavy lifting is done using the `SpelExpressionEvaluator` which exposes two functions: `compile()` and `eval()` | ||
`compile()` pre-compiles a SpEL expression, and returns an object with an `eval()` method that takes a context and optional locals: | ||
```js | ||
import { StandardContext, SpelExpressionEvaluator } from 'spel2js'; | ||
const expression = '#toDoList.owner == authentication.details.name'; | ||
const spelContext = StandardContext.create(authentication, principal); | ||
const locals = { | ||
toDoList: { | ||
owner: 'Darth Vader' | ||
} | ||
}; | ||
const compiledExpression = SpelExpressionEvaluator.compile(expression); | ||
compiledExpression.eval(spelContext, locals); // true | ||
``` | ||
`eval()` is just a shortcut for immediately evaluating an expression instead of pre-compiling: | ||
```js | ||
import { StandardContext, SpelExpressionEvaluator } from 'spel2js'; | ||
const expression = '#toDoList.owner == authentication.details.name'; | ||
const spelContext = StandardContext.create(authentication, principal); | ||
const locals = { | ||
toDoList: { | ||
owner: 'Darth Vader' | ||
} | ||
}; | ||
SpelExpressionEvaluator.eval(expression, spelContext, locals); // true | ||
``` | ||
### Recommended Usage | ||
Create a single context that contains information about the current user and reuse it for all evaluations. | ||
This way, you only have to supply an expression and locals when evaluating. | ||
Always pre-compile your expressions! Compilation takes much longer than evaluation; doing it up-front saves CPU when evaluating later. | ||
## Example | ||
@@ -46,3 +135,37 @@ | ||
```javascript | ||
```js | ||
//spel-service.js | ||
import { StandardContext, SpelExpressionEvaluator } from 'spel2js'; | ||
// wraps spel2js in a stateful service that simplifies evaluation | ||
angular.module('ToDo').factory('SpelService', function () { | ||
return { | ||
context: null, | ||
// assume this is called on page load | ||
setContext(authentication, principal) { | ||
this.context = StandardContext.create(authentication, principal); | ||
}, | ||
getContext() { return this.context; }, | ||
compile(expression) { | ||
const compiledExpression = SpelExpressionEvaluator.compile(expression); | ||
return { | ||
eval(locals) { | ||
return compiledExpression.eval(this.getContext(), locals); | ||
} | ||
}; | ||
}, | ||
eval(expression, locals) { | ||
return SpelExpressionEvaluator.eval(expression, this.getContext(), locals); | ||
} | ||
}; | ||
}); | ||
//list-controller.js | ||
@@ -52,2 +175,3 @@ | ||
// retrieve all permissions and pre-compile them | ||
$http.get('/api/permissions').success(function (permissions) { | ||
@@ -59,2 +183,3 @@ angular.forEach(permissions, function (spelExpression, key) { | ||
// $scope will be used as locals | ||
$scope.list = { | ||
@@ -70,4 +195,5 @@ name: 'My List', | ||
// EXPAMPLE 1: authorize a request before making it | ||
$scope.addListItem = function (list, newListItem) { | ||
if ($scope.permissions.ADD_LIST_ITEM_PERMISSION.eval(SpelService.getContext(), $scope)) { | ||
if ($scope.permissions.ADD_LIST_ITEM_PERMISSION.eval($scope)) { | ||
$http.post('/todolists/' + list.id + '/items', item).success(function () {...}); | ||
@@ -89,3 +215,5 @@ } | ||
<input type="text" ng-model="newListItem.text" /> | ||
<button ng-click="addListItem(list, newListItem)" spel-if="permissions.ADD_LIST_ITEM_PERMISSION">Add</button> | ||
<!-- EXAMPLE 2: Hide the button if the user does not have permission --> | ||
<button ng-click="addListItem(list, newListItem)" ng-if="permissions.ADD_LIST_ITEM_PERMISSION.eval(this)">Add</button> | ||
</li> | ||
@@ -96,6 +224,3 @@ ... | ||
Seems like it might be a lot of work for such a simple piece of functionality; however, what happens when you add role-based | ||
permissions as a new feature? If you already have this set up, it's as simple as adding " or hasRole('SuperUser')" to | ||
the SpEL, and exposing a minimal projection of the Authentication to the browser or Node app (which it probably already | ||
has access to.) Now the UI can always stay in sync with the server-side authorities. | ||
Now the UI can always stay in sync with the server-side authorities. | ||
@@ -127,24 +252,13 @@ ## Features | ||
There are a few AngularJS directives (I just need to put them on GH): | ||
- spelShow | ||
- spelHide | ||
- spelIf | ||
If someone wants to implement a REST-compliant way in Spring to expose the permissions (and maybe the custom | ||
PermissionEvaluators) that would be awesome. | ||
## Building Locally | ||
## Install Choices | ||
- `bower install spel2js` | ||
- `npm install spel2js` | ||
- [download the zip](https://github.com/benmarch/spel2js/archive/master.zip) | ||
```sh | ||
$ npm i | ||
$ npm run build | ||
$ npm test | ||
``` | ||
## Tasks | ||
All tasks can be run by simply running `grunt` or with the `npm test` command, or individually: | ||
* `grunt lint` will lint source code for syntax errors and anti-patterns. | ||
* `grunt test` will run the jasmine unit tests against the source code. | ||
## Credits | ||
@@ -151,0 +265,0 @@ |
@@ -10,3 +10,3 @@ var addSorting = (function () { | ||
// returns the summary table element | ||
function getTable() { return document.querySelector('.coverage-summary table'); } | ||
function getTable() { return document.querySelector('.coverage-summary'); } | ||
// returns the thead element of the summary table | ||
@@ -136,3 +136,5 @@ function getTableHeader() { return getTable().querySelector('thead tr'); } | ||
if (cols[i].sortable) { | ||
el = getNthColumn(i).querySelector('.sorter'); | ||
// add the click event handler on the th so users | ||
// dont have to click on those tiny arrows | ||
el = getNthColumn(i).querySelector('.sorter').parentElement; | ||
if (el.addEventListener) { | ||
@@ -139,0 +141,0 @@ el.addEventListener('click', ithSorter(i)); |
@@ -12,3 +12,16 @@ /** | ||
'use strict'; | ||
require('babel-register'); | ||
var webpackConfig = require('../webpack.config.babel').default; | ||
webpackConfig.externals = {}; | ||
webpackConfig.module.rules.unshift( | ||
//get coverage info from precompiled source code | ||
{ | ||
test: /\.js$/, | ||
enforce: 'pre', | ||
exclude: /(test|node_modules|bower_components)\//, | ||
use: 'babel-istanbul-loader', | ||
} | ||
); | ||
module.exports = function (config) { | ||
@@ -22,7 +35,12 @@ config.set({ | ||
/* | ||
Test results reporter to use: | ||
dots, progress, nyan, story, coverage etc. | ||
Test framework to use: | ||
jasmine, mocha, qunit etc. | ||
*/ | ||
reporters: ['dots', 'coverage'], | ||
frameworks: ['jasmine', 'source-map-support'], | ||
files: [ | ||
'src/main.js', | ||
'test/spec/**/*.spec.js' | ||
], | ||
/* | ||
@@ -32,7 +50,18 @@ Test pre-processors | ||
preprocessors: { | ||
'src/**/*.js': ['browserify'], | ||
'test/**/*spec.js': ['browserify'] | ||
'src/**/*.js': ['webpack', 'sourcemap'], | ||
'test/**/*spec.js': ['webpack', 'sourcemap'] | ||
}, | ||
webpack: webpackConfig, | ||
webpackMiddleware: { | ||
noInfo: true, | ||
}, | ||
/* | ||
Test results reporter to use: | ||
dots, progress, nyan, story, coverage etc. | ||
*/ | ||
reporters: ['dots', 'coverage'], | ||
/* | ||
Test coverage reporters: | ||
@@ -51,7 +80,2 @@ html, lcovonly, lcov, cobertura, text-summary, text, teamcity, clover etc. | ||
browserify: { | ||
debug: true, | ||
transform: [ 'babelify' ] | ||
}, | ||
/* | ||
@@ -89,14 +113,3 @@ Locally installed browsers | ||
logLevel: 'INFO', | ||
/* | ||
Test framework to use: | ||
jasmine, mocha, qunit etc. | ||
*/ | ||
frameworks: ['browserify', 'jasmine'], | ||
files: [ | ||
'src/**/*.js', | ||
'test/spec/**/*.spec.js' | ||
] | ||
}); | ||
}; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
890104
1
309
1
30
113
8715