diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
new file mode 100644
index 00000000..153f301b
--- /dev/null
+++ b/.github/CONTRIBUTING.md
@@ -0,0 +1,103 @@
+# Contributing to Meteor Roles
+
+Any contribution to this repository is highly appreciated!
+
+## Setup development env
+
+### Clone project and create a new branch to work on
+
+First, clone this repository and create a new branch to work on.
+Branch names should follow the GitFlow standard and start with a descriptive prefix of their intended outcome, for example:
+
+- `feature/` for features
+- `fix/` for general fixes
+
+Then the name of the branch should describe the purpose of the branch and potentially reference the issue number it is solving.
+
+```shell
+$ git clone git@github.com:Meteor-Community-Packages/meteor-roles.git
+$ cd meteor-roles
+$ git checkout -b fix/some-issue
+```
+
+### Initialize test app
+
+We use a proxy Meteor application to run our tests and handle coverage etc.
+This app contains several npm scripts to provide the complete toolchain that is required
+for your development and testing needs.
+
+The setup is very easy. Go into the `testapp` directory, install dependencies and link
+the package:
+
+```shell
+$ cd testapp
+$ meteor npm install
+$ meteor npm run setup # this is important for the tools to work!
+```
+
+## Development toolchain
+
+The `testapp` comes with some builtin scripts you can utilize during your development.
+They will also be picked up by our CI during pull requests.
+Therefore, it's a good call for you, that if they pass or fail, the CI will do so, too.
+
+**Note: all tools require the npm `setup` script has been executed at least once!**
+
+### Linter
+
+We use `standard` as our linter. You can run either the linter or use it's autofix feature for
+the most common issues:
+
+```shell
+# in testapp
+$ meteor npm run lint # show only outputs
+$ meteor npm run lint:fix # with fixes + outputs
+```
+
+### Tests
+
+We provide three forms of tests: once, watch, coverage
+
+#### Once
+
+Simply runs the test suite once, without coverage collection:
+
+```shell
+$ meteor npm run test
+```
+
+#### Watch
+
+Runs the test suite in watch mode, good to use during active development, where your changes
+are picked up automatically to re-run the tests:
+
+```shell
+$ meteor npm run test:watch
+```
+
+#### Coverage
+
+Runs the test suite once, including coverage report generation.
+Generates an html and json report output.
+
+```shell
+$ meteor npm run test:coverage
+$ meteor npm run report # summary output in console
+```
+
+If you want to watch the HTML output to find (un)covered lines, open
+the file at `testapp/.coverage/index.html` in your browser.
+
+## Open a pull request
+
+If you open a pull request, please make sure the following requirements are met:
+
+- the `lint` script is passing
+- the `test` script is passing
+- your contribution is on point and solves one issue (not multiple)
+- your commit messages are descriptive and informative
+- complex changes are documented in the code with comments or jsDoc-compatible documentation
+
+Please understand, that there will be a review process and your contribution
+might require changes before being merged. This is entirely to ensure quality and is
+never used as a personal offense.
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 00000000..ad1babd0
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,37 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: bug
+assignees: ''
+
+---
+
+**Describe the bug**
+
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+
+Steps to reproduce the behavior:
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+**Expected behavior**
+
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+
+If applicable, add screenshots to help explain your problem.
+
+**Versions (please complete the following information):**
+ - Meteor version: [e.g. 1.8.2]
+ - Browser: [e.g. Firefox, Chrome, Safari]
+ - Version: [e.g. 1.0.0]
+
+**Additional context**
+
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 00000000..a294cc20
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,24 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: enhancement
+assignees: ''
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+
+Add any other context or screenshots about the feature request here.
diff --git a/.github/SECURITY.md b/.github/SECURITY.md
new file mode 100644
index 00000000..a1d37606
--- /dev/null
+++ b/.github/SECURITY.md
@@ -0,0 +1,26 @@
+# Security Policy
+
+## Supported Versions
+
+Unless otherwise noted, MCP packages and projects support the latest version of Meteor.
+As of the time of writing this in September 2023 it is Meteor 2.13 and, in general the 2.x series.
+Support for Meteor 1.x is not guaranteed and at best can include Meteor 1.12.1.
+At the present time, we are also preparing to support Meteor 3.0 which is in development.
+
+| Meteor Version | Supported |
+| -------------- | ------------------ |
+| 3.0 | 🚧 |
+| > 2.0 | ✅ |
+| < 1.12 | ❌ |
+
+## Reporting a Vulnerability
+
+We are not providing support for Meteor itself. See their [SECURITY](https://github.com/meteor/meteor/blob/devel/SECURITY.md) for more information.
+
+Any security concerns should be first raised in the repository of the affected package.
+If you want to report a vulnerability privately, please contact the maintainer of the package directly.
+If you are not sure who that is, you can contact one of the following people, and they will take it from there:
+
+Jan Dvorak
+hello+mcp-security@jandvorak.me
+
diff --git a/.github/workflows/testsuite.yml b/.github/workflows/testsuite.yml
index 54a3d379..410363ac 100644
--- a/.github/workflows/testsuite.yml
+++ b/.github/workflows/testsuite.yml
@@ -12,10 +12,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: setup node
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: 16
@@ -36,16 +36,15 @@ jobs:
strategy:
matrix:
meteorRelease:
- - '2.3'
- - '2.7.3'
- - '2.12'
+ - '2.8.0'
+ - '2.14'
# Latest version
steps:
- name: Checkout code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Install Node.js
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: 16
diff --git a/.gitignore b/.gitignore
index 497cb990..936ee1e8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,6 +16,7 @@ npm-debug.log
smart.lock
.idea
+.DS_Store
docs/
node_modules/
.npm/
diff --git a/.versions b/.versions
index 06d249bb..2d09a9df 100644
--- a/.versions
+++ b/.versions
@@ -1,5 +1,5 @@
accounts-base@2.2.8
-alanning:roles@3.5.1
+alanning:roles@3.6.0
allow-deny@1.1.1
babel-compiler@7.10.4
babel-runtime@1.5.1
@@ -12,7 +12,7 @@ ddp@1.4.1
ddp-client@2.6.1
ddp-common@1.4.0
ddp-rate-limiter@1.2.0
-ddp-server@2.6.1
+ddp-server@2.6.2
diff-sequence@1.1.2
dynamic-import@0.7.3
ecmascript@0.16.7
@@ -22,28 +22,28 @@ ecmascript-runtime-server@0.11.0
ejson@1.1.3
fetch@0.1.3
geojson-utils@1.0.11
-http@1.4.2
+http@2.0.0
id-map@1.1.1
inter-process-messaging@0.1.1
-lmieulet:meteor-coverage@3.2.0
-lmieulet:meteor-packages-coverage@0.2.0
-local-test:alanning:roles@3.5.1
+lmieulet:meteor-coverage@4.1.0
+lmieulet:meteor-legacy-coverage@0.1.0
+local-test:alanning:roles@3.6.0
localstorage@1.2.0
logging@1.3.2
-meteor@1.11.2
-meteortesting:browser-tests@0.1.2
-meteortesting:mocha@0.4.4
+meteor@1.11.3
+meteortesting:browser-tests@1.4.2
+meteortesting:mocha@2.1.0
+meteortesting:mocha-core@8.0.1
minimongo@1.9.3
modern-browsers@0.1.9
modules@0.19.0
modules-runtime@0.13.1
-mongo@1.16.6
+mongo@1.16.7
mongo-decimal@0.1.3
mongo-dev-server@1.1.0
mongo-id@1.0.8
npm-mongo@4.16.0
ordered-dict@1.1.0
-practicalmeteor:mocha-core@1.0.1
promise@0.12.2
random@1.2.1
rate-limit@1.1.1
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index de40874f..9db04a00 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -7,13 +7,12 @@ Any contribution to this repository is highly appreciated!
### Clone project and create a new branch to work on
First, clone this repository and create a new branch to work on.
-Branch names should start with a descriptive suffix of their intended outcome, for example:
+Branch names should follow the GitFlow standard and start with a descriptive prefix of their intended outcome, for example:
-- `feature-` for features
-- `tests-` for contributions that improve testing
-- `fix-` for general fixes
-- `build-` for contributions that update the build process
-- `ci-` for contributions that improve/update the ci
+- `feature/` for features
+- `fix/` for general fixes
+
+Then the name of the branch should describe the purpose of the branch and potentially reference the issue number it is solving.
```shell
$ git clone git@github.com:Meteor-Community-Packages/meteor-roles.git
diff --git a/History.md b/History.md
index 7242730e..cac88de0 100644
--- a/History.md
+++ b/History.md
@@ -1,5 +1,17 @@
# Changelog
+## v3.6.1
+
+* Added types for async functions [#386](https://github.com/Meteor-Community-Packages/meteor-roles/pull/386) [@storytellercz](https://github.com/sponsors/StorytellerCZ)
+* Updated docs with async functions [@storytellercz](https://github.com/sponsors/StorytellerCZ)
+* Updated `zodern:types` to v1.0.10
+
+## v3.6.0
+
+* Added async versions of functions [#361](https://github.com/Meteor-Community-Packages/meteor-roles/pull/361) [#378](https://github.com/Meteor-Community-Packages/meteor-roles/pull/378) [@bratelefant](https://github.com/bratelefant) [@storytellercz](https://github.com/sponsors/StorytellerCZ) [@jankapunkt](https://github.com/sponsors/jankapunkt)
+* Added missing types [#383](https://github.com/Meteor-Community-Packages/meteor-roles/pull/383) [@storytellercz](https://github.com/sponsors/StorytellerCZ)
+* Add complete test suite [#375](https://github.com/Meteor-Community-Packages/meteor-roles/pull/375) [@jankapunkt](https://github.com/sponsors/jankapunkt) [#380](https://github.com/Meteor-Community-Packages/meteor-roles/pull/380) [@bratelefant](https://github.com/bratelefant)
+
## v3.5.1
* Fix for index creation functions losing context [#371](https://github.com/Meteor-Community-Packages/meteor-roles/pull/371) [@copleykj](https://github.com/sponsors/copleykj)
diff --git a/README.md b/README.md
index 97424b9d..05247ecf 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,11 @@
meteor-roles v3
===============
+[![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active)
+![GitHub](https://img.shields.io/github/license/Meteor-Community-Packages/meteor-roles)
+[![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
+[![CodeQL](https://github.com/Meteor-Community-Packages/meteor-roles/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/Meteor-Community-Packages/meteor-roles/actions/workflows/github-code-scanning/codeql)
+![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/Meteor-Community-Packages/meteor-roles?label=latest&sort=semver)
+[![](https://img.shields.io/badge/semver-2.0.0-success)](http://semver.org/spec/v2.0.0.html)
Authorization package for Meteor - compatible with built-in accounts package.
@@ -10,24 +16,27 @@ There are also older versions of this package:
-### Table of Contents
-* [Contributors](#roles-contributors)
-* [Authorization](#roles-authorization)
-* [Permissions vs roles](#roles-naming)
-* [What are "scopes"?](#roles-scopes)
-* [Changes to default Meteor](#roles-changes)
-* [Installation](#roles-installing)
-* [Migration to 3.0](#roles-migration)
-* [Usage examples](#roles-usage)
-* [Online API docs](#roles-docs)
-* [Example apps](#roles-example-apps)
-* [Running tests](#roles-contributions-development-and-tests)
+## Table of Contents
+- [meteor-roles v3](#meteor-roles-v3)
+ - [Table of Contents](#table-of-contents)
+ - [Contributors](#contributors)
+ - [Authorization](#authorization)
+ - [Permissions vs roles (or What's in a name...)](#permissions-vs-roles--or-whats-in-a-name)
+ - [What are "scopes"?](#what-are-scopes)
+ - [Changes to default Meteor behavior](#changes-to-default-meteor-behavior)
+ - [Installing](#installing)
+ - [Migration to 3.0](#migration-to-30)
+ - [Changes between 2.x and 3.0](#changes-between-2x-and-30)
+ - [Usage Examples](#usage-examples)
+ - [API Docs](#api-docs)
+ - [Example Apps](#example-apps)
+ - [Contributions, development and tests](#contributions-development-and-tests)
-### Contributors
+## Contributors
Thanks to:
@@ -51,9 +60,9 @@ Thanks to:
-### Authorization
+## Authorization
-This package lets you attach roles to a user which you can then check against later when deciding whether to grant
+This package lets you attach roles to a user, which you can then check against later when deciding whether to grant
access to Meteor methods or publish data. The core concept is very simple, essentially you are creating an assignment
of roles to a user and then checking for the existence of those roles later. This package provides helper methods
to make the process of adding, removing, and verifying those roles easier.
@@ -61,7 +70,7 @@ to make the process of adding, removing, and verifying those roles easier.
-### Permissions vs roles (or What's in a name...)
+## Permissions vs roles (or What's in a name...)
Although the name of this package is `roles`, you can define your **roles**, **scopes** or **permissions** however you like.
They are essentially just tags that you assign to a user and which you can check upon later.
@@ -96,16 +105,16 @@ Roles.addRolesToParent('POST_EDIT', 'user');
-### What are "scopes"?
+## What are "scopes"?
Sometimes it is useful to let a user have independent sets of roles. The `roles` package calls these independent
-sets "scopes" for lack of a better term. You can use them to represent various communities inside of your
+sets "scopes" for lack of a better term. You can use them to represent various communities inside your
application. Or maybe your application supports [multiple tenants](https://en.wikipedia.org/wiki/Multitenancy).
You can put each of those tenants into their own scope. Alternatively, you can use scopes to represent
various resources you have.
Users can have both scope roles assigned, and global roles. Global roles are in effect for all scopes.
-But scopes are independent from each other. Users can have one set of roles in scope A and another set
+But scopes are independent of each other. Users can have one set of roles in scope A and another set
of roles in scope B. Let's go through an example of this using soccer/football teams as scopes.
```javascript
@@ -116,7 +125,7 @@ Roles.userIsInRole(joesUserId, 'manage-team', 'manchester-united.com'); // true
Roles.userIsInRole(joesUserId, 'manage-team', 'real-madrid.com'); // false
```
-In this example we can see that Joe manages Manchester United and plays for Real Madrid. By using scopes, we can
+In this example, we can see that Joe manages Manchester United and plays for Real Madrid. By using scopes, we can
assign roles independently and make sure that they don't get mixed up between scopes.
Now, let's take a look at how to use the global roles. Say we want to give Joe permission to do something across
@@ -134,16 +143,16 @@ if (Roles.userIsInRole(joesUserId, ['manage-team', 'super-admin'], 'real-madrid.
-### Changes to default Meteor behavior
+## Changes to default Meteor behavior
- 1. A new collection `Meteor.roleAssignment` contains the information which role has been assigned to which user.
+ 1. A new collection `Meteor.roleAssignment` contains the information about which role has been assigned to which user.
1. A new collection `Meteor.roles` contains a global list of defined role names.
1. All existing roles are automatically published to the client.
-### Installing
+## Installing
1. Add one of the built-in accounts packages so the `Meteor.users` collection exists. From a command prompt:
```bash
@@ -174,7 +183,7 @@ if (Roles.userIsInRole(joesUserId, ['manage-team', 'super-admin'], 'real-madrid.
-### Migration to 3.0
+## Migration to 3.0
If you are currently using this package in a version older than 2.x, please upgrade to 2.0 by running the migration script required there: https://github.com/Meteor-Community-Packages/meteor-roles/tree/v2#migration-to-20
@@ -185,19 +194,19 @@ meteor shell
> Package['alanning:roles'].Roles._forwardMigrate2()
```
-In case something fails, there is also a script available for rolling back the changes. But be warned that a backward migration takes a magnitude longer than a foward migration. To migrate the database back to the old schema, run `Meteor._backwardMigrate2()` on the server:
+In case something fails, there is also a script available for rolling back the changes. But be warned that a backward migration takes a magnitude longer than a forward migration. To migrate the database back to the old schema, run `Meteor._backwardMigrate2()` on the server:
```bash
meteor shell
> Package['alanning:roles'].Roles._backwardMigrate2()
```
-#### Changes between 2.x and 3.0
+### Changes between 2.x and 3.0
Here is the list of important changes between meteor-roles 2.x and 3.0 to consider when migrating to 3.0:
* Role assignments have been moved from the `users` documents to a separate collection called `role-assignment`, available at `Meteor.roleAssignment`.
-* Role assignments are not published automatically. If you want all your role-assignments to be published automatically please include the following code:
+* Role assignments are not published automatically. If you want all your role-assignments to be published automatically, please include the following code:
```js
Meteor.publish(null, function () {
if (this.userId) {
@@ -217,7 +226,7 @@ Meteor.publish(null, function () {
-### Usage Examples
+## Usage Examples
@@ -378,7 +387,7 @@ Meteor.methods({
-- **Client** --
-Client javascript does not by default have access to all the same Roles functions as the server unless you publish
+Client JavaScript does not by default have access to all the same Roles functions as the server unless you publish
these role-assignments. In addition, Blaze will have the addition of a `isInRole` handlebars helper which is
automatically registered by the Roles package.
@@ -388,10 +397,10 @@ for latency compensation during Meteor method calls. Roles functions which modif
called directly, but inside the Meteor methods.
NOTE: Any sensitive data needs to be controlled server-side to prevent unwanted disclosure. To be clear, Meteor sends
-all templates, client-side javascript, and published data to the client's browser. This is by design and is a good thing.
+all templates, client-side JavaScript, and published data to the client's browser. This is by design and is a good thing.
The following example is just sugar to help improve the user experience for normal users. Those interested in seeing
the 'admin_nav' template in the example below will still be able to do so by manually reading the bundled `client.js`
-file. It won't be pretty but it is possible. But this is not a problem as long as the actual data is restricted server-side.
+file. It won't be pretty, but it is possible. But this is not a problem as long as the actual data is restricted server-side.
To check for global roles or when not using scopes:
@@ -426,7 +435,7 @@ To check for roles when using scopes:
-### API Docs
+## API Docs
Online API docs found here: https://meteor-community-packages.github.io/meteor-roles/
@@ -448,7 +457,7 @@ To serve documentation locally:
-### Example Apps
+## Example Apps
The `examples` directory contains Meteor apps which show off the following features:
* Server-side publishing with authorization to secure sensitive data
@@ -467,7 +476,7 @@ The `examples` directory contains Meteor apps which show off the following featu
-### Contributions, development and tests
+## Contributions, development and tests
Please read our [contribution guidelines](./CONTRIBUTING.md),
-which also describe how to set up and run the linter and tests.
+which also describes how to set up and run the linter and tests.
diff --git a/definitions.d.ts b/definitions.d.ts
index bc7b8b10..22775654 100644
--- a/definitions.d.ts
+++ b/definitions.d.ts
@@ -69,6 +69,11 @@ declare namespace Roles {
roles: string | string[],
options?: string | { scope?: string; ifExists?: boolean }
): void
+ function addUsersToRolesAsync(
+ users: string | string[] | Meteor.User | Meteor.User[],
+ roles: string | string[],
+ options?: string | { scope?: string; ifExists?: boolean }
+ ): Promise
/**
* Create a new role.
@@ -80,6 +85,7 @@ declare namespace Roles {
* @return {String} ID of the new role or null.
*/
function createRole(roleName: string, options?: { unlessExists: boolean }): string
+ function createRoleAsync(roleName: string, options?: { unlessExists: boolean }): Promise
/**
* Delete an existing role.
@@ -90,6 +96,7 @@ declare namespace Roles {
* @param {String} roleName Name of role.
*/
function deleteRole(roleName: string): void
+ function deleteRoleAsync(roleName: string): Promise
/**
* Rename an existing role.
@@ -99,6 +106,7 @@ declare namespace Roles {
* @param {String} newName New name of a role.
*/
function renameRole(oldName: string, newName: string): void
+ function renameRoleAsync(oldName: string, newName: string): Promise
/**
* Add role parent to roles.
@@ -111,6 +119,7 @@ declare namespace Roles {
* @param {String} parentName Name of parent role.
*/
function addRolesToParent(rolesNames: string | string[], parentName: string): void
+ function addRolesToParentAsync(rolesNames: string | string[], parentName: string): Promise
/**
* Remove role parent from roles.
@@ -123,6 +132,7 @@ declare namespace Roles {
* @param {String} parentName Name of parent role.
*/
function removeRolesFromParent(rolesNames: string | string[], parentName: string): void
+ function removeRolesFromParentAsync(rolesNames: string | string[], parentName: string): Promise
/**
* Retrieve cursor of all existing roles.
@@ -144,6 +154,7 @@ declare namespace Roles {
* @return {Array} Array of user's groups, unsorted. Roles.GLOBAL_GROUP will be omitted
*/
function getGroupsForUser(user: string | Meteor.User, role?: string): string[]
+ function getGroupsForUserAsync(user: string | Meteor.User, role?: string): Promise
/**
* Retrieve users scopes, if any.
@@ -155,6 +166,7 @@ declare namespace Roles {
* @return {Array} Array of user's scopes, unsorted.
*/
function getScopesForUser(user: string | Meteor.User, roles?: string | string[]): string[]
+ function getScopesForUserAsync(user: string | Meteor.User, roles?: string | string[]): Promise
/**
* Rename a scope.
@@ -166,6 +178,7 @@ declare namespace Roles {
* @param {String} newName New name of a scope.
*/
function renameScope(oldName: string, newName: string): void
+ function renameScopeAsync(oldName: string, newName: string): Promise
/**
* Remove a scope.
@@ -177,6 +190,7 @@ declare namespace Roles {
*
*/
function removeScope(name: String): void
+ function removeScopeAsync(name: String): Promise
/**
* Find out if a role is an ancestor of another role.
@@ -189,6 +203,7 @@ declare namespace Roles {
* @return {Boolean}
*/
function isParentOf(parentRoleName: string, childRoleName: string): boolean
+ function isParentOfAsync(parentRoleName: string, childRoleName: string): Promise
/**
* Retrieve user's roles.
@@ -214,6 +229,13 @@ declare namespace Roles {
onlyAssigned?: boolean;
fullObjects?: boolean
}): string[]
+ function getRolesForUserAsync(user: string | Meteor.User, options?: string | {
+ scope?: string;
+ anyScope?: boolean;
+ onlyScoped?: boolean;
+ onlyAssigned?: boolean;
+ fullObjects?: boolean
+ }): Promise
/**
* Retrieve all assignments of a user which are for the target role.
@@ -269,6 +291,11 @@ declare namespace Roles {
options?: string | { scope?: string; anyScope?: boolean; onlyScoped?: boolean; queryOptions?: QueryOptions },
queryOptions?: QueryOptions
): Mongo.Cursor
+ function getUsersInRoleAsync(
+ roles: string | string[],
+ options?: string | { scope?: string; anyScope?: boolean; onlyScoped?: boolean; queryOptions?: QueryOptions },
+ queryOptions?: QueryOptions
+ ): Promise>
/**
* Remove users from assigned roles.
@@ -292,6 +319,11 @@ declare namespace Roles {
roles?: string | string[],
options?: string | { scope?: string; anyScope?: boolean }
): void
+ function removeUsersFromRolesAsync(
+ users: string | string[] | Meteor.User | Meteor.User[],
+ roles?: string | string[],
+ options?: string | { scope?: string; anyScope?: boolean }
+ ): Promise
/**
* Set users' roles.
@@ -319,6 +351,11 @@ declare namespace Roles {
roles: string | string[],
options?: string | { scope?: string; anyScope?: boolean; ifExists?: boolean }
): void
+ function setUserRolesAsync(
+ users: string | string[] | Meteor.User | Meteor.User[],
+ roles: string | string[],
+ options?: string | { scope?: string; anyScope?: boolean; ifExists?: boolean }
+ ): Promise
/**
* Check if user has specified roles.
@@ -353,12 +390,20 @@ declare namespace Roles {
roles: string | string[],
options?: string | { scope?: string; anyScope?: boolean }
): boolean
+ function userIsInRoleAsync(
+ user: string | string[] | Meteor.User | Meteor.User[],
+ roles: string | string[],
+ options?: string | { scope?: string; anyScope?: boolean }
+ ): Promise
+ // The schema for the roles collection
interface Role {
_id: string
name: string
+ children: { _id: string }[]
}
+ // The schema for the role-assignment collection
interface RoleAssignment {
_id: string
user: {
@@ -387,4 +432,5 @@ declare namespace Roles {
declare namespace Meteor {
var roles: Mongo.Collection
+ var roleAssignment: Mongo.Collection
}
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 00000000..48e341a0
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,3 @@
+{
+ "lockfileVersion": 1
+}
diff --git a/package.js b/package.js
index 5e6ffe18..c1eb6702 100644
--- a/package.js
+++ b/package.js
@@ -20,13 +20,14 @@ Package.onUse(function (api) {
'check'
], both)
- api.use('zodern:types@1.0.9')
+ api.use('zodern:types@1.0.10')
api.use(['blaze@2.7.1'], 'client', { weak: true })
api.export('Roles')
api.addFiles('roles/roles_common.js', both)
+ api.addFiles('roles/roles_common_async.js', both)
api.addFiles('roles/roles_server.js', 'server')
api.addFiles([
'roles/client/debug.js',
@@ -56,5 +57,7 @@ Package.onTest(function (api) {
], both)
api.addFiles('roles/tests/server.js', 'server')
+ api.addFiles('roles/tests/serverAsync.js', 'server')
api.addFiles('roles/tests/client.js', 'client')
+ api.addFiles('roles/tests/clientAsync.js', 'client')
})
diff --git a/roles/.gitignore b/roles/.gitignore
index 677a6fc2..964bc064 100644
--- a/roles/.gitignore
+++ b/roles/.gitignore
@@ -1 +1,2 @@
.build*
+.DS_Store
diff --git a/roles/roles_common.js b/roles/roles_common.js
index f05d6206..3dfcefe3 100644
--- a/roles/roles_common.js
+++ b/roles/roles_common.js
@@ -706,7 +706,7 @@ Object.assign(Roles, {
* - `onlyAssigned`: return only assigned roles and not automatically inferred (like subroles)
* - `fullObjects`: return full roles objects (`true`) or just names (`false`) (`onlyAssigned` option is ignored) (default `false`)
* If you have a use-case for this option, please file a feature-request. You shouldn't need to use it as it's
- * result strongly dependant on the internal data structure of this plugin.
+ * result strongly dependent on the internal data structure of this plugin.
*
* Alternatively, it can be a scope name string.
* @return {Array} Array of user's roles, unsorted.
diff --git a/roles/roles_common_async.js b/roles/roles_common_async.js
new file mode 100644
index 00000000..6ca1a9f6
--- /dev/null
+++ b/roles/roles_common_async.js
@@ -0,0 +1,1324 @@
+/* global Roles */
+import { Meteor } from 'meteor/meteor'
+import { Mongo } from 'meteor/mongo'
+
+/**
+ * Provides functions related to user authorization. Compatible with built-in Meteor accounts packages.
+ *
+ * Roles are accessible throgh `Meteor.roles` collection and documents consist of:
+ * - `_id`: role name
+ * - `children`: list of subdocuments:
+ * - `_id`
+ *
+ * Children list elements are subdocuments so that they can be easier extended in the future or by plugins.
+ *
+ * Roles can have multiple parents and can be children (subroles) of multiple roles.
+ *
+ * Example: `{_id: 'admin', children: [{_id: 'editor'}]}`
+ *
+ * The assignment of a role to a user is stored in a collection, accessible through `Meteor.roleAssignment`.
+ * It's documents consist of
+ * - `_id`: Internal MongoDB id
+ * - `role`: A role object which got assigned. Usually only contains the `_id` property
+ * - `user`: A user object, usually only contains the `_id` property
+ * - `scope`: scope name
+ * - `inheritedRoles`: A list of all the roles objects inherited by the assigned role.
+ *
+ * @module Roles
+ */
+if (!Meteor.roles) {
+ Meteor.roles = new Mongo.Collection('roles')
+}
+
+if (!Meteor.roleAssignment) {
+ Meteor.roleAssignment = new Mongo.Collection('role-assignment')
+}
+
+/**
+ * @class Roles
+ */
+if (typeof Roles === 'undefined') {
+ Roles = {} // eslint-disable-line no-global-assign
+}
+
+let getGroupsForUserDeprecationWarning = false
+
+/**
+ * Helper, resolves async some
+ * @param {*} arr
+ * @param {*} predicate
+ * @returns {Promise}
+ */
+const asyncSome = async (arr, predicate) => {
+ for (const e of arr) {
+ if (await predicate(e)) return true
+ }
+ return false
+}
+
+Object.assign(Roles, {
+ /**
+ * Used as a global group (now scope) name. Not used anymore.
+ *
+ * @property GLOBAL_GROUP
+ * @static
+ * @deprecated
+ */
+ GLOBAL_GROUP: null,
+
+ /**
+ * Create a new role.
+ *
+ * @method createRoleAsync
+ * @param {String} roleName Name of role.
+ * @param {Object} [options] Options:
+ * - `unlessExists`: if `true`, exception will not be thrown in the role already exists
+ * @return {Promise} ID of the new role or null.
+ * @static
+ */
+ createRoleAsync: async function (roleName, options) {
+ Roles._checkRoleName(roleName)
+
+ options = Object.assign(
+ {
+ unlessExists: false
+ },
+ options
+ )
+
+ let insertedId = null
+
+ const existingRole = await Meteor.roles.findOneAsync({ _id: roleName })
+
+ if (existingRole) {
+ await Meteor.roles.updateAsync(
+ { _id: roleName },
+ { $setOnInsert: { children: [] } }
+ )
+ return null
+ } else {
+ insertedId = await Meteor.roles.insertAsync({
+ _id: roleName,
+ children: []
+ })
+ }
+
+ if (!insertedId) {
+ if (options.unlessExists) return null
+ throw new Error("Role '" + roleName + "' already exists.")
+ }
+
+ return insertedId
+ },
+
+ /**
+ * Delete an existing role.
+ *
+ * If the role is set for any user, it is automatically unset.
+ *
+ * @method deleteRoleAsync
+ * @param {String} roleName Name of role.
+ * @returns {Promise}
+ * @static
+ */
+ deleteRoleAsync: async function (roleName) {
+ let roles
+ let inheritedRoles
+
+ Roles._checkRoleName(roleName)
+
+ // Remove all assignments
+ await Meteor.roleAssignment.removeAsync({
+ 'role._id': roleName
+ })
+
+ do {
+ // For all roles who have it as a dependency ...
+ roles = Roles._getParentRoleNames(
+ await Meteor.roles.findOneAsync({ _id: roleName })
+ )
+
+ for (const r of await Meteor.roles
+ .find({ _id: { $in: roles } })
+ .fetchAsync()) {
+ await Meteor.roles.updateAsync(
+ {
+ _id: r._id
+ },
+ {
+ $pull: {
+ children: {
+ _id: roleName
+ }
+ }
+ }
+ )
+
+ inheritedRoles = await Roles._getInheritedRoleNamesAsync(
+ await Meteor.roles.findOneAsync({ _id: r._id })
+ )
+ await Meteor.roleAssignment.updateAsync(
+ {
+ 'role._id': r._id
+ },
+ {
+ $set: {
+ inheritedRoles: [r._id, ...inheritedRoles].map((r2) => ({
+ _id: r2
+ }))
+ }
+ },
+ { multi: true }
+ )
+ }
+ } while (roles.length > 0)
+
+ // And finally remove the role itself
+ await Meteor.roles.removeAsync({ _id: roleName })
+ },
+
+ /**
+ * Rename an existing role.
+ *
+ * @method renameRoleAsync
+ * @param {String} oldName Old name of a role.
+ * @param {String} newName New name of a role.
+ * @returns {Promise}
+ * @static
+ */
+ renameRoleAsync: async function (oldName, newName) {
+ let count
+
+ Roles._checkRoleName(oldName)
+ Roles._checkRoleName(newName)
+
+ if (oldName === newName) return
+
+ const role = await Meteor.roles.findOneAsync({ _id: oldName })
+
+ if (!role) {
+ throw new Error("Role '" + oldName + "' does not exist.")
+ }
+
+ role._id = newName
+
+ await Meteor.roles.insertAsync(role)
+
+ do {
+ count = await Meteor.roleAssignment.updateAsync(
+ {
+ 'role._id': oldName
+ },
+ {
+ $set: {
+ 'role._id': newName
+ }
+ },
+ { multi: true }
+ )
+ } while (count > 0)
+
+ do {
+ count = await Meteor.roleAssignment.updateAsync(
+ {
+ 'inheritedRoles._id': oldName
+ },
+ {
+ $set: {
+ 'inheritedRoles.$._id': newName
+ }
+ },
+ { multi: true }
+ )
+ } while (count > 0)
+
+ do {
+ count = await Meteor.roles.updateAsync(
+ {
+ 'children._id': oldName
+ },
+ {
+ $set: {
+ 'children.$._id': newName
+ }
+ },
+ { multi: true }
+ )
+ } while (count > 0)
+
+ await Meteor.roles.removeAsync({ _id: oldName })
+ },
+
+ /**
+ * Add role parent to roles.
+ *
+ * Previous parents are kept (role can have multiple parents). For users which have the
+ * parent role set, new subroles are added automatically.
+ *
+ * @method addRolesToParentAsync
+ * @param {Array|String} rolesNames Name(s) of role(s).
+ * @param {String} parentName Name of parent role.
+ * @returns {Promise}
+ * @static
+ */
+ addRolesToParentAsync: async function (rolesNames, parentName) {
+ // ensure arrays
+ if (!Array.isArray(rolesNames)) rolesNames = [rolesNames]
+
+ for (const roleName of rolesNames) {
+ await Roles._addRoleToParentAsync(roleName, parentName)
+ }
+ },
+
+ /**
+ * @method _addRoleToParentAsync
+ * @param {String} roleName Name of role.
+ * @param {String} parentName Name of parent role.
+ * @returns {Promise}
+ * @private
+ * @static
+ */
+ _addRoleToParentAsync: async function (roleName, parentName) {
+ Roles._checkRoleName(roleName)
+ Roles._checkRoleName(parentName)
+
+ // query to get role's children
+ const role = await Meteor.roles.findOneAsync({ _id: roleName })
+
+ if (!role) {
+ throw new Error("Role '" + roleName + "' does not exist.")
+ }
+
+ // detect cycles
+ if ((await Roles._getInheritedRoleNamesAsync(role)).includes(parentName)) {
+ throw new Error(
+ "Roles '" + roleName + "' and '" + parentName + "' would form a cycle."
+ )
+ }
+
+ const count = await Meteor.roles.updateAsync(
+ {
+ _id: parentName,
+ 'children._id': {
+ $ne: role._id
+ }
+ },
+ {
+ $push: {
+ children: {
+ _id: role._id
+ }
+ }
+ }
+ )
+
+ // if there was no change, parent role might not exist, or role is
+ // already a sub-role; in any case we do not have anything more to do
+ if (!count) return
+
+ await Meteor.roleAssignment.updateAsync(
+ {
+ 'inheritedRoles._id': parentName
+ },
+ {
+ $push: {
+ inheritedRoles: {
+ $each: [
+ role._id,
+ ...(await Roles._getInheritedRoleNamesAsync(role))
+ ].map((r) => ({ _id: r }))
+ }
+ }
+ },
+ { multi: true }
+ )
+ },
+
+ /**
+ * Remove role parent from roles.
+ *
+ * Other parents are kept (role can have multiple parents). For users which have the
+ * parent role set, removed subrole is removed automatically.
+ *
+ * @method removeRolesFromParentAsync
+ * @param {Array|String} rolesNames Name(s) of role(s).
+ * @param {String} parentName Name of parent role.
+ * @returns {Promise}
+ * @static
+ */
+ removeRolesFromParentAsync: async function (rolesNames, parentName) {
+ // ensure arrays
+ if (!Array.isArray(rolesNames)) rolesNames = [rolesNames]
+
+ for (const roleName of rolesNames) {
+ await Roles._removeRoleFromParentAsync(roleName, parentName)
+ }
+ },
+
+ /**
+ * @method _removeRoleFromParentAsync
+ * @param {String} roleName Name of role.
+ * @param {String} parentName Name of parent role.
+ * @returns {Promise}
+ * @private
+ * @static
+ */
+ _removeRoleFromParentAsync: async function (roleName, parentName) {
+ Roles._checkRoleName(roleName)
+ Roles._checkRoleName(parentName)
+
+ // check for role existence
+ // this would not really be needed, but we are trying to match addRolesToParent
+ const role = await Meteor.roles.findOneAsync(
+ { _id: roleName },
+ { fields: { _id: 1 } }
+ )
+
+ if (!role) {
+ throw new Error("Role '" + roleName + "' does not exist.")
+ }
+
+ const count = await Meteor.roles.updateAsync(
+ {
+ _id: parentName
+ },
+ {
+ $pull: {
+ children: {
+ _id: role._id
+ }
+ }
+ }
+ )
+
+ // if there was no change, parent role might not exist, or role was
+ // already not a subrole; in any case we do not have anything more to do
+ if (!count) return
+
+ // For all roles who have had it as a dependency ...
+ const roles = [
+ ...(await Roles._getParentRoleNamesAsync(
+ await Meteor.roles.findOneAsync({ _id: parentName })
+ )),
+ parentName
+ ]
+
+ for (const r of await Meteor.roles
+ .find({ _id: { $in: roles } })
+ .fetchAsync()) {
+ const inheritedRoles = await Roles._getInheritedRoleNamesAsync(
+ await Meteor.roles.findOneAsync({ _id: r._id })
+ )
+ await Meteor.roleAssignment.updateAsync(
+ {
+ 'role._id': r._id,
+ 'inheritedRoles._id': role._id
+ },
+ {
+ $set: {
+ inheritedRoles: [r._id, ...inheritedRoles].map((r2) => ({
+ _id: r2
+ }))
+ }
+ },
+ { multi: true }
+ )
+ }
+ },
+
+ /**
+ * Add users to roles.
+ *
+ * Adds roles to existing roles for each user.
+ *
+ * @example
+ * Roles.addUsersToRolesAsync(userId, 'admin')
+ * Roles.addUsersToRolesAsync(userId, ['view-secrets'], 'example.com')
+ * Roles.addUsersToRolesAsync([user1, user2], ['user','editor'])
+ * Roles.addUsersToRolesAsync([user1, user2], ['glorious-admin', 'perform-action'], 'example.org')
+ *
+ * @method addUsersToRolesAsync
+ * @param {Array|String} users User ID(s) or object(s) with an `_id` field.
+ * @param {Array|String} roles Name(s) of roles to add users to. Roles have to exist.
+ * @param {Object|String} [options] Options:
+ * - `scope`: name of the scope, or `null` for the global role
+ * - `ifExists`: if `true`, do not throw an exception if the role does not exist
+ * @returns {Promise}
+ *
+ * Alternatively, it can be a scope name string.
+ * @static
+ */
+ addUsersToRolesAsync: async function (users, roles, options) {
+ let id
+
+ if (!users) throw new Error("Missing 'users' param.")
+ if (!roles) throw new Error("Missing 'roles' param.")
+
+ options = Roles._normalizeOptions(options)
+
+ // ensure arrays
+ if (!Array.isArray(users)) users = [users]
+ if (!Array.isArray(roles)) roles = [roles]
+
+ Roles._checkScopeName(options.scope)
+
+ options = Object.assign(
+ {
+ ifExists: false
+ },
+ options
+ )
+
+ for (const user of users) {
+ if (typeof user === 'object') {
+ id = user._id
+ } else {
+ id = user
+ }
+
+ for (const role of roles) {
+ await Roles._addUserToRoleAsync(id, role, options)
+ }
+ }
+ },
+
+ /**
+ * Set users' roles.
+ *
+ * Replaces all existing roles with a new set of roles.
+ *
+ * @example
+ * await Roles.setUserRolesAsync(userId, 'admin')
+ * await Roles.setUserRolesAsync(userId, ['view-secrets'], 'example.com')
+ * await Roles.setUserRolesAsync([user1, user2], ['user','editor'])
+ * await Roles.setUserRolesAsync([user1, user2], ['glorious-admin', 'perform-action'], 'example.org')
+ *
+ * @method setUserRolesAsync
+ * @param {Array|String} users User ID(s) or object(s) with an `_id` field.
+ * @param {Array|String} roles Name(s) of roles to add users to. Roles have to exist.
+ * @param {Object|String} [options] Options:
+ * - `scope`: name of the scope, or `null` for the global role
+ * - `anyScope`: if `true`, remove all roles the user has, of any scope, if `false`, only the one in the same scope
+ * - `ifExists`: if `true`, do not throw an exception if the role does not exist
+ * @returns {Promise}
+ *
+ * Alternatively, it can be a scope name string.
+ * @static
+ */
+ setUserRolesAsync: async function (users, roles, options) {
+ let id
+
+ if (!users) throw new Error("Missing 'users' param.")
+ if (!roles) throw new Error("Missing 'roles' param.")
+
+ options = Roles._normalizeOptions(options)
+
+ // ensure arrays
+ if (!Array.isArray(users)) users = [users]
+ if (!Array.isArray(roles)) roles = [roles]
+
+ Roles._checkScopeName(options.scope)
+
+ options = Object.assign(
+ {
+ ifExists: false,
+ anyScope: false
+ },
+ options
+ )
+
+ for (const user of users) {
+ if (typeof user === 'object') {
+ id = user._id
+ } else {
+ id = user
+ }
+ // we first clear all roles for the user
+ const selector = { 'user._id': id }
+ if (!options.anyScope) {
+ selector.scope = options.scope
+ }
+
+ await Meteor.roleAssignment.removeAsync(selector)
+
+ // and then add all
+ for (const role of roles) {
+ await Roles._addUserToRole(id, role, options)
+ }
+ }
+ },
+
+ /**
+ * Add one user to one role.
+ *
+ * @method _addUserToRole
+ * @param {String} userId The user ID.
+ * @param {String} roleName Name of the role to add the user to. The role have to exist.
+ * @param {Object} options Options:
+ * - `scope`: name of the scope, or `null` for the global role
+ * - `ifExists`: if `true`, do not throw an exception if the role does not exist
+ * @returns {Promise}
+ * @private
+ * @static
+ */
+ _addUserToRoleAsync: async function (userId, roleName, options) {
+ Roles._checkRoleName(roleName)
+ Roles._checkScopeName(options.scope)
+
+ if (!userId) {
+ return
+ }
+
+ const role = await Meteor.roles.findOneAsync(
+ { _id: roleName },
+ { fields: { children: 1 } }
+ )
+
+ if (!role) {
+ if (options.ifExists) {
+ return []
+ } else {
+ throw new Error("Role '" + roleName + "' does not exist.")
+ }
+ }
+
+ // This might create duplicates, because we don't have a unique index, but that's all right. In case there are two, withdrawing the role will effectively kill them both.
+ // TODO revisit this
+ /* const res = await Meteor.roleAssignment.upsertAsync(
+ {
+ "user._id": userId,
+ "role._id": roleName,
+ scope: options.scope,
+ },
+ {
+ $setOnInsert: {
+ user: { _id: userId },
+ role: { _id: roleName },
+ scope: options.scope,
+ },
+ }
+ ); */
+ const existingAssignment = await Meteor.roleAssignment.findOneAsync({
+ 'user._id': userId,
+ 'role._id': roleName,
+ scope: options.scope
+ })
+
+ let insertedId
+ let res
+ if (existingAssignment) {
+ await Meteor.roleAssignment.updateAsync(existingAssignment._id, {
+ $set: {
+ user: { _id: userId },
+ role: { _id: roleName },
+ scope: options.scope
+ }
+ })
+
+ res = await Meteor.roleAssignment.findOneAsync(existingAssignment._id)
+ } else {
+ insertedId = await Meteor.roleAssignment.insertAsync({
+ user: { _id: userId },
+ role: { _id: roleName },
+ scope: options.scope
+ })
+ }
+
+ if (insertedId) {
+ await Meteor.roleAssignment.updateAsync(
+ { _id: insertedId },
+ {
+ $set: {
+ inheritedRoles: [
+ roleName,
+ ...(await Roles._getInheritedRoleNamesAsync(role))
+ ].map((r) => ({ _id: r }))
+ }
+ }
+ )
+
+ res = await Meteor.roleAssignment.findOneAsync({ _id: insertedId })
+ }
+ res.insertedId = insertedId // For backward compatibility
+
+ return res
+ },
+
+ /**
+ * Returns an array of role names the given role name is a child of.
+ *
+ * @example
+ * Roles._getParentRoleNames({ _id: 'admin', children; [] })
+ *
+ * @method _getParentRoleNames
+ * @param {object} role The role object
+ * @returns {Promise}
+ * @private
+ * @static
+ */
+ _getParentRoleNamesAsync: async function (role) {
+ if (!role) {
+ return []
+ }
+
+ const parentRoles = new Set([role._id])
+
+ for (const roleName of parentRoles) {
+ for (const parentRole of await Meteor.roles
+ .find({ 'children._id': roleName })
+ .fetchAsync()) {
+ parentRoles.add(parentRole._id)
+ }
+ }
+
+ parentRoles.delete(role._id)
+
+ return [...parentRoles]
+ },
+
+ /**
+ * Returns an array of role names the given role name is a parent of.
+ *
+ * @example
+ * Roles._getInheritedRoleNames({ _id: 'admin', children; [] })
+ *
+ * @method _getInheritedRoleNames
+ * @param {object} role The role object
+ * @returns {Promise}
+ * @private
+ * @static
+ */
+ _getInheritedRoleNamesAsync: async function (role) {
+ const inheritedRoles = new Set()
+ const nestedRoles = new Set([role])
+
+ for (const r of nestedRoles) {
+ const roles = await Meteor.roles
+ .find(
+ { _id: { $in: r.children.map((r) => r._id) } },
+ { fields: { children: 1 } }
+ )
+ .fetchAsync()
+
+ for (const r2 of roles) {
+ inheritedRoles.add(r2._id)
+ nestedRoles.add(r2)
+ }
+ }
+
+ return [...inheritedRoles]
+ },
+
+ /**
+ * Remove users from assigned roles.
+ *
+ * @example
+ * await Roles.removeUsersFromRolesAsync(userId, 'admin')
+ * await Roles.removeUsersFromRolesAsync([userId, user2], ['editor'])
+ * await Roles.removeUsersFromRolesAsync(userId, ['user'], 'group1')
+ *
+ * @method removeUsersFromRolesAsync
+ * @param {Array|String} users User ID(s) or object(s) with an `_id` field.
+ * @param {Array|String} roles Name(s) of roles to remove users from. Roles have to exist.
+ * @param {Object|String} [options] Options:
+ * - `scope`: name of the scope, or `null` for the global role
+ * - `anyScope`: if set, role can be in any scope (`scope` option is ignored)
+ * @returns {Promise}
+ *
+ * Alternatively, it can be a scope name string.
+ * @static
+ */
+ removeUsersFromRolesAsync: async function (users, roles, options) {
+ if (!users) throw new Error("Missing 'users' param.")
+ if (!roles) throw new Error("Missing 'roles' param.")
+
+ options = Roles._normalizeOptions(options)
+
+ // ensure arrays
+ if (!Array.isArray(users)) users = [users]
+ if (!Array.isArray(roles)) roles = [roles]
+
+ Roles._checkScopeName(options.scope)
+
+ for (const user of users) {
+ if (!user) return
+
+ for (const role of roles) {
+ let id
+ if (typeof user === 'object') {
+ id = user._id
+ } else {
+ id = user
+ }
+
+ await Roles._removeUserFromRoleAsync(id, role, options)
+ }
+ }
+ },
+
+ /**
+ * Remove one user from one role.
+ *
+ * @method _removeUserFromRole
+ * @param {String} userId The user ID.
+ * @param {String} roleName Name of the role to add the user to. The role have to exist.
+ * @param {Object} options Options:
+ * - `scope`: name of the scope, or `null` for the global role
+ * - `anyScope`: if set, role can be in any scope (`scope` option is ignored)
+ * @returns {Promise}
+ * @private
+ * @static
+ */
+ _removeUserFromRoleAsync: async function (userId, roleName, options) {
+ Roles._checkRoleName(roleName)
+ Roles._checkScopeName(options.scope)
+
+ if (!userId) return
+
+ const selector = {
+ 'user._id': userId,
+ 'role._id': roleName
+ }
+
+ if (!options.anyScope) {
+ selector.scope = options.scope
+ }
+
+ await Meteor.roleAssignment.removeAsync(selector)
+ },
+
+ /**
+ * Check if user has specified roles.
+ *
+ * @example
+ * // global roles
+ * await Roles.userIsInRoleAsync(user, 'admin')
+ * await Roles.userIsInRoleAsync(user, ['admin','editor'])
+ * await Roles.userIsInRoleAsync(userId, 'admin')
+ * await Roles.userIsInRoleAsync(userId, ['admin','editor'])
+ *
+ * // scope roles (global roles are still checked)
+ * await Roles.userIsInRoleAsync(user, 'admin', 'group1')
+ * await Roles.userIsInRoleAsync(userId, ['admin','editor'], 'group1')
+ * await Roles.userIsInRoleAsync(userId, ['admin','editor'], {scope: 'group1'})
+ *
+ * @method userIsInRoleAsync
+ * @param {String|Object} user User ID or an actual user object.
+ * @param {Array|String} roles Name of role or an array of roles to check against. If array,
+ * will return `true` if user is in _any_ role.
+ * Roles do not have to exist.
+ * @param {Object|String} [options] Options:
+ * - `scope`: name of the scope; if supplied, limits check to just that scope
+ * the user's global roles will always be checked whether scope is specified or not
+ * - `anyScope`: if set, role can be in any scope (`scope` option is ignored)
+ *
+ * Alternatively, it can be a scope name string.
+ * @return {Promise} `true` if user is in _any_ of the target roles
+ * @static
+ */
+ userIsInRoleAsync: async function (user, roles, options) {
+ let id
+
+ options = Roles._normalizeOptions(options)
+
+ // ensure array to simplify code
+ if (!Array.isArray(roles)) roles = [roles]
+
+ roles = roles.filter((r) => r != null)
+
+ if (!roles.length) return false
+
+ Roles._checkScopeName(options.scope)
+
+ options = Object.assign(
+ {
+ anyScope: false
+ },
+ options
+ )
+
+ if (user && typeof user === 'object') {
+ id = user._id
+ } else {
+ id = user
+ }
+
+ if (!id) return false
+ if (typeof id !== 'string') return false
+
+ const selector = {
+ 'user._id': id
+ }
+
+ if (!options.anyScope) {
+ selector.scope = { $in: [options.scope, null] }
+ }
+
+ const res = await asyncSome(roles, async (roleName) => {
+ selector['inheritedRoles._id'] = roleName
+ const out =
+ (await Meteor.roleAssignment
+ .find(selector, { limit: 1 })
+ .countAsync()) > 0
+ return out
+ })
+
+ return res
+ },
+
+ /**
+ * Retrieve user's roles.
+ *
+ * @method getRolesForUserAsync
+ * @param {String|Object} user User ID or an actual user object.
+ * @param {Object|String} [options] Options:
+ * - `scope`: name of scope to provide roles for; if not specified, global roles are returned
+ * - `anyScope`: if set, role can be in any scope (`scope` and `onlyAssigned` options are ignored)
+ * - `onlyScoped`: if set, only roles in the specified scope are returned
+ * - `onlyAssigned`: return only assigned roles and not automatically inferred (like subroles)
+ * - `fullObjects`: return full roles objects (`true`) or just names (`false`) (`onlyAssigned` option is ignored) (default `false`)
+ * If you have a use-case for this option, please file a feature-request. You shouldn't need to use it as it's
+ * result strongly dependent on the internal data structure of this plugin.
+ *
+ * Alternatively, it can be a scope name string.
+ * @return {Promise} Array of user's roles, unsorted.
+ * @static
+ */
+ getRolesForUserAsync: async function (user, options) {
+ let id
+
+ options = Roles._normalizeOptions(options)
+
+ Roles._checkScopeName(options.scope)
+
+ options = Object.assign({
+ fullObjects: false,
+ onlyAssigned: false,
+ anyScope: false,
+ onlyScoped: false
+ }, options)
+
+ if (user && typeof user === 'object') {
+ id = user._id
+ } else {
+ id = user
+ }
+
+ if (!id) return []
+
+ const selector = {
+ 'user._id': id
+ }
+
+ const filter = {
+ fields: { 'inheritedRoles._id': 1 }
+ }
+
+ if (!options.anyScope) {
+ selector.scope = { $in: [options.scope] }
+
+ if (!options.onlyScoped) {
+ selector.scope.$in.push(null)
+ }
+ }
+
+ if (options.onlyAssigned) {
+ delete filter.fields['inheritedRoles._id']
+ filter.fields['role._id'] = 1
+ }
+
+ if (options.fullObjects) {
+ delete filter.fields
+ }
+
+ const roles = await Meteor.roleAssignment.find(selector, filter).fetchAsync()
+
+ if (options.fullObjects) {
+ return roles
+ }
+
+ return [
+ ...new Set(
+ roles.reduce((rev, current) => {
+ if (current.inheritedRoles) {
+ return rev.concat(current.inheritedRoles.map((r) => r._id))
+ } else if (current.role) {
+ rev.push(current.role._id)
+ }
+ return rev
+ }, [])
+ )
+ ]
+ },
+
+ /**
+ * Retrieve cursor of all existing roles.
+ *
+ * @method getAllRoles
+ * @param {Object} [queryOptions] Options which are passed directly
+ * through to `Meteor.roles.find(query, options)`.
+ * @return {Cursor} Cursor of existing roles.
+ * @static
+ */
+ getAllRoles: function (queryOptions) {
+ queryOptions = queryOptions || { sort: { _id: 1 } }
+
+ return Meteor.roles.find({}, queryOptions)
+ },
+
+ /**
+ * Retrieve all users who are in target role.
+ *
+ * Options:
+ *
+ * @method getUsersInRoleAsync
+ * @param {Array|String} roles Name of role or an array of roles. If array, users
+ * returned will have at least one of the roles
+ * specified but need not have _all_ roles.
+ * Roles do not have to exist.
+ * @param {Object|String} [options] Options:
+ * - `scope`: name of the scope to restrict roles to; user's global
+ * roles will also be checked
+ * - `anyScope`: if set, role can be in any scope (`scope` option is ignored)
+ * - `onlyScoped`: if set, only roles in the specified scope are returned
+ * - `queryOptions`: options which are passed directly
+ * through to `Meteor.users.find(query, options)`
+ *
+ * Alternatively, it can be a scope name string.
+ * @param {Object} [queryOptions] Options which are passed directly
+ * through to `Meteor.users.find(query, options)`
+ * @return {Promise} Cursor of users in roles.
+ * @static
+ */
+ getUsersInRoleAsync: async function (roles, options, queryOptions) {
+ const ids = (
+ await Roles.getUserAssignmentsForRole(roles, options).fetchAsync()
+ ).map((a) => a.user._id)
+
+ return Meteor.users.find(
+ { _id: { $in: ids } },
+ (options && options.queryOptions) || queryOptions || {}
+ )
+ },
+
+ /**
+ * Retrieve all assignments of a user which are for the target role.
+ *
+ * Options:
+ *
+ * @method getUserAssignmentsForRole
+ * @param {Array|String} roles Name of role or an array of roles. If array, users
+ * returned will have at least one of the roles
+ * specified but need not have _all_ roles.
+ * Roles do not have to exist.
+ * @param {Object|String} [options] Options:
+ * - `scope`: name of the scope to restrict roles to; user's global
+ * roles will also be checked
+ * - `anyScope`: if set, role can be in any scope (`scope` option is ignored)
+ * - `queryOptions`: options which are passed directly
+ * through to `Meteor.roleAssignment.find(query, options)`
+
+ * Alternatively, it can be a scope name string.
+ * @return {Cursor} Cursor of user assignments for roles.
+ * @static
+ */
+ getUserAssignmentsForRole: function (roles, options) {
+ options = Roles._normalizeOptions(options)
+
+ options = Object.assign(
+ {
+ anyScope: false,
+ queryOptions: {}
+ },
+ options
+ )
+
+ return Roles._getUsersInRoleCursor(roles, options, options.queryOptions)
+ },
+
+ /**
+ * @method _getUsersInRoleCursor
+ * @param {Array|String} roles Name of role or an array of roles. If array, ids of users are
+ * returned which have at least one of the roles
+ * assigned but need not have _all_ roles.
+ * Roles do not have to exist.
+ * @param {Object|String} [options] Options:
+ * - `scope`: name of the scope to restrict roles to; user's global
+ * roles will also be checked
+ * - `anyScope`: if set, role can be in any scope (`scope` option is ignored)
+ *
+ * Alternatively, it can be a scope name string.
+ * @param {Object} [filter] Options which are passed directly
+ * through to `Meteor.roleAssignment.find(query, options)`
+ * @return {Object} Cursor to the assignment documents
+ * @private
+ * @static
+ */
+ _getUsersInRoleCursor: function (roles, options, filter) {
+ options = Roles._normalizeOptions(options)
+
+ options = Object.assign(
+ {
+ anyScope: false,
+ onlyScoped: false
+ },
+ options
+ )
+
+ // ensure array to simplify code
+ if (!Array.isArray(roles)) roles = [roles]
+
+ Roles._checkScopeName(options.scope)
+
+ filter = Object.assign(
+ {
+ fields: { 'user._id': 1 }
+ },
+ filter
+ )
+
+ const selector = {
+ 'inheritedRoles._id': { $in: roles }
+ }
+
+ if (!options.anyScope) {
+ selector.scope = { $in: [options.scope] }
+
+ if (!options.onlyScoped) {
+ selector.scope.$in.push(null)
+ }
+ }
+
+ return Meteor.roleAssignment.find(selector, filter)
+ },
+
+ /**
+ * Deprecated. Use `getScopesForUser` instead.
+ *
+ * @method getGroupsForUserAsync
+ * @returns {Promise}
+ * @static
+ * @deprecated
+ */
+ getGroupsForUserAsync: async function (...args) {
+ if (!getGroupsForUserDeprecationWarning) {
+ getGroupsForUserDeprecationWarning = true
+ console &&
+ console.warn(
+ 'getGroupsForUser has been deprecated. Use getScopesForUser instead.'
+ )
+ }
+
+ return await Roles.getScopesForUser(...args)
+ },
+
+ /**
+ * Retrieve users scopes, if any.
+ *
+ * @method getScopesForUserAsync
+ * @param {String|Object} user User ID or an actual user object.
+ * @param {Array|String} [roles] Name of roles to restrict scopes to.
+ *
+ * @return {Promise} Array of user's scopes, unsorted.
+ * @static
+ */
+ getScopesForUserAsync: async function (user, roles) {
+ let id
+
+ if (roles && !Array.isArray(roles)) roles = [roles]
+
+ if (user && typeof user === 'object') {
+ id = user._id
+ } else {
+ id = user
+ }
+
+ if (!id) return []
+
+ const selector = {
+ 'user._id': id,
+ scope: { $ne: null }
+ }
+
+ if (roles) {
+ selector['inheritedRoles._id'] = { $in: roles }
+ }
+
+ const scopes = (
+ await Meteor.roleAssignment
+ .find(selector, { fields: { scope: 1 } })
+ .fetchAsync()
+ ).map((obi) => obi.scope)
+
+ return [...new Set(scopes)]
+ },
+
+ /**
+ * Rename a scope.
+ *
+ * Roles assigned with a given scope are changed to be under the new scope.
+ *
+ * @method renameScopeAsync
+ * @param {String} oldName Old name of a scope.
+ * @param {String} newName New name of a scope.
+ * @returns {Promise}
+ * @static
+ */
+ renameScopeAsync: async function (oldName, newName) {
+ let count
+
+ Roles._checkScopeName(oldName)
+ Roles._checkScopeName(newName)
+
+ if (oldName === newName) return
+
+ do {
+ count = await Meteor.roleAssignment.updateAsync(
+ {
+ scope: oldName
+ },
+ {
+ $set: {
+ scope: newName
+ }
+ },
+ { multi: true }
+ )
+ } while (count > 0)
+ },
+
+ /**
+ * Remove a scope.
+ *
+ * Roles assigned with a given scope are removed.
+ *
+ * @method removeScopeAsync
+ * @param {String} name The name of a scope.
+ * @returns {Promise}
+ * @static
+ */
+ removeScopeAsync: async function (name) {
+ Roles._checkScopeName(name)
+
+ await Meteor.roleAssignment.removeAsync({ scope: name })
+ },
+
+ /**
+ * Throw an exception if `roleName` is an invalid role name.
+ *
+ * @method _checkRoleName
+ * @param {String} roleName A role name to match against.
+ * @private
+ * @static
+ */
+ _checkRoleName: function (roleName) {
+ if (
+ !roleName ||
+ typeof roleName !== 'string' ||
+ roleName.trim() !== roleName
+ ) {
+ throw new Error("Invalid role name '" + roleName + "'.")
+ }
+ },
+
+ /**
+ * Find out if a role is an ancestor of another role.
+ *
+ * WARNING: If you check this on the client, please make sure all roles are published.
+ *
+ * @method isParentOfAsync
+ * @param {String} parentRoleName The role you want to research.
+ * @param {String} childRoleName The role you expect to be among the children of parentRoleName.
+ * @returns {Promise}
+ * @static
+ */
+ isParentOfAsync: async function (parentRoleName, childRoleName) {
+ if (parentRoleName === childRoleName) {
+ return true
+ }
+
+ if (parentRoleName == null || childRoleName == null) {
+ return false
+ }
+
+ Roles._checkRoleName(parentRoleName)
+ Roles._checkRoleName(childRoleName)
+
+ let rolesToCheck = [parentRoleName]
+ while (rolesToCheck.length !== 0) {
+ const roleName = rolesToCheck.pop()
+
+ if (roleName === childRoleName) {
+ return true
+ }
+
+ const role = await Meteor.roles.findOneAsync({ _id: roleName })
+
+ // This should not happen, but this is a problem to address at some other time.
+ if (!role) continue
+
+ rolesToCheck = rolesToCheck.concat(role.children.map((r) => r._id))
+ }
+
+ return false
+ },
+
+ /**
+ * Normalize options.
+ *
+ * @method _normalizeOptions
+ * @param {Object} options Options to normalize.
+ * @return {Object} Normalized options.
+ * @private
+ * @static
+ */
+ _normalizeOptions: function (options) {
+ options = options === undefined ? {} : options
+
+ if (options === null || typeof options === 'string') {
+ options = { scope: options }
+ }
+
+ options.scope = Roles._normalizeScopeName(options.scope)
+
+ return options
+ },
+
+ /**
+ * Normalize scope name.
+ *
+ * @method _normalizeScopeName
+ * @param {String} scopeName A scope name to normalize.
+ * @return {String} Normalized scope name.
+ * @private
+ * @static
+ */
+ _normalizeScopeName: function (scopeName) {
+ // map undefined and null to null
+ if (scopeName == null) {
+ return null
+ } else {
+ return scopeName
+ }
+ },
+
+ /**
+ * Throw an exception if `scopeName` is an invalid scope name.
+ *
+ * @method _checkRoleName
+ * @param {String} scopeName A scope name to match against.
+ * @private
+ * @static
+ */
+ _checkScopeName: function (scopeName) {
+ if (scopeName === null) return
+
+ if (
+ !scopeName ||
+ typeof scopeName !== 'string' ||
+ scopeName.trim() !== scopeName
+ ) {
+ throw new Error("Invalid scope name '" + scopeName + "'.")
+ }
+ }
+})
diff --git a/roles/roles_server.js b/roles/roles_server.js
index 78c11c39..fdf03794 100644
--- a/roles/roles_server.js
+++ b/roles/roles_server.js
@@ -79,7 +79,7 @@ Object.assign(Roles, {
* @static
*/
_isNewField: function (roles) {
- return Array.isArray(roles) && (typeof roles[0] === 'object')
+ return Array.isArray(roles) && typeof roles[0] === 'object'
},
/**
@@ -92,7 +92,10 @@ Object.assign(Roles, {
* @static
*/
_isOldField: function (roles) {
- return (Array.isArray(roles) && (typeof roles[0] === 'string')) || ((typeof roles === 'object') && !Array.isArray(roles))
+ return (
+ (Array.isArray(roles) && typeof roles[0] === 'string') ||
+ (typeof roles === 'object' && !Array.isArray(roles))
+ )
},
/**
@@ -104,7 +107,7 @@ Object.assign(Roles, {
* @static
*/
_convertToNewRole: function (oldRole) {
- if (!(typeof oldRole.name === 'string')) throw new Error("Role name '" + oldRole.name + "' is not a string.")
+ if (!(typeof oldRole.name === 'string')) { throw new Error("Role name '" + oldRole.name + "' is not a string.") }
return {
_id: oldRole.name,
@@ -121,7 +124,7 @@ Object.assign(Roles, {
* @static
*/
_convertToOldRole: function (newRole) {
- if (!(typeof newRole._id === 'string')) throw new Error("Role name '" + newRole._id + "' is not a string.")
+ if (!(typeof newRole._id === 'string')) { throw new Error("Role name '" + newRole._id + "' is not a string.") }
return {
name: newRole._id
@@ -141,7 +144,7 @@ Object.assign(Roles, {
const roles = []
if (Array.isArray(oldRoles)) {
oldRoles.forEach(function (role, index) {
- if (!(typeof role === 'string')) throw new Error("Role '" + role + "' is not a string.")
+ if (!(typeof role === 'string')) { throw new Error("Role '" + role + "' is not a string.") }
roles.push({
_id: role,
@@ -159,7 +162,7 @@ Object.assign(Roles, {
}
rolesArray.forEach(function (role) {
- if (!(typeof role === 'string')) throw new Error("Role '" + role + "' is not a string.")
+ if (!(typeof role === 'string')) { throw new Error("Role '" + role + "' is not a string.") }
roles.push({
_id: role,
@@ -191,18 +194,26 @@ Object.assign(Roles, {
}
newRoles.forEach(function (userRole) {
- if (!(typeof userRole === 'object')) throw new Error("Role '" + userRole + "' is not an object.")
+ if (!(typeof userRole === 'object')) { throw new Error("Role '" + userRole + "' is not an object.") }
// We assume that we are converting back a failed migration, so values can only be
// what were valid values in 1.0. So no group names starting with $ and no subroles.
if (userRole.scope) {
- if (!usingGroups) throw new Error("Role '" + userRole._id + "' with scope '" + userRole.scope + "' without enabled groups.")
+ if (!usingGroups) {
+ throw new Error(
+ "Role '" +
+ userRole._id +
+ "' with scope '" +
+ userRole.scope +
+ "' without enabled groups."
+ )
+ }
// escape
const scope = userRole.scope.replace(/\./g, '_')
- if (scope[0] === '$') throw new Error("Group name '" + scope + "' start with $.")
+ if (scope[0] === '$') { throw new Error("Group name '" + scope + "' start with $.") }
roles[scope] = roles[scope] || []
roles[scope].push(userRole._id)
@@ -227,13 +238,16 @@ Object.assign(Roles, {
* @static
*/
_defaultUpdateUser: function (user, roles) {
- Meteor.users.update({
- _id: user._id,
- // making sure nothing changed in meantime
- roles: user.roles
- }, {
- $set: { roles }
- })
+ Meteor.users.update(
+ {
+ _id: user._id,
+ // making sure nothing changed in meantime
+ roles: user.roles
+ },
+ {
+ $set: { roles }
+ }
+ )
},
/**
@@ -294,7 +308,10 @@ Object.assign(Roles, {
Meteor.users.find().forEach(function (user, index, cursor) {
if (!Roles._isNewField(user.roles)) {
- updateUser(user, Roles._convertToNewField(user.roles, convertUnderscoresToDots))
+ updateUser(
+ user,
+ Roles._convertToNewField(user.roles, convertUnderscoresToDots)
+ )
}
})
},
@@ -313,10 +330,15 @@ Object.assign(Roles, {
Object.assign(userSelector, { roles: { $ne: null } })
Meteor.users.find(userSelector).forEach(function (user, index) {
- user.roles.filter((r) => r.assigned).forEach(r => {
- // Added `ifExists` to make it less error-prone
- Roles._addUserToRole(user._id, r._id, { scope: r.scope, ifExists: true })
- })
+ user.roles
+ .filter((r) => r.assigned)
+ .forEach((r) => {
+ // Added `ifExists` to make it less error-prone
+ Roles._addUserToRole(user._id, r._id, {
+ scope: r.scope,
+ ifExists: true
+ })
+ })
Meteor.users.update({ _id: user._id }, { $unset: { roles: '' } })
})
@@ -381,10 +403,12 @@ Object.assign(Roles, {
Meteor.users._ensureIndex({ 'roles.scope': 1 })
}
- Meteor.roleAssignment.find(assignmentSelector).forEach(r => {
+ Meteor.roleAssignment.find(assignmentSelector).forEach((r) => {
const roles = Meteor.users.findOne({ _id: r.user._id }).roles || []
- const currentRole = roles.find(oldRole => oldRole._id === r.role._id && oldRole.scope === r.scope)
+ const currentRole = roles.find(
+ (oldRole) => oldRole._id === r.role._id && oldRole.scope === r.scope
+ )
if (currentRole) {
currentRole.assigned = true
} else {
@@ -394,8 +418,11 @@ Object.assign(Roles, {
assigned: true
})
- r.inheritedRoles.forEach(inheritedRole => {
- const currentInheritedRole = roles.find(oldRole => oldRole._id === inheritedRole._id && oldRole.scope === r.scope)
+ r.inheritedRoles.forEach((inheritedRole) => {
+ const currentInheritedRole = roles.find(
+ (oldRole) =>
+ oldRole._id === inheritedRole._id && oldRole.scope === r.scope
+ )
if (!currentInheritedRole) {
roles.push({
diff --git a/roles/tests/clientAsync.js b/roles/tests/clientAsync.js
new file mode 100644
index 00000000..ce1575e4
--- /dev/null
+++ b/roles/tests/clientAsync.js
@@ -0,0 +1,141 @@
+/* eslint-env mocha */
+/* global Roles */
+
+import { Meteor } from 'meteor/meteor'
+import chai, { assert } from 'chai'
+import chaiAsPromised from 'chai-as-promised'
+
+// To ensure that the files are loaded for coverage
+import '../roles_common'
+
+chai.use(chaiAsPromised)
+
+const safeInsert = async (collection, data) => {
+ try {
+ await collection.insertAsync(data)
+ } catch (e) {}
+}
+
+describe('roles async', function () {
+ const roles = ['admin', 'editor', 'user']
+ const users = {
+ eve: {
+ _id: 'eve'
+ },
+ bob: {
+ _id: 'bob'
+ },
+ joe: {
+ _id: 'joe'
+ }
+ }
+
+ async function testUser (username, expectedRoles, scope) {
+ const user = users[username]
+
+ // test using user object rather than userId to avoid mocking
+ for (const role of roles) {
+ const expected = expectedRoles.includes(role)
+ const msg = username + ' expected to have \'' + role + '\' permission but does not'
+ const nmsg = username + ' had un-expected permission ' + role
+
+ if (expected) {
+ assert.isTrue(await Roles.userIsInRoleAsync(user, role, scope), msg)
+ } else {
+ assert.isFalse(await Roles.userIsInRoleAsync(user, role, scope), nmsg)
+ }
+ }
+ }
+
+ let meteorUserMethod
+ before(() => {
+ meteorUserMethod = Meteor.user
+ // Mock Meteor.user() for isInRole handlebars helper testing
+ Meteor.user = function () {
+ return users.eve
+ }
+ })
+
+ after(() => {
+ Meteor.user = meteorUserMethod
+ })
+
+ beforeEach(async () => {
+ await safeInsert(Meteor.roleAssignment, {
+ user: users.eve,
+ role: { _id: 'admin' },
+ inheritedRoles: [{ _id: 'admin' }]
+ })
+ await safeInsert(Meteor.roleAssignment, {
+ user: users.eve,
+ role: { _id: 'editor' },
+ inheritedRoles: [{ _id: 'editor' }]
+ })
+
+ await safeInsert(Meteor.roleAssignment, {
+ user: users.bob,
+ role: { _id: 'user' },
+ inheritedRoles: [{ _id: 'user' }],
+ scope: 'group1'
+ })
+ await safeInsert(Meteor.roleAssignment, {
+ user: users.bob,
+ role: { _id: 'editor' },
+ inheritedRoles: [{ _id: 'editor' }],
+ scope: 'group2'
+ })
+
+ await safeInsert(Meteor.roleAssignment, {
+ user: users.joe,
+ role: { _id: 'admin' },
+ inheritedRoles: [{ _id: 'admin' }]
+ })
+ await safeInsert(Meteor.roleAssignment, {
+ user: users.joe,
+ role: { _id: 'editor' },
+ inheritedRoles: [{ _id: 'editor' }],
+ scope: 'group1'
+ })
+ })
+
+ it('can check current users roles via template helper', function () {
+ let expected
+ let actual
+
+ if (!Roles._handlebarsHelpers) {
+ // probably running package tests outside of a Meteor app.
+ // skip this test.
+ return
+ }
+
+ const isInRole = Roles._handlebarsHelpers.isInRole
+ assert.equal(typeof isInRole, 'function', "'isInRole' helper not registered")
+
+ expected = true
+ actual = isInRole('admin, editor')
+ assert.equal(actual, expected)
+
+ expected = true
+ actual = isInRole('admin')
+ assert.equal(actual, expected)
+
+ expected = false
+ actual = isInRole('unknown')
+ assert.equal(actual, expected)
+ })
+
+ it('can check if user is in role', async function () {
+ await testUser('eve', ['admin', 'editor'])
+ })
+
+ it('can check if user is in role by group', async function () {
+ await testUser('bob', ['user'], 'group1')
+ await testUser('bob', ['editor'], 'group2')
+ })
+
+ it('can check if user is in role with Roles.GLOBAL_GROUP', async function () {
+ await testUser('joe', ['admin'])
+ await testUser('joe', ['admin'], Roles.GLOBAL_GROUP)
+ await testUser('joe', ['admin', 'editor'], 'group1')
+ })
+})
diff --git a/roles/tests/server.js b/roles/tests/server.js
index b3fb854d..5ed65b56 100644
--- a/roles/tests/server.js
+++ b/roles/tests/server.js
@@ -1418,206 +1418,210 @@ describe('roles', function () {
assert.isFalse(Roles.userIsInRole(users.eve, 'user', 'scope2'))
})
- it('migration without global groups (to v2)', function () {
- assert.isOk(Meteor.roles.insert({ name: 'admin' }))
- assert.isOk(Meteor.roles.insert({ name: 'editor' }))
- assert.isOk(Meteor.roles.insert({ name: 'user' }))
-
- assert.isOk(Meteor.users.update(users.eve, { $set: { roles: ['admin', 'editor'] } }))
- assert.isOk(Meteor.users.update(users.bob, { $set: { roles: [] } }))
- assert.isOk(Meteor.users.update(users.joe, { $set: { roles: ['user'] } }))
-
- Roles._forwardMigrate()
-
- assert.deepEqual(Meteor.users.findOne(users.eve, { fields: { roles: 1, _id: 0 } }), {
- roles: [{
+ describe('v2 migration', function () {
+ it('migration without global groups (to v2)', function () {
+ assert.isOk(Meteor.roles.insert({ name: 'admin' }))
+ assert.isOk(Meteor.roles.insert({ name: 'editor' }))
+ assert.isOk(Meteor.roles.insert({ name: 'user' }))
+
+ assert.isOk(Meteor.users.update(users.eve, { $set: { roles: ['admin', 'editor'] } }))
+ assert.isOk(Meteor.users.update(users.bob, { $set: { roles: [] } }))
+ assert.isOk(Meteor.users.update(users.joe, { $set: { roles: ['user'] } }))
+
+ Roles._forwardMigrate()
+
+ assert.deepEqual(Meteor.users.findOne(users.eve, { fields: { roles: 1, _id: 0 } }), {
+ roles: [{
+ _id: 'admin',
+ scope: null,
+ assigned: true
+ }, {
+ _id: 'editor',
+ scope: null,
+ assigned: true
+ }]
+ })
+ assert.deepEqual(Meteor.users.findOne(users.bob, { fields: { roles: 1, _id: 0 } }), {
+ roles: []
+ })
+ assert.deepEqual(Meteor.users.findOne(users.joe, { fields: { roles: 1, _id: 0 } }), {
+ roles: [{
+ _id: 'user',
+ scope: null,
+ assigned: true
+ }]
+ })
+
+ assert.deepEqual(Meteor.roles.findOne({ _id: 'admin' }), {
_id: 'admin',
- scope: null,
- assigned: true
- }, {
+ children: []
+ })
+ assert.deepEqual(Meteor.roles.findOne({ _id: 'editor' }), {
_id: 'editor',
- scope: null,
- assigned: true
- }]
- })
- assert.deepEqual(Meteor.users.findOne(users.bob, { fields: { roles: 1, _id: 0 } }), {
- roles: []
- })
- assert.deepEqual(Meteor.users.findOne(users.joe, { fields: { roles: 1, _id: 0 } }), {
- roles: [{
+ children: []
+ })
+ assert.deepEqual(Meteor.roles.findOne({ _id: 'user' }), {
_id: 'user',
- scope: null,
- assigned: true
- }]
- })
-
- assert.deepEqual(Meteor.roles.findOne({ _id: 'admin' }), {
- _id: 'admin',
- children: []
- })
- assert.deepEqual(Meteor.roles.findOne({ _id: 'editor' }), {
- _id: 'editor',
- children: []
- })
- assert.deepEqual(Meteor.roles.findOne({ _id: 'user' }), {
- _id: 'user',
- children: []
- })
-
- Roles._backwardMigrate(null, null, false)
-
- assert.deepEqual(Meteor.users.findOne(users.eve, { fields: { roles: 1, _id: 0 } }), {
- roles: ['admin', 'editor']
- })
- assert.deepEqual(Meteor.users.findOne(users.bob, { fields: { roles: 1, _id: 0 } }), {
- roles: []
- })
- assert.deepEqual(Meteor.users.findOne(users.joe, { fields: { roles: 1, _id: 0 } }), {
- roles: ['user']
+ children: []
+ })
+
+ Roles._backwardMigrate(null, null, false)
+
+ assert.deepEqual(Meteor.users.findOne(users.eve, { fields: { roles: 1, _id: 0 } }), {
+ roles: ['admin', 'editor']
+ })
+ assert.deepEqual(Meteor.users.findOne(users.bob, { fields: { roles: 1, _id: 0 } }), {
+ roles: []
+ })
+ assert.deepEqual(Meteor.users.findOne(users.joe, { fields: { roles: 1, _id: 0 } }), {
+ roles: ['user']
+ })
+
+ assert.deepEqual(Meteor.roles.findOne({ name: 'admin' }, { fields: { _id: 0 } }), {
+ name: 'admin'
+ })
+ assert.deepEqual(Meteor.roles.findOne({ name: 'editor' }, { fields: { _id: 0 } }), {
+ name: 'editor'
+ })
+ assert.deepEqual(Meteor.roles.findOne({ name: 'user' }, { fields: { _id: 0 } }), {
+ name: 'user'
+ })
})
- assert.deepEqual(Meteor.roles.findOne({ name: 'admin' }, { fields: { _id: 0 } }), {
- name: 'admin'
- })
- assert.deepEqual(Meteor.roles.findOne({ name: 'editor' }, { fields: { _id: 0 } }), {
- name: 'editor'
- })
- assert.deepEqual(Meteor.roles.findOne({ name: 'user' }, { fields: { _id: 0 } }), {
- name: 'user'
- })
- })
-
- it('migration without global groups (to v3)')
-
- it('migration with global groups (to v2)', function () {
- assert.isOk(Meteor.roles.insert({ name: 'admin' }))
- assert.isOk(Meteor.roles.insert({ name: 'editor' }))
- assert.isOk(Meteor.roles.insert({ name: 'user' }))
-
- assert.isOk(Meteor.users.update(users.eve, { $set: { roles: { __global_roles__: ['admin', 'editor'], foo_bla: ['user'] } } }))
- assert.isOk(Meteor.users.update(users.bob, { $set: { roles: { } } }))
- assert.isOk(Meteor.users.update(users.joe, { $set: { roles: { __global_roles__: ['user'], foo_bla: ['user'] } } }))
-
- Roles._forwardMigrate(null, null, false)
-
- assert.deepEqual(Meteor.users.findOne(users.eve, { fields: { roles: 1, _id: 0 } }), {
- roles: [{
+ it('migration with global groups (to v2)', function () {
+ assert.isOk(Meteor.roles.insert({ name: 'admin' }))
+ assert.isOk(Meteor.roles.insert({ name: 'editor' }))
+ assert.isOk(Meteor.roles.insert({ name: 'user' }))
+
+ assert.isOk(Meteor.users.update(users.eve, { $set: { roles: { __global_roles__: ['admin', 'editor'], foo_bla: ['user'] } } }))
+ assert.isOk(Meteor.users.update(users.bob, { $set: { roles: { } } }))
+ assert.isOk(Meteor.users.update(users.joe, { $set: { roles: { __global_roles__: ['user'], foo_bla: ['user'] } } }))
+
+ Roles._forwardMigrate(null, null, false)
+
+ assert.deepEqual(Meteor.users.findOne(users.eve, { fields: { roles: 1, _id: 0 } }), {
+ roles: [{
+ _id: 'admin',
+ scope: null,
+ assigned: true
+ }, {
+ _id: 'editor',
+ scope: null,
+ assigned: true
+ }, {
+ _id: 'user',
+ scope: 'foo_bla',
+ assigned: true
+ }]
+ })
+ assert.deepEqual(Meteor.users.findOne(users.bob, { fields: { roles: 1, _id: 0 } }), {
+ roles: []
+ })
+ assert.deepEqual(Meteor.users.findOne(users.joe, { fields: { roles: 1, _id: 0 } }), {
+ roles: [{
+ _id: 'user',
+ scope: null,
+ assigned: true
+ }, {
+ _id: 'user',
+ scope: 'foo_bla',
+ assigned: true
+ }]
+ })
+
+ assert.deepEqual(Meteor.roles.findOne({ _id: 'admin' }), {
_id: 'admin',
- scope: null,
- assigned: true
- }, {
+ children: []
+ })
+ assert.deepEqual(Meteor.roles.findOne({ _id: 'editor' }), {
_id: 'editor',
- scope: null,
- assigned: true
- }, {
+ children: []
+ })
+ assert.deepEqual(Meteor.roles.findOne({ _id: 'user' }), {
_id: 'user',
- scope: 'foo_bla',
- assigned: true
- }]
- })
- assert.deepEqual(Meteor.users.findOne(users.bob, { fields: { roles: 1, _id: 0 } }), {
- roles: []
- })
- assert.deepEqual(Meteor.users.findOne(users.joe, { fields: { roles: 1, _id: 0 } }), {
- roles: [{
- _id: 'user',
- scope: null,
- assigned: true
- }, {
- _id: 'user',
- scope: 'foo_bla',
- assigned: true
- }]
- })
-
- assert.deepEqual(Meteor.roles.findOne({ _id: 'admin' }), {
- _id: 'admin',
- children: []
- })
- assert.deepEqual(Meteor.roles.findOne({ _id: 'editor' }), {
- _id: 'editor',
- children: []
- })
- assert.deepEqual(Meteor.roles.findOne({ _id: 'user' }), {
- _id: 'user',
- children: []
- })
-
- Roles._backwardMigrate(null, null, true)
-
- assert.deepEqual(Meteor.users.findOne(users.eve, { fields: { roles: 1, _id: 0 } }), {
- roles: {
- __global_roles__: ['admin', 'editor'],
- foo_bla: ['user']
- }
- })
- assert.deepEqual(Meteor.users.findOne(users.bob, { fields: { roles: 1, _id: 0 } }), {
- roles: {}
- })
- assert.deepEqual(Meteor.users.findOne(users.joe, { fields: { roles: 1, _id: 0 } }), {
- roles: {
- __global_roles__: ['user'],
- foo_bla: ['user']
- }
- })
-
- assert.deepEqual(Meteor.roles.findOne({ name: 'admin' }, { fields: { _id: 0 } }), {
- name: 'admin'
- })
- assert.deepEqual(Meteor.roles.findOne({ name: 'editor' }, { fields: { _id: 0 } }), {
- name: 'editor'
- })
- assert.deepEqual(Meteor.roles.findOne({ name: 'user' }, { fields: { _id: 0 } }), {
- name: 'user'
- })
-
- Roles._forwardMigrate(null, null, true)
-
- assert.deepEqual(Meteor.users.findOne(users.eve, { fields: { roles: 1, _id: 0 } }), {
- roles: [{
+ children: []
+ })
+
+ Roles._backwardMigrate(null, null, true)
+
+ assert.deepEqual(Meteor.users.findOne(users.eve, { fields: { roles: 1, _id: 0 } }), {
+ roles: {
+ __global_roles__: ['admin', 'editor'],
+ foo_bla: ['user']
+ }
+ })
+ assert.deepEqual(Meteor.users.findOne(users.bob, { fields: { roles: 1, _id: 0 } }), {
+ roles: {}
+ })
+ assert.deepEqual(Meteor.users.findOne(users.joe, { fields: { roles: 1, _id: 0 } }), {
+ roles: {
+ __global_roles__: ['user'],
+ foo_bla: ['user']
+ }
+ })
+
+ assert.deepEqual(Meteor.roles.findOne({ name: 'admin' }, { fields: { _id: 0 } }), {
+ name: 'admin'
+ })
+ assert.deepEqual(Meteor.roles.findOne({ name: 'editor' }, { fields: { _id: 0 } }), {
+ name: 'editor'
+ })
+ assert.deepEqual(Meteor.roles.findOne({ name: 'user' }, { fields: { _id: 0 } }), {
+ name: 'user'
+ })
+
+ Roles._forwardMigrate(null, null, true)
+
+ assert.deepEqual(Meteor.users.findOne(users.eve, { fields: { roles: 1, _id: 0 } }), {
+ roles: [{
+ _id: 'admin',
+ scope: null,
+ assigned: true
+ }, {
+ _id: 'editor',
+ scope: null,
+ assigned: true
+ }, {
+ _id: 'user',
+ scope: 'foo.bla',
+ assigned: true
+ }]
+ })
+ assert.deepEqual(Meteor.users.findOne(users.bob, { fields: { roles: 1, _id: 0 } }), {
+ roles: []
+ })
+ assert.deepEqual(Meteor.users.findOne(users.joe, { fields: { roles: 1, _id: 0 } }), {
+ roles: [{
+ _id: 'user',
+ scope: null,
+ assigned: true
+ }, {
+ _id: 'user',
+ scope: 'foo.bla',
+ assigned: true
+ }]
+ })
+
+ assert.deepEqual(Meteor.roles.findOne({ _id: 'admin' }), {
_id: 'admin',
- scope: null,
- assigned: true
- }, {
+ children: []
+ })
+ assert.deepEqual(Meteor.roles.findOne({ _id: 'editor' }), {
_id: 'editor',
- scope: null,
- assigned: true
- }, {
- _id: 'user',
- scope: 'foo.bla',
- assigned: true
- }]
- })
- assert.deepEqual(Meteor.users.findOne(users.bob, { fields: { roles: 1, _id: 0 } }), {
- roles: []
- })
- assert.deepEqual(Meteor.users.findOne(users.joe, { fields: { roles: 1, _id: 0 } }), {
- roles: [{
- _id: 'user',
- scope: null,
- assigned: true
- }, {
+ children: []
+ })
+ assert.deepEqual(Meteor.roles.findOne({ _id: 'user' }), {
_id: 'user',
- scope: 'foo.bla',
- assigned: true
- }]
- })
-
- assert.deepEqual(Meteor.roles.findOne({ _id: 'admin' }), {
- _id: 'admin',
- children: []
- })
- assert.deepEqual(Meteor.roles.findOne({ _id: 'editor' }), {
- _id: 'editor',
- children: []
- })
- assert.deepEqual(Meteor.roles.findOne({ _id: 'user' }), {
- _id: 'user',
- children: []
+ children: []
+ })
})
})
- it('migration with global groups (to v3)')
+ describe('v3 migration', function () {
+ it('migration without global groups (to v3)')
+
+ it('migration with global groups (to v3)')
+ })
it('_addUserToRole', function () {
Roles.createRole('admin')
diff --git a/roles/tests/serverAsync.js b/roles/tests/serverAsync.js
new file mode 100644
index 00000000..8d118aef
--- /dev/null
+++ b/roles/tests/serverAsync.js
@@ -0,0 +1,2123 @@
+/* eslint-env mocha */
+/* global Roles */
+
+import { Meteor } from 'meteor/meteor'
+import chai, { assert } from 'chai'
+import chaiAsPromised from 'chai-as-promised'
+
+// To ensure that the files are loaded for coverage
+import '../roles_server'
+import '../roles_common'
+
+chai.use(chaiAsPromised)
+
+// To allow inserting on the client, needed for testing.
+Meteor.roleAssignment.allow({
+ insert () { return true },
+ update () { return true },
+ remove () { return true }
+})
+
+const hasProp = (target, prop) => Object.hasOwnProperty.call(target, prop)
+
+describe('roles async', async function () {
+ let users = {}
+ const roles = ['admin', 'editor', 'user']
+
+ Meteor.publish('_roleAssignments', function () {
+ const loggedInUserId = this.userId
+
+ if (!loggedInUserId) {
+ this.ready()
+ return
+ }
+
+ return Meteor.roleAssignment.find({ _id: loggedInUserId })
+ })
+
+ async function addUser (name) {
+ return await Meteor.users.insertAsync({ username: name })
+ }
+
+ async function testUser (username, expectedRoles, scope) {
+ const userId = users[username]
+ const userObj = await Meteor.users.findOneAsync({ _id: userId })
+
+ // check using user ids (makes db calls)
+ await _innerTest(userId, username, expectedRoles, scope)
+
+ // check using passed-in user object
+ await _innerTest(userObj, username, expectedRoles, scope)
+ }
+
+ async function _innerTest (userParam, username, expectedRoles, scope) {
+ // test that user has only the roles expected and no others
+ for (const role of roles) {
+ const expected = expectedRoles.includes(role)
+ const msg = username + ' expected to have \'' + role + '\' role but does not'
+ const nmsg = username + ' had the following un-expected role: ' + role
+
+ if (expected) {
+ assert.isTrue(await Roles.userIsInRoleAsync(userParam, role, scope), msg)
+ } else {
+ assert.isFalse(await Roles.userIsInRoleAsync(userParam, role, scope), nmsg)
+ }
+ }
+ }
+
+ beforeEach(async function () {
+ await Meteor.roles.removeAsync({})
+ await Meteor.roleAssignment.removeAsync({})
+ await Meteor.users.removeAsync({})
+
+ users = {
+ eve: await addUser('eve'),
+ bob: await addUser('bob'),
+ joe: await addUser('joe')
+ }
+ })
+
+ it('can create and delete roles', async function () {
+ const role1Id = await Roles.createRoleAsync('test1')
+ const test1a = await Meteor.roles.findOneAsync()
+ const test1b = await Meteor.roles.findOneAsync(role1Id)
+ assert.equal(test1a._id, 'test1')
+ assert.equal(test1b._id, 'test1')
+
+ const role2Id = await Roles.createRoleAsync('test2')
+ const test2a = await Meteor.roles.findOneAsync({ _id: 'test2' })
+ const test2b = await Meteor.roles.findOneAsync(role2Id)
+ assert.equal(test2a._id, 'test2')
+ assert.equal(test2b._id, 'test2')
+
+ assert.equal(await Meteor.roles.countDocuments(), 2)
+
+ await Roles.deleteRoleAsync('test1')
+ const undefinedTest = await Meteor.roles.findOneAsync({ _id: 'test1' })
+ assert.equal(typeof undefinedTest, 'undefined')
+
+ await Roles.deleteRoleAsync('test2')
+ const undefinedTest2 = await Meteor.roles.findOneAsync()
+ assert.equal(typeof undefinedTest2, 'undefined')
+ })
+
+ it('can try to remove non-existing roles without crashing', async function () {
+ try {
+ await Roles.deleteRoleAsync('non-existing-role')
+ } catch (e) {
+ assert.notExists(e)
+ }
+ // Roles.deleteRoleAsync('non-existing-role').should.be.fulfilled
+ })
+
+ it('can\'t create duplicate roles', async function () {
+ try {
+ await Roles.createRoleAsync('test1')
+ } catch (e) {
+ assert.notExists(e)
+ }
+ // assert.eventually.throws(Roles.createRoleAsync('test1'))
+ try {
+ await Roles.createRoleAsync('test1')
+ } catch (e) {
+ assert.exists(e)
+ }
+ assert.isNull(await Roles.createRoleAsync('test1', { unlessExists: true }))
+ })
+
+ it('can\'t create role with empty names', async function () {
+ await assert.isRejected(Roles.createRoleAsync(''), /Invalid role name/)
+ await assert.isRejected(Roles.createRoleAsync(null), /Invalid role name/)
+ await assert.isRejected(Roles.createRoleAsync(' '), /Invalid role name/)
+ await assert.isRejected(Roles.createRoleAsync(' foobar'), /Invalid role name/)
+ await assert.isRejected(Roles.createRoleAsync(' foobar '), /Invalid role name/)
+ })
+
+ it('can\'t use invalid scope names', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+ await Roles.addUsersToRolesAsync(users.eve, ['admin', 'user'], 'scope1')
+ await Roles.addUsersToRolesAsync(users.eve, ['editor'], 'scope2')
+
+ await assert.isRejected(Roles.addUsersToRolesAsync(users.eve, ['admin', 'user'], ''), /Invalid scope name/)
+ await assert.isRejected(Roles.addUsersToRolesAsync(users.eve, ['admin', 'user'], ' '), /Invalid scope name/)
+ await assert.isRejected(Roles.addUsersToRolesAsync(users.eve, ['admin', 'user'], ' foobar'), /Invalid scope name/)
+ await assert.isRejected(Roles.addUsersToRolesAsync(users.eve, ['admin', 'user'], ' foobar '), /Invalid scope name/)
+ await assert.isRejected(Roles.addUsersToRolesAsync(users.eve, ['admin', 'user'], 42), /Invalid scope name/)
+ })
+
+ it('can check if user is in role', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+ await Roles.addUsersToRolesAsync(users.eve, ['admin', 'user'])
+
+ await testUser('eve', ['admin', 'user'])
+ })
+
+ it('can check if user is in role by scope', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+ await Roles.addUsersToRolesAsync(users.eve, ['admin', 'user'], 'scope1')
+ await Roles.addUsersToRolesAsync(users.eve, ['editor'], 'scope2')
+
+ testUser('eve', ['admin', 'user'], 'scope1')
+ testUser('eve', ['editor'], 'scope2')
+
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, ['admin', 'user'], 'scope2'))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, ['editor'], 'scope1'))
+
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, ['admin', 'user'], { anyScope: true }))
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, ['editor'], { anyScope: true }))
+ })
+
+ it('can check if user is in role by scope through options', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+ await Roles.addUsersToRolesAsync(users.eve, ['admin', 'user'], { scope: 'scope1' })
+ await Roles.addUsersToRolesAsync(users.eve, ['editor'], { scope: 'scope2' })
+
+ await testUser('eve', ['admin', 'user'], { scope: 'scope1' })
+ await testUser('eve', ['editor'], { scope: 'scope2' })
+ })
+
+ it('can check if user is in role by scope with global role', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+ await Roles.addUsersToRolesAsync(users.eve, ['admin', 'user'], 'scope1')
+ await Roles.addUsersToRolesAsync(users.eve, ['editor'], 'scope2')
+ await Roles.addUsersToRolesAsync(users.eve, ['admin'])
+
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, ['user'], 'scope1'))
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, ['editor'], 'scope2'))
+
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, ['user']))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, ['editor']))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, ['user'], null))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, ['editor'], null))
+
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, ['user'], 'scope2'))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, ['editor'], 'scope1'))
+
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, ['admin'], 'scope2'))
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, ['admin'], 'scope1'))
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, ['admin']))
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, ['admin'], null))
+ })
+
+ it('renaming scopes', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+ await Roles.addUsersToRolesAsync(users.eve, ['admin', 'user'], 'scope1')
+ await Roles.addUsersToRolesAsync(users.eve, ['editor'], 'scope2')
+
+ await testUser('eve', ['admin', 'user'], 'scope1')
+ await testUser('eve', ['editor'], 'scope2')
+
+ await Roles.renameScopeAsync('scope1', 'scope3')
+
+ await testUser('eve', ['admin', 'user'], 'scope3')
+ await testUser('eve', ['editor'], 'scope2')
+
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, ['admin', 'user'], 'scope1'))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, ['admin', 'user'], 'scope2'))
+
+ await assert.isRejected(Roles.renameScopeAsync('scope3'), /Invalid scope name/)
+
+ await Roles.renameScopeAsync('scope3', null)
+
+ await testUser('eve', ['admin', 'user', 'editor'], 'scope2')
+
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, ['editor']))
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, ['admin']))
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, ['user']))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, ['editor'], null))
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, ['admin'], null))
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, ['user'], null))
+
+ await Roles.renameScopeAsync(null, 'scope2')
+
+ await testUser('eve', ['admin', 'user', 'editor'], 'scope2')
+
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, ['editor']))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, ['admin']))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, ['user']))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, ['editor'], null))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, ['admin'], null))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, ['user'], null))
+ })
+
+ it('removing scopes', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+ await Roles.addUsersToRolesAsync(users.eve, ['admin', 'user'], 'scope1')
+ await Roles.addUsersToRolesAsync(users.eve, ['editor'], 'scope2')
+
+ await testUser('eve', ['admin', 'user'], 'scope1')
+ await testUser('eve', ['editor'], 'scope2')
+
+ await Roles.removeScopeAsync('scope1')
+
+ await testUser('eve', ['editor'], 'scope2')
+
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, ['admin', 'user'], 'scope1'))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, ['admin', 'user'], 'scope2'))
+ })
+
+ it('can check if non-existant user is in role', async function () {
+ assert.isFalse(await Roles.userIsInRoleAsync('1', 'admin'))
+ })
+
+ it('can check if null user is in role', async function () {
+ assert.isFalse(await Roles.userIsInRoleAsync(null, 'admin'))
+ })
+
+ it('can check user against several roles at once', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+
+ await Roles.addUsersToRolesAsync(users.eve, ['admin', 'user'])
+ const user = await Meteor.users.findOneAsync({ _id: users.eve })
+
+ // we can check the non-existing role
+ assert.isTrue(await Roles.userIsInRoleAsync(user, ['editor', 'admin']))
+ })
+
+ it('can\'t add non-existent user to role', async function () {
+ await Roles.createRoleAsync('admin')
+
+ await Roles.addUsersToRolesAsync(['1'], ['admin'])
+ assert.equal(await Meteor.users.findOneAsync({ _id: '1' }), undefined)
+ })
+
+ it('can\'t add user to non-existent role', async function () {
+ await assert.isRejected(Roles.addUsersToRolesAsync(users.eve, ['admin']), /Role 'admin' does not exist/)
+ await Roles.addUsersToRolesAsync(users.eve, ['admin'], { ifExists: true })
+ })
+
+ it('can\'t set non-existent user to role', async function () {
+ await Roles.createRoleAsync('admin')
+
+ await Roles.setUserRolesAsync(['1'], ['admin'])
+ assert.equal(await Meteor.users.findOneAsync({ _id: '1' }), undefined)
+ })
+
+ it('can\'t set user to non-existent role', async function () {
+ await assert.isRejected(Roles.setUserRolesAsync(users.eve, ['admin']), /Role 'admin' does not exist/)
+ await Roles.setUserRolesAsync(users.eve, ['admin'], { ifExists: true })
+ })
+
+ it('can add individual users to roles', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+
+ await Roles.addUsersToRolesAsync(users.eve, ['admin', 'user'])
+
+ await testUser('eve', ['admin', 'user'])
+ await testUser('bob', [])
+ await testUser('joe', [])
+
+ await Roles.addUsersToRolesAsync(users.joe, ['editor', 'user'])
+
+ await testUser('eve', ['admin', 'user'])
+ await testUser('bob', [])
+ await testUser('joe', ['editor', 'user'])
+ })
+
+ it('can add individual users to roles by scope', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+
+ await Roles.addUsersToRolesAsync(users.eve, ['admin', 'user'], 'scope1')
+
+ await testUser('eve', ['admin', 'user'], 'scope1')
+ await testUser('bob', [], 'scope1')
+ await testUser('joe', [], 'scope1')
+
+ await testUser('eve', [], 'scope2')
+ await testUser('bob', [], 'scope2')
+ await testUser('joe', [], 'scope2')
+
+ await Roles.addUsersToRolesAsync(users.joe, ['editor', 'user'], 'scope1')
+ await Roles.addUsersToRolesAsync(users.bob, ['editor', 'user'], 'scope2')
+
+ await testUser('eve', ['admin', 'user'], 'scope1')
+ await testUser('bob', [], 'scope1')
+ await testUser('joe', ['editor', 'user'], 'scope1')
+
+ await testUser('eve', [], 'scope2')
+ await testUser('bob', ['editor', 'user'], 'scope2')
+ await testUser('joe', [], 'scope2')
+ })
+
+ it('can add user to roles via user object', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+
+ const eve = await Meteor.users.findOneAsync({ _id: users.eve })
+ const bob = await Meteor.users.findOneAsync({ _id: users.bob })
+
+ await Roles.addUsersToRolesAsync(eve, ['admin', 'user'])
+
+ await testUser('eve', ['admin', 'user'])
+ await testUser('bob', [])
+ await testUser('joe', [])
+
+ await Roles.addUsersToRolesAsync(bob, ['editor'])
+
+ await testUser('eve', ['admin', 'user'])
+ await testUser('bob', ['editor'])
+ await testUser('joe', [])
+ })
+
+ it('can add user to roles multiple times', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+
+ await Roles.addUsersToRolesAsync(users.eve, ['admin', 'user'])
+ await Roles.addUsersToRolesAsync(users.eve, ['admin', 'user'])
+
+ await testUser('eve', ['admin', 'user'])
+ await testUser('bob', [])
+ await testUser('joe', [])
+
+ await Roles.addUsersToRolesAsync(users.bob, ['admin'])
+ await Roles.addUsersToRolesAsync(users.bob, ['editor'])
+
+ await testUser('eve', ['admin', 'user'])
+ await testUser('bob', ['admin', 'editor'])
+ await testUser('joe', [])
+ })
+
+ it('can add user to roles multiple times by scope', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+
+ await Roles.addUsersToRolesAsync(users.eve, ['admin', 'user'], 'scope1')
+ await Roles.addUsersToRolesAsync(users.eve, ['admin', 'user'], 'scope1')
+
+ await testUser('eve', ['admin', 'user'], 'scope1')
+ await testUser('bob', [], 'scope1')
+ await testUser('joe', [], 'scope1')
+
+ await Roles.addUsersToRolesAsync(users.bob, ['admin'], 'scope1')
+ await Roles.addUsersToRolesAsync(users.bob, ['editor'], 'scope1')
+
+ await testUser('eve', ['admin', 'user'], 'scope1')
+ await testUser('bob', ['admin', 'editor'], 'scope1')
+ await testUser('joe', [], 'scope1')
+ })
+
+ it('can add multiple users to roles', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+
+ await Roles.addUsersToRolesAsync([users.eve, users.bob], ['admin', 'user'])
+
+ await testUser('eve', ['admin', 'user'])
+ await testUser('bob', ['admin', 'user'])
+ await testUser('joe', [])
+
+ await Roles.addUsersToRolesAsync([users.bob, users.joe], ['editor', 'user'])
+
+ await testUser('eve', ['admin', 'user'])
+ await testUser('bob', ['admin', 'editor', 'user'])
+ await testUser('joe', ['editor', 'user'])
+ })
+
+ it('can add multiple users to roles by scope', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+
+ await Roles.addUsersToRolesAsync([users.eve, users.bob], ['admin', 'user'], 'scope1')
+
+ await testUser('eve', ['admin', 'user'], 'scope1')
+ await testUser('bob', ['admin', 'user'], 'scope1')
+ await testUser('joe', [], 'scope1')
+
+ await testUser('eve', [], 'scope2')
+ await testUser('bob', [], 'scope2')
+ await testUser('joe', [], 'scope2')
+
+ await Roles.addUsersToRolesAsync([users.bob, users.joe], ['editor', 'user'], 'scope1')
+ await Roles.addUsersToRolesAsync([users.bob, users.joe], ['editor', 'user'], 'scope2')
+
+ await testUser('eve', ['admin', 'user'], 'scope1')
+ await testUser('bob', ['admin', 'editor', 'user'], 'scope1')
+ await testUser('joe', ['editor', 'user'], 'scope1')
+
+ await testUser('eve', [], 'scope2')
+ await testUser('bob', ['editor', 'user'], 'scope2')
+ await testUser('joe', ['editor', 'user'], 'scope2')
+ })
+
+ it('can remove individual users from roles', async function () {
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+
+ // remove user role - one user
+ await Roles.addUsersToRolesAsync([users.eve, users.bob], ['editor', 'user'])
+ await testUser('eve', ['editor', 'user'])
+ await testUser('bob', ['editor', 'user'])
+ await Roles.removeUsersFromRolesAsync(users.eve, ['user'])
+ await testUser('eve', ['editor'])
+ await testUser('bob', ['editor', 'user'])
+ })
+
+ it('can remove user from roles multiple times', async function () {
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+
+ // remove user role - one user
+ await Roles.addUsersToRolesAsync([users.eve, users.bob], ['editor', 'user'])
+ await testUser('eve', ['editor', 'user'])
+ await testUser('bob', ['editor', 'user'])
+ await Roles.removeUsersFromRolesAsync(users.eve, ['user'])
+ await testUser('eve', ['editor'])
+ await testUser('bob', ['editor', 'user'])
+
+ // try remove again
+ await Roles.removeUsersFromRolesAsync(users.eve, ['user'])
+ await testUser('eve', ['editor'])
+ })
+
+ it('can remove users from roles via user object', async function () {
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+
+ const eve = await Meteor.users.findOneAsync({ _id: users.eve })
+ const bob = await Meteor.users.findOneAsync({ _id: users.bob })
+
+ // remove user role - one user
+ await Roles.addUsersToRolesAsync([eve, bob], ['editor', 'user'])
+ await testUser('eve', ['editor', 'user'])
+ await testUser('bob', ['editor', 'user'])
+ await Roles.removeUsersFromRolesAsync(eve, ['user'])
+ await testUser('eve', ['editor'])
+ await testUser('bob', ['editor', 'user'])
+ })
+
+ it('can remove individual users from roles by scope', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+
+ // remove user role - one user
+ await Roles.addUsersToRolesAsync([users.eve, users.bob], ['editor', 'user'], 'scope1')
+ await Roles.addUsersToRolesAsync([users.joe, users.bob], ['admin'], 'scope2')
+ await testUser('eve', ['editor', 'user'], 'scope1')
+ await testUser('bob', ['editor', 'user'], 'scope1')
+ await testUser('joe', [], 'scope1')
+ await testUser('eve', [], 'scope2')
+ await testUser('bob', ['admin'], 'scope2')
+ await testUser('joe', ['admin'], 'scope2')
+
+ await Roles.removeUsersFromRolesAsync(users.eve, ['user'], 'scope1')
+ await testUser('eve', ['editor'], 'scope1')
+ await testUser('bob', ['editor', 'user'], 'scope1')
+ await testUser('joe', [], 'scope1')
+ await testUser('eve', [], 'scope2')
+ await testUser('bob', ['admin'], 'scope2')
+ await testUser('joe', ['admin'], 'scope2')
+ })
+
+ it('can remove individual users from roles by scope through options', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+
+ // remove user role - one user
+ await Roles.addUsersToRolesAsync([users.eve, users.bob], ['editor', 'user'], { scope: 'scope1' })
+ await Roles.addUsersToRolesAsync([users.joe, users.bob], ['admin'], { scope: 'scope2' })
+ await testUser('eve', ['editor', 'user'], 'scope1')
+ await testUser('bob', ['editor', 'user'], 'scope1')
+ await testUser('joe', [], 'scope1')
+ await testUser('eve', [], 'scope2')
+ await testUser('bob', ['admin'], 'scope2')
+ await testUser('joe', ['admin'], 'scope2')
+
+ await Roles.removeUsersFromRolesAsync(users.eve, ['user'], { scope: 'scope1' })
+ await testUser('eve', ['editor'], 'scope1')
+ await testUser('bob', ['editor', 'user'], 'scope1')
+ await testUser('joe', [], 'scope1')
+ await testUser('eve', [], 'scope2')
+ await testUser('bob', ['admin'], 'scope2')
+ await testUser('joe', ['admin'], 'scope2')
+ })
+
+ it('can remove multiple users from roles', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+
+ // remove user role - two users
+ await Roles.addUsersToRolesAsync([users.eve, users.bob], ['editor', 'user'])
+ await testUser('eve', ['editor', 'user'])
+ await testUser('bob', ['editor', 'user'])
+
+ assert.isFalse(await Roles.userIsInRoleAsync(users.joe, 'admin'))
+ await Roles.addUsersToRolesAsync([users.bob, users.joe], ['admin', 'user'])
+ await testUser('bob', ['admin', 'user', 'editor'])
+ await testUser('joe', ['admin', 'user'])
+ await Roles.removeUsersFromRolesAsync([users.bob, users.joe], ['admin'])
+ await testUser('bob', ['user', 'editor'])
+ await testUser('joe', ['user'])
+ })
+
+ it('can remove multiple users from roles by scope', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+
+ // remove user role - one user
+ await Roles.addUsersToRolesAsync([users.eve, users.bob], ['editor', 'user'], 'scope1')
+ await Roles.addUsersToRolesAsync([users.joe, users.bob], ['admin'], 'scope2')
+ await testUser('eve', ['editor', 'user'], 'scope1')
+ await testUser('bob', ['editor', 'user'], 'scope1')
+ await testUser('joe', [], 'scope1')
+ await testUser('eve', [], 'scope2')
+ await testUser('bob', ['admin'], 'scope2')
+ await testUser('joe', ['admin'], 'scope2')
+
+ await Roles.removeUsersFromRolesAsync([users.eve, users.bob], ['user'], 'scope1')
+ await testUser('eve', ['editor'], 'scope1')
+ await testUser('bob', ['editor'], 'scope1')
+ await testUser('joe', [], 'scope1')
+ await testUser('eve', [], 'scope2')
+ await testUser('bob', ['admin'], 'scope2')
+ await testUser('joe', ['admin'], 'scope2')
+
+ await Roles.removeUsersFromRolesAsync([users.joe, users.bob], ['admin'], 'scope2')
+ await testUser('eve', [], 'scope2')
+ await testUser('bob', [], 'scope2')
+ await testUser('joe', [], 'scope2')
+ })
+
+ it('can remove multiple users from roles of any scope', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+
+ // remove user role - one user
+ await Roles.addUsersToRolesAsync([users.eve, users.bob], ['editor', 'user'], 'scope1')
+ await Roles.addUsersToRolesAsync([users.joe, users.bob], ['user'], 'scope2')
+ await testUser('eve', ['editor', 'user'], 'scope1')
+ await testUser('bob', ['editor', 'user'], 'scope1')
+ await testUser('joe', [], 'scope1')
+ await testUser('eve', [], 'scope2')
+ await testUser('bob', ['user'], 'scope2')
+ await testUser('joe', ['user'], 'scope2')
+
+ await Roles.removeUsersFromRolesAsync([users.eve, users.bob], ['user'], { anyScope: true })
+ await testUser('eve', ['editor'], 'scope1')
+ await testUser('bob', ['editor'], 'scope1')
+ await testUser('joe', [], 'scope1')
+ await testUser('eve', [], 'scope2')
+ await testUser('bob', [], 'scope2')
+ await testUser('joe', ['user'], 'scope2')
+ })
+
+ it('can set user roles', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+
+ const eve = await Meteor.users.findOneAsync({ _id: users.eve })
+ const bob = await Meteor.users.findOneAsync({ _id: users.bob })
+
+ await Roles.setUserRolesAsync([users.eve, bob], ['editor', 'user'])
+ await testUser('eve', ['editor', 'user'])
+ await testUser('bob', ['editor', 'user'])
+ await testUser('joe', [])
+
+ // use addUsersToRoles add some roles
+ await Roles.addUsersToRolesAsync([bob, users.joe], ['admin'])
+ await testUser('eve', ['editor', 'user'])
+ await testUser('bob', ['admin', 'editor', 'user'])
+ await testUser('joe', ['admin'])
+
+ await Roles.setUserRolesAsync([eve, bob], ['user'])
+ await testUser('eve', ['user'])
+ await testUser('bob', ['user'])
+ await testUser('joe', ['admin'])
+
+ await Roles.setUserRolesAsync(bob, 'editor')
+ await testUser('eve', ['user'])
+ await testUser('bob', ['editor'])
+ await testUser('joe', ['admin'])
+
+ await Roles.setUserRolesAsync([users.joe, users.bob], [])
+ await testUser('eve', ['user'])
+ await testUser('bob', [])
+ await testUser('joe', [])
+ })
+
+ it('can set user roles by scope', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+
+ const eve = await Meteor.users.findOneAsync({ _id: users.eve })
+ const bob = await Meteor.users.findOneAsync({ _id: users.bob })
+ const joe = await Meteor.users.findOneAsync({ _id: users.joe })
+
+ await Roles.setUserRolesAsync([users.eve, users.bob], ['editor', 'user'], 'scope1')
+ await Roles.setUserRolesAsync([users.bob, users.joe], ['admin'], 'scope2')
+ await testUser('eve', ['editor', 'user'], 'scope1')
+ await testUser('bob', ['editor', 'user'], 'scope1')
+ await testUser('joe', [], 'scope1')
+ await testUser('eve', [], 'scope2')
+ await testUser('bob', ['admin'], 'scope2')
+ await testUser('joe', ['admin'], 'scope2')
+
+ // use addUsersToRoles add some roles
+ await Roles.addUsersToRolesAsync([users.eve, users.bob], ['admin'], 'scope1')
+ await Roles.addUsersToRolesAsync([users.bob, users.joe], ['editor'], 'scope2')
+ await testUser('eve', ['admin', 'editor', 'user'], 'scope1')
+ await testUser('bob', ['admin', 'editor', 'user'], 'scope1')
+ await testUser('joe', [], 'scope1')
+ await testUser('eve', [], 'scope2')
+ await testUser('bob', ['admin', 'editor'], 'scope2')
+ await testUser('joe', ['admin', 'editor'], 'scope2')
+
+ await Roles.setUserRolesAsync([eve, bob], ['user'], 'scope1')
+ await Roles.setUserRolesAsync([eve, joe], ['editor'], 'scope2')
+ await testUser('eve', ['user'], 'scope1')
+ await testUser('bob', ['user'], 'scope1')
+ await testUser('joe', [], 'scope1')
+ await testUser('eve', ['editor'], 'scope2')
+ await testUser('bob', ['admin', 'editor'], 'scope2')
+ await testUser('joe', ['editor'], 'scope2')
+
+ await Roles.setUserRolesAsync(bob, 'editor', 'scope1')
+ await testUser('eve', ['user'], 'scope1')
+ await testUser('bob', ['editor'], 'scope1')
+ await testUser('joe', [], 'scope1')
+ await testUser('eve', ['editor'], 'scope2')
+ await testUser('bob', ['admin', 'editor'], 'scope2')
+ await testUser('joe', ['editor'], 'scope2')
+
+ const bobRoles1 = await Roles.getRolesForUserAsync(users.bob, { anyScope: true, fullObjects: true })
+ const joeRoles1 = await Roles.getRolesForUserAsync(users.joe, { anyScope: true, fullObjects: true })
+ assert.isTrue(bobRoles1.map(r => r.scope).includes('scope1'))
+ assert.isFalse(joeRoles1.map(r => r.scope).includes('scope1'))
+
+ await Roles.setUserRolesAsync([bob, users.joe], [], 'scope1')
+ await testUser('eve', ['user'], 'scope1')
+ await testUser('bob', [], 'scope1')
+ await testUser('joe', [], 'scope1')
+ await testUser('eve', ['editor'], 'scope2')
+ await testUser('bob', ['admin', 'editor'], 'scope2')
+ await testUser('joe', ['editor'], 'scope2')
+
+ // When roles in a given scope are removed, we do not want any dangling database content for that scope.
+ const bobRoles2 = await Roles.getRolesForUserAsync(users.bob, { anyScope: true, fullObjects: true })
+ const joeRoles2 = await Roles.getRolesForUserAsync(users.joe, { anyScope: true, fullObjects: true })
+ assert.isFalse(bobRoles2.map(r => r.scope).includes('scope1'))
+ assert.isFalse(joeRoles2.map(r => r.scope).includes('scope1'))
+ })
+
+ it('can set user roles by scope including GLOBAL_SCOPE', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('editor')
+
+ const eve = await Meteor.users.findOneAsync({ _id: users.eve })
+
+ await Roles.addUsersToRolesAsync(eve, 'admin', Roles.GLOBAL_SCOPE)
+ await testUser('eve', ['admin'], 'scope1')
+ await testUser('eve', ['admin'])
+
+ await Roles.setUserRolesAsync(eve, 'editor', Roles.GLOBAL_SCOPE)
+ await testUser('eve', ['editor'], 'scope2')
+ await testUser('eve', ['editor'])
+ })
+
+ it('can set user roles by scope and anyScope', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('editor')
+
+ const eve = await Meteor.users.findOneAsync({ _id: users.eve })
+
+ const eveRoles = await Roles.getRolesForUserAsync(users.eve, { anyScope: true, fullObjects: true })
+ assert.sameDeepMembers(eveRoles.map(obj => { delete obj._id; return obj }), [])
+
+ await Roles.addUsersToRolesAsync(eve, 'admin')
+
+ const eveRoles2 = await Roles.getRolesForUserAsync(users.eve, { anyScope: true, fullObjects: true })
+ assert.sameDeepMembers(eveRoles2.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'admin' },
+ scope: null,
+ user: { _id: users.eve },
+ inheritedRoles: [{ _id: 'admin' }]
+ }])
+
+ await Roles.setUserRolesAsync(eve, 'editor', { anyScope: true, scope: 'scope2' })
+
+ const eveRoles3 = await Roles.getRolesForUserAsync(users.eve, { anyScope: true, fullObjects: true })
+ assert.sameDeepMembers(eveRoles3.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'editor' },
+ scope: 'scope2',
+ user: { _id: users.eve },
+ inheritedRoles: [{ _id: 'editor' }]
+ }])
+ })
+
+ it('can get all roles', async function () {
+ for (const role of roles) {
+ await Roles.createRoleAsync(role)
+ }
+
+ // compare roles, sorted alphabetically
+ const expected = roles
+ const actual = Roles.getAllRoles().fetch().map(r => r._id)
+
+ assert.sameMembers(actual, expected)
+
+ assert.sameMembers(Roles.getAllRoles({ sort: { _id: -1 } }).fetch().map(r => r._id), expected.reverse())
+ })
+
+ it('get an empty list of roles for an empty user', async function () {
+ assert.sameMembers(await Roles.getRolesForUserAsync(undefined), [])
+ assert.sameMembers(await Roles.getRolesForUserAsync(null), [])
+ assert.sameMembers(await Roles.getRolesForUserAsync({}), [])
+ })
+
+ it('get an empty list of roles for non-existant user', async function () {
+ assert.sameMembers(await Roles.getRolesForUserAsync('1'), [])
+ assert.sameMembers(await Roles.getRolesForUserAsync('1', 'scope1'), [])
+ })
+
+ it('can get all roles for user', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+
+ const userId = users.eve
+ let userObj
+
+ // by userId
+ assert.sameMembers(await Roles.getRolesForUserAsync(userId), [])
+
+ // by user object
+ userObj = await Meteor.users.findOneAsync({ _id: userId })
+ assert.sameMembers(await Roles.getRolesForUserAsync(userObj), [])
+
+ await Roles.addUsersToRolesAsync(userId, ['admin', 'user'])
+
+ // by userId
+ assert.sameMembers(await Roles.getRolesForUserAsync(userId), ['admin', 'user'])
+
+ // by user object
+ userObj = await Meteor.users.findOneAsync({ _id: userId })
+ assert.sameMembers(await Roles.getRolesForUserAsync(userObj), ['admin', 'user'])
+
+ const userRoles = await Roles.getRolesForUserAsync(userId, { fullObjects: true })
+ assert.sameDeepMembers(userRoles.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'admin' },
+ scope: null,
+ user: { _id: userId },
+ inheritedRoles: [{ _id: 'admin' }]
+ }, {
+ role: { _id: 'user' },
+ scope: null,
+ user: { _id: userId },
+ inheritedRoles: [{ _id: 'user' }]
+ }])
+ })
+
+ it('can get all roles for user by scope', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+
+ const userId = users.eve
+ let userObj
+
+ // by userId
+ assert.sameMembers(await Roles.getRolesForUserAsync(userId, 'scope1'), [])
+
+ // by user object
+ userObj = await Meteor.users.findOneAsync({ _id: userId })
+ assert.sameMembers(await Roles.getRolesForUserAsync(userObj, 'scope1'), [])
+
+ // add roles
+ await Roles.addUsersToRolesAsync(userId, ['admin', 'user'], 'scope1')
+ await Roles.addUsersToRolesAsync(userId, ['admin'], 'scope2')
+
+ // by userId
+ assert.sameMembers(await Roles.getRolesForUserAsync(userId, 'scope1'), ['admin', 'user'])
+ assert.sameMembers(await Roles.getRolesForUserAsync(userId, 'scope2'), ['admin'])
+ assert.sameMembers(await Roles.getRolesForUserAsync(userId), [])
+
+ // by user object
+ userObj = await Meteor.users.findOneAsync({ _id: userId })
+ assert.sameMembers(await Roles.getRolesForUserAsync(userObj, 'scope1'), ['admin', 'user'])
+ assert.sameMembers(await Roles.getRolesForUserAsync(userObj, 'scope2'), ['admin'])
+ assert.sameMembers(await Roles.getRolesForUserAsync(userObj), [])
+
+ const userRoles = await Roles.getRolesForUserAsync(userId, { fullObjects: true, scope: 'scope1' })
+ assert.sameDeepMembers(userRoles.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'admin' },
+ scope: 'scope1',
+ user: { _id: userId },
+ inheritedRoles: [{ _id: 'admin' }]
+ }, {
+ role: { _id: 'user' },
+ scope: 'scope1',
+ user: { _id: userId },
+ inheritedRoles: [{ _id: 'user' }]
+ }])
+ const userRoles2 = await Roles.getRolesForUserAsync(userId, { fullObjects: true, scope: 'scope2' })
+ assert.sameDeepMembers(userRoles2.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'admin' },
+ scope: 'scope2',
+ user: { _id: userId },
+ inheritedRoles: [{ _id: 'admin' }]
+ }])
+
+ const userRoles3 = await Roles.getRolesForUserAsync(userId, { fullObjects: true, anyScope: true })
+ assert.sameDeepMembers(userRoles3.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'admin' },
+ scope: 'scope1',
+ user: { _id: userId },
+ inheritedRoles: [{ _id: 'admin' }]
+ }, {
+ role: { _id: 'user' },
+ scope: 'scope1',
+ user: { _id: userId },
+ inheritedRoles: [{ _id: 'user' }]
+ }, {
+ role: { _id: 'admin' },
+ scope: 'scope2',
+ user: { _id: userId },
+ inheritedRoles: [{ _id: 'admin' }]
+ }])
+
+ await Roles.createRoleAsync('PERMISSION')
+ await Roles.addRolesToParentAsync('PERMISSION', 'user')
+
+ const userRoles4 = await Roles.getRolesForUserAsync(userId, { fullObjects: true, scope: 'scope1' })
+ assert.sameDeepMembers(userRoles4.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'admin' },
+ scope: 'scope1',
+ user: { _id: userId },
+ inheritedRoles: [{ _id: 'admin' }]
+ }, {
+ role: { _id: 'user' },
+ scope: 'scope1',
+ user: { _id: userId },
+ inheritedRoles: [{ _id: 'user' }, { _id: 'PERMISSION' }]
+ }])
+ const userRoles5 = await Roles.getRolesForUserAsync(userId, { fullObjects: true, scope: 'scope2' })
+ assert.sameDeepMembers(userRoles5.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'admin' },
+ scope: 'scope2',
+ user: { _id: userId },
+ inheritedRoles: [{ _id: 'admin' }]
+ }])
+ assert.sameMembers(await Roles.getRolesForUserAsync(userId, { scope: 'scope1' }), ['admin', 'user', 'PERMISSION'])
+ assert.sameMembers(await Roles.getRolesForUserAsync(userId, { scope: 'scope2' }), ['admin'])
+
+ const userRoles6 = await Roles.getRolesForUserAsync(userId, { fullObjects: true, anyScope: true })
+ assert.sameDeepMembers(userRoles6.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'admin' },
+ scope: 'scope1',
+ user: { _id: userId },
+ inheritedRoles: [{ _id: 'admin' }]
+ }, {
+ role: { _id: 'user' },
+ scope: 'scope1',
+ user: { _id: userId },
+ inheritedRoles: [{ _id: 'user' }, { _id: 'PERMISSION' }]
+ }, {
+ role: { _id: 'admin' },
+ scope: 'scope2',
+ user: { _id: userId },
+ inheritedRoles: [{ _id: 'admin' }]
+ }])
+ assert.sameMembers(await Roles.getRolesForUserAsync(userId, { anyScope: true }), ['admin', 'user', 'PERMISSION'])
+
+ const userRoles7 = await Roles.getRolesForUserAsync(userId, { fullObjects: true, scope: 'scope1', onlyAssigned: true })
+ assert.sameDeepMembers(userRoles7.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'admin' },
+ scope: 'scope1',
+ user: { _id: userId },
+ inheritedRoles: [{ _id: 'admin' }]
+ }, {
+ role: { _id: 'user' },
+ scope: 'scope1',
+ user: { _id: userId },
+ inheritedRoles: [{ _id: 'user' }, { _id: 'PERMISSION' }]
+ }])
+ const userRoles8 = await Roles.getRolesForUserAsync(userId, { fullObjects: true, scope: 'scope2', onlyAssigned: true })
+ assert.sameDeepMembers(userRoles8.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'admin' },
+ scope: 'scope2',
+ user: { _id: userId },
+ inheritedRoles: [{ _id: 'admin' }]
+ }])
+ assert.sameMembers(await Roles.getRolesForUserAsync(userId, { scope: 'scope1', onlyAssigned: true }), ['admin', 'user'])
+ assert.sameMembers(await Roles.getRolesForUserAsync(userId, { scope: 'scope2', onlyAssigned: true }), ['admin'])
+
+ const userRoles9 = await Roles.getRolesForUserAsync(userId, { fullObjects: true, anyScope: true, onlyAssigned: true })
+ assert.sameDeepMembers(userRoles9.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'admin' },
+ scope: 'scope1',
+ user: { _id: userId },
+ inheritedRoles: [{ _id: 'admin' }]
+ }, {
+ role: { _id: 'user' },
+ scope: 'scope1',
+ user: { _id: userId },
+ inheritedRoles: [{ _id: 'user' }, { _id: 'PERMISSION' }]
+ }, {
+ role: { _id: 'admin' },
+ scope: 'scope2',
+ user: { _id: userId },
+ inheritedRoles: [{ _id: 'admin' }]
+ }])
+ assert.sameMembers(await Roles.getRolesForUserAsync(userId, { anyScope: true, onlyAssigned: true }), ['admin', 'user'])
+ })
+
+ it('can get only scoped roles for user', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+
+ const userId = users.eve
+
+ // add roles
+ await Roles.addUsersToRolesAsync(userId, ['user'], 'scope1')
+ await Roles.addUsersToRolesAsync(userId, ['admin'])
+
+ await Roles.createRoleAsync('PERMISSION')
+ await Roles.addRolesToParentAsync('PERMISSION', 'user')
+
+ assert.sameMembers(await Roles.getRolesForUserAsync(userId, { onlyScoped: true, scope: 'scope1' }), ['user', 'PERMISSION'])
+ assert.sameMembers(await Roles.getRolesForUserAsync(userId, { onlyScoped: true, onlyAssigned: true, scope: 'scope1' }), ['user'])
+ const userRoles = await Roles.getRolesForUserAsync(userId, { onlyScoped: true, fullObjects: true, scope: 'scope1' })
+ assert.sameDeepMembers(userRoles.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'user' },
+ scope: 'scope1',
+ user: { _id: userId },
+ inheritedRoles: [{ _id: 'user' }, { _id: 'PERMISSION' }]
+ }])
+ })
+
+ it('can get all roles for user by scope with periods in name', async function () {
+ await Roles.createRoleAsync('admin')
+
+ await Roles.addUsersToRolesAsync(users.joe, ['admin'], 'example.k12.va.us')
+
+ assert.sameMembers(await Roles.getRolesForUserAsync(users.joe, 'example.k12.va.us'), ['admin'])
+ })
+
+ it('can get all roles for user by scope including Roles.GLOBAL_SCOPE', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+
+ const userId = users.eve
+
+ await Roles.addUsersToRolesAsync([users.eve], ['editor'], Roles.GLOBAL_SCOPE)
+ await Roles.addUsersToRolesAsync([users.eve], ['admin', 'user'], 'scope1')
+
+ // by userId
+ assert.sameMembers(await Roles.getRolesForUserAsync(userId, 'scope1'), ['editor', 'admin', 'user'])
+ assert.sameMembers(await Roles.getRolesForUserAsync(userId), ['editor'])
+
+ // by user object
+ const userObj = await Meteor.users.findOneAsync({ _id: userId })
+ assert.sameMembers(await Roles.getRolesForUserAsync(userObj, 'scope1'), ['editor', 'admin', 'user'])
+ assert.sameMembers(await Roles.getRolesForUserAsync(userObj), ['editor'])
+ })
+
+ describe('getRolesForUser', function () {
+ it('should not return null entries if user has no roles for scope', async function () {
+ await Roles.createRoleAsync('editor')
+
+ const userId = users.eve
+ let userObj
+
+ // by userId
+ assert.sameMembers(await Roles.getRolesForUserAsync(userId, 'scope1'), [])
+ assert.sameMembers(await Roles.getRolesForUserAsync(userId), [])
+
+ // by user object
+ userObj = await Meteor.users.findOneAsync({ _id: userId })
+ assert.sameMembers(await Roles.getRolesForUserAsync(userObj, 'scope1'), [])
+ assert.sameMembers(await Roles.getRolesForUserAsync(userObj), [])
+
+ await Roles.addUsersToRolesAsync([users.eve], ['editor'], Roles.GLOBAL_SCOPE)
+
+ // by userId
+ assert.sameMembers(await Roles.getRolesForUserAsync(userId, 'scope1'), ['editor'])
+ assert.sameMembers(await Roles.getRolesForUserAsync(userId), ['editor'])
+
+ // by user object
+ userObj = await Meteor.users.findOneAsync({ _id: userId })
+ assert.sameMembers(await Roles.getRolesForUserAsync(userObj, 'scope1'), ['editor'])
+ assert.sameMembers(await Roles.getRolesForUserAsync(userObj), ['editor'])
+ })
+
+ it('should not fail during a call of addUsersToRoles', async function () {
+ await Roles.createRoleAsync('editor')
+
+ const userId = users.eve
+ const promises = []
+ const interval = setInterval(() => {
+ promises.push(Promise.resolve().then(async () => {
+ await Roles.getRolesForUserAsync(userId)
+ }))
+ }, 0)
+
+ await Roles.addUsersToRolesAsync([users.eve], ['editor'], Roles.GLOBAL_SCOPE)
+ clearInterval(interval)
+
+ return Promise.all(promises)
+ })
+ })
+
+ it('returns an empty list of scopes for null as user-id', async function () {
+ assert.sameMembers(await Roles.getScopesForUserAsync(undefined), [])
+ assert.sameMembers(await Roles.getScopesForUserAsync(null), [])
+ assert.sameMembers(await Roles.getScopesForUserAsync('foo'), [])
+ assert.sameMembers(await Roles.getScopesForUserAsync({}), [])
+ assert.sameMembers(await Roles.getScopesForUserAsync({ _id: 'foo' }), [])
+ })
+
+ it('can get all scopes for user', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+
+ const userId = users.eve
+
+ await Roles.addUsersToRolesAsync([users.eve], ['editor'], 'scope1')
+ await Roles.addUsersToRolesAsync([users.eve], ['admin', 'user'], 'scope2')
+
+ // by userId
+ assert.sameMembers(await Roles.getScopesForUserAsync(userId), ['scope1', 'scope2'])
+
+ // by user object
+ const userObj = await Meteor.users.findOneAsync({ _id: userId })
+ assert.sameMembers(await Roles.getScopesForUserAsync(userObj), ['scope1', 'scope2'])
+ })
+
+ it('can get all scopes for user by role', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+
+ const userId = users.eve
+
+ await Roles.addUsersToRolesAsync([users.eve], ['editor'], 'scope1')
+ await Roles.addUsersToRolesAsync([users.eve], ['editor', 'user'], 'scope2')
+
+ // by userId
+ assert.sameMembers(await Roles.getScopesForUserAsync(userId, 'user'), ['scope2'])
+ assert.sameMembers(await Roles.getScopesForUserAsync(userId, 'editor'), ['scope1', 'scope2'])
+ assert.sameMembers(await Roles.getScopesForUserAsync(userId, 'admin'), [])
+
+ // by user object
+ const userObj = await Meteor.users.findOneAsync({ _id: userId })
+ assert.sameMembers(await Roles.getScopesForUserAsync(userObj, 'user'), ['scope2'])
+ assert.sameMembers(await Roles.getScopesForUserAsync(userObj, 'editor'), ['scope1', 'scope2'])
+ assert.sameMembers(await Roles.getScopesForUserAsync(userObj, 'admin'), [])
+ })
+
+ it('getScopesForUser returns [] when not using scopes', async function () {
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+
+ const userId = users.eve
+
+ await Roles.addUsersToRolesAsync([users.eve], ['editor', 'user'])
+
+ // by userId
+ assert.sameMembers(await Roles.getScopesForUserAsync(userId), [])
+ assert.sameMembers(await Roles.getScopesForUserAsync(userId, 'editor'), [])
+ assert.sameMembers(await Roles.getScopesForUserAsync(userId, ['editor']), [])
+ assert.sameMembers(await Roles.getScopesForUserAsync(userId, ['editor', 'user']), [])
+
+ // by user object
+ const userObj = await Meteor.users.findOneAsync({ _id: userId })
+ assert.sameMembers(await Roles.getScopesForUserAsync(userObj), [])
+ assert.sameMembers(await Roles.getScopesForUserAsync(userObj, 'editor'), [])
+ assert.sameMembers(await Roles.getScopesForUserAsync(userObj, ['editor']), [])
+ assert.sameMembers(await Roles.getScopesForUserAsync(userObj, ['editor', 'user']), [])
+ })
+
+ it('can get all groups for user by role array', async function () {
+ const userId = users.eve
+
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+ await Roles.createRoleAsync('moderator')
+ await Roles.createRoleAsync('admin')
+
+ await Roles.addUsersToRolesAsync([users.eve], ['editor'], 'group1')
+ await Roles.addUsersToRolesAsync([users.eve], ['editor', 'user'], 'group2')
+ await Roles.addUsersToRolesAsync([users.eve], ['moderator'], 'group3')
+
+ // by userId, one role
+ assert.sameMembers(await Roles.getScopesForUserAsync(userId, ['user']), ['group2'])
+ assert.sameMembers(await Roles.getScopesForUserAsync(userId, ['editor']), ['group1', 'group2'])
+ assert.sameMembers(await Roles.getScopesForUserAsync(userId, ['admin']), [])
+
+ // by userId, multiple roles
+ assert.sameMembers(await Roles.getScopesForUserAsync(userId, ['editor', 'user']), ['group1', 'group2'])
+ assert.sameMembers(await Roles.getScopesForUserAsync(userId, ['editor', 'moderator']), ['group1', 'group2', 'group3'])
+ assert.sameMembers(await Roles.getScopesForUserAsync(userId, ['user', 'moderator']), ['group2', 'group3'])
+
+ // by user object, one role
+ const userObj = await Meteor.users.findOneAsync({ _id: userId })
+ assert.sameMembers(await Roles.getScopesForUserAsync(userObj, ['user']), ['group2'])
+ assert.sameMembers(await Roles.getScopesForUserAsync(userObj, ['editor']), ['group1', 'group2'])
+ assert.sameMembers(await Roles.getScopesForUserAsync(userObj, ['admin']), [])
+
+ // by user object, multiple roles
+ assert.sameMembers(await Roles.getScopesForUserAsync(userObj, ['editor', 'user']), ['group1', 'group2'])
+ assert.sameMembers(await Roles.getScopesForUserAsync(userObj, ['editor', 'moderator']), ['group1', 'group2', 'group3'])
+ assert.sameMembers(await Roles.getScopesForUserAsync(userObj, ['user', 'moderator']), ['group2', 'group3'])
+ })
+
+ it('getting all scopes for user does not include GLOBAL_SCOPE', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+
+ const userId = users.eve
+
+ await Roles.addUsersToRolesAsync([users.eve], ['editor'], 'scope1')
+ await Roles.addUsersToRolesAsync([users.eve], ['editor', 'user'], 'scope2')
+ await Roles.addUsersToRolesAsync([users.eve], ['editor', 'user', 'admin'], Roles.GLOBAL_SCOPE)
+
+ // by userId
+ assert.sameMembers(await Roles.getScopesForUserAsync(userId, 'user'), ['scope2'])
+ assert.sameMembers(await Roles.getScopesForUserAsync(userId, 'editor'), ['scope1', 'scope2'])
+ assert.sameMembers(await Roles.getScopesForUserAsync(userId, 'admin'), [])
+ assert.sameMembers(await Roles.getScopesForUserAsync(userId, ['user']), ['scope2'])
+ assert.sameMembers(await Roles.getScopesForUserAsync(userId, ['editor']), ['scope1', 'scope2'])
+ assert.sameMembers(await Roles.getScopesForUserAsync(userId, ['admin']), [])
+ assert.sameMembers(await Roles.getScopesForUserAsync(userId, ['user', 'editor', 'admin']), ['scope1', 'scope2'])
+
+ // by user object
+ const userObj = await Meteor.users.findOneAsync({ _id: userId })
+ assert.sameMembers(await Roles.getScopesForUserAsync(userObj, 'user'), ['scope2'])
+ assert.sameMembers(await Roles.getScopesForUserAsync(userObj, 'editor'), ['scope1', 'scope2'])
+ assert.sameMembers(await Roles.getScopesForUserAsync(userObj, 'admin'), [])
+ assert.sameMembers(await Roles.getScopesForUserAsync(userObj, ['user']), ['scope2'])
+ assert.sameMembers(await Roles.getScopesForUserAsync(userObj, ['editor']), ['scope1', 'scope2'])
+ assert.sameMembers(await Roles.getScopesForUserAsync(userObj, ['admin']), [])
+ assert.sameMembers(await Roles.getScopesForUserAsync(userObj, ['user', 'editor', 'admin']), ['scope1', 'scope2'])
+ })
+
+ it('can get all users in role', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+
+ await Roles.addUsersToRolesAsync([users.eve, users.joe], ['admin', 'user'])
+ await Roles.addUsersToRolesAsync([users.bob, users.joe], ['editor'])
+
+ const expected = [users.eve, users.joe]
+ const cursor = await Roles.getUsersInRoleAsync('admin')
+ const actual = cursor.fetch().map(r => r._id)
+
+ assert.sameMembers(actual, expected)
+ })
+
+ it('can get all users in role by scope', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+
+ await Roles.addUsersToRolesAsync([users.eve, users.joe], ['admin', 'user'], 'scope1')
+ await Roles.addUsersToRolesAsync([users.bob, users.joe], ['admin'], 'scope2')
+
+ let expected = [users.eve, users.joe]
+ const cursor1 = await Roles.getUsersInRoleAsync('admin', 'scope1')
+ let actual = cursor1.fetch().map(r => r._id)
+
+ assert.sameMembers(actual, expected)
+
+ expected = [users.eve, users.joe]
+ const cursor2 = await Roles.getUsersInRoleAsync('admin', { scope: 'scope1' })
+ actual = cursor2.fetch().map(r => r._id)
+ assert.sameMembers(actual, expected)
+
+ expected = [users.eve, users.bob, users.joe]
+ const cursor3 = await Roles.getUsersInRoleAsync('admin', { anyScope: true })
+ actual = cursor3.fetch().map(r => r._id)
+ assert.sameMembers(actual, expected)
+
+ const cursor4 = await Roles.getUsersInRoleAsync('admin')
+ actual = cursor4.fetch().map(r => r._id)
+ assert.sameMembers(actual, [])
+ })
+
+ // it('can get all users in role by scope including Roles.GLOBAL_SCOPE', function () {
+ // Roles.createRoleAsync('admin')
+ // Roles.createRoleAsync('user')
+ //
+ // Roles.addUsersToRolesAsync([users.eve], ['admin', 'user'], Roles.GLOBAL_SCOPE)
+ // Roles.addUsersToRolesAsync([users.bob, users.joe], ['admin'], 'scope2')
+ //
+ // let expected = [users.eve]
+ // let actual = await Roles.getUsersInRoleAsync('admin', 'scope1').fetch().map(r => r._id)
+ //
+ // assert.sameMembers(actual, expected)
+ //
+ // expected = [users.eve, users.bob, users.joe]
+ // actual = await Roles.getUsersInRoleAsync('admin', 'scope2').fetch().map(r => r._id)
+ //
+ // assert.sameMembers(actual, expected)
+ //
+ // expected = [users.eve]
+ // actual = await Roles.getUsersInRoleAsync('admin').fetch().map(r => r._id)
+ //
+ // assert.sameMembers(actual, expected)
+ //
+ // expected = [users.eve, users.bob, users.joe]
+ // actual = await Roles.getUsersInRoleAsync('admin', { anyScope: true }).fetch().map(r => r._id)
+ //
+ // assert.sameMembers(actual, expected)
+ // })
+ //
+ // it('can get all users in role by scope excluding Roles.GLOBAL_SCOPE', function () {
+ // Roles.createRoleAsync('admin')
+ //
+ // Roles.addUsersToRolesAsync([users.eve], ['admin'], Roles.GLOBAL_SCOPE)
+ // Roles.addUsersToRolesAsync([users.bob], ['admin'], 'scope1')
+ //
+ // let expected = [users.eve]
+ // let actual = await Roles.getUsersInRoleAsync('admin').fetch().map(r => r._id)
+ // assert.sameMembers(actual, expected)
+ //
+ // expected = [users.eve, users.bob]
+ // actual = await Roles.getUsersInRoleAsync('admin', { scope: 'scope1' }).fetch().map(r => r._id)
+ // assert.sameMembers(actual, expected)
+ //
+ // expected = [users.bob]
+ // actual = await Roles.getUsersInRoleAsync('admin', { scope: 'scope1', onlyScoped: true }).fetch().map(r => r._id)
+ // assert.sameMembers(actual, expected)
+ // })
+
+ it('can get all users in role by scope and passes through mongo query arguments', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+
+ await Roles.addUsersToRolesAsync([users.eve, users.joe], ['admin', 'user'], 'scope1')
+ await Roles.addUsersToRolesAsync([users.bob, users.joe], ['admin'], 'scope2')
+
+ const cursor = await Roles.getUsersInRoleAsync('admin', 'scope1', { fields: { username: 0 }, limit: 1 })
+ const results = cursor.fetch()
+
+ assert.equal(1, results.length)
+ assert.isTrue(hasProp(results[0], '_id'))
+ assert.isFalse(hasProp(results[0], 'username'))
+ })
+
+ it('can use Roles.GLOBAL_SCOPE to assign blanket roles', async function () {
+ await Roles.createRoleAsync('admin')
+
+ await Roles.addUsersToRolesAsync([users.joe, users.bob], ['admin'], Roles.GLOBAL_SCOPE)
+
+ await testUser('eve', [], 'scope1')
+ await testUser('joe', ['admin'], 'scope2')
+ await testUser('joe', ['admin'], 'scope1')
+ await testUser('bob', ['admin'], 'scope2')
+ await testUser('bob', ['admin'], 'scope1')
+
+ await Roles.removeUsersFromRolesAsync(users.joe, ['admin'], Roles.GLOBAL_SCOPE)
+
+ await testUser('eve', [], 'scope1')
+ await testUser('joe', [], 'scope2')
+ await testUser('joe', [], 'scope1')
+ await testUser('bob', ['admin'], 'scope2')
+ await testUser('bob', ['admin'], 'scope1')
+ })
+
+ it('Roles.GLOBAL_SCOPE is independent of other scopes', async function () {
+ await Roles.createRoleAsync('admin')
+
+ await Roles.addUsersToRolesAsync([users.joe, users.bob], ['admin'], 'scope5')
+ await Roles.addUsersToRolesAsync([users.joe, users.bob], ['admin'], Roles.GLOBAL_SCOPE)
+
+ await testUser('eve', [], 'scope1')
+ await testUser('joe', ['admin'], 'scope5')
+ await testUser('joe', ['admin'], 'scope2')
+ await testUser('joe', ['admin'], 'scope1')
+ await testUser('bob', ['admin'], 'scope5')
+ await testUser('bob', ['admin'], 'scope2')
+ await testUser('bob', ['admin'], 'scope1')
+
+ await Roles.removeUsersFromRolesAsync(users.joe, ['admin'], Roles.GLOBAL_SCOPE)
+
+ await testUser('eve', [], 'scope1')
+ await testUser('joe', ['admin'], 'scope5')
+ await testUser('joe', [], 'scope2')
+ await testUser('joe', [], 'scope1')
+ await testUser('bob', ['admin'], 'scope5')
+ await testUser('bob', ['admin'], 'scope2')
+ await testUser('bob', ['admin'], 'scope1')
+ })
+
+ it('Roles.GLOBAL_SCOPE also checked when scope not specified', async function () {
+ await Roles.createRoleAsync('admin')
+
+ await Roles.addUsersToRolesAsync(users.joe, 'admin', Roles.GLOBAL_SCOPE)
+
+ await testUser('joe', ['admin'])
+
+ await Roles.removeUsersFromRolesAsync(users.joe, 'admin', Roles.GLOBAL_SCOPE)
+
+ await testUser('joe', [])
+ })
+
+ it('can use \'.\' in scope name', async function () {
+ await Roles.createRoleAsync('admin')
+
+ await Roles.addUsersToRolesAsync(users.joe, ['admin'], 'example.com')
+ await testUser('joe', ['admin'], 'example.com')
+ })
+
+ it('can use multiple periods in scope name', async function () {
+ await Roles.createRoleAsync('admin')
+
+ await Roles.addUsersToRolesAsync(users.joe, ['admin'], 'example.k12.va.us')
+ await testUser('joe', ['admin'], 'example.k12.va.us')
+ })
+
+ it('renaming of roles', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+
+ await Roles.setUserRolesAsync([users.eve, users.bob], ['editor', 'user'], 'scope1')
+ await Roles.setUserRolesAsync([users.bob, users.joe], ['user', 'admin'], 'scope2')
+
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'editor', 'scope1'))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, 'editor', 'scope2'))
+
+ assert.isFalse(await Roles.userIsInRoleAsync(users.joe, 'admin', 'scope1'))
+ assert.isTrue(await Roles.userIsInRoleAsync(users.joe, 'admin', 'scope2'))
+
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'user', 'scope1'))
+ assert.isTrue(await Roles.userIsInRoleAsync(users.bob, 'user', 'scope1'))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.joe, 'user', 'scope1'))
+
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, 'user', 'scope2'))
+ assert.isTrue(await Roles.userIsInRoleAsync(users.bob, 'user', 'scope2'))
+ assert.isTrue(await Roles.userIsInRoleAsync(users.joe, 'user', 'scope2'))
+
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, 'user2', 'scope1'))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, 'user2', 'scope2'))
+
+ await Roles.renameRoleAsync('user', 'user2')
+
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'editor', 'scope1'))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, 'editor', 'scope2'))
+
+ assert.isFalse(await Roles.userIsInRoleAsync(users.joe, 'admin', 'scope1'))
+ assert.isTrue(await Roles.userIsInRoleAsync(users.joe, 'admin', 'scope2'))
+
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'user2', 'scope1'))
+ assert.isTrue(await Roles.userIsInRoleAsync(users.bob, 'user2', 'scope1'))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.joe, 'user2', 'scope1'))
+
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, 'user2', 'scope2'))
+ assert.isTrue(await Roles.userIsInRoleAsync(users.bob, 'user2', 'scope2'))
+ assert.isTrue(await Roles.userIsInRoleAsync(users.joe, 'user2', 'scope2'))
+
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, 'user', 'scope1'))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, 'user', 'scope2'))
+ })
+
+ it('_addUserToRole', async function () {
+ await Roles.createRoleAsync('admin')
+
+ const userRoles = await Roles.getRolesForUserAsync(users.eve, { anyScope: true, fullObjects: true })
+ assert.sameDeepMembers(userRoles.map(obj => { delete obj._id; return obj }), [])
+
+ const roles = await Roles._addUserToRoleAsync(users.eve, 'admin', { scope: null, ifExists: false })
+ assert.hasAnyKeys(roles, 'insertedId')
+
+ const userRoles2 = await Roles.getRolesForUserAsync(users.eve, { anyScope: true, fullObjects: true })
+ assert.sameDeepMembers(userRoles2.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'admin' },
+ scope: null,
+ user: { _id: users.eve },
+ inheritedRoles: [{ _id: 'admin' }]
+ }])
+
+ const roles2 = await Roles._addUserToRoleAsync(users.eve, 'admin', { scope: null, ifExists: false })
+ assert.hasAnyKeys(roles2, 'insertedId')
+
+ const roles3 = await Roles.getRolesForUserAsync(users.eve, { anyScope: true, fullObjects: true })
+ assert.sameDeepMembers(roles3.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'admin' },
+ scope: null,
+ user: { _id: users.eve },
+ inheritedRoles: [{ _id: 'admin' }]
+ }])
+ })
+
+ it('_removeUserFromRole', async function () {
+ await Roles.createRoleAsync('admin')
+
+ await Roles.addUsersToRolesAsync(users.eve, 'admin')
+
+ const rolesForUser = await Roles.getRolesForUserAsync(users.eve, { anyScope: true, fullObjects: true })
+ assert.sameDeepMembers(rolesForUser.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'admin' },
+ scope: null,
+ user: { _id: users.eve },
+ inheritedRoles: [{ _id: 'admin' }]
+ }])
+
+ Roles._removeUserFromRole(users.eve, 'admin', { scope: null })
+
+ const rolesForUser2 = await Roles.getRolesForUserAsync(users.eve, { anyScope: true, fullObjects: true })
+ assert.sameDeepMembers(rolesForUser2.map(obj => { delete obj._id; return obj }), [])
+ })
+
+ it('keep assigned roles', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('ALL_PERMISSIONS')
+ await Roles.createRoleAsync('VIEW_PERMISSION')
+ await Roles.createRoleAsync('EDIT_PERMISSION')
+ await Roles.createRoleAsync('DELETE_PERMISSION')
+ await Roles.addRolesToParentAsync('ALL_PERMISSIONS', 'user')
+ await Roles.addRolesToParentAsync('EDIT_PERMISSION', 'ALL_PERMISSIONS')
+ await Roles.addRolesToParentAsync('VIEW_PERMISSION', 'ALL_PERMISSIONS')
+ await Roles.addRolesToParentAsync('DELETE_PERMISSION', 'admin')
+
+ await Roles.addUsersToRolesAsync(users.eve, ['user'])
+
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'VIEW_PERMISSION'))
+
+ const rolesForUser = await Roles.getRolesForUserAsync(users.eve, { anyScope: true, fullObjects: true })
+ assert.sameDeepMembers(rolesForUser.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'user' },
+ scope: null,
+ user: { _id: users.eve },
+ inheritedRoles: [
+ { _id: 'user' },
+ { _id: 'ALL_PERMISSIONS' },
+ { _id: 'EDIT_PERMISSION' },
+ { _id: 'VIEW_PERMISSION' }
+ ]
+ }])
+
+ await Roles.addUsersToRolesAsync(users.eve, 'VIEW_PERMISSION')
+
+ assert.eventually.isTrue(Roles.userIsInRoleAsync(users.eve, 'VIEW_PERMISSION'))
+
+ const rolesForUser2 = await Roles.getRolesForUserAsync(users.eve, { anyScope: true, fullObjects: true })
+ assert.sameDeepMembers(rolesForUser2.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'user' },
+ scope: null,
+ user: { _id: users.eve },
+ inheritedRoles: [
+ { _id: 'user' },
+ { _id: 'ALL_PERMISSIONS' },
+ { _id: 'EDIT_PERMISSION' },
+ { _id: 'VIEW_PERMISSION' }
+ ]
+ }, {
+ role: { _id: 'VIEW_PERMISSION' },
+ scope: null,
+ user: { _id: users.eve },
+ inheritedRoles: [
+ { _id: 'VIEW_PERMISSION' }
+ ]
+ }])
+
+ await Roles.removeUsersFromRolesAsync(users.eve, 'user')
+
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'VIEW_PERMISSION'))
+
+ const rolesForUser3 = await Roles.getRolesForUserAsync(users.eve, { anyScope: true, fullObjects: true })
+ assert.sameDeepMembers(rolesForUser3.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'VIEW_PERMISSION' },
+ scope: null,
+ user: { _id: users.eve },
+ inheritedRoles: [
+ { _id: 'VIEW_PERMISSION' }
+ ]
+ }])
+
+ await Roles.removeUsersFromRolesAsync(users.eve, 'VIEW_PERMISSION')
+
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, 'VIEW_PERMISSION'))
+
+ const rolesForUser4 = await Roles.getRolesForUserAsync(users.eve, { anyScope: true, fullObjects: true })
+ assert.sameDeepMembers(rolesForUser4.map(obj => { delete obj._id; return obj }), [])
+ })
+
+ it('adds children of the added role to the assignments', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('ALBUM.ADMIN')
+ await Roles.createRoleAsync('ALBUM.VIEW')
+ await Roles.createRoleAsync('TRACK.ADMIN')
+ await Roles.createRoleAsync('TRACK.VIEW')
+
+ await Roles.addRolesToParentAsync('ALBUM.VIEW', 'ALBUM.ADMIN')
+ await Roles.addRolesToParentAsync('TRACK.VIEW', 'TRACK.ADMIN')
+
+ await Roles.addRolesToParentAsync('ALBUM.ADMIN', 'admin')
+
+ await Roles.addUsersToRolesAsync(users.eve, ['admin'])
+
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, 'TRACK.VIEW'))
+
+ await Roles.addRolesToParentAsync('TRACK.ADMIN', 'admin')
+
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'TRACK.VIEW'))
+ })
+
+ it('removes children of the removed role from the assignments', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('ALBUM.ADMIN')
+ await Roles.createRoleAsync('ALBUM.VIEW')
+ await Roles.createRoleAsync('TRACK.ADMIN')
+ await Roles.createRoleAsync('TRACK.VIEW')
+
+ await Roles.addRolesToParentAsync('ALBUM.VIEW', 'ALBUM.ADMIN')
+ await Roles.addRolesToParentAsync('TRACK.VIEW', 'TRACK.ADMIN')
+
+ await Roles.addRolesToParentAsync('ALBUM.ADMIN', 'admin')
+ await Roles.addRolesToParentAsync('TRACK.ADMIN', 'admin')
+
+ await Roles.addUsersToRolesAsync(users.eve, ['admin'])
+
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'TRACK.VIEW'))
+
+ await Roles.removeRolesFromParentAsync('TRACK.ADMIN', 'admin')
+
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, 'TRACK.VIEW'))
+ })
+
+ it('modify assigned hierarchical roles', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('ALL_PERMISSIONS')
+ await Roles.createRoleAsync('VIEW_PERMISSION')
+ await Roles.createRoleAsync('EDIT_PERMISSION')
+ await Roles.createRoleAsync('DELETE_PERMISSION')
+ await Roles.addRolesToParentAsync('ALL_PERMISSIONS', 'user')
+ await Roles.addRolesToParentAsync('EDIT_PERMISSION', 'ALL_PERMISSIONS')
+ await Roles.addRolesToParentAsync('VIEW_PERMISSION', 'ALL_PERMISSIONS')
+ await Roles.addRolesToParentAsync('DELETE_PERMISSION', 'admin')
+
+ await Roles.addUsersToRolesAsync(users.eve, ['user'])
+ await Roles.addUsersToRolesAsync(users.eve, ['ALL_PERMISSIONS'], 'scope')
+
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, 'MODERATE_PERMISSION'))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, 'MODERATE_PERMISSION', 'scope'))
+
+ const usersRoles = await Roles.getRolesForUserAsync(users.eve, { anyScope: true, fullObjects: true })
+ assert.sameDeepMembers(usersRoles.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'user' },
+ scope: null,
+ user: { _id: users.eve },
+ inheritedRoles: [
+ { _id: 'user' },
+ { _id: 'ALL_PERMISSIONS' },
+ { _id: 'EDIT_PERMISSION' },
+ { _id: 'VIEW_PERMISSION' }
+ ]
+ }, {
+ role: { _id: 'ALL_PERMISSIONS' },
+ scope: 'scope',
+ user: { _id: users.eve },
+ inheritedRoles: [
+ { _id: 'ALL_PERMISSIONS' },
+ { _id: 'EDIT_PERMISSION' },
+ { _id: 'VIEW_PERMISSION' }
+ ]
+ }])
+
+ await Roles.createRoleAsync('MODERATE_PERMISSION')
+
+ await Roles.addRolesToParentAsync('MODERATE_PERMISSION', 'ALL_PERMISSIONS')
+
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'MODERATE_PERMISSION'))
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'MODERATE_PERMISSION', 'scope'))
+
+ const usersRoles2 = await Roles.getRolesForUserAsync(users.eve, { anyScope: true, fullObjects: true })
+ assert.sameDeepMembers(usersRoles2.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'user' },
+ scope: null,
+ user: { _id: users.eve },
+ inheritedRoles: [
+ { _id: 'user' },
+ { _id: 'ALL_PERMISSIONS' },
+ { _id: 'EDIT_PERMISSION' },
+ { _id: 'VIEW_PERMISSION' },
+ { _id: 'MODERATE_PERMISSION' }
+ ]
+ }, {
+ role: { _id: 'ALL_PERMISSIONS' },
+ scope: 'scope',
+ user: { _id: users.eve },
+ inheritedRoles: [
+ { _id: 'ALL_PERMISSIONS' },
+ { _id: 'EDIT_PERMISSION' },
+ { _id: 'VIEW_PERMISSION' },
+ { _id: 'MODERATE_PERMISSION' }
+ ]
+ }])
+
+ await Roles.addUsersToRolesAsync(users.eve, ['admin'])
+
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'DELETE_PERMISSION'))
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'DELETE_PERMISSION', 'scope'))
+
+ const usersRoles3 = await Roles.getRolesForUserAsync(users.eve, { anyScope: true, fullObjects: true })
+ assert.sameDeepMembers(usersRoles3.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'user' },
+ scope: null,
+ user: { _id: users.eve },
+ inheritedRoles: [
+ { _id: 'user' },
+ { _id: 'ALL_PERMISSIONS' },
+ { _id: 'EDIT_PERMISSION' },
+ { _id: 'VIEW_PERMISSION' },
+ { _id: 'MODERATE_PERMISSION' }
+ ]
+ }, {
+ role: { _id: 'ALL_PERMISSIONS' },
+ scope: 'scope',
+ user: { _id: users.eve },
+ inheritedRoles: [
+ { _id: 'ALL_PERMISSIONS' },
+ { _id: 'EDIT_PERMISSION' },
+ { _id: 'VIEW_PERMISSION' },
+ { _id: 'MODERATE_PERMISSION' }
+ ]
+ }, {
+ role: { _id: 'admin' },
+ scope: null,
+ user: { _id: users.eve },
+ inheritedRoles: [
+ { _id: 'admin' },
+ { _id: 'DELETE_PERMISSION' }
+ ]
+ }])
+
+ await Roles.addRolesToParentAsync('DELETE_PERMISSION', 'ALL_PERMISSIONS')
+
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'DELETE_PERMISSION'))
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'DELETE_PERMISSION', 'scope'))
+
+ const usersRoles4 = await Roles.getRolesForUserAsync(users.eve, { anyScope: true, fullObjects: true })
+ assert.sameDeepMembers(usersRoles4.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'user' },
+ scope: null,
+ user: { _id: users.eve },
+ inheritedRoles: [
+ { _id: 'user' },
+ { _id: 'ALL_PERMISSIONS' },
+ { _id: 'EDIT_PERMISSION' },
+ { _id: 'VIEW_PERMISSION' },
+ { _id: 'MODERATE_PERMISSION' },
+ { _id: 'DELETE_PERMISSION' }
+ ]
+ }, {
+ role: { _id: 'ALL_PERMISSIONS' },
+ scope: 'scope',
+ user: { _id: users.eve },
+ inheritedRoles: [
+ { _id: 'ALL_PERMISSIONS' },
+ { _id: 'EDIT_PERMISSION' },
+ { _id: 'VIEW_PERMISSION' },
+ { _id: 'MODERATE_PERMISSION' },
+ { _id: 'DELETE_PERMISSION' }
+ ]
+ }, {
+ role: { _id: 'admin' },
+ scope: null,
+ user: { _id: users.eve },
+ inheritedRoles: [
+ { _id: 'admin' },
+ { _id: 'DELETE_PERMISSION' }
+ ]
+ }])
+
+ await Roles.removeUsersFromRolesAsync(users.eve, ['admin'])
+
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'DELETE_PERMISSION'))
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'DELETE_PERMISSION', 'scope'))
+
+ const usersRoles5 = await Roles.getRolesForUserAsync(users.eve, { anyScope: true, fullObjects: true })
+ assert.sameDeepMembers(usersRoles5.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'user' },
+ scope: null,
+ user: { _id: users.eve },
+ inheritedRoles: [
+ { _id: 'user' },
+ { _id: 'ALL_PERMISSIONS' },
+ { _id: 'EDIT_PERMISSION' },
+ { _id: 'VIEW_PERMISSION' },
+ { _id: 'MODERATE_PERMISSION' },
+ { _id: 'DELETE_PERMISSION' }
+ ]
+ }, {
+ role: { _id: 'ALL_PERMISSIONS' },
+ scope: 'scope',
+ user: { _id: users.eve },
+ inheritedRoles: [
+ { _id: 'ALL_PERMISSIONS' },
+ { _id: 'EDIT_PERMISSION' },
+ { _id: 'VIEW_PERMISSION' },
+ { _id: 'MODERATE_PERMISSION' },
+ { _id: 'DELETE_PERMISSION' }
+ ]
+ }])
+
+ await await Roles.deleteRoleAsync('ALL_PERMISSIONS')
+
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, 'DELETE_PERMISSION'))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, 'DELETE_PERMISSION', 'scope'))
+
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, 'MODERATE_PERMISSION'))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, 'MODERATE_PERMISSION', 'scope'))
+
+ const usersRoles6 = await Roles.getRolesForUserAsync(users.eve, { anyScope: true, fullObjects: true })
+ assert.sameDeepMembers(usersRoles6.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'user' },
+ scope: null,
+ user: { _id: users.eve },
+ inheritedRoles: [
+ { _id: 'user' }
+ ]
+ }])
+ })
+
+ it('delete role with overlapping hierarchical roles', async function () {
+ await Roles.createRoleAsync('role1')
+ await Roles.createRoleAsync('role2')
+ await Roles.createRoleAsync('COMMON_PERMISSION_1')
+ await Roles.createRoleAsync('COMMON_PERMISSION_2')
+ await Roles.createRoleAsync('COMMON_PERMISSION_3')
+ await Roles.createRoleAsync('EXTRA_PERMISSION_ROLE_1')
+ await Roles.createRoleAsync('EXTRA_PERMISSION_ROLE_2')
+
+ await Roles.addRolesToParentAsync('COMMON_PERMISSION_1', 'role1')
+ await Roles.addRolesToParentAsync('COMMON_PERMISSION_2', 'role1')
+ await Roles.addRolesToParentAsync('COMMON_PERMISSION_3', 'role1')
+ await Roles.addRolesToParentAsync('EXTRA_PERMISSION_ROLE_1', 'role1')
+
+ await Roles.addRolesToParentAsync('COMMON_PERMISSION_1', 'role2')
+ await Roles.addRolesToParentAsync('COMMON_PERMISSION_2', 'role2')
+ await Roles.addRolesToParentAsync('COMMON_PERMISSION_3', 'role2')
+ await Roles.addRolesToParentAsync('EXTRA_PERMISSION_ROLE_2', 'role2')
+
+ await Roles.addUsersToRolesAsync(users.eve, 'role1')
+ await Roles.addUsersToRolesAsync(users.eve, 'role2')
+
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'COMMON_PERMISSION_1'))
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'EXTRA_PERMISSION_ROLE_1'))
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'EXTRA_PERMISSION_ROLE_2'))
+
+ const usersRoles = await Roles.getRolesForUserAsync(users.eve, { anyScope: true, fullObjects: true })
+ assert.sameDeepMembers(usersRoles.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'role1' },
+ scope: null,
+ user: { _id: users.eve },
+ inheritedRoles: [
+ { _id: 'role1' },
+ { _id: 'COMMON_PERMISSION_1' },
+ { _id: 'COMMON_PERMISSION_2' },
+ { _id: 'COMMON_PERMISSION_3' },
+ { _id: 'EXTRA_PERMISSION_ROLE_1' }
+ ]
+ }, {
+ role: { _id: 'role2' },
+ scope: null,
+ user: { _id: users.eve },
+ inheritedRoles: [
+ { _id: 'role2' },
+ { _id: 'COMMON_PERMISSION_1' },
+ { _id: 'COMMON_PERMISSION_2' },
+ { _id: 'COMMON_PERMISSION_3' },
+ { _id: 'EXTRA_PERMISSION_ROLE_2' }
+ ]
+ }])
+
+ await Roles.removeUsersFromRolesAsync(users.eve, 'role2')
+
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'COMMON_PERMISSION_1'))
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'EXTRA_PERMISSION_ROLE_1'))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, 'EXTRA_PERMISSION_ROLE_2'))
+
+ const usersRoles2 = await Roles.getRolesForUserAsync(users.eve, { anyScope: true, fullObjects: true })
+ assert.sameDeepMembers(usersRoles2.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'role1' },
+ scope: null,
+ user: { _id: users.eve },
+ inheritedRoles: [
+ { _id: 'role1' },
+ { _id: 'COMMON_PERMISSION_1' },
+ { _id: 'COMMON_PERMISSION_2' },
+ { _id: 'COMMON_PERMISSION_3' },
+ { _id: 'EXTRA_PERMISSION_ROLE_1' }
+ ]
+ }])
+
+ await Roles.addUsersToRolesAsync(users.eve, 'role2')
+
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'COMMON_PERMISSION_1'))
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'EXTRA_PERMISSION_ROLE_1'))
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'EXTRA_PERMISSION_ROLE_2'))
+
+ const usersRoles3 = await Roles.getRolesForUserAsync(users.eve, { anyScope: true, fullObjects: true })
+ assert.sameDeepMembers(usersRoles3.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'role1' },
+ scope: null,
+ user: { _id: users.eve },
+ inheritedRoles: [
+ { _id: 'role1' },
+ { _id: 'COMMON_PERMISSION_1' },
+ { _id: 'COMMON_PERMISSION_2' },
+ { _id: 'COMMON_PERMISSION_3' },
+ { _id: 'EXTRA_PERMISSION_ROLE_1' }
+ ]
+ }, {
+ role: { _id: 'role2' },
+ scope: null,
+ user: { _id: users.eve },
+ inheritedRoles: [
+ { _id: 'role2' },
+ { _id: 'COMMON_PERMISSION_1' },
+ { _id: 'COMMON_PERMISSION_2' },
+ { _id: 'COMMON_PERMISSION_3' },
+ { _id: 'EXTRA_PERMISSION_ROLE_2' }
+ ]
+ }])
+
+ await Roles.deleteRoleAsync('role2')
+
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'COMMON_PERMISSION_1'))
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'EXTRA_PERMISSION_ROLE_1'))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, 'EXTRA_PERMISSION_ROLE_2'))
+
+ const usersRoles4 = await Roles.getRolesForUserAsync(users.eve, { anyScope: true, fullObjects: true })
+ assert.sameDeepMembers(usersRoles4.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'role1' },
+ scope: null,
+ user: { _id: users.eve },
+ inheritedRoles: [
+ { _id: 'role1' },
+ { _id: 'COMMON_PERMISSION_1' },
+ { _id: 'COMMON_PERMISSION_2' },
+ { _id: 'COMMON_PERMISSION_3' },
+ { _id: 'EXTRA_PERMISSION_ROLE_1' }
+ ]
+ }])
+ })
+
+ it('set parent on assigned role', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('EDIT_PERMISSION')
+
+ await Roles.addUsersToRolesAsync(users.eve, 'EDIT_PERMISSION')
+
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'EDIT_PERMISSION'))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, 'admin'))
+
+ const usersRoles = await Roles.getRolesForUserAsync(users.eve, { anyScope: true, fullObjects: true })
+ assert.sameDeepMembers(usersRoles.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'EDIT_PERMISSION' },
+ scope: null,
+ user: { _id: users.eve },
+ inheritedRoles: [{ _id: 'EDIT_PERMISSION' }]
+ }])
+
+ await Roles.addRolesToParentAsync('EDIT_PERMISSION', 'admin')
+
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'EDIT_PERMISSION'))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, 'admin'))
+
+ const usersRoles2 = await Roles.getRolesForUserAsync(users.eve, { anyScope: true, fullObjects: true })
+ assert.sameDeepMembers(usersRoles2.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'EDIT_PERMISSION' },
+ scope: null,
+ user: { _id: users.eve },
+ inheritedRoles: [{ _id: 'EDIT_PERMISSION' }]
+ }])
+ })
+
+ it('remove parent on assigned role', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('EDIT_PERMISSION')
+
+ await Roles.addRolesToParentAsync('EDIT_PERMISSION', 'admin')
+
+ await Roles.addUsersToRolesAsync(users.eve, 'EDIT_PERMISSION')
+
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'EDIT_PERMISSION'))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, 'admin'))
+
+ const usersRoles = await Roles.getRolesForUserAsync(users.eve, { anyScope: true, fullObjects: true })
+ assert.sameDeepMembers(usersRoles.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'EDIT_PERMISSION' },
+ scope: null,
+ user: { _id: users.eve },
+ inheritedRoles: [{ _id: 'EDIT_PERMISSION' }]
+ }])
+
+ await Roles.removeRolesFromParentAsync('EDIT_PERMISSION', 'admin')
+
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'EDIT_PERMISSION'))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, 'admin'))
+
+ const usersRoles2 = await Roles.getRolesForUserAsync(users.eve, { anyScope: true, fullObjects: true })
+ assert.sameDeepMembers(usersRoles2.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'EDIT_PERMISSION' },
+ scope: null,
+ user: { _id: users.eve },
+ inheritedRoles: [{ _id: 'EDIT_PERMISSION' }]
+ }])
+ })
+
+ it('adding and removing extra role parents', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('EDIT_PERMISSION')
+
+ await Roles.addRolesToParentAsync('EDIT_PERMISSION', 'admin')
+
+ await Roles.addUsersToRolesAsync(users.eve, 'EDIT_PERMISSION')
+
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'EDIT_PERMISSION'))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, 'admin'))
+
+ const usersRoles = await Roles.getRolesForUserAsync(users.eve, { anyScope: true, fullObjects: true })
+ assert.sameDeepMembers(usersRoles.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'EDIT_PERMISSION' },
+ scope: null,
+ user: { _id: users.eve },
+ inheritedRoles: [{ _id: 'EDIT_PERMISSION' }]
+ }])
+
+ await Roles.addRolesToParentAsync('EDIT_PERMISSION', 'user')
+
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'EDIT_PERMISSION'))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, 'admin'))
+
+ const usersRoles2 = await Roles.getRolesForUserAsync(users.eve, { anyScope: true, fullObjects: true })
+ assert.sameDeepMembers(usersRoles2.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'EDIT_PERMISSION' },
+ scope: null,
+ user: { _id: users.eve },
+ inheritedRoles: [{ _id: 'EDIT_PERMISSION' }]
+ }])
+
+ await Roles.removeRolesFromParentAsync('EDIT_PERMISSION', 'user')
+
+ assert.isTrue(await Roles.userIsInRoleAsync(users.eve, 'EDIT_PERMISSION'))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, 'admin'))
+
+ const usersRoles3 = await Roles.getRolesForUserAsync(users.eve, { anyScope: true, fullObjects: true })
+ assert.sameDeepMembers(usersRoles3.map(obj => { delete obj._id; return obj }), [{
+ role: { _id: 'EDIT_PERMISSION' },
+ scope: null,
+ user: { _id: users.eve },
+ inheritedRoles: [{ _id: 'EDIT_PERMISSION' }]
+ }])
+ })
+
+ it('cyclic roles', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('editor')
+ await Roles.createRoleAsync('user')
+
+ await Roles.addRolesToParentAsync('editor', 'admin')
+ await Roles.addRolesToParentAsync('user', 'editor')
+
+ await assert.isRejected(Roles.addRolesToParentAsync('admin', 'user'), /form a cycle/)
+ })
+
+ describe('userIsInRole', function () {
+ it('userIsInRole returns false for unknown roles', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('user')
+ await Roles.createRoleAsync('editor')
+ await Roles.addUsersToRolesAsync(users.eve, ['admin', 'user'])
+ await Roles.addUsersToRolesAsync(users.eve, ['editor'])
+
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, 'unknown'))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, []))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, null))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, undefined))
+
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, 'unknown', { anyScope: true }))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, [], { anyScope: true }))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, null, { anyScope: true }))
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, undefined, { anyScope: true }))
+
+ assert.isFalse(await Roles.userIsInRoleAsync(users.eve, ['Role1', 'Role2', undefined], 'GroupName'))
+ })
+
+ it('userIsInRole returns false if user is a function', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.addUsersToRolesAsync(users.eve, ['admin'])
+
+ assert.isFalse(await Roles.userIsInRoleAsync(() => {}, 'admin'))
+ })
+ })
+
+ describe('isParentOf', function () {
+ it('returns false for unknown roles', async function () {
+ await Roles.createRoleAsync('admin')
+
+ assert.isFalse(await Roles.isParentOfAsync('admin', 'unknown'))
+ assert.isFalse(await Roles.isParentOfAsync('admin', null))
+ assert.isFalse(await Roles.isParentOfAsync('admin', undefined))
+
+ assert.isFalse(await Roles.isParentOfAsync('unknown', 'admin'))
+ assert.isFalse(await Roles.isParentOfAsync(null, 'admin'))
+ assert.isFalse(await Roles.isParentOfAsync(undefined, 'admin'))
+ })
+
+ it('returns false if role is not parent of', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('editor')
+ await Roles.createRoleAsync('user')
+ await Roles.addRolesToParentAsync(['editor'], 'admin')
+ await Roles.addRolesToParentAsync(['user'], 'editor')
+
+ assert.isFalse(await Roles.isParentOfAsync('user', 'admin'))
+ assert.isFalse(await Roles.isParentOfAsync('editor', 'admin'))
+ })
+
+ it('returns true if role is parent of the demanded role', async function () {
+ await Roles.createRoleAsync('admin')
+ await Roles.createRoleAsync('editor')
+ await Roles.createRoleAsync('user')
+ await Roles.addRolesToParentAsync(['editor'], 'admin')
+ await Roles.addRolesToParentAsync(['user'], 'editor')
+
+ assert.isTrue(await Roles.isParentOfAsync('admin', 'user'))
+ assert.isTrue(await Roles.isParentOfAsync('editor', 'user'))
+ assert.isTrue(await Roles.isParentOfAsync('admin', 'editor'))
+
+ assert.isTrue(await Roles.isParentOfAsync('admin', 'admin'))
+ assert.isTrue(await Roles.isParentOfAsync('editor', 'editor'))
+ assert.isTrue(await Roles.isParentOfAsync('user', 'user'))
+ })
+ })
+})
diff --git a/testapp/.meteor/packages b/testapp/.meteor/packages
index 63f32aa2..0d165be5 100644
--- a/testapp/.meteor/packages
+++ b/testapp/.meteor/packages
@@ -4,15 +4,15 @@
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.
-meteor@1.11.2 # Shared foundation for all Meteor packages
+meteor@1.11.4 # Shared foundation for all Meteor packages
static-html@1.3.2 # Define static page content in .html files
standard-minifier-css@1.9.2 # CSS minifier run for production mode
standard-minifier-js@2.8.1 # JS minifier run for production mode
es5-shim@4.8.0 # ECMAScript 5 compatibility for older browsers
-ecmascript@0.16.7 # Enable ECMAScript2015+ syntax in app code
-typescript@4.9.4 # Enable TypeScript syntax in .ts and .tsx modules
+ecmascript@0.16.8 # Enable ECMAScript2015+ syntax in app code
+typescript@4.9.5 # Enable TypeScript syntax in .ts and .tsx modules
shell-server@0.5.0 # Server-side component of the `meteor shell` command
-webapp@1.13.5 # Serves a Meteor app over HTTP
+webapp@1.13.6 # Serves a Meteor app over HTTP
server-render@0.4.1 # Support for server-side rendering
hot-module-replacement@0.5.3 # Rebuilds the client if there is a change on the client without restarting the server
diff --git a/testapp/.meteor/release b/testapp/.meteor/release
index e8cfc7ec..c500c39d 100644
--- a/testapp/.meteor/release
+++ b/testapp/.meteor/release
@@ -1 +1 @@
-METEOR@2.12
+METEOR@2.14
diff --git a/testapp/.meteor/versions b/testapp/.meteor/versions
index 24b50021..00869796 100644
--- a/testapp/.meteor/versions
+++ b/testapp/.meteor/versions
@@ -1,9 +1,9 @@
autoupdate@1.8.0
-babel-compiler@7.10.4
+babel-compiler@7.10.5
babel-runtime@1.5.1
base64@1.0.12
blaze-tools@1.1.3
-boilerplate-generator@1.7.1
+boilerplate-generator@1.7.2
caching-compiler@1.2.2
caching-html-compiler@1.2.1
callback-hook@1.5.1
@@ -11,52 +11,47 @@ check@1.3.2
ddp@1.4.1
ddp-client@2.6.1
ddp-common@1.4.0
-ddp-server@2.6.1
+ddp-server@2.7.0
diff-sequence@1.1.2
dynamic-import@0.7.3
-ecmascript@0.16.7
+ecmascript@0.16.8
ecmascript-runtime@0.8.1
ecmascript-runtime-client@0.12.1
ecmascript-runtime-server@0.11.0
ejson@1.1.3
es5-shim@4.8.0
-fetch@0.1.3
+fetch@0.1.4
hot-code-push@1.0.4
hot-module-replacement@0.5.3
html-tools@1.1.3
htmljs@1.1.1
-http@1.0.10
id-map@1.1.1
inter-process-messaging@0.1.1
-logging@1.3.2
-meteor@1.11.2
-meteortesting:browser-tests@1.4.2
-meteortesting:mocha@2.1.0
-meteortesting:mocha-core@8.0.1
+logging@1.3.3
+meteor@1.11.4
minifier-css@1.6.4
minifier-js@2.7.5
-modern-browsers@0.1.9
-modules@0.19.0
+modern-browsers@0.1.10
+modules@0.20.0
modules-runtime@0.13.1
modules-runtime-hot@0.14.2
mongo-id@1.0.8
promise@0.12.2
random@1.2.1
-react-fast-refresh@0.2.7
+react-fast-refresh@0.2.8
reload@1.3.1
retry@1.1.0
routepolicy@1.1.1
server-render@0.4.1
shell-server@0.5.0
-socket-stream-client@0.5.1
+socket-stream-client@0.5.2
spacebars-compiler@1.3.1
standard-minifier-css@1.9.2
standard-minifier-js@2.8.1
static-html@1.3.2
templating-tools@1.2.2
-tracker@1.3.2
-typescript@4.9.4
+tracker@1.3.3
+typescript@4.9.5
underscore@1.0.13
-url@1.3.2
-webapp@1.13.5
+webapp@1.13.6
webapp-hashing@1.1.1
diff --git a/testapp/package-lock.json b/testapp/package-lock.json
index 4d42b56e..7cb48d5c 100644
--- a/testapp/package-lock.json
+++ b/testapp/package-lock.json
@@ -88,16 +88,6 @@
"integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==",
"dev": true
},
- "@babel/helper-function-name": {
- "version": "7.22.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz",
- "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==",
- "dev": true,
- "requires": {
- "@babel/template": "^7.22.5",
- "@babel/types": "^7.22.5"
- }
- },
"@babel/helper-hoist-variables": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
@@ -200,11 +190,11 @@
"dev": true
},
"@babel/runtime": {
- "version": "7.22.6",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz",
- "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==",
+ "version": "7.23.6",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.6.tgz",
+ "integrity": "sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==",
"requires": {
- "regenerator-runtime": "^0.13.11"
+ "regenerator-runtime": "^0.14.0"
}
},
"@babel/template": {
@@ -219,21 +209,106 @@
}
},
"@babel/traverse": {
- "version": "7.22.8",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.8.tgz",
- "integrity": "sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw==",
+ "version": "7.23.2",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
+ "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
"dev": true,
"requires": {
- "@babel/code-frame": "^7.22.5",
- "@babel/generator": "^7.22.7",
- "@babel/helper-environment-visitor": "^7.22.5",
- "@babel/helper-function-name": "^7.22.5",
+ "@babel/code-frame": "^7.22.13",
+ "@babel/generator": "^7.23.0",
+ "@babel/helper-environment-visitor": "^7.22.20",
+ "@babel/helper-function-name": "^7.23.0",
"@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
- "@babel/parser": "^7.22.7",
- "@babel/types": "^7.22.5",
+ "@babel/parser": "^7.23.0",
+ "@babel/types": "^7.23.0",
"debug": "^4.1.0",
"globals": "^11.1.0"
+ },
+ "dependencies": {
+ "@babel/code-frame": {
+ "version": "7.22.13",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
+ "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
+ "dev": true,
+ "requires": {
+ "@babel/highlight": "^7.22.13",
+ "chalk": "^2.4.2"
+ }
+ },
+ "@babel/generator": {
+ "version": "7.23.0",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
+ "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.23.0",
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "@jridgewell/trace-mapping": "^0.3.17",
+ "jsesc": "^2.5.1"
+ }
+ },
+ "@babel/helper-environment-visitor": {
+ "version": "7.22.20",
+ "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
+ "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
+ "dev": true
+ },
+ "@babel/helper-function-name": {
+ "version": "7.23.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
+ "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
+ "dev": true,
+ "requires": {
+ "@babel/template": "^7.22.15",
+ "@babel/types": "^7.23.0"
+ }
+ },
+ "@babel/helper-validator-identifier": {
+ "version": "7.22.20",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
+ "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
+ "dev": true
+ },
+ "@babel/highlight": {
+ "version": "7.22.20",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
+ "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.22.20",
+ "chalk": "^2.4.2",
+ "js-tokens": "^4.0.0"
+ }
+ },
+ "@babel/parser": {
+ "version": "7.23.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
+ "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
+ "dev": true
+ },
+ "@babel/template": {
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
+ "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.22.13",
+ "@babel/parser": "^7.22.15",
+ "@babel/types": "^7.22.15"
+ }
+ },
+ "@babel/types": {
+ "version": "7.23.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
+ "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-string-parser": "^7.22.5",
+ "@babel/helper-validator-identifier": "^7.22.20",
+ "to-fast-properties": "^2.0.0"
+ }
+ }
}
},
"@babel/types": {
@@ -969,18 +1044,27 @@
"dev": true
},
"chai": {
- "version": "4.3.7",
- "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz",
- "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==",
+ "version": "4.3.10",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz",
+ "integrity": "sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==",
"dev": true,
"requires": {
"assertion-error": "^1.1.0",
- "check-error": "^1.0.2",
- "deep-eql": "^4.1.2",
- "get-func-name": "^2.0.0",
- "loupe": "^2.3.1",
+ "check-error": "^1.0.3",
+ "deep-eql": "^4.1.3",
+ "get-func-name": "^2.0.2",
+ "loupe": "^2.3.6",
"pathval": "^1.1.1",
- "type-detect": "^4.0.5"
+ "type-detect": "^4.0.8"
+ }
+ },
+ "chai-as-promised": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz",
+ "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==",
+ "dev": true,
+ "requires": {
+ "check-error": "^1.0.2"
}
},
"chalk": {
@@ -995,10 +1079,13 @@
}
},
"check-error": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
- "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==",
- "dev": true
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz",
+ "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==",
+ "dev": true,
+ "requires": {
+ "get-func-name": "^2.0.2"
+ }
},
"chownr": {
"version": "1.1.4",
@@ -2231,9 +2318,9 @@
"dev": true
},
"get-func-name": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
- "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
+ "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==",
"dev": true
},
"get-intrinsic": {
@@ -2978,12 +3065,12 @@
}
},
"loupe": {
- "version": "2.3.6",
- "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz",
- "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==",
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz",
+ "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==",
"dev": true,
"requires": {
- "get-func-name": "^2.0.0"
+ "get-func-name": "^2.0.1"
}
},
"lru-cache": {
@@ -3047,9 +3134,9 @@
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
},
"meteor-node-stubs": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/meteor-node-stubs/-/meteor-node-stubs-1.2.5.tgz",
- "integrity": "sha512-FLlOFZx3KnZ5s3yPCK+x58DyX9ewN+oQ12LcpwBXMLtzJ/YyprMQVivd6KIrahZbKJrNenPNUGuDS37WUFg+Mw==",
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/meteor-node-stubs/-/meteor-node-stubs-1.2.7.tgz",
+ "integrity": "sha512-20bAFUhEIOD/Cos2nmvhqf2NOKpTf63WVQ+nwuaX2OFj31sU6GL4KkNylkWum8McwsH0LsMr/F+UHhduTX7KRg==",
"requires": {
"assert": "^2.0.0",
"browserify-zlib": "^0.2.0",
@@ -4408,9 +4495,9 @@
}
},
"regenerator-runtime": {
- "version": "0.13.11",
- "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
- "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
},
"regexp.prototype.flags": {
"version": "1.5.0",
diff --git a/testapp/package.json b/testapp/package.json
index f894d6e5..b1d84ef2 100644
--- a/testapp/package.json
+++ b/testapp/package.json
@@ -11,15 +11,16 @@
"lint:fix": "standard --fix ../"
},
"dependencies": {
- "@babel/runtime": "^7.17.9",
- "meteor-node-stubs": "^1.2.1",
- "yuidocjs": "^0.10.2"
+ "@babel/runtime": "7.23.6",
+ "meteor-node-stubs": "1.2.7",
+ "yuidocjs": "0.10.2"
},
"devDependencies": {
"babel-plugin-istanbul": "^6.1.1",
- "chai": "^4.3.7",
+ "chai": "^4.3.10",
+ "chai-as-promised": "^7.1.1",
"nyc": "^15.1.0",
- "puppeteer": "^19.4.0",
+ "puppeteer": "^19.11.1",
"standard": "^17.1.0"
},
"babel": {
diff --git a/yuidoc.json b/yuidoc.json
index 18329a2a..de1aa908 100644
--- a/yuidoc.json
+++ b/yuidoc.json
@@ -1,7 +1,7 @@
{
"name": "The meteor-roles API",
"description": "The meteor-roles API: an authorization package for Meteor",
- "version": "v3.4.0",
+ "version": "v3.6.0",
"options": {
"outdir": "./docs",
"exclude": ".meteor,.build,tests",