├── .gitignore
├── .jshintrc
├── .npmignore
├── .sailsrc
├── .travis.yml
├── LICENSE
├── README.md
├── api
├── controllers
│ ├── ModelController.js
│ ├── PermissionController.js
│ └── RoleController.js
├── hooks
│ └── permissions
│ │ └── index.js
├── models
│ ├── Criteria.js
│ ├── Model.js
│ ├── Passport.js
│ ├── Permission.js
│ ├── RequestLog.js
│ ├── Role.js
│ ├── SecurityLog.js
│ └── User.js
├── policies
│ ├── AuditPolicy.js
│ ├── CriteriaPolicy.js
│ ├── ModelPolicy.js
│ ├── OwnerPolicy.js
│ ├── PermissionPolicy.js
│ └── RolePolicy.js
└── services
│ ├── ModelService.js
│ └── PermissionService.js
├── config
├── env
│ └── testing.js
├── fixtures
│ ├── model.js
│ ├── permission.js
│ ├── role.js
│ └── user.js
├── permissions.js
├── policies.js
└── session.js
├── generator.js
├── gulpfile.js
├── package.json
└── test
├── bootstrap.test.js
├── mocha.opts
└── unit
├── controllers
├── ModelController.test.js
├── PermissionController.test.js
└── UserController.test.js
└── services
└── PermissionService.test.js
/.gitignore:
--------------------------------------------------------------------------------
1 | #api/policies/basicAuth.js
2 | #api/policies/sessionAuth.js
3 | #api/services/protocols/basic.js
4 | dist/
5 |
6 | .tmp
7 | *.sw*
8 | # Logs
9 | logs
10 | *.log
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 |
17 | # Directory for instrumented libs generated by jscoverage/JSCover
18 | lib-cov
19 |
20 | # Coverage directory used by tools like istanbul
21 | coverage
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Compiled binary addons (http://nodejs.org/api/addons.html)
27 | build/Release
28 |
29 | # Dependency directory
30 | # Deployed apps should consider commenting this line out:
31 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
32 | node_modules
33 |
34 | .idea
35 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "indent": 2,
3 | "maxdepth": 6,
4 | "maxlen": 200,
5 | "esnext": true,
6 | "expr": true,
7 | "trailing": true,
8 | "node": true,
9 | "esnext": false,
10 | "globals": {
11 | "_": true,
12 | "sails": true,
13 | "describe": true,
14 | "it": true,
15 | "before": true,
16 | "Permission": true
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trailsjs/sails-permissions/826d504711662bf43a5c266667dd4487553a8a41/.npmignore
--------------------------------------------------------------------------------
/.sailsrc:
--------------------------------------------------------------------------------
1 | {
2 | "hooks": {
3 | "grunt": false
4 | },
5 | "generators": {
6 | "modules": {
7 | "auth-api": "sails-auth"
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '0.12'
4 | - '4.2'
5 | - 'stable'
6 |
7 | addons:
8 | postgresql: "9.4"
9 |
10 | sudo: false
11 |
12 | env:
13 | global:
14 | - REDIS_URL=redis://:@localhost:6379
15 |
16 | services:
17 | - redis-server
18 |
19 | notifications:
20 | email: false
21 |
22 | deploy:
23 | provider: npm
24 | email: me@traviswebb.com
25 | api_key:
26 | secure: AitUbALDH1eqxEuTIUMynFcaumHP2jFVZyCqUOqVmoNmHo6T5l+S6fwE1AlnPaeEic8q0gosDnMYG6W3GhQuXd0UkfAB7Gz72SHfNvNe29/svNN5exP7c/PWZZSqBxarnYM6NU7rkqdP11sF+UO6oqzd0QguT7VJPylg5EXg5I8=
27 | on:
28 | tags: true
29 | all_branches: true
30 | repo: tjwebb/sails-permissions
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Travis Webb
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # sails-permissions
2 |
3 | [![Gitter][gitter-image]][gitter-url]
4 | [![NPM version][npm-image]][npm-url]
5 | [![Build status][travis-image]][travis-url]
6 | [![Dependency Status][daviddm-image]][daviddm-url]
7 |
8 | Comprehensive sails.js user permissions and entitlements system. Supports user authentication with passport.js, role-based permissioning, object ownership, and row-level security.
9 |
10 | ## Install
11 | ```sh
12 | $ npm install sails-permissions sails-auth --save
13 | ```
14 |
15 | ## Quickstart
16 |
17 | **Note:** Complete documentation available in the sails-permissions wiki: https://github.com/langateam/sails-permissions/wiki
18 |
19 | ### 1. configure sailsrc
20 |
21 | ```json
22 | {
23 | "generators": {
24 | "modules": {
25 | "permissions-api": "sails-permissions/generator"
26 | }
27 | }
28 | }
29 | ```
30 |
31 | ### 2. run generator
32 |
33 | ```sh
34 | $ sails generate permissions-api
35 | ```
36 |
37 | ### 3. Set environment variables
38 |
39 | | variable | description | default |
40 | |:---|:---|:---|
41 | | `ADMIN_USERNAME` | admin username | `admin` |
42 | | `ADMIN_EMAIL` | admin user email address | `admin@example.com` |
43 | | `ADMIN_PASSWORD` | admin user password | `admin1234` |
44 |
45 | ##### e.g in config/local.js (file is in .gitignore)
46 | ```
47 | sails.config.permissions.adminUsername = 'admin'
48 | sails.config.permissions.adminEmail = 'admin@example.com'
49 | sails.config.permissions.adminPassword = 'admin1234'
50 | ```
51 | #### 4. update configs
52 |
53 | #### config/policies.js
54 | ```js
55 | '*': [
56 | 'basicAuth',
57 | 'passport',
58 | 'sessionAuth',
59 | 'ModelPolicy',
60 | 'AuditPolicy',
61 | 'OwnerPolicy',
62 | 'PermissionPolicy',
63 | 'RolePolicy',
64 | 'CriteriaPolicy'
65 | ],
66 |
67 | AuthController: {
68 | '*': [ 'passport' ]
69 | }
70 | ```
71 |
72 | #### 5. Login
73 | You can now login using the aforementioned default login data or the admin settings you specified using the `/auth/local` endpoint.
74 | ```json
75 | {
76 | "identifier": "admin@example.com",
77 | "password": "admin1234"
78 | }
79 | ```
80 |
81 | ## License
82 | MIT
83 |
84 | ## Maintained By
85 | [
](http://langa.io)
86 |
87 | [npm-image]: https://img.shields.io/npm/v/sails-permissions.svg?style=flat-square
88 | [npm-url]: https://npmjs.org/package/sails-permissions
89 | [travis-image]: https://img.shields.io/travis/langateam/sails-permissions.svg?style=flat-square
90 | [travis-url]: https://travis-ci.org/langateam/sails-permissions
91 | [daviddm-image]: http://img.shields.io/david/langateam/sails-permissions.svg?style=flat-square
92 | [daviddm-url]: https://david-dm.org/langateam/sails-permissions
93 | [gitter-image]: http://img.shields.io/badge/+%20GITTER-JOIN%20CHAT%20%E2%86%92-1DCE73.svg?style=flat-square
94 | [gitter-url]: https://gitter.im/langateam/sails-permissions
95 |
96 | [hacktober-image]: http://i.imgur.com/FM9yVCI.png
97 | [hacktober-url]: https://twitter.com/langateam/status/782995392212369408
98 |
--------------------------------------------------------------------------------
/api/controllers/ModelController.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ModelController
3 | *
4 | * @description :: Server-side logic for managing models
5 | * @help :: See http://links.sailsjs.org/docs/controllers
6 | */
7 |
8 | module.exports = {
9 |
10 | };
11 |
12 |
--------------------------------------------------------------------------------
/api/controllers/PermissionController.js:
--------------------------------------------------------------------------------
1 | /**
2 | * PermissionController
3 | *
4 | * @description :: Server-side logic for managing permissions
5 | * @help :: See http://links.sailsjs.org/docs/controllers
6 | */
7 |
8 | module.exports = {
9 |
10 | };
11 |
12 |
--------------------------------------------------------------------------------
/api/controllers/RoleController.js:
--------------------------------------------------------------------------------
1 | /**
2 | * RoleController
3 | *
4 | * @description :: Server-side logic for managing roles
5 | * @help :: See http://links.sailsjs.org/docs/controllers
6 | */
7 |
8 | module.exports = {
9 |
10 | };
11 |
12 |
--------------------------------------------------------------------------------
/api/hooks/permissions/index.js:
--------------------------------------------------------------------------------
1 | var permissionPolicies = [
2 | 'passport',
3 | 'sessionAuth',
4 | 'ModelPolicy',
5 | 'OwnerPolicy',
6 | 'PermissionPolicy',
7 | 'RolePolicy'
8 | ]
9 | import path from 'path'
10 | import _ from 'lodash'
11 | import Marlinspike from 'marlinspike'
12 |
13 | class Permissions extends Marlinspike {
14 | constructor (sails) {
15 | super(sails, module)
16 | }
17 |
18 | configure () {
19 | if (!_.isObject(sails.config.permissions)) sails.config.permissions = { }
20 |
21 | /**
22 | * Local cache of Model name -> id mappings to avoid excessive database lookups.
23 | */
24 | this.sails.config.blueprints.populate = false
25 | }
26 |
27 | initialize (next) {
28 | let config = this.sails.config.permissions
29 |
30 | this.installModelOwnership()
31 | this.sails.after(config.afterEvent, () => {
32 | if (!this.validateDependencies()) {
33 | this.sails.log.error('Cannot find sails-auth hook. Did you "npm install sails-auth --save"?')
34 | this.sails.log.error('Please see README for installation instructions: https://github.com/tjwebb/sails-permissions')
35 | return this.sails.lower()
36 | }
37 |
38 | if (!this.validatePolicyConfig()) {
39 | this.sails.log.warn('One or more required policies are missing.')
40 | this.sails.log.warn('Please see README for installation instructions: https://github.com/tjwebb/sails-permissions')
41 | }
42 |
43 | })
44 |
45 | this.sails.after('hook:orm:loaded', () => {
46 | sails.models.model.count()
47 | .then(count => {
48 | if (count === _.keys(this.sails.models).length) return next()
49 |
50 | return this.initializeFixtures()
51 | .then(() => {
52 | next()
53 | })
54 | })
55 | .catch(error => {
56 | this.sails.log.error(error)
57 | next(error)
58 | })
59 | })
60 | }
61 |
62 | validatePolicyConfig () {
63 | var policies = this.sails.config.policies
64 | return _.all([
65 | _.isArray(policies['*']),
66 | _.intersection(permissionPolicies, policies['*']).length === permissionPolicies.length,
67 | policies.AuthController && _.contains(policies.AuthController['*'], 'passport')
68 | ])
69 | }
70 |
71 | installModelOwnership () {
72 | var models = this.sails.models
73 | if (this.sails.config.models.autoCreatedBy === false) return
74 |
75 | _.each(models, model => {
76 | if (model.autoCreatedBy === false) return
77 |
78 | _.defaults(model.attributes, {
79 | createdBy: {
80 | model: 'User',
81 | index: true
82 | },
83 | owner: {
84 | model: 'User',
85 | index: true
86 | }
87 | })
88 | })
89 | }
90 |
91 | /**
92 | * Install the application. Sets up default Roles, Users, Models, and
93 | * Permissions, and creates an admin user.
94 | */
95 | initializeFixtures () {
96 | let fixturesPath = path.resolve(__dirname, '../../../config/fixtures/')
97 | return require(path.resolve(fixturesPath, 'model')).createModels()
98 | .then(models => {
99 | this.models = models
100 | this.sails.hooks.permissions._modelCache = _.indexBy(models, 'identity')
101 |
102 | return require(path.resolve(fixturesPath, 'role')).create()
103 | })
104 | .then(roles => {
105 | this.roles = roles
106 | var userModel = _.find(this.models, { name: 'User' })
107 | return require(path.resolve(fixturesPath, 'user')).create(this.roles, userModel)
108 | })
109 | .then(() => {
110 | return sails.models.user.findOne({ email: this.sails.config.permissions.adminEmail })
111 | })
112 | .then(user => {
113 | this.sails.log('sails-permissions: created admin user:', user)
114 | user.createdBy = user.id
115 | user.owner = user.id
116 | return user.save()
117 | })
118 | .then(admin => {
119 | return require(path.resolve(fixturesPath, 'permission')).create(this.roles, this.models, admin, this.sails.config.permissions);
120 | })
121 | .catch(error => {
122 | this.sails.log.error(error)
123 | })
124 | }
125 |
126 | validateDependencies () {
127 | return !!this.sails.hooks.auth;
128 | }
129 | }
130 |
131 | export default Marlinspike.createSailsHook(Permissions)
132 |
--------------------------------------------------------------------------------
/api/models/Criteria.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module Criteria
3 | *
4 | * @description
5 | * Criteria specify limits on a permission, via a 'where' clause and an attribute blacklist.
6 | * For the blacklist, if the request action is update or create, and there is a blacklisted attribute in the request,
7 | * the request will fail. If the request action is read, the blacklisted attributes will be filtered.
8 | * The blacklist is not relevant for delete requests.
9 | * A where clause uses waterline query syntax to determine if a permission is allowed, ie where: { id: { '>': 5 } }
10 | */
11 | module.exports = {
12 | autoCreatedBy: false,
13 |
14 | description: 'Specifies more granular limits on a permission',
15 |
16 | attributes: {
17 | where: 'json',
18 | blacklist: 'array',
19 | permission: {
20 | model: 'Permission'
21 | }
22 | }
23 | };
24 |
--------------------------------------------------------------------------------
/api/models/Model.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module Model
3 | *
4 | * @description
5 | * Abstract representation of a Waterline Model.
6 | */
7 | module.exports = {
8 | description: 'Represents a Waterline collection that a User can create, query, etc.',
9 |
10 | autoPK: true,
11 | autoCreatedBy: false,
12 | autoCreatedAt: false,
13 | autoUpdatedAt: false,
14 |
15 | attributes: {
16 | name: {
17 | type: 'string',
18 | notNull: true,
19 | unique: true
20 | },
21 | identity: {
22 | type: 'string',
23 | notNull: true
24 | },
25 | attributes: {
26 | type: 'json'
27 | },
28 | permissions: {
29 | collection: 'Permission',
30 | via: 'model'
31 | }
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/api/models/Passport.js:
--------------------------------------------------------------------------------
1 |
2 | var _ = require('lodash');
3 | var _super = require('sails-auth/api/models/Passport');
4 |
5 | _.merge(exports, _super);
6 | _.merge(exports, {
7 |
8 | autoCreatedBy: false
9 |
10 | // Extend with custom logic here by adding additional fields, methods, etc.
11 |
12 | /**
13 | * For example:
14 | *
15 | * foo: function (bar) {
16 | * bar.x = 1;
17 | * bar.y = 2;
18 | * return _super.foo(bar);
19 | * }
20 | */
21 | });
22 |
--------------------------------------------------------------------------------
/api/models/Permission.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module Permission
3 | *
4 | * @description
5 | * The actions a Role is granted on a particular Model and its attributes
6 | */
7 | import _ from 'lodash'
8 | module.exports = {
9 | autoCreatedBy: false,
10 |
11 | description: [
12 | 'Defines a particular `action` that a `Role` can perform on a `Model`.',
13 | 'A `User` can perform an `action` on a `Model` by having a `Role` which',
14 | 'grants the necessary `Permission`.'
15 | ].join(' '),
16 |
17 | attributes: {
18 |
19 | /**
20 | * The Model that this Permission applies to.
21 | */
22 | model: {
23 | model: 'Model',
24 | required: true
25 | },
26 |
27 | action: {
28 | type: 'string',
29 | index: true,
30 | notNull: true,
31 | /**
32 | * TODO remove enum and support permissions based on all controller
33 | * actions, including custom ones
34 | */
35 | enum: [
36 | 'create',
37 | 'read',
38 | 'update',
39 | 'delete'
40 | ]
41 | },
42 |
43 | relation: {
44 | type: 'string',
45 | enum: [
46 | 'role',
47 | 'owner',
48 | 'user'
49 | ],
50 | defaultsTo: 'role',
51 | index: true
52 | },
53 |
54 | /**
55 | * The Role to which this Permission grants create, read, update, and/or
56 | * delete privileges.
57 | */
58 | role: {
59 | model: 'Role',
60 | // Validate manually
61 | //required: true
62 | },
63 |
64 | /**
65 | * The User to which this Permission grants create, read, update, and/or
66 | * delete privileges.
67 | */
68 | user: {
69 | model: 'User'
70 | // Validate manually
71 | },
72 |
73 | /**
74 | * A list of criteria. If any of the criteria match the request, the action is allowed.
75 | * If no criteria are specified, it is ignored altogether.
76 | */
77 | criteria: {
78 | collection: 'Criteria',
79 | via: 'permission'
80 | }
81 | },
82 |
83 | afterValidate: [
84 | function validateOwnerCreateTautology (permission, next) {
85 | if (permission.relation == 'owner' && permission.action == 'create') {
86 | next(new Error('Creating a Permission with relation=owner and action=create is tautological'));
87 | }
88 |
89 | if (permission.action === 'delete' &&
90 | _.filter(permission.criteria, function (criteria) { return !_.isEmpty(criteria.blacklist); }).length) {
91 | next(new Error('Creating a Permission with an attribute blacklist is not allowed when action=delete'));
92 | }
93 |
94 | if (permission.relation == 'user' && permission.user === "") {
95 | next(new Error('A Permission with relation user MUST have the user attribute set'));
96 | }
97 |
98 | if (permission.relation == 'role' && permission.role === "") {
99 | next(new Error('A Permission with relation role MUST have the role attribute set'));
100 | }
101 |
102 | next();
103 | }
104 | ]
105 | };
106 |
--------------------------------------------------------------------------------
/api/models/RequestLog.js:
--------------------------------------------------------------------------------
1 | /**
2 | * RequestLog.js
3 | *
4 | * @description :: TODO: You might write a short summary of how this model works and what it represents here.
5 | * @docs :: http://sailsjs.org/#!documentation/models
6 | */
7 |
8 | module.exports = {
9 | autoPK: false,
10 | autoCreatedBy: false,
11 | autoUpdatedAt: false,
12 |
13 | attributes: {
14 | id: {
15 | type: 'string',
16 | primaryKey: true
17 | },
18 | ipAddress: {
19 | type: 'string'
20 | },
21 | method: {
22 | type: 'string'
23 | },
24 | url: {
25 | type: 'string',
26 | url: true
27 | },
28 | body: {
29 | type: 'json'
30 | },
31 | user: {
32 | model: 'User'
33 | },
34 | model: {
35 | type: 'string'
36 | }
37 | }
38 | };
39 |
40 |
--------------------------------------------------------------------------------
/api/models/Role.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module Role
3 | *
4 | * @description
5 | * Roles endow Users with Permissions. Exposes Postgres-like API for
6 | * resolving granted Permissions for a User.
7 | *
8 | * @see
9 | */
10 | module.exports = {
11 | autoCreatedBy: false,
12 |
13 | description: 'Confers `Permission` to `User`',
14 |
15 | attributes: {
16 | name: {
17 | type: 'string',
18 | index: true,
19 | notNull: true,
20 | unique: true
21 | },
22 | users: {
23 | collection: 'User',
24 | via: 'roles'
25 | },
26 | active: {
27 | type: 'boolean',
28 | defaultsTo: true,
29 | index: true
30 | },
31 | permissions: {
32 | collection: 'Permission',
33 | via: 'role'
34 | }
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/api/models/SecurityLog.js:
--------------------------------------------------------------------------------
1 | /**
2 | * SecurityLog.js
3 | *
4 | * @description :: TODO: You might write a short summary of how this model works and what it represents here.
5 | * @docs :: http://sailsjs.org/#!documentation/models
6 | */
7 |
8 | module.exports = {
9 | autoPK: false,
10 | autoUpdatedAt: false,
11 | autoCreatedAt: false,
12 |
13 | attributes: {
14 | request: {
15 | model: 'RequestLog',
16 | primaryKey: true
17 | }
18 | }
19 | };
20 |
21 |
--------------------------------------------------------------------------------
/api/models/User.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash');
2 | var _super = require('sails-auth/api/models/User');
3 |
4 | _.merge(exports, _super);
5 | _.merge(exports, {
6 | attributes: {
7 | roles: {
8 | collection: 'Role',
9 | via: 'users',
10 | dominant: true
11 | },
12 | permissions: {
13 | collection: "Permission",
14 | via: "user"
15 | }
16 | },
17 |
18 | /**
19 | * Attach default Role to a new User
20 | */
21 | afterCreate: [
22 | function setOwner (user, next) {
23 | sails.log.verbose('User.afterCreate.setOwner', user);
24 | User
25 | .update({ id: user.id }, { owner: user.id })
26 | .then(function (user) {
27 | next();
28 | })
29 | .catch(function (e) {
30 | sails.log.error(e);
31 | next(e);
32 | });
33 | },
34 | function attachDefaultRole (user, next) {
35 | sails.log('User.afterCreate.attachDefaultRole', user);
36 | User.findOne(user.id)
37 | .populate('roles')
38 | .then(function (_user) {
39 | user = _user;
40 | return Role.findOne({ name: 'registered' });
41 | })
42 | .then(function (role) {
43 | user.roles.add(role.id);
44 | return user.save();
45 | })
46 | .then(function (updatedUser) {
47 | sails.log.silly('role "registered" attached to user', user.username);
48 | next();
49 | })
50 | .catch(function (e) {
51 | sails.log.error(e);
52 | next(e);
53 | })
54 | }
55 | ]
56 | });
57 |
--------------------------------------------------------------------------------
/api/policies/AuditPolicy.js:
--------------------------------------------------------------------------------
1 | var fnv = require('fnv-plus');
2 | var _ = require('lodash');
3 | var url = require('url');
4 |
5 | module.exports = function (req, res, next) {
6 | var ipAddress = req.headers['x-forwarded-for'] || (req.connection && req.connection.remoteAddress);
7 | req.requestId = fnv.hash(new Date().valueOf() + ipAddress, 128).str();
8 |
9 | sails.models.requestlog.create({
10 | id: req.requestId,
11 | ipAddress: ipAddress,
12 | url: sanitizeRequestUrl(req),
13 | method: req.method,
14 | body: _.omit(req.body, 'password'),
15 | model: req.options.modelIdentity,
16 | user: (req.user || {}).id
17 | }).exec(_.identity);
18 |
19 | // persist RequestLog entry in the background; continue immediately
20 | next();
21 | };
22 |
23 | function sanitizeRequestUrl (req) {
24 | var requestUrl = url.format({
25 | protocol: req.protocol,
26 | host: req.host || sails.getHost(),
27 | pathname: req.originalUrl || req.url,
28 | query: req.query
29 | });
30 |
31 | return requestUrl.replace(/(password=).*?(&|$)/ig, '$1$2');
32 | }
33 |
--------------------------------------------------------------------------------
/api/policies/CriteriaPolicy.js:
--------------------------------------------------------------------------------
1 | /**
2 | * CriteriaPolicy
3 | * @depends PermissionPolicy
4 | *
5 | * Verify that the User fulfills permission 'where' conditions and attribute blacklist restrictions
6 | */
7 | var wlFilter = require('waterline-criteria');
8 | import _ from 'lodash'
9 |
10 | module.exports = function(req, res, next) {
11 | var permissions = req.permissions;
12 |
13 | if (_.isEmpty(permissions)) {
14 | return next();
15 | }
16 |
17 | var action = PermissionService.getMethod(req.method);
18 |
19 | var body = req.body || req.query;
20 |
21 | // if we are creating, we don't need to query the db, just check the where clause vs the passed in data
22 | if (action === 'create') {
23 | if (!PermissionService.hasPassingCriteria(body, permissions, body)) {
24 | return res.send(403, {
25 | error: 'Can\'t create this object, because of failing where clause'
26 | });
27 | }
28 | return next();
29 | }
30 |
31 |
32 | // set up response filters if we are not mutating an existing object
33 | if (!_.contains(['update', 'delete'], action)) {
34 |
35 | // get all of the where clauses and blacklists into one flat array
36 | // if a permission has no criteria then it is always true
37 | var criteria = _.compact(_.flatten(
38 | _.map(
39 | _.pluck(permissions, 'criteria'),
40 | function(c) {
41 | if (c.length == 0) {
42 | return [{where: {}}];
43 | }
44 | return c;
45 | }
46 | )
47 | ));
48 |
49 | if (criteria.length) {
50 | bindResponsePolicy(req, res, criteria);
51 | }
52 | return next();
53 | }
54 |
55 | PermissionService.findTargetObjects(req)
56 | .then(function(objects) {
57 |
58 | // attributes are not important for a delete request
59 | if (action === 'delete') {
60 | body = undefined;
61 | }
62 |
63 | if (!PermissionService.hasPassingCriteria(objects, permissions, body, req.user.id)) {
64 | return res.send(403, {
65 | error: 'Can\'t ' + action + ', because of failing where clause or attribute permissions'
66 | });
67 | }
68 |
69 | next();
70 | })
71 | .catch(next);
72 | };
73 |
74 | function bindResponsePolicy(req, res, criteria) {
75 | res._ok = res.ok;
76 |
77 | res.ok = _.bind(responsePolicy, {
78 | req: req,
79 | res: res
80 | }, criteria);
81 | }
82 |
83 | function responsePolicy(criteria, _data, options) {
84 | var req = this.req;
85 | var res = this.res;
86 | var user = req.owner;
87 | var method = PermissionService.getMethod(req);
88 | var isResponseArray = _.isArray(_data);
89 |
90 | var data = isResponseArray ? _data : [_data];
91 |
92 | // remove undefined, since that is invalid input for waterline-criteria
93 | data = data.filter(item => { return item !== undefined })
94 |
95 | var permitted = data.reduce(function(memo, item) {
96 | criteria.some(function(crit) {
97 | var filtered = wlFilter([item], {
98 | where: {
99 | or: [crit.where || {}]
100 | }
101 | }).results;
102 |
103 | if (filtered.length) {
104 |
105 | if (crit.blacklist && crit.blacklist.length) {
106 | crit.blacklist.forEach(function (term) {
107 | delete item[term];
108 | });
109 | }
110 | memo.push(item);
111 | return true;
112 | }
113 | });
114 | return memo;
115 | }, []);
116 |
117 | if (isResponseArray) {
118 | return res._ok(permitted, options);
119 | } else if (permitted.length === 0) {
120 | sails.log.silly('permitted.length === 0');
121 | return res.send(404);
122 | } else {
123 | res._ok(permitted[0], options);
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/api/policies/ModelPolicy.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash');
2 |
3 | /**
4 | * Simplified version of sails/lib/hooks/blueprints/actionUtil
5 | * see: https://github.com/balderdashy/sails/blob/b4eed1775d01f436b263362180eb3f8447af1b87/lib/hooks/blueprints/actionUtil.js#L302
6 | */
7 | function parseModel (req) {
8 | return req.options.model || req.options.controller
9 | }
10 |
11 | /**
12 | * Query the Model that is being acted upon, and set it on the req object.
13 | */
14 | module.exports = function ModelPolicy (req, res, next) {
15 | var modelCache = sails.hooks.permissions._modelCache;
16 | req.options.modelIdentity = parseModel(req);
17 |
18 | if (_.isEmpty(req.options.modelIdentity)) {
19 | return next();
20 | }
21 |
22 | req.options.modelDefinition = sails.models[req.options.modelIdentity];
23 | req.model = modelCache[req.options.modelIdentity];
24 |
25 | if (_.isObject(req.model) && !_.isNull(req.model.id)) {
26 | return next();
27 | }
28 |
29 | sails.log.warn('Model [', req.options.modelIdentity, '] not found in model cache');
30 |
31 | // if the model is not found in the cache for some reason, get it from the database
32 | Model.findOne({ identity: req.options.modelIdentity })
33 | .then(function (model) {
34 | if (!_.isObject(model)) {
35 | req.options.unknownModel = true;
36 |
37 | model = sails.models[req.options.modelIdentity];
38 | }
39 |
40 | req.model = model;
41 | next();
42 | })
43 | .catch(next);
44 | };
45 |
--------------------------------------------------------------------------------
/api/policies/OwnerPolicy.js:
--------------------------------------------------------------------------------
1 | /**
2 | * TODO - this is setting createdBy, not owner.
3 | * The comment below, and the name of this file/function is confusing to me
4 | * Ensure that the 'owner' property of an Object is set upon creation.
5 | */
6 | module.exports = function OwnerPolicy (req, res, next) {
7 | //sails.log('OwnerPolicy()');
8 | if (!req.user || !req.user.id) {
9 | req.logout();
10 | return res.send(500, new Error('req.user is not set'));
11 | }
12 |
13 | /*
14 | sails.log.verbose('OwnerPolicy user', req.user);
15 | sails.log.verbose('OwnerPolicy method', req.method);
16 | sails.log.verbose('OwnerPolicy req.body', req.body);
17 | */
18 |
19 | if (req.options.modelDefinition.autoCreatedBy === false) {
20 | // sails.log.verbose('OwnerPolicy hasOwnershipPolicy: false');
21 | return next();
22 | }
23 |
24 | if ('POST' == req.method) {
25 | //req.body || (req.body = { });
26 | req.body.createdBy = req.user.id;
27 | req.body.owner = req.user.id;
28 | }
29 |
30 | //sails.log.verbose('OwnerPolicy req.model', req.model);
31 | next();
32 | };
33 |
--------------------------------------------------------------------------------
/api/policies/PermissionPolicy.js:
--------------------------------------------------------------------------------
1 | /**
2 | * PermissionPolicy
3 | * @depends OwnerPolicy
4 | * @depends ModelPolicy
5 | *
6 | * In order to proceed to the controller, the following verifications
7 | * must pass:
8 | * 1. User is logged in (handled previously by sails-auth sessionAuth policy)
9 | * 2. User has Permission to perform action on Model
10 | * 3. User has Permission to perform action on Attribute (if applicable) [TODO]
11 | * 4. User is satisfactorily related to the Object's owner (if applicable)
12 | *
13 | * This policy verifies #1-2 here, before any controller is invoked. However
14 | * it is not generally possible to determine ownership relationship until after
15 | * the object has been queried. Verification of #4 occurs in RolePolicy.
16 | *
17 | * @param {Object} req
18 | * @param {Object} res
19 | * @param {Function} next
20 | */
21 | module.exports = function (req, res, next) {
22 | var options = {
23 | model: req.model,
24 | method: req.method,
25 | user: req.user
26 | };
27 |
28 | if (req.options.unknownModel) {
29 | return next();
30 | }
31 |
32 | PermissionService
33 | .findModelPermissions(options)
34 | .then(function (permissions) {
35 | sails.log.silly('PermissionPolicy:', permissions.length, 'permissions grant',
36 | req.method, 'on', req.model.name, 'for', req.user.username);
37 |
38 | if (!permissions || permissions.length === 0) {
39 | return res.send(403, { error: PermissionService.getErrorMessage(options) });
40 | }
41 |
42 | req.permissions = permissions;
43 |
44 | next();
45 | });
46 | };
47 |
--------------------------------------------------------------------------------
/api/policies/RolePolicy.js:
--------------------------------------------------------------------------------
1 | /**
2 | * RolePolicy
3 | * @depends PermissionPolicy
4 | * @depends OwnerPolicy
5 | * @depends ModelPolicy
6 | *
7 | * Verify that User is satisfactorily related to the Object's owner.
8 | * By this point, we know we have some permissions related to the action and object
9 | * If they are 'owner' permissions, verify that the objects that are being accessed are owned by the current user
10 | */
11 | import _ from 'lodash'
12 |
13 | module.exports = function(req, res, next) {
14 | var permissions = req.permissions;
15 | var relations = _.groupBy(permissions, 'relation');
16 | var action = PermissionService.getMethod(req.method);
17 |
18 | // continue if there exist role Permissions which grant the asserted privilege
19 | if (!_.isEmpty(relations.role)) {
20 | return next();
21 | }
22 | if (req.options.unknownModel) {
23 | return next();
24 | }
25 |
26 | /*
27 | * This block allows us to filter reads by the owner attribute, rather than failing an entire request
28 | * if some of the results are not owned by the user.
29 | * We don't want to take this same course of action for an update or delete action, we would prefer to fail the entire request.
30 | * There is no notion of 'create' for an owner permission, so it is not relevant here.
31 | */
32 | if (!_.contains(['update', 'delete'], action) && req.options.modelDefinition.attributes.owner) {
33 | // Some parsing must happen on the query down the line,
34 | // as req.query has no impact on the results from PermissionService.findTargetObjects.
35 | // I had to look at the actionUtil parseCriteria method to see where to augment the criteria
36 | req.params.all().where = req.params.all().where || {};
37 | req.params.all().where.owner = req.user.id;
38 | req.query.owner = req.user.id;
39 | _.isObject(req.body) && (req.body.owner = req.user.id);
40 | }
41 |
42 | PermissionService.findTargetObjects(req)
43 | .then(function(objects) {
44 | // PermissionService.isAllowedToPerformAction checks if the user has 'user' based permissions (vs role or owner based permissions)
45 | return PermissionService.isAllowedToPerformAction(objects, req.user, action, ModelService.getTargetModelName(req), req.body)
46 | .then(function(hasUserPermissions) {
47 | if (hasUserPermissions) {
48 | return next();
49 | }
50 | if (PermissionService.hasForeignObjects(objects, req.user)) {
51 | return res.send(403, {
52 | error: 'Cannot perform action [' + action + '] on foreign object'
53 | });
54 | }
55 | next();
56 | });
57 |
58 | })
59 | .catch(next);
60 | };
61 |
--------------------------------------------------------------------------------
/api/services/ModelService.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash'
2 | var pluralize = require('pluralize');
3 |
4 | module.exports = {
5 | /**
6 | * Return the type of model acted upon by this request.
7 | */
8 | getTargetModelName: function (req) {
9 | // TODO there has to be a more sails-y way to do this without including
10 | // external modules
11 | if (_.isString(req.options.alias)) {
12 | sails.log.silly('singularizing', req.options.alias, 'to use as target model');
13 | return pluralize.singular(req.options.alias);
14 | } else if (_.isString(req.options.model)) {
15 | return req.options.model;
16 | } else {
17 | return req.model && req.model.identity;
18 | }
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/api/services/PermissionService.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash');
2 |
3 | var methodMap = {
4 | POST: 'create',
5 | GET: 'read',
6 | PUT: 'update',
7 | DELETE: 'delete'
8 | };
9 |
10 | var wlFilter = require('waterline-criteria');
11 |
12 | module.exports = {
13 |
14 | /**
15 | * Given an object, or a list of objects, return true if the list contains
16 | * objects not owned by the specified user.
17 | */
18 | hasForeignObjects: function(objects, user) {
19 | if (!_.isArray(objects)) {
20 | return PermissionService.isForeignObject(user.id)(objects);
21 | }
22 | return _.any(objects, PermissionService.isForeignObject(user.id));
23 | },
24 |
25 | /**
26 | * Return whether the specified object is NOT owned by the specified user.
27 | */
28 | isForeignObject: function(owner) {
29 | return function(object) {
30 | //sails.log.verbose('object', object);
31 | //sails.log.verbose('object.owner: ', object.owner, ', owner:', owner);
32 | return object.owner !== owner;
33 | };
34 | },
35 |
36 | /**
37 | * Find objects that some arbitrary action would be performed on, given the
38 | * same request.
39 | *
40 | * @param options.model
41 | * @param options.query
42 | *
43 | * TODO this will be less expensive when waterline supports a caching layer
44 | */
45 | findTargetObjects: function(req) {
46 |
47 |
48 | // handle add/remove routes that have :parentid as the primary key field
49 | var originalId;
50 | if (req.params.parentid) {
51 | originalId = req.params.id;
52 | req.params.id = req.params.parentid;
53 | }
54 |
55 | return new Promise(function(resolve, reject) {
56 | sails.hooks.blueprints.middleware.find(req, {
57 | ok: resolve,
58 | serverError: reject,
59 | // this isn't perfect, since it returns a 500 error instead of a 404 error
60 | // but it is better than crashing the app when a record doesn't exist
61 | notFound: reject
62 | });
63 | })
64 | .then(function(result) {
65 | if (originalId !== undefined) {
66 | req.params.id = originalId;
67 | }
68 | return result;
69 | });
70 | },
71 |
72 | /**
73 | * Query Permissions that grant privileges to a role/user on an action for a
74 | * model.
75 | *
76 | * @param options.method
77 | * @param options.model
78 | * @param options.user
79 | */
80 | findModelPermissions: function(options) {
81 | var action = PermissionService.getMethod(options.method);
82 |
83 | //console.log('findModelPermissions options', options)
84 | //console.log('findModelPermissions action', action)
85 |
86 | return User.findOne(options.user.id)
87 | .populate('roles')
88 | .then(function(user) {
89 | var permissionCriteria = {
90 | model: options.model.id,
91 | action: action,
92 | or: [
93 | { role: _.pluck(user.roles, 'id') },
94 | { user: user.id }
95 | ]
96 | };
97 |
98 | return Permission.find(permissionCriteria).populate('criteria')
99 | });
100 | },
101 |
102 | /**
103 | * Given a list of objects, determine if they all satisfy at least one permission's
104 | * where clause/attribute blacklist combination
105 | *
106 | * @param {Array of objects} objects - The result of the query, or if the action is create,
107 | * the body of the object to be created
108 | * @param {Array of Permission objects} permissions - An array of permission objects
109 | * that are relevant to this particular user query
110 | * @param {Object} attributes - The body of the request, in an update or create request.
111 | * The keys of this object are checked against the permissions blacklist
112 | * @returns boolean - True if there is at least one granted permission that allows the requested action,
113 | * otherwise false
114 | */
115 | hasPassingCriteria: function(objects, permissions, attributes, user) {
116 | // return success if there are no permissions or objects
117 | if (_.isEmpty(permissions) || _.isEmpty(objects)) return true;
118 |
119 | if (!_.isArray(objects)) {
120 | objects = [objects];
121 | }
122 |
123 | var criteria = permissions.reduce(function (memo, perm) {
124 | if (perm) {
125 | if (!perm.criteria || perm.criteria.length==0) {
126 | // If a permission has no criteria then it passes for all cases
127 | // (like the admin role)
128 | memo = memo.concat([{where:{}}]);
129 | }
130 | else {
131 | memo = memo.concat(perm.criteria);
132 | }
133 | if (perm.relation === 'owner') {
134 | perm.criteria.forEach(function (criteria) {
135 | criteria.owner = true;
136 | });
137 | }
138 | return memo;
139 | }
140 | }, []);
141 |
142 |
143 | if (!_.isArray(criteria)) {
144 | criteria = [criteria];
145 | }
146 |
147 | if (_.isEmpty(criteria)) {
148 | return true;
149 | }
150 |
151 | // every object must have at least one permission that has a passing criteria and a passing attribute check
152 | return objects.every(function(obj) {
153 | return criteria.some(function(criteria) {
154 | var match = wlFilter([obj], {
155 | where: criteria.where
156 | }).results;
157 | var hasUnpermittedAttributes = PermissionService.hasUnpermittedAttributes(attributes, criteria.blacklist);
158 | var hasOwnership = true; // edge case for scenario where a user has some permissions that are owner based and some that are role based
159 | if (criteria.owner) {
160 | hasOwnership = !PermissionService.isForeignObject(user)(obj);
161 | }
162 | return match.length === 1 && !hasUnpermittedAttributes && hasOwnership;
163 | });
164 | });
165 |
166 | },
167 |
168 | hasUnpermittedAttributes: function(attributes, blacklist) {
169 | if (_.isEmpty(attributes) || _.isEmpty(blacklist)) {
170 | return false;
171 | }
172 | return _.intersection(Object.keys(attributes), blacklist).length ? true : false;
173 | },
174 |
175 | /**
176 | * Return true if the specified model supports the ownership policy; false
177 | * otherwise.
178 | */
179 | hasOwnershipPolicy: function(model) {
180 | return model.autoCreatedBy;
181 | },
182 |
183 | /**
184 | * Build an error message
185 | */
186 | getErrorMessage: function(options) {
187 | var user = options.user.email || options.user.username
188 | return [
189 | 'User', user, 'is not permitted to', options.method, options.model.name
190 | ].join(' ');
191 | },
192 |
193 | /**
194 | * Given an action, return the CRUD method it maps to.
195 | */
196 | getMethod: function(method) {
197 | return methodMap[method];
198 | },
199 |
200 | /**
201 | * create a new role
202 | * @param options
203 | * @param options.name {string} - role name
204 | * @param options.permissions {permission object, or array of permissions objects}
205 | * @param options.permissions.model {string} - the name of the model that the permission is associated with
206 | * @param options.permissions.criteria - optional criteria object
207 | * @param options.permissions.criteria.where - optional waterline query syntax object for specifying permissions
208 | * @param options.permissions.criteria.blacklist {string array} - optional attribute blacklist
209 | * @param options.users {array of user names} - optional array of user ids that have this role
210 | */
211 | createRole: function(options) {
212 |
213 | var ok = Promise.resolve();
214 | var permissions = options.permissions;
215 |
216 | if (!_.isArray(permissions)) {
217 | permissions = [permissions];
218 | }
219 |
220 |
221 | // look up the model id based on the model name for each permission, and change it to an id
222 | ok = ok.then(function() {
223 | return Promise.all(permissions.map(function(permission) {
224 | return Model.findOne({
225 | name: permission.model
226 | })
227 | .then(function(model) {
228 | permission.model = model.id;
229 | return permission;
230 | });
231 | }));
232 | });
233 |
234 | // look up user ids based on usernames, and replace the names with ids
235 | ok = ok.then(function(permissions) {
236 | if (options.users) {
237 | return User.find({
238 | username: options.users
239 | })
240 | .then(function(users) {
241 | options.users = users;
242 | });
243 | }
244 | });
245 |
246 | ok = ok.then(function(users) {
247 | return Role.create(options);
248 | });
249 |
250 | return ok;
251 | },
252 |
253 | /**
254 | *
255 | * @param options {permission object, or array of permissions objects}
256 | * @param options.role {string} - the role name that the permission is associated with,
257 | * either this or user should be supplied, but not both
258 | * @param options.user {string} - the user than that the permission is associated with,
259 | * either this or role should be supplied, but not both
260 | * @param options.model {string} - the model name that the permission is associated with
261 | * @param options.action {string} - the http action that the permission allows
262 | * @param options.criteria - optional criteria object
263 | * @param options.criteria.where - optional waterline query syntax object for specifying permissions
264 | * @param options.criteria.blacklist {string array} - optional attribute blacklist
265 | */
266 | grant: function(permissions) {
267 | if (!_.isArray(permissions)) {
268 | permissions = [permissions];
269 | }
270 |
271 | // look up the models based on name, and replace them with ids
272 | var ok = Promise.all(permissions.map(function(permission) {
273 | var findRole = permission.role ? Role.findOne({
274 | name: permission.role
275 | }) : null;
276 | var findUser = permission.user ? User.findOne({
277 | username: permission.user
278 | }) : null;
279 | return Promise.all([findRole, findUser, Model.findOne({
280 | name: permission.model
281 | })])
282 | .then(([ role, user, model]) => {
283 | permission.model = model.id;
284 | if (role && role.id) {
285 | permission.role = role.id;
286 | } else if (user && user.id) {
287 | permission.user = user.id;
288 | } else {
289 | return Promise.reject(new Error('no role or user specified'));
290 | }
291 | });
292 | }));
293 |
294 | ok = ok.then(function() {
295 | return Permission.create(permissions);
296 | });
297 |
298 | return ok;
299 | },
300 |
301 | /**
302 | * add one or more users to a particular role
303 | * TODO should this work with multiple roles?
304 | * @param usernames {string or string array} - list of names of users
305 | * @param rolename {string} - the name of the role that the users should be added to
306 | */
307 | addUsersToRole: function(usernames, rolename) {
308 | if (_.isEmpty(usernames)) {
309 | return Promise.reject(new Error('One or more usernames must be provided'));
310 | }
311 |
312 | if (!_.isArray(usernames)) {
313 | usernames = [usernames];
314 | }
315 |
316 | return Role.findOne({
317 | name: rolename
318 | }).populate('users').then(function(role) {
319 | return User.find({
320 | username: usernames
321 | }).then(function(users) {
322 | role.users.add(_.pluck(users, 'id'));
323 | return role.save();
324 | });
325 | });
326 | },
327 |
328 | /**
329 | * remove one or more users from a particular role
330 | * TODO should this work with multiple roles
331 | * @params usernames {string or string array} - name or list of names of users
332 | * @params rolename {string} - the name of the role that the users should be removed from
333 | */
334 | removeUsersFromRole: function(usernames, rolename) {
335 | if (_.isEmpty(usernames)) {
336 | return Promise.reject(new Error('One or more usernames must be provided'));
337 | }
338 |
339 | if (!_.isArray(usernames)) {
340 | usernames = [usernames];
341 | }
342 |
343 | return Role.findOne({
344 | name: rolename
345 | })
346 | .populate('users')
347 | .then(function(role) {
348 | return User.find({
349 | username: usernames
350 | }, {
351 | select: ['id']
352 | }).then(function(users) {
353 | users.map(function(user) {
354 | role.users.remove(user.id);
355 | });
356 | return role.save();
357 | });
358 | });
359 | },
360 |
361 | /**
362 | * revoke permission from role
363 | * @param options
364 | * @param options.role {string} - the name of the role related to the permission. This, or options.user should be set, but not both.
365 | * @param options.user {string} - the name of the user related to the permission. This, or options.role should be set, but not both.
366 | * @param options.model {string} - the name of the model for the permission
367 | * @param options.action {string} - the name of the action for the permission
368 | * @param options.relation {string} - the type of the relation (owner or role)
369 | */
370 | revoke: function(options) {
371 | var findRole = options.role ? Role.findOne({
372 | name: options.role
373 | }) : null;
374 | var findUser = options.user ? User.findOne({
375 | username: options.user
376 | }) : null;
377 | var ok = Promise.all([findRole, findUser, Model.findOne({
378 | name: options.model
379 | })]);
380 |
381 | ok = ok.then(([ role, user, model ]) => {
382 |
383 | var query = {
384 | model: model.id,
385 | action: options.action,
386 | relation: options.relation
387 | };
388 |
389 | if (role && role.id) {
390 | query.role = role.id;
391 | } else if (user && user.id) {
392 | query.user = user.id;
393 | } else {
394 | return Promise.reject(new Error('You must provide either a user or role to revoke the permission from'));
395 | }
396 |
397 | return Permission.destroy(query);
398 | });
399 |
400 | return ok;
401 | },
402 |
403 | /**
404 | * Check if the user (out of role) is granted to perform action on given objects
405 | * @param objects
406 | * @param user
407 | * @param action
408 | * @param model
409 | * @param body
410 | * @returns {*}
411 | */
412 | isAllowedToPerformAction: function(objects, user, action, model, body) {
413 | if (!_.isArray(objects)) {
414 | return PermissionService.isAllowedToPerformSingle(user.id, action, model, body)(objects);
415 | }
416 | return Promise.all(objects.map(PermissionService.isAllowedToPerformSingle(user.id, action, model, body)))
417 | .then(function (allowedArray) {
418 | return allowedArray.every(function (allowed) {
419 | return allowed === true;
420 | });
421 | });
422 | },
423 |
424 | /**
425 | * Resolve if the user have the permission to perform this action
426 | * @param user
427 | * @param action
428 | * @param model
429 | * @param body
430 | * @returns {Function}
431 | */
432 | isAllowedToPerformSingle: function(user, action, model, body) {
433 | return function(obj) {
434 | return new Promise(function(resolve, reject) {
435 | Model.findOne({
436 | identity: model
437 | }).then(function(model) {
438 | return Permission.find({
439 | model: model.id,
440 | action: action,
441 | relation: 'user',
442 | user: user
443 | }).populate('criteria');
444 | }).then(function(permission) {
445 | if (permission.length > 0 && PermissionService.hasPassingCriteria(obj, permission, body)) {
446 | resolve(true);
447 | } else {
448 | resolve(false);
449 | }
450 | }).catch(reject);
451 | });
452 | };
453 | }
454 | };
455 |
--------------------------------------------------------------------------------
/config/env/testing.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash');
2 | var path = require('path');
3 |
4 | /**
5 | * Testing environment settings
6 | *
7 | * This file can include shared settings for a development team,
8 | * such as API keys or remote database passwords. If you're using
9 | * a version control solution for your Sails app, this file will
10 | * be committed to your repository unless you add it to your .gitignore
11 | * file. If your repository will be publicly viewable, don't add
12 | * any private information to this file!
13 | *
14 | */
15 | module.exports = {
16 | log: { level: 'debug' },
17 | models: {
18 | migrate: 'drop',
19 | connection: 'testing'
20 | },
21 | connections: {
22 | testing: {
23 | adapter: 'waterline-postgresql'
24 | }
25 | },
26 | hooks: { grunt: false },
27 | port: 1336,
28 | routes: {
29 | "DELETE /role/:parentid/users/:id": {
30 | controller: 'RoleController',
31 | action: 'remove',
32 | alias: 'users'
33 | }
34 | }
35 | };
36 |
--------------------------------------------------------------------------------
/config/fixtures/model.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Creates database representations of the Model types.
3 | *
4 | * @public
5 | */
6 | import _ from 'lodash'
7 | exports.createModels = function () {
8 | sails.log.verbose('sails-permissions: syncing waterline models');
9 |
10 | var models = _.compact(_.map(sails.models, function (model, name) {
11 | return model && model.globalId && model.identity && {
12 | name: model.globalId,
13 | identity: model.identity,
14 | attributes: _.omit(model.attributes, _.functions(model.attributes))
15 | };
16 | }));
17 |
18 | return Promise.all(_.map(models, function (model) {
19 | return sails.models.model.findOrCreate({ name: model.name }, model);
20 | }));
21 | };
22 |
--------------------------------------------------------------------------------
/config/fixtures/permission.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash');
2 |
3 | var grants = {
4 | admin: [
5 | { action: 'create' },
6 | { action: 'read' },
7 | { action: 'update' },
8 | { action: 'delete' }
9 | ],
10 | registered: [
11 | { action: 'create' },
12 | { action: 'read' }
13 | ],
14 | public: [
15 | { action: 'read' }
16 | ]
17 | };
18 |
19 | var modelRestrictions = {
20 | registered: [
21 | 'Role',
22 | 'Permission',
23 | 'User',
24 | 'Passport'
25 | ],
26 | public: [
27 | 'Role',
28 | 'Permission',
29 | 'User',
30 | 'Model',
31 | 'Passport'
32 | ]
33 | };
34 |
35 | // TODO let users override this in the actual model definition
36 |
37 | /**
38 | * Create default Role permissions
39 | */
40 | exports.create = function (roles, models, admin, config) {
41 | return Promise.all([
42 | grantAdminPermissions(roles, models, admin, config),
43 | grantRegisteredPermissions(roles, models, admin, config)
44 | ])
45 | .then(function (permissions) {
46 | //sails.log.verbose('created', permissions.length, 'permissions');
47 | return permissions;
48 | });
49 | };
50 |
51 | function grantAdminPermissions (roles, models, admin, config) {
52 | var adminRole = _.find(roles, { name: 'admin' });
53 | var permissions = _.flatten(_.map(models, function (modelEntity) {
54 | //var model = sails.models[modelEntity.identity];
55 | grants.admin = _.get(config, 'grants.admin') || grants.admin;
56 |
57 | return _.map(grants.admin, function (permission) {
58 | var newPermission = {
59 | model: modelEntity.id,
60 | action: permission.action,
61 | role: adminRole.id,
62 | };
63 | return sails.models.permission.findOrCreate(newPermission, newPermission);
64 | });
65 | }));
66 |
67 | return Promise.all(permissions);
68 | }
69 |
70 | function grantRegisteredPermissions (roles, models, admin, config) {
71 | var registeredRole = _.find(roles, { name: 'registered' });
72 | var basePermissions = [
73 | {
74 | model: _.find(models, { name: 'Permission' }).id,
75 | action: 'read',
76 | role: registeredRole.id
77 | },
78 | {
79 | model: _.find(models, { name: 'Model' }).id,
80 | action: 'read',
81 | role: registeredRole.id
82 | },
83 | {
84 | model: _.find(models, { name: 'User' }).id,
85 | action: 'update',
86 | role: registeredRole.id,
87 | relation: 'owner'
88 | },
89 | {
90 | model: _.find(models, { name: 'User' }).id,
91 | action: 'read',
92 | role: registeredRole.id,
93 | relation: 'owner'
94 | }
95 | ];
96 |
97 | // XXX copy/paste from above. terrible. improve.
98 | var permittedModels = _.filter(models, function (model) {
99 | return !_.contains(modelRestrictions.registered, model.name);
100 | });
101 | var grantPermissions = _.flatten(_.map(permittedModels, function (modelEntity) {
102 |
103 | grants.registered = _.get(config, 'grants.registered') || grants.registered;
104 |
105 | return _.map(grants.registered, function (permission) {
106 | return {
107 | model: modelEntity.id,
108 | action: permission.action,
109 | role: registeredRole.id,
110 | };
111 | });
112 | }));
113 |
114 |
115 | return Promise.all(
116 | [ ...basePermissions, ...grantPermissions ].map(permission => {
117 | return sails.models.permission.findOrCreate(permission, permission);
118 | })
119 | );
120 | }
121 |
--------------------------------------------------------------------------------
/config/fixtures/role.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Creates default Roles
3 | *
4 | * @public
5 | */
6 | exports.create = function () {
7 | return Promise.all([
8 | sails.models.role.findOrCreate({ name: 'admin' }, { name: 'admin' }),
9 | sails.models.role.findOrCreate({ name: 'registered' }, { name: 'registered' }),
10 | sails.models.role.findOrCreate({ name: 'public' }, { name: 'public' })
11 | ]);
12 | };
13 |
--------------------------------------------------------------------------------
/config/fixtures/user.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Create admin user.
3 | * @param adminRole - the admin role which grants all permissions
4 | */
5 | import _ from 'lodash'
6 | exports.create = function (roles, userModel) {
7 | if (_.isEmpty(sails.config.permissions.adminUsername)) {
8 | throw new Error('sails.config.permissions.adminUsername is not set');
9 | }
10 | if (_.isEmpty(sails.config.permissions.adminPassword)) {
11 | throw new Error('sails.config.permissions.adminPassword is not set');
12 | }
13 | if (_.isEmpty(sails.config.permissions.adminEmail)) {
14 | throw new Error('sails.config.permissions.adminEmail is not set');
15 | }
16 | return sails.models.user.findOne({ username: sails.config.permissions.adminUsername })
17 | .then(function (user) {
18 | if (user) return user;
19 |
20 | sails.log.info('sails-permissions: admin user does not exist; creating...');
21 | return sails.models.user.register({
22 | username: sails.config.permissions.adminUsername,
23 | password: sails.config.permissions.adminPassword,
24 | email: sails.config.permissions.adminEmail,
25 | roles: [ _.find(roles, { name: 'admin' }).id ],
26 | createdBy: 1,
27 | owner: 1,
28 | model: userModel.id
29 | });
30 | });
31 | };
32 |
--------------------------------------------------------------------------------
/config/permissions.js:
--------------------------------------------------------------------------------
1 | module.exports.permissions = {
2 | name: 'permissions',
3 |
4 | adminEmail: process.env.ADMIN_EMAIL || 'admin@example.com',
5 | adminUsername: process.env.ADMIN_USERNAME || 'admin',
6 | adminPassword: process.env.ADMIN_PASSWORD || 'admin1234',
7 |
8 | afterEvents: [
9 | 'hook:auth:initialized'
10 | ]
11 | };
12 |
--------------------------------------------------------------------------------
/config/policies.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Policy Mappings
3 | * (sails.config.policies)
4 | *
5 | * Policies are simple functions which run **before** your controllers.
6 | * You can apply one or more policies to a given controller, or protect
7 | * its actions individually.
8 | *
9 | * Any policy file (e.g. `api/policies/authenticated.js`) can be accessed
10 | * below by its filename, minus the extension, (e.g. "authenticated")
11 | *
12 | * For more information on how policies work, see:
13 | * http://sailsjs.org/#/documentation/concepts/Policies
14 | *
15 | * For more information on configuring policies, check out:
16 | * http://sailsjs.org/#/documentation/reference/sails.config/sails.config.policies.html
17 | */
18 |
19 | module.exports.policies = {
20 |
21 | '*': [
22 | 'basicAuth',
23 | 'passport',
24 | 'sessionAuth',
25 | 'ModelPolicy',
26 | 'AuditPolicy',
27 | 'OwnerPolicy',
28 | 'PermissionPolicy',
29 | 'RolePolicy',
30 | 'CriteriaPolicy'
31 | ],
32 |
33 | AuthController: {
34 | '*': [ 'passport' ]
35 | },
36 |
37 | /***************************************************************************
38 | * *
39 | * Default policy for all controllers and actions (`true` allows public *
40 | * access) *
41 | * *
42 | ***************************************************************************/
43 |
44 | // '*': true,
45 |
46 | /***************************************************************************
47 | * *
48 | * Here's an example of mapping some policies to run before a controller *
49 | * and its actions *
50 | * *
51 | ***************************************************************************/
52 | // RabbitController: {
53 |
54 | // Apply the `false` policy as the default for all of RabbitController's actions
55 | // (`false` prevents all access, which ensures that nothing bad happens to our rabbits)
56 | // '*': false,
57 |
58 | // For the action `nurture`, apply the 'isRabbitMother' policy
59 | // (this overrides `false` above)
60 | // nurture : 'isRabbitMother',
61 |
62 | // Apply the `isNiceToAnimals` AND `hasRabbitFood` policies
63 | // before letting any users feed our rabbits
64 | // feed : ['isNiceToAnimals', 'hasRabbitFood']
65 | // }
66 | };
67 |
--------------------------------------------------------------------------------
/config/session.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Session Configuration
3 | * (sails.config.session)
4 | *
5 | * Sails session integration leans heavily on the great work already done by
6 | * Express, but also unifies Socket.io with the Connect session store. It uses
7 | * Connect's cookie parser to normalize configuration differences between Express
8 | * and Socket.io and hooks into Sails' middleware interpreter to allow you to access
9 | * and auto-save to `req.session` with Socket.io the same way you would with Express.
10 | *
11 | * For more information on configuring the session, check out:
12 | * http://links.sailsjs.org/docs/config/session
13 | */
14 |
15 | module.exports.session = {
16 | secret: '00000000000000000000000000000000'
17 | };
18 |
--------------------------------------------------------------------------------
/generator.js:
--------------------------------------------------------------------------------
1 | module.exports = require('sails-generate-entities')({
2 | module: 'sails-permissions',
3 | id: 'permissions-api',
4 | statics: [
5 | 'config/permissions.js'
6 | ],
7 | });
8 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require("gulp");
2 | var babel = require("gulp-babel");
3 |
4 | gulp.task("default", function () {
5 | gulp.src("api/**")
6 | .pipe(babel())
7 | .pipe(gulp.dest("dist/api"));
8 |
9 | gulp.src("config/**")
10 | .pipe(babel())
11 | .pipe(gulp.dest("dist/config"));
12 | });
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sails-permissions",
3 | "version": "2.2.0",
4 | "description": "Comprehensive user permissions and entitlements system for sails.js and Waterline. Supports user authentication with passport.js, role-based permissioning, object ownership, and row-level security.",
5 | "main": "dist/api/hooks/permissions/index.js",
6 | "scripts": {
7 | "test": "gulp && mocha --reporter spec --compilers js:babel/register",
8 | "prepublish": "gulp"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "https://github.com/tjwebb/sails-permissions.git"
13 | },
14 | "keywords": [
15 | "sails",
16 | "sails.js",
17 | "permissions",
18 | "privileges",
19 | "entitlements",
20 | "access",
21 | "restriction",
22 | "passport",
23 | "grant",
24 | "roles",
25 | "security",
26 | "rbac",
27 | "acl",
28 | "enterprise",
29 | "audit",
30 | "trail",
31 | "tracking"
32 | ],
33 | "author": "Travis Webb ",
34 | "license": "MIT",
35 | "bugs": {
36 | "url": "https://github.com/tjwebb/sails-permissions/issues"
37 | },
38 | "homepage": "https://github.com/tjwebb/sails-permissions",
39 | "devDependencies": {
40 | "babel": "^5.8.21",
41 | "gulp": "^3.9.0",
42 | "gulp-babel": "^5.2.1",
43 | "jshint": "^2.8.0",
44 | "mocha": "^2.x.x",
45 | "request": "^2.58.0",
46 | "sails": "balderdashy/sails",
47 | "sails-memory": "^0.10.5",
48 | "supertest": "^0.15.0",
49 | "waterline-postgresql": "^0.12.0"
50 | },
51 | "dependencies": {
52 | "fnv-plus": "^1.2.10",
53 | "lodash": "^3.10.0",
54 | "marlinspike": "^1.0",
55 | "pluralize": "^1.0.1",
56 | "sails-auth": "^2.0",
57 | "sails-generate-entities": "latest",
58 | "waterline-criteria": "^0.11.1"
59 | },
60 | "bundledDependencies": [
61 | "fnv-plus",
62 | "lodash",
63 | "pluralize"
64 | ],
65 | "engines": {
66 | "node": ">= 0.10",
67 | "npm": ">= 2.3"
68 | },
69 | "sails": {
70 | "isHook": true,
71 | "hookName": "permissions"
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/test/bootstrap.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test starter - with this version of sails.js we can only start one sails server,
3 | * to solve this problem we use only one before All and after All to start and
4 | * stop the server
5 | */
6 | var _ = require('lodash');
7 | var Sails = require('sails');
8 | var testingConfig = require('../config/env/testing');
9 | var path = require('path');
10 | var sails;
11 |
12 | before(function(done) {
13 | this.timeout(30000);
14 |
15 | var config = _.extend(testingConfig, {
16 | appPath: path.resolve(__dirname, '..')
17 | })
18 |
19 | Sails.lift(config, function(err, server) {
20 | global.sails = server;
21 |
22 | if (err) {
23 | return done(err);
24 | }
25 | // here you can load fixtures, etc.
26 | done(err, sails);
27 | });
28 |
29 | });
30 |
31 | after(function(done) {
32 | global.sails.lower(done);
33 | });
34 |
--------------------------------------------------------------------------------
/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --recursive
2 | --colors
3 | --timeout 30000
4 |
--------------------------------------------------------------------------------
/test/unit/controllers/ModelController.test.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var request = require('supertest');
3 |
4 | var adminAuth = {
5 | Authorization: 'Basic YWRtaW5AZXhhbXBsZS5jb206YWRtaW4xMjM0'
6 | };
7 |
8 | var registeredAuth = {
9 | Authorization: 'Basic bmV3dXNlcjp1c2VyMTIzNA=='
10 | };
11 |
12 | describe('Model Controller', function () {
13 |
14 | describe('User with Admin Role', function () {
15 |
16 | describe('#find()', function () {
17 |
18 | it('should be able to read models', function (done) {
19 |
20 | request(sails.hooks.http.app)
21 | .get('/model')
22 | .set('Authorization', adminAuth.Authorization)
23 | .expect(200)
24 | .end(function (err, res) {
25 |
26 | var models = res.body;
27 |
28 | assert.equal(models.length, 8);
29 | assert.equal(_.intersection(_.pluck(models, 'name'), [
30 | 'Model',
31 | 'Permission',
32 | 'Role',
33 | 'User'
34 | ]).length, 4);
35 |
36 | done(err || models.error);
37 |
38 | });
39 | });
40 |
41 | });
42 |
43 | });
44 |
45 | describe('User with Public Role', function () {
46 |
47 | describe('#find()', function () {
48 |
49 | it('should not be able to read models', function (done) {
50 |
51 | request(sails.hooks.http.app)
52 | .get('/model')
53 | .expect(403)
54 | .end(function (err, res) {
55 |
56 | assert(_.isString(res.body.error));
57 | done(err);
58 |
59 | });
60 | });
61 |
62 | });
63 |
64 | });
65 |
66 | });
67 |
68 |
69 |
--------------------------------------------------------------------------------
/test/unit/controllers/PermissionController.test.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var request = require('supertest');
3 |
4 | var adminAuth = {
5 | Authorization: 'Basic YWRtaW5AZXhhbXBsZS5jb206YWRtaW4xMjM0'
6 | };
7 |
8 | describe('PermissionController', function () {
9 |
10 | var agent;
11 | before(function(done) {
12 |
13 | agent = request.agent(sails.hooks.http.app);
14 |
15 | agent
16 | .post('/user')
17 | .set('Authorization', adminAuth.Authorization)
18 | .send({
19 | username: 'newuser1',
20 | email: 'newuser1@example.com',
21 | password: 'lalalal1234'
22 | })
23 | .expect(200, function (err) {
24 |
25 | if (err)
26 | return done(err);
27 |
28 | agent
29 | .post("/permission")
30 | .set('Authorization', adminAuth.Authorization)
31 | .send({
32 | model: 4,
33 | criteria: {
34 | where: {
35 | id: 1
36 | }
37 | },
38 | action: "delete",
39 | relation: "user",
40 | user: 2
41 | })
42 | .expect(201, function (err) {
43 | if (err)
44 | return done(err);
45 |
46 | agent
47 | .post('/auth/local')
48 | .send({
49 | identifier: 'newuser1',
50 | password: 'lalalal1234'
51 | })
52 | .expect(200)
53 | .end(function (err, res) {
54 | agent.saveCookies(res);
55 | return done(err);
56 | });
57 | });
58 |
59 | });
60 |
61 | });
62 |
63 | describe('Permission Controller', function () {
64 |
65 | describe('User with Registered Role', function () {
66 |
67 | describe('#find()', function () {
68 |
69 | it('should be able to read permissions', function (done) {
70 |
71 | agent
72 | .get('/permission')
73 | .expect(200)
74 | .end(function (err, res) {
75 |
76 | var permissions = res.body;
77 |
78 | assert.ifError(permissions.error);
79 | done(err || permissions.error);
80 |
81 | });
82 |
83 | });
84 |
85 | });
86 |
87 | });
88 |
89 | describe('User with Registered Role and granted to delete Permission 1', function () {
90 | describe("#delete()", function () {
91 | it('should be able to delete permission 1', function (done) {
92 | agent
93 | .delete("/permission/1")
94 | .expect(200)
95 | .end(function (err, res) {
96 | var permissions = res.body;
97 |
98 | assert.ifError(permissions.error);
99 | done(err || permissions.error);
100 | });
101 | });
102 | });
103 | });
104 |
105 | });
106 |
107 |
108 | });
109 |
--------------------------------------------------------------------------------
/test/unit/controllers/UserController.test.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var request = require('supertest');
3 | var _ = require('lodash');
4 |
5 | var adminAuth = {
6 | Authorization: 'Basic YWRtaW5AZXhhbXBsZS5jb206YWRtaW4xMjM0'
7 | };
8 | var registeredAuth = {
9 | Authorization: 'Basic bmV3dXNlcjp1c2VyMTIzNA=='
10 | };
11 | var newUserAuth = {
12 | Authorization: 'Basic bmV3dXNlcjp1c2VyMTIzNA=='
13 | };
14 |
15 | describe('User Controller', function() {
16 |
17 | var adminUserId;
18 | var newUserId;
19 | var roleId;
20 | var inactiveRoleId;
21 |
22 | describe('User with Admin Role', function() {
23 |
24 | describe('#find()', function() {
25 |
26 | it('should be able to read all users', function(done) {
27 |
28 | request(sails.hooks.http.app)
29 | .get('/user')
30 | .set('Authorization', adminAuth.Authorization)
31 | .expect(200)
32 | .end(function(err, res) {
33 |
34 | var users = res.body;
35 |
36 | assert.ifError(err);
37 | assert.ifError(users.error);
38 | assert.equal(users[0].username, 'admin');
39 | adminUserId = users[0].id;
40 |
41 | done(err);
42 |
43 | });
44 | });
45 |
46 | it('should be able to remove a user from a role', function(done) {
47 | var ok = Role.find({
48 | name: 'registered'
49 | });
50 |
51 | ok.then(function(role) {
52 | request(sails.hooks.http.app)
53 | .post('/user')
54 | .set('Authorization', adminAuth.Authorization)
55 | .send({
56 | username: 'abouttoberemoveduser',
57 | email: 'abouttoberemoveduser@example.com',
58 | password: 'user1234'
59 | })
60 | .expect(200)
61 | .end(function(err, res) {
62 |
63 | assert.ifError(err);
64 | var user = res.body;
65 | Role.findOne({
66 | name: 'registered'
67 | }).populate('users', {
68 | id: user.id
69 | })
70 | .then(function(role) {
71 | assert.equal(user.username, 'abouttoberemoveduser');
72 | assert(_.contains(_.pluck(role.users, 'id'), user.id));
73 |
74 | request(sails.hooks.http.app)
75 | .delete('/role/' + role.id + '/users/' + user.id)
76 | .set('Authorization', adminAuth.Authorization)
77 | .expect(200)
78 | .end(function(err, res) {
79 | // the user id should not be in the list anymore
80 | assert(!_.includes(_.pluck(res.users, 'id'), user.id));
81 | done();
82 | });
83 | });
84 |
85 | });
86 | })
87 | .catch(done);
88 |
89 | });
90 |
91 | });
92 |
93 | describe('#create()', function() {
94 |
95 | it('should be able to create a new user', function(done) {
96 |
97 | request(sails.hooks.http.app)
98 | .post('/user')
99 | .set('Authorization', adminAuth.Authorization)
100 | .send({
101 | username: 'newuser',
102 | email: 'newuser@example.com',
103 | password: 'user1234'
104 | })
105 | .expect(200)
106 | .end(function(err, res) {
107 |
108 | var user = res.body;
109 |
110 | assert.ifError(err);
111 | assert.ifError(user.error);
112 | assert.equal(user.username, 'newuser');
113 | newUserId = user.id;
114 |
115 | done(err);
116 |
117 | });
118 |
119 | });
120 |
121 | it('should return an error if a user already exists', function(done) {
122 |
123 | request(sails.hooks.http.app)
124 | .post('/user')
125 | .set('Authorization', adminAuth.Authorization)
126 | .send({
127 | username: 'newuser',
128 | email: 'newuser@example.com',
129 | password: 'user1234'
130 | })
131 | .expect(400)
132 | .end(function(err) {
133 | done(err);
134 | });
135 |
136 | });
137 |
138 | it('should be able to create a new role, and assign a new user to it', function(done) {
139 |
140 | request(sails.hooks.http.app)
141 | .post('/role')
142 | .set('Authorization', adminAuth.Authorization)
143 | .send({
144 | name: 'testrole',
145 | users: [newUserId]
146 | })
147 | .expect(201)
148 | .end(function(err, res) {
149 | roleId = res.body.id; // 4
150 | done(err);
151 | });
152 | });
153 |
154 | it('should be able to create a new permission for updating active roles', function(done) {
155 | request(sails.hooks.http.app)
156 | .get('/model?name=role')
157 | .set('Authorization', adminAuth.Authorization)
158 | .expect(200)
159 | .end(function(err, res) {
160 |
161 | // haha roleModel
162 | var roleModel = res.body[0];
163 |
164 | request(sails.hooks.http.app)
165 | .post('/permission')
166 | .set('Authorization', adminAuth.Authorization)
167 | .send({
168 | model: roleModel.id,
169 | action: 'update',
170 | role: roleId,
171 | createdBy: adminUserId,
172 | criteria: {
173 | blacklist: ['id', 'stream'],
174 | where: {
175 | active: true
176 | }
177 | }
178 | })
179 | .expect(201)
180 | .end(function(err, res) {
181 | done(err);
182 | });
183 |
184 | });
185 | });
186 |
187 | it('should be able to create a new permission for updating owned roles', function(done) {
188 | request(sails.hooks.http.app)
189 | .get('/model?name=role')
190 | .set('Authorization', adminAuth.Authorization)
191 | .expect(200)
192 | .end(function(err, res) {
193 |
194 | // haha roleModel
195 | var roleModel = res.body[0];
196 |
197 | request(sails.hooks.http.app)
198 | .post('/permission')
199 | .set('Authorization', adminAuth.Authorization)
200 | .send({
201 | model: roleModel.id,
202 | action: 'update',
203 | role: roleId,
204 | createdBy: adminUserId,
205 | criteria: {
206 | blacklist: ['id']
207 | },
208 | relation: 'owner'
209 | })
210 | .expect(201)
211 | .end(function(err, res) {
212 | done(err);
213 | });
214 |
215 | });
216 | });
217 |
218 | it('should be able to create a new role and set it as inactive', function(done) {
219 | request(sails.hooks.http.app)
220 | .post('/role')
221 | .set('Authorization', adminAuth.Authorization)
222 | .send({
223 | name: 'inactiveRole',
224 | users: [newUserId],
225 | active: false
226 | })
227 | .expect(201)
228 | .end(function(err, res) {
229 | inactiveRoleId = res.body.id;
230 | done(err);
231 | });
232 | });
233 |
234 | it('should be able to create a read permission with a where clause for roles and a blacklist', function(done) {
235 |
236 | request(sails.hooks.http.app)
237 | .get('/model?name=role')
238 | .set('Authorization', adminAuth.Authorization)
239 | .expect(200)
240 | .end(function(err, res) {
241 |
242 | // haha roleModel
243 | var roleModel = res.body[0];
244 |
245 | request(sails.hooks.http.app)
246 | .post('/permission')
247 | .set('Authorization', adminAuth.Authorization)
248 | .send({
249 | model: roleModel.id,
250 | action: 'read',
251 | role: roleId,
252 | createdBy: adminUserId,
253 | criteria: {
254 | where: {
255 | active: true
256 | },
257 | blacklist: ['name', 'createdAt']
258 | }
259 | })
260 | .expect(201)
261 | .end(function(err, res) {
262 | done(err);
263 | });
264 | });
265 | });
266 |
267 | it('should be able to create a read permission with a where clause for a role that should filter out all results', function(done) {
268 |
269 | request(sails.hooks.http.app)
270 | .get('/model?name=permission')
271 | .set('Authorization', adminAuth.Authorization)
272 | .expect(200)
273 | .end(function(err, res) {
274 |
275 | var permissionModel = res.body[0];
276 |
277 | request(sails.hooks.http.app)
278 | .post('/permission')
279 | .set('Authorization', adminAuth.Authorization)
280 | .send({
281 | model: permissionModel.id,
282 | action: 'read',
283 | role: roleId,
284 | createdBy: adminUserId,
285 | criteria: {
286 | where: {
287 | id: {
288 | '>': 99999
289 | }
290 | }
291 | }
292 | })
293 | .expect(201)
294 | .end(function(err, res) {
295 | done(err);
296 | });
297 | });
298 | });
299 |
300 | });
301 |
302 |
303 | });
304 |
305 | describe('User with Registered Role', function() {
306 |
307 | describe('#create()', function() {
308 |
309 | it('should not be able to create a new user', function(done) {
310 |
311 | request(sails.hooks.http.app)
312 | .post('/user')
313 | .set('Authorization', registeredAuth.Authorization)
314 | .send({
315 | username: 'newuser1',
316 | email: 'newuser1@example.com',
317 | password: 'lalalal1234'
318 | })
319 | .expect(403)
320 | .end(function(err, res) {
321 |
322 | var user = res.body;
323 |
324 | assert.ifError(err);
325 | assert(_.isString(user.error), JSON.stringify(user));
326 |
327 | done(err);
328 |
329 | });
330 |
331 | });
332 |
333 | });
334 |
335 | describe('#update()', function() {
336 |
337 | it('should be able to update themselves', function(done) {
338 |
339 | request(sails.hooks.http.app)
340 | .put('/user/' + newUserId)
341 | .set('Authorization', registeredAuth.Authorization)
342 | .send({
343 | email: 'newuserupdated@example.com'
344 | })
345 | .expect(200)
346 | .end(function(err, res) {
347 | var user = res.body;
348 |
349 | assert.ifError(err);
350 | assert.equal(user.email, 'newuserupdated@example.com');
351 |
352 | done(err);
353 | });
354 |
355 | });
356 |
357 | it('should be able to update role name', function(done) {
358 | // it should be able to do this, because an earlier test set up the role and permission for it
359 | request(sails.hooks.http.app)
360 | .put('/role/' + roleId)
361 | .set('Authorization', newUserAuth.Authorization)
362 | .send({
363 | name: 'updatedName'
364 | })
365 | .expect(200)
366 | .end(function(err, res) {
367 | assert.ifError(err);
368 | assert.equal(res.body.name, 'updatedName');
369 | done(err);
370 |
371 | });
372 |
373 | });
374 |
375 | it('should not be able to update role id', function(done) {
376 | // it should be able to do this, because an earlier test set up the role and permission for it
377 | request(sails.hooks.http.app)
378 | .put('/role/' + roleId)
379 | .set('Authorization', newUserAuth.Authorization)
380 | .send({
381 | id: 99
382 | })
383 | .expect(403)
384 | .end(function(err, res) {
385 | assert(res.body.hasOwnProperty('error'));
386 | assert.ifError(err);
387 | done(err);
388 |
389 | });
390 |
391 | });
392 |
393 | it('should not be able to update role name when role is inactive', function(done) {
394 | // attribute is ok but where clause fails
395 | request(sails.hooks.http.app)
396 | .put('/role/' + inactiveRoleId)
397 | .set('Authorization', newUserAuth.Authorization)
398 | .send({
399 | name: 'updatedInactiveName'
400 | })
401 | .expect(403)
402 | .end(function(err, res) {
403 | assert(res.body.hasOwnProperty('error'));
404 | assert.ifError(err);
405 | done(err);
406 |
407 | });
408 | });
409 |
410 |
411 | // this test depends on a previous test that set a permission with a particular where clause/blacklist
412 | it('should read only active roles, and should not have blacklisted attributes', function(done) {
413 |
414 | request(sails.hooks.http.app)
415 | .get('/role')
416 | .set('Authorization', newUserAuth.Authorization)
417 | .send({
418 | name: 'updatedInactiveName'
419 | })
420 | .expect(200)
421 | .end(function(err, res) {
422 | res.body.forEach(function(role) {
423 | assert(!role.hasOwnProperty('name'));
424 | assert(!role.hasOwnProperty('createdAt'));
425 | assert(role.active);
426 | });
427 | done(err);
428 |
429 | });
430 | });
431 |
432 | it.skip('should have filtered out all of the permissions results', function(done) {
433 |
434 | request(sails.hooks.http.app)
435 | .get('/permission')
436 | .set('Authorization', newUserAuth.Authorization)
437 | .expect(200)
438 | .end(function(err, res) {
439 | assert.equal(res.body.length, 0);
440 | done(err);
441 | });
442 | });
443 |
444 | it('should return data from /user/me', function(done) {
445 |
446 | request(sails.hooks.http.app)
447 | .get('/user/me')
448 | .set('Authorization', newUserAuth.Authorization)
449 | .expect(200)
450 | .end(function(err, res) {
451 | assert.ifError(err);
452 | assert(res.body.username == 'newuser');
453 | done(err);
454 | });
455 | });
456 |
457 | it('should not be able to update another user', function(done) {
458 |
459 | request(sails.hooks.http.app)
460 | .put('/user/' + adminUserId)
461 | .set('Authorization', registeredAuth.Authorization)
462 | .send({
463 | email: 'crapadminemail@example.com'
464 | })
465 | .expect(403)
466 | .end(function(err, res) {
467 |
468 | var user = res.body;
469 |
470 | assert.ifError(err);
471 | assert(_.isString(user.error), JSON.stringify(user));
472 |
473 | done(err);
474 |
475 | });
476 |
477 | });
478 |
479 |
480 |
481 | });
482 |
483 | });
484 |
485 | });
486 |
--------------------------------------------------------------------------------
/test/unit/services/PermissionService.test.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 |
3 | describe('Permission Service', function() {
4 |
5 | it('should exist', function() {
6 |
7 | assert.ok(sails.services.permissionservice);
8 | assert.ok(global.PermissionService);
9 |
10 | });
11 |
12 | describe('#isForeignObject()', function() {
13 |
14 | it('should return true if object is not owned by the requesting user', function(done) {
15 |
16 | var objectNotOwnedByUser = {
17 | owner: 2
18 | };
19 | var user = 1;
20 |
21 | assert.equal(sails.services.permissionservice.isForeignObject(user)(objectNotOwnedByUser), true);
22 |
23 | done();
24 |
25 | });
26 |
27 | it('should return false if object is owned by the requesting user', function(done) {
28 |
29 | var objectOwnedByUser = {
30 | owner: 1
31 | };
32 | var user = 1;
33 |
34 | assert.equal(sails.services.permissionservice.isForeignObject(user)(objectOwnedByUser), false);
35 |
36 | done();
37 | });
38 |
39 | });
40 |
41 | describe('#hasForeignObjects()', function() {
42 |
43 | it('should return true if any object is not owned by the requesting user', function(done) {
44 |
45 | var objectOwnedByUser = {
46 | owner: 1
47 | };
48 | var objectNotOwnedByUser = {
49 | owner: 2
50 | };
51 | var user = {
52 | id: 1
53 | };
54 |
55 | assert.equal(sails.services.permissionservice.hasForeignObjects([objectNotOwnedByUser, objectOwnedByUser], user), true);
56 |
57 | done();
58 | });
59 |
60 | it('should return false if all objects are owned by the requesting user', function(done) {
61 |
62 | var objectOwnedByUser = {
63 | owner: 1
64 | };
65 | var objectOwnedByUser2 = {
66 | owner: 1
67 | };
68 | var user = {
69 | id: 1
70 | };
71 |
72 | assert.equal(sails.services.permissionservice.hasForeignObjects([objectOwnedByUser2, objectOwnedByUser], user), false);
73 | done();
74 |
75 | });
76 |
77 | });
78 |
79 | describe('#hasOwnershipPolicy()', function() {
80 |
81 | it('should return true if object supports ownership policy', function(done) {
82 |
83 | assert.equal(sails.services.permissionservice.hasOwnershipPolicy({
84 | autoCreatedBy: true
85 | }), true);
86 | done();
87 | });
88 |
89 | it('should return false if object does not support ownership policy', function(done) {
90 |
91 | assert.equal(sails.services.permissionservice.hasOwnershipPolicy({
92 | autoCreatedBy: false
93 | }), false);
94 | done();
95 |
96 | });
97 |
98 | });
99 |
100 | describe('#getMethod()', function() {
101 |
102 | it('should return \'create\' if POST request', function(done) {
103 |
104 | assert.equal(sails.services.permissionservice.getMethod('POST'), 'create');
105 | done();
106 |
107 | });
108 |
109 | it('should return \'update\' if PUT request', function(done) {
110 |
111 | assert.equal(sails.services.permissionservice.getMethod('PUT'), 'update');
112 | done();
113 |
114 | });
115 |
116 | it('should return \'read\' if GET request', function(done) {
117 |
118 | assert.equal(sails.services.permissionservice.getMethod('GET'), 'read');
119 | done();
120 |
121 | });
122 |
123 | it('should return \'delete\' if DELETE request', function(done) {
124 |
125 | assert.equal(sails.services.permissionservice.getMethod('DELETE'), 'delete');
126 | done();
127 |
128 | });
129 |
130 | });
131 |
132 | describe('#hasPassingCriteria()', function() {
133 |
134 | it('should return an array of items that don\'t match the given criteria', function(done) {
135 | var objects = [{
136 | x: 1
137 | }, {
138 | x: 2
139 | }, {
140 | x: 3
141 | }];
142 | var permissions = [{
143 | criteria: {
144 | where: {
145 | x: 2
146 | }
147 | }
148 | }];
149 | assert.equal(sails.services.permissionservice.hasPassingCriteria(objects, permissions), false);
150 | done();
151 | });
152 |
153 | it('should return an array of items that don\'t match the given criteria, if the criteria has many values for the same key', function(done) {
154 | var objects = [{
155 | x: 1
156 | }, {
157 | x: 2
158 | }, {
159 | x: 3
160 | }];
161 | var permissions = [{
162 | criteria: {
163 | where: {
164 | x: 2
165 | }
166 | }
167 | }, {
168 | criteria: {
169 | where: {
170 | x: 3
171 | }
172 | }
173 | }];
174 | assert.equal(sails.services.permissionservice.hasPassingCriteria(objects, permissions), false);
175 | done();
176 | });
177 |
178 | it('should return an array of items that don\'t match the given criteria, if the criteria has many values for the same key', function(done) {
179 | var objects = {
180 | x: 2
181 | };
182 | var permissions = [{
183 | criteria: {
184 | where: {
185 | x: 2
186 | }
187 | }
188 | }, {
189 | criteria: {
190 | where: {
191 | x: 3
192 | }
193 | }
194 | }];
195 | assert(sails.services.permissionservice.hasPassingCriteria(objects, permissions));
196 | done();
197 | });
198 |
199 | it('should return an empty array if there is no criteria', function(done) {
200 | var objects = [{
201 | x: 1
202 | }, {
203 | x: 2
204 | }, {
205 | x: 3
206 | }];
207 | assert(sails.services.permissionservice.hasPassingCriteria(objects));
208 | done();
209 | });
210 |
211 | it('should match without where clause and blacklist', function(done) {
212 | var objects = [{
213 | x: 1
214 | }, {
215 | x: 2
216 | }, {
217 | x: 3
218 | }];
219 | var permissions = [{
220 | criteria: {
221 | blacklist: ['x']
222 | }
223 | }];
224 | assert(sails.services.permissionservice.hasPassingCriteria(objects, permissions));
225 | done();
226 | });
227 |
228 | it('should match with where clause and attributes', function(done) {
229 | var objects = [{
230 | x: 1
231 | }, {
232 | x: 2
233 | }, {
234 | x: 3
235 | }];
236 | var permissions = [{
237 | criteria: {
238 | where: {
239 | x: {
240 | '>': 0
241 | }
242 | },
243 | blacklist: ['y']
244 | }
245 | }];
246 | assert(sails.services.permissionservice.hasPassingCriteria(objects, permissions, {
247 | x: 5
248 | }));
249 | done();
250 | });
251 |
252 | it('should fail with bad where clause and good blacklist', function(done) {
253 | var objects = [{
254 | x: 1
255 | }, {
256 | x: 2
257 | }, {
258 | x: 3
259 | }];
260 | var permissions = [{
261 | criteria: {
262 | where: {
263 | x: {
264 | '<': 0
265 | }
266 | },
267 | blacklist: ['y']
268 | }
269 | }];
270 | assert.equal(sails.services.permissionservice.hasPassingCriteria(objects, permissions, {
271 | x: 5
272 | }), false);
273 | done();
274 | });
275 |
276 | it('should fail with good where clause and bad blacklist', function(done) {
277 | var objects = [{
278 | x: 1
279 | }, {
280 | x: 2
281 | }, {
282 | x: 3
283 | }];
284 | var permissions = [{
285 | criteria: {
286 | where: {
287 | x: {
288 | '>': 0
289 | }
290 | },
291 | blacklist: ['x']
292 | }
293 | }];
294 | assert.equal(sails.services.permissionservice.hasPassingCriteria(objects, permissions, {
295 | x: 5
296 | }), false);
297 | done();
298 | });
299 |
300 | });
301 |
302 | describe('#hasUnpermittedAttributes', function() {
303 | it('should return true if any of the attributes are in the blacklist', function(done) {
304 | var attributes = {
305 | ok: 1,
306 | fine: 2
307 | };
308 | var blacklist = ["ok", "alright", "fine"];
309 | assert(sails.services.permissionservice.hasUnpermittedAttributes(attributes, blacklist));
310 | done();
311 | });
312 |
313 | it('should return true if any attributes are not permitted', function(done) {
314 | var attributes = {
315 | ok: 1,
316 | fine: 2,
317 | whatever: 3
318 | };
319 | var blacklist = ["ok", "alright", "fine"];
320 | assert(sails.services.permissionservice.hasUnpermittedAttributes(attributes, blacklist));
321 | done();
322 | });
323 |
324 | it('should return false if none of the keys are in the blacklist', function(done) {
325 | var attributes = {
326 | ok: 1,
327 | fine: 2,
328 | whatever: 3
329 | };
330 | var blacklist = ["notallowed"];
331 | assert.equal(sails.services.permissionservice.hasUnpermittedAttributes(attributes, blacklist), false);
332 | done();
333 | });
334 |
335 | it('should return false if there are no attributes', function(done) {
336 | var attributes = {};
337 | var blacklist = ["ok", "alright", "fine"];
338 | assert.equal(sails.services.permissionservice.hasUnpermittedAttributes(attributes, blacklist), false);
339 | done();
340 | });
341 |
342 | it('should return false if blacklist is empty', function(done) {
343 | var attributes = {
344 | ok: 1,
345 | fine: 2,
346 | whatever: 3
347 | };
348 | var blacklist = [];
349 | assert.equal(sails.services.permissionservice.hasUnpermittedAttributes(attributes, blacklist), false);
350 | done();
351 | });
352 |
353 | });
354 |
355 | describe('role and permission helpers', function() {
356 | it('should create a role', function(done) {
357 | // make sure there is no existing role with this name
358 | Role.find({
359 | name: 'fakeRole'
360 | })
361 | .then(function(role) {
362 | assert.equal(role.length, 0);
363 | // use the helper to create a new role
364 | var newRole = {
365 | name: 'fakeRole',
366 | permissions: [{
367 | model: 'Permission',
368 | action: 'delete',
369 | relation: 'role',
370 | }],
371 | users: ['newuser']
372 | };
373 | return sails.services.permissionservice.createRole(newRole);
374 | })
375 | .then(function(result) {
376 | // make sure the role exists now that we have created it
377 | return Role.findOne({
378 | name: 'fakeRole'
379 | });
380 | })
381 | .then(function(role) {
382 | assert(role && role.id);
383 | })
384 | .done(done, done);
385 | });
386 |
387 | it('should create a permission', function(done) {
388 | var permissionModelId;
389 | // find any existing permission for this action, and delete it
390 | Model.findOne({
391 | name: 'Permission'
392 | }).then(function(permissionModel) {
393 | permissionModelId = permissionModel.id;
394 | return Permission.destroy({
395 | action: 'create',
396 | model: permissionModelId,
397 | relation: 'role'
398 | });
399 | })
400 | .then(function(destroyed) {
401 | // make sure we actually destroyed it
402 | return Permission.find({
403 | action: 'create',
404 | relation: 'role',
405 | model: permissionModelId
406 | });
407 | })
408 | .then(function(permission) {
409 | assert.equal(permission.length, 0);
410 | // create a new permission
411 | var newPermissions = [{
412 | role: 'fakeRole',
413 | model: 'Permission',
414 | action: 'create',
415 | relation: 'role',
416 | criteria: {
417 | where: {
418 | x: 1
419 | },
420 | blacklist: ['y']
421 | }
422 | }, {
423 | role: 'fakeRole',
424 | model: 'Role',
425 | action: 'update',
426 | relation: 'role',
427 | criteria: {
428 | where: {
429 | x: 1
430 | },
431 | blacklist: ['y']
432 | }
433 | }];
434 | return sails.services.permissionservice.grant(newPermissions);
435 | })
436 | .then(function(perm) {
437 | // verify that it was created
438 | return Permission.findOne({
439 | action: 'create',
440 | relation: 'role',
441 | model: permissionModelId
442 | });
443 | })
444 | .then(function(permission) {
445 | assert(permission && permission.id);
446 | })
447 | .done(done, done);
448 | });
449 |
450 |
451 | it('should grant a permission directly to a user', function(done) {
452 | var permissionModelId;
453 | // find any existing permission for this action, and delete it
454 | Model.findOne({
455 | name: 'Permission'
456 | }).then(function(permissionModel) {
457 | permissionModelId = permissionModel.id;
458 | return Permission.destroy({
459 | action: 'create',
460 | model: permissionModelId,
461 | relation: 'role'
462 | });
463 | })
464 | .then(function(destroyed) {
465 | // make sure we actually destroyed it
466 | return Permission.find({
467 | action: 'create',
468 | relation: 'role',
469 | model: permissionModelId
470 | });
471 | })
472 | .then(function(permission) {
473 | assert.equal(permission.length, 0);
474 | // create a new permission
475 | var newPermissions = [{
476 | user: 'admin',
477 | model: 'Permission',
478 | action: 'create',
479 | relation: 'role',
480 | criteria: {
481 | where: {
482 | x: 1
483 | },
484 | blacklist: ['y']
485 | }
486 | }, {
487 | user: 'admin',
488 | model: 'Role',
489 | action: 'update',
490 | relation: 'role',
491 | criteria: {
492 | where: {
493 | x: 1
494 | },
495 | blacklist: ['y']
496 | }
497 | }];
498 | return sails.services.permissionservice.grant(newPermissions);
499 | })
500 | .then(function(perm) {
501 | // verify that it was created
502 | return Permission.findOne({
503 | action: 'create',
504 | relation: 'role',
505 | model: permissionModelId
506 | });
507 | })
508 | .then(function(permission) {
509 | assert(permission && permission.id);
510 | })
511 | .done(done, done);
512 | });
513 |
514 | it('should revoke a permission', function(done) {
515 | var permissionModelId;
516 |
517 | // make sure there is already an existing permission for this case
518 | Model.findOne({
519 | name: 'Permission'
520 | }).then(function(permissionModel) {
521 | permissionModelId = permissionModel.id;
522 | return Permission.find({
523 | action: 'create',
524 | relation: 'role',
525 | model: permissionModelId
526 | });
527 | })
528 | .then(function(permission) {
529 | assert.equal(permission.length, 1);
530 | return sails.services.permissionservice.revoke({
531 | user: 'admin',
532 | model: 'Permission',
533 | relation: 'role',
534 | action: 'create'
535 | });
536 | })
537 | .then(function() {
538 | return Permission.find({
539 | action: 'create',
540 | relation: 'role',
541 | model: permissionModelId
542 | });
543 | })
544 | .then(function(permission) {
545 | assert.equal(permission.length, 0);
546 | })
547 | .done(done, done);
548 | });
549 |
550 | it('should not revoke a permission if no user or role is supplied', function(done) {
551 | var permissionModelId;
552 | var newPermissions = [{
553 | user: 'admin',
554 | model: 'Permission',
555 | action: 'create',
556 | relation: 'role',
557 | criteria: {
558 | where: {
559 | x: 1
560 | },
561 | blacklist: ['y']
562 | }
563 | }, {
564 | user: 'admin',
565 | model: 'Role',
566 | action: 'update',
567 | relation: 'role',
568 | criteria: {
569 | where: {
570 | x: 1
571 | },
572 | blacklist: ['y']
573 | }
574 | }];
575 |
576 | return sails.services.permissionservice.grant(newPermissions)
577 | .then(function() {
578 | // make sure there is already an existing permission for this case
579 | Model.findOne({
580 | name: 'Permission'
581 | }).then(function(permissionModel) {
582 | permissionModelId = permissionModel.id;
583 | return Permission.find({
584 | action: 'create',
585 | relation: 'role',
586 | model: permissionModelId
587 | });
588 | })
589 | .then(function(permission) {
590 | assert.equal(permission.length, 1);
591 | return sails.services.permissionservice.revoke({
592 | model: 'Permission',
593 | relation: 'role',
594 | action: 'create'
595 | });
596 | })
597 | .catch(function(err) {
598 | assert.equal(err.message, 'You must provide either a user or role to revoke the permission from');
599 | })
600 | .then(function() {
601 | return Permission.find({
602 | action: 'create',
603 | relation: 'role',
604 | model: permissionModelId
605 | });
606 | })
607 | .then(function(permission) {
608 | assert.equal(permission.length, 1);
609 | })
610 | .done(done, done);
611 | });
612 | });
613 |
614 | it('should remove users from a role', function(done) {
615 | var user;
616 | var ok = User.create({
617 | username: 'test',
618 | email: 'testemail@test.test'
619 | });
620 |
621 | ok = ok.then(function(usr) {
622 | user = usr;
623 | return PermissionService.addUsersToRole('test', 'admin');
624 | });
625 |
626 | ok = ok.then(function (role) {
627 | assert(_.contains(_.pluck(role.users, 'id'), user.id));
628 | return PermissionService.removeUsersFromRole('test', 'admin');
629 | });
630 |
631 | ok = ok.then(function (role) {
632 | assert(!_.contains(_.pluck(role.users, 'id'), user.id));
633 | })
634 | .done(done, done);
635 |
636 | });
637 |
638 | });
639 | //TODO: add unit tests for #findTargetObjects()
640 |
641 | //TODO: add unit tests for #findModelPermissions()
642 |
643 | });
644 |
--------------------------------------------------------------------------------