Compare commits

..

No commits in common. "master" and "v3.22.2" have entirely different histories.

112 changed files with 2797 additions and 3365 deletions

37
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,37 @@
<!--
Questions:
https://groups.google.com/forum/#!forum/loopbackjs
https://gitter.im/strongloop/loopback
Immediate support:
https://strongloop.com/api-connect-faqs/
https://strongloop.com/node-js/subscription-plans/
-->
# Description/Steps to reproduce
<!--
If feature: A description of the feature
If bug: Steps to reproduce
-->
# Link to reproduction sandbox
<!--
Link to an app sandbox for reproduction
Note: Failure to provide a sandbox application for reproduction purposes will result in the issue being closed.
-->
# Expected result
<!--
Also include actual results if bug
-->
# Additional information
<!--
Copy+paste the output of these two commands:
node -e 'console.log(process.platform, process.arch, process.versions.node)'
npm ls --prod --depth 0 | grep loopback
-->

View File

@ -1,53 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
labels: bug
---
<!-- 🚨 STOP 🚨 STOP 🚨 STOP 🚨
Are you using LoopBack version 4? Please report the bug here:
https://github.com/strongloop/loopback-next/issues/new
HELP US HELP YOU, PLEASE
- Do a quick search to avoid duplicate issues
- Provide as much information as possible (reproduction sandbox, use case for features, etc.)
- Consider using a more suitable venue for questions such as Stack Overflow, Gitter, etc.
Please fill in the *entire* template below.
-->
## Steps to reproduce
<!-- Describe how to reproduce the issue -->
## Current Behavior
<!-- Describe the observed result -->
## Expected Behavior
<!-- Describe what did you expect instead, what is the desired outcome? -->
## Link to reproduction sandbox
<!--
See https://loopback.io/doc/en/contrib/Reporting-issues.html#loopback-3x-bugs
Note: Failure to provide a sandbox application for reproduction purposes will result in the issue being closed.
-->
## Additional information
<!--
Copy+paste the output of these two commands:
node -e 'console.log(process.platform, process.arch, process.versions.node)'
npm ls --prod --depth 0 | grep loopback
-->
## Related Issues
<!-- Did you find other bugs that looked similar? -->
_See [Reporting Issues](http://loopback.io/doc/en/contrib/Reporting-issues.html) for more tips on writing good issues_

View File

@ -1,34 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
labels: feature
---
<!-- 🚨 STOP 🚨 STOP 🚨 STOP 🚨
LoopBack version 3 is in LTS mode, we are not accepting new features.
We are actively developing version 4, you can find the new GitHub
repository here: https://github.com/strongloop/loopback-next
-->
## Suggestion
<!-- A summary of what you'd like to see added or changed -->
## Use Cases
<!--
What do you want to use this for?
What shortcomings exist with current approaches?
-->
## Examples
<!-- Show how this would be used and what the behavior would be -->
## Acceptance criteria
TBD - will be filled by the team.

View File

@ -1,27 +0,0 @@
---
name: Question
about: The issue tracker is not for questions. Please use Stack Overflow or other resources for help.
labels: question
---
<!-- 🚨 STOP 🚨 STOP 🚨 STOP 🚨
THE ISSUE TRACKER IS NOT FOR QUESTIONS.
DO NOT CREATE A NEW ISSUE TO ASK A QUESTION.
Please use one of the following resources for help:
**Questions**
- https://stackoverflow.com/tags/loopbackjs
- https://groups.google.com/forum/#!forum/loopbackjs
- https://gitter.im/strongloop/loopback
**Immediate support**
- https://strongloop.com/api-connect-faqs/
- https://strongloop.com/node-js/subscription-plans/
-->

View File

@ -1,16 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Report a security vulnerability
url: https://loopback.io/doc/en/contrib/Reporting-issues.html#security-issues
about: >
LoopBack 3 has reached End-of-Life. No new security fixes will be provided
or accepted.
Do not report security vulnerabilities using GitHub issues. Please send an
email to `reachsl@us.ibm.com` instead.
- name: Get help on StackOverflow
url: https://stackoverflow.com/tags/loopbackjs
about: Please ask and answer questions on StackOverflow.
- name: Join our mailing list
url: https://groups.google.com/forum/#!forum/loopbackjs
about: You can also post your question to our mailing list.

View File

@ -1,18 +1,25 @@
### Description
#### Related issues
<!-- <!--
Please provide a high-level description of the changes made by your pull request. Please use the following link syntaxes:
Include references to all related GitHub issues and other pull requests, for example: - connect to #49 (to reference issues in the current repository)
- connect to strongloop/loopback#49 (to reference issues in another repository)
Fixes #123
Implements #254
See also #23
--> -->
## Checklist - connect to <link_to_referenced_issue>
👉 [Read and sign the CLA (Contributor License Agreement)](https://cla.strongloop.com/agreements/strongloop/loopback) 👈 ### Checklist
<!--
- Please mark your choice with an "x" (i.e. [x], see
https://github.com/blog/1375-task-lists-in-gfm-issues-pulls-comments)
- PR's without test coverage will be closed.
-->
- [ ] `npm test` passes on your machine
- [ ] New tests added or existing tests modified to cover all changes - [ ] New tests added or existing tests modified to cover all changes
- [ ] Code conforms with the [style guide](https://loopback.io/doc/en/contrib/style-guide-es6.html) - [ ] Code conforms with the [style
- [ ] Commit messages are following our [guidelines](https://loopback.io/doc/en/contrib/git-commit-messages.html) guide](http://loopback.io/doc/en/contrib/style-guide.html)

2
.github/stale.yml vendored
View File

@ -7,6 +7,8 @@ exemptLabels:
- pinned - pinned
- security - security
- critical - critical
- p1
- major
# Label to use when marking an issue as stale # Label to use when marking an issue as stale
staleLabel: stale staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable # Comment to post when marking an issue as stale. Set to `false` to disable

View File

@ -1,15 +1,22 @@
sudo: false sudo: false
language: node_js language: node_js
node_js: node_js:
- "6"
- "8" - "8"
- "10" - "10"
- "12"
- "14"
addons:
chrome: stable
after_success: npm run coverage after_success: npm run coverage
# see https://www.npmjs.com/package/phantomjs-prebuilt#continuous-integration
cache:
directories:
- travis_phantomjs
before_install: before_install:
- npm config set registry http://ci.strongloop.com:4873/ - npm config set registry http://ci.strongloop.com:4873/
# Upgrade PhantomJS to v2.1.1.
- "export PHANTOMJS_VERSION=2.1.1"
- "export PATH=$PWD/travis_phantomjs/phantomjs-$PHANTOMJS_VERSION-linux-x86_64/bin:$PATH"
- "if [ $(phantomjs --version) != $PHANTOMJS_VERSION ]; then rm -rf $PWD/travis_phantomjs; mkdir -p $PWD/travis_phantomjs; fi"
- "if [ $(phantomjs --version) != $PHANTOMJS_VERSION ]; then wget https://github.com/Medium/phantomjs/releases/download/v$PHANTOMJS_VERSION/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2 -O $PWD/travis_phantomjs/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2; fi"
- "if [ $(phantomjs --version) != $PHANTOMJS_VERSION ]; then tar -xvf $PWD/travis_phantomjs/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2 -C $PWD/travis_phantomjs; fi"
- "phantomjs --version"

View File

@ -1,151 +1,3 @@
2020-11-25, Version 3.28.0
==========================
* upgrade nodemailer to greater than 6.4.16 (jannyHou)
* chore: sync LoopBack 4 with Node.js v14 EOL (Rifa Achrinza)
* chore: add Node.js 14 to travis (Diana Lau)
* Remove "major" and "p1" from stalebot (Miroslav Bajtoš)
2020-03-06, Version 3.27.0
==========================
* Update LTS status in README (Miroslav Bajtoš)
* chore: update copyright year (Diana Lau)
* feat: change hasone relation error message (Sujesh T)
* chore: disable security issue reporting (Nora)
* chore: fix eslint violations (Nora)
* fixup! manual fixes (Miroslav Bajtoš)
* fixup! eslint --fix . (Miroslav Bajtoš)
* chore: update eslint & eslint-config to latest (Miroslav Bajtoš)
* chore: update dev-dependencies (Miroslav Bajtoš)
* chore: update chai to v4, dirty-chai to v2 (Miroslav Bajtoš)
* Updated "ismail" package to v3.2 (Stanislav Sarbinski)
* Introduce issue templates for bugs, features, etc. (Miroslav Bajtoš)
* Improve PULL_REQUEST_TEMPLATE (Miroslav Bajtoš)
* test: use Chromium (not Chrome) when available (Miroslav Bajtoš)
* test: disable Chrome sandboxing when inside Docker (Miroslav Bajtoš)
* test: switch from PhantomJS to HeadlessChrome (Miroslav Bajtoš)
2019-05-31, Version 3.26.0
==========================
* fix: disallow queries in username and email fields (Hage Yaapa)
* Ignore failing downstream dependencies (Miroslav Bajtoš)
* Upgrade nyc to version 14 (Miroslav Bajtoš)
* Update Karma dependencies to latest versions (Miroslav Bajtoš)
* Drop Node.js 6.x from the supported versions (Miroslav Bajtoš)
* Fix Model.exists() to work with remote connector (Maxim Sharai)
* chore: update copyrights years (Agnes Lin)
* Update LTS status (Diana Lau)
* Enable Node.js 12.x on Travis CI (Miroslav Bajtoš)
* chore: update copyright year (Diana Lau)
* chore: update LB3 EOL date (Diana Lau)
2019-03-15, Version 3.25.1
==========================
* Back-ticks added to highlight example JSON (Quentin Presley)
* Add same change to description for findOne (Quentin Presley)
* Update the description for persisted-models (Quentin Presley)
* handle $2b$ in hashed password check (Sylvain Dumont)
2019-02-05, Version 3.25.0
==========================
* Support middleware injected by AppDynamics. (Mike Li)
2019-01-11, Version 3.24.2
==========================
* Fix crash when modifying an unknown user (Matheus Horstmann)
2019-01-08, Version 3.24.1
==========================
* Update underscore.string to 3.3.5 (Francois)
* Fix: treat empty access token string as undefined (andrey-abramow)
2018-11-15, Version 3.24.0
==========================
* Set juggler options for remote calls (Raymond Feng)
* Speed up ACL tests by reducing saltWorkFactor (Miroslav Bajtoš)
2018-10-25, Version 3.23.2
==========================
* Fix ACL check to support model wildcard (Moshe Malka)
2018-10-18, Version 3.23.1
==========================
* README: highlight Active LTS at the top (Miroslav Bajtoš)
2018-10-09, Version 3.23.0
==========================
* Clear handler cache when a method is added/removed (Mohammed Essehemy)
* Add `options.preserveAccessTokens` (lchaglla)
* Update LB3 to be active LTS (Diana Lau)
* Fix ACL tests to wait until all assertions finish (Moshe Malka)
* chore: update to latest linting rules (virkt25)
2018-09-12, Version 3.22.3
==========================
* chore: use grunt to install optional phantomjs (virkt25)
* [WebFM] fr translation (candytangnb)
2018-08-29, Version 3.22.2 2018-08-29, Version 3.22.2
========================== ==========================

View File

@ -1,10 +1,10 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
/* global module:false */
'use strict'; 'use strict';
module.exports = function(grunt) { module.exports = function(grunt) {
// Do not report warnings from unit-tests exercising deprecated paths // Do not report warnings from unit-tests exercising deprecated paths
process.env.NO_DEPRECATION = 'loopback'; process.env.NO_DEPRECATION = 'loopback';
@ -104,7 +104,7 @@ module.exports = function(grunt) {
karma: { karma: {
'unit-once': { 'unit-once': {
configFile: 'test/karma.conf.js', configFile: 'test/karma.conf.js',
browsers: ['ChromeDocker'], browsers: ['PhantomJS'],
singleRun: true, singleRun: true,
reporters: ['dots', 'junit'], reporters: ['dots', 'junit'],
@ -218,8 +218,8 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-karma'); grunt.loadNpmTasks('grunt-karma');
grunt.registerTask('e2e-server', function() { grunt.registerTask('e2e-server', function() {
const done = this.async(); var done = this.async();
const app = require('./test/fixtures/e2e/app'); var app = require('./test/fixtures/e2e/app');
app.listen(0, function() { app.listen(0, function() {
process.env.PORT = this.address().port; process.env.PORT = this.address().port;
done(); done();

View File

@ -4,22 +4,6 @@
[![Module LTS Adopted'](https://img.shields.io/badge/Module%20LTS-Adopted-brightgreen.svg?style=flat)](http://github.com/CloudNativeJS/ModuleLTS) [![Module LTS Adopted'](https://img.shields.io/badge/Module%20LTS-Adopted-brightgreen.svg?style=flat)](http://github.com/CloudNativeJS/ModuleLTS)
[![IBM Support](https://img.shields.io/badge/IBM%20Support-Frameworks-brightgreen.svg?style=flat)](http://ibm.biz/node-support) [![IBM Support](https://img.shields.io/badge/IBM%20Support-Frameworks-brightgreen.svg?style=flat)](http://ibm.biz/node-support)
**⚠️ LoopBack 3 has reached end of life. We are no longer accepting pull requests or providing
support for community users. The only exception is fixes for critical bugs and security
vulnerabilities provided as part of support for IBM API Connect customers.
We urge all LoopBack 3 users to migrate their applications to LoopBack 4 as soon as possible.
Learn more about
<a href="https://loopback.io/doc/en/contrib/Long-term-support.html">LoopBack's long term support policy.</a>
will be provided or accepted. (See
[Module Long Term Support Policy](#module-long-term-support-policy) below.)**
We urge all LoopBack 3 users to migrate their applications to LoopBack 4 as
soon as possible. Refer to our
[Migration Guide](https://loopback.io/doc/en/lb4/migration-overview.html)
for more information on how to upgrade.
## Overview
LoopBack is a highly-extensible, open-source Node.js framework that enables you to: LoopBack is a highly-extensible, open-source Node.js framework that enables you to:
* Create dynamic end-to-end REST APIs with little or no coding. * Create dynamic end-to-end REST APIs with little or no coding.
@ -43,15 +27,12 @@ For more details, see [https://loopback.io/](https://loopback.io/).
## Module Long Term Support Policy ## Module Long Term Support Policy
LoopBack 3.x has reached End-of-Life.
This module adopts the [Module Long Term Support (LTS)](http://github.com/CloudNativeJS/ModuleLTS) policy, with the following End Of Life (EOL) dates: This module adopts the [Module Long Term Support (LTS)](http://github.com/CloudNativeJS/ModuleLTS) policy, with the following End Of Life (EOL) dates:
| Version | Status | Published | EOL | | Module Version | Release Date | Minimum EOL | EOL With | Status |
| ---------- | --------------- | --------- | -------------------- | |------------------|--------------|-------------|--------------|---------|
| LoopBack 4 | Current | Oct 2018 | Apr 2023 _(minimum)_ | | 3.x.x | Sept 2016 | Dec 2019 | | Current |
| LoopBack 3 | End-of-Life | Dec 2016 | Dec 2020 | | 2.x.x | July 2014 | Apr 2019 | Node.js 6.x | LTS |
| LoopBack 2 | End-of-Life | Jul 2014 | Apr 2019 |
Learn more about our LTS plan in [docs](https://loopback.io/doc/en/contrib/Long-term-support.html). Learn more about our LTS plan in [docs](https://loopback.io/doc/en/contrib/Long-term-support.html).

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -8,11 +8,11 @@
*/ */
'use strict'; 'use strict';
const g = require('../../lib/globalize'); var g = require('../../lib/globalize');
const loopback = require('../../lib/loopback'); var loopback = require('../../lib/loopback');
const assert = require('assert'); var assert = require('assert');
const uid = require('uid2'); var uid = require('uid2');
const DEFAULT_TOKEN_LEN = 64; var DEFAULT_TOKEN_LEN = 64;
/** /**
* Token based authentication and access control. * Token based authentication and access control.
@ -93,11 +93,11 @@ module.exports = function(AccessToken) {
*/ */
AccessToken.getIdForRequest = function(req, options) { AccessToken.getIdForRequest = function(req, options) {
options = options || {}; options = options || {};
let params = options.params || []; var params = options.params || [];
let headers = options.headers || []; var headers = options.headers || [];
let cookies = options.cookies || []; var cookies = options.cookies || [];
let i = 0; var i = 0;
let length, id; var length, id;
// https://github.com/strongloop/loopback/issues/1326 // https://github.com/strongloop/loopback/issues/1326
if (options.searchDefaultTokenKeys !== false) { if (options.searchDefaultTokenKeys !== false) {
@ -107,7 +107,7 @@ module.exports = function(AccessToken) {
} }
for (length = params.length; i < length; i++) { for (length = params.length; i < length; i++) {
const param = params[i]; var param = params[i];
// replacement for deprecated req.param() // replacement for deprecated req.param()
id = req.params && req.params[param] !== undefined ? req.params[param] : id = req.params && req.params[param] !== undefined ? req.params[param] :
req.body && req.body[param] !== undefined ? req.body[param] : req.body && req.body[param] !== undefined ? req.body[param] :
@ -125,16 +125,11 @@ module.exports = function(AccessToken) {
if (typeof id === 'string') { if (typeof id === 'string') {
// Add support for oAuth 2.0 bearer token // Add support for oAuth 2.0 bearer token
// http://tools.ietf.org/html/rfc6750 // http://tools.ietf.org/html/rfc6750
// To prevent Error: Model::findById requires the id argument
// with loopback-datasource-juggler 2.56.0+
if (id === '') continue;
if (id.indexOf('Bearer ') === 0) { if (id.indexOf('Bearer ') === 0) {
id = id.substring(7); id = id.substring(7);
if (options.bearerTokenBase64Encoded) { if (options.bearerTokenBase64Encoded) {
// Decode from base64 // Decode from base64
const buf = new Buffer(id, 'base64'); var buf = new Buffer(id, 'base64');
id = buf.toString('utf8'); id = buf.toString('utf8');
} }
} else if (/^Basic /i.test(id)) { } else if (/^Basic /i.test(id)) {
@ -147,7 +142,7 @@ module.exports = function(AccessToken) {
// "a2b2c3:" (curl http://a2b2c3@localhost:3000/) // "a2b2c3:" (curl http://a2b2c3@localhost:3000/)
// "token:a2b2c3" (curl http://token:a2b2c3@localhost:3000/) // "token:a2b2c3" (curl http://token:a2b2c3@localhost:3000/)
// ":a2b2c3" // ":a2b2c3"
const parts = /^([^:]*):(.*)$/.exec(id); var parts = /^([^:]*):(.*)$/.exec(id);
if (parts) { if (parts) {
id = parts[2].length > parts[1].length ? parts[2] : parts[1]; id = parts[2].length > parts[1].length ? parts[2] : parts[1];
} }
@ -186,7 +181,7 @@ module.exports = function(AccessToken) {
} else if (isValid) { } else if (isValid) {
cb(null, token); cb(null, token);
} else { } else {
const e = new Error(g.f('Invalid Access Token')); var e = new Error(g.f('Invalid Access Token'));
e.status = e.statusCode = 401; e.status = e.statusCode = 401;
e.code = 'INVALID_TOKEN'; e.code = 'INVALID_TOKEN';
cb(e); cb(e);
@ -213,7 +208,7 @@ module.exports = function(AccessToken) {
options = {}; options = {};
} }
const id = this.getIdForRequest(req, options); var id = this.getIdForRequest(req, options);
if (id) { if (id) {
this.resolve(id, cb); this.resolve(id, cb);
@ -233,15 +228,15 @@ module.exports = function(AccessToken) {
try { try {
assert( assert(
this.created && typeof this.created.getTime === 'function', this.created && typeof this.created.getTime === 'function',
'token.created must be a valid Date', 'token.created must be a valid Date'
); );
assert(this.ttl !== 0, 'token.ttl must be not be 0'); assert(this.ttl !== 0, 'token.ttl must be not be 0');
assert(this.ttl, 'token.ttl must exist'); assert(this.ttl, 'token.ttl must exist');
assert(this.ttl >= -1, 'token.ttl must be >= -1'); assert(this.ttl >= -1, 'token.ttl must be >= -1');
const AccessToken = this.constructor; var AccessToken = this.constructor;
const userRelation = AccessToken.relations.user; // may not be set up var userRelation = AccessToken.relations.user; // may not be set up
let User = userRelation && userRelation.modelTo; var User = userRelation && userRelation.modelTo;
// redefine user model if accessToken's principalType is available // redefine user model if accessToken's principalType is available
if (this.principalType) { if (this.principalType) {
@ -253,13 +248,13 @@ module.exports = function(AccessToken) {
} }
} }
const now = Date.now(); var now = Date.now();
const created = this.created.getTime(); var created = this.created.getTime();
const elapsedSeconds = (now - created) / 1000; var elapsedSeconds = (now - created) / 1000;
const secondsToLive = this.ttl; var secondsToLive = this.ttl;
const eternalTokensAllowed = !!(User && User.settings.allowEternalTokens); var eternalTokensAllowed = !!(User && User.settings.allowEternalTokens);
const isEternalToken = secondsToLive === -1; var isEternalToken = secondsToLive === -1;
const isValid = isEternalToken ? var isValid = isEternalToken ?
eternalTokensAllowed : eternalTokensAllowed :
elapsedSeconds < secondsToLive; elapsedSeconds < secondsToLive;

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -30,20 +30,20 @@
Map to oAuth 2.0 scopes Map to oAuth 2.0 scopes
*/ */
const g = require('../../lib/globalize'); var g = require('../../lib/globalize');
const loopback = require('../../lib/loopback'); var loopback = require('../../lib/loopback');
const utils = require('../../lib/utils'); var utils = require('../../lib/utils');
const async = require('async'); var async = require('async');
const extend = require('util')._extend; var extend = require('util')._extend;
const assert = require('assert'); var assert = require('assert');
const debug = require('debug')('loopback:security:acl'); var debug = require('debug')('loopback:security:acl');
const ctx = require('../../lib/access-context'); var ctx = require('../../lib/access-context');
const AccessContext = ctx.AccessContext; var AccessContext = ctx.AccessContext;
const Principal = ctx.Principal; var Principal = ctx.Principal;
const AccessRequest = ctx.AccessRequest; var AccessRequest = ctx.AccessRequest;
const Role = loopback.Role; var Role = loopback.Role;
assert(Role, 'Role model must be defined before ACL model'); assert(Role, 'Role model must be defined before ACL model');
/** /**
@ -107,18 +107,18 @@ module.exports = function(ACL) {
* @returns {Number} * @returns {Number}
*/ */
ACL.getMatchingScore = function getMatchingScore(rule, req) { ACL.getMatchingScore = function getMatchingScore(rule, req) {
const props = ['model', 'property', 'accessType']; var props = ['model', 'property', 'accessType'];
let score = 0; var score = 0;
for (let i = 0; i < props.length; i++) { for (var i = 0; i < props.length; i++) {
// Shift the score by 4 for each of the properties as the weight // Shift the score by 4 for each of the properties as the weight
score = score * 4; score = score * 4;
const ruleValue = rule[props[i]] || ACL.ALL; var ruleValue = rule[props[i]] || ACL.ALL;
const requestedValue = req[props[i]] || ACL.ALL; var requestedValue = req[props[i]] || ACL.ALL;
const isMatchingMethodName = props[i] === 'property' && var isMatchingMethodName = props[i] === 'property' &&
req.methodNames.indexOf(ruleValue) !== -1; req.methodNames.indexOf(ruleValue) !== -1;
let isMatchingAccessType = ruleValue === requestedValue; var isMatchingAccessType = ruleValue === requestedValue;
if (props[i] === 'accessType' && !isMatchingAccessType) { if (props[i] === 'accessType' && !isMatchingAccessType) {
switch (ruleValue) { switch (ruleValue) {
case ACL.EXECUTE: case ACL.EXECUTE:
@ -219,11 +219,11 @@ module.exports = function(ACL) {
acls = acls.sort(function(rule1, rule2) { acls = acls.sort(function(rule1, rule2) {
return ACL.getMatchingScore(rule2, req) - ACL.getMatchingScore(rule1, req); return ACL.getMatchingScore(rule2, req) - ACL.getMatchingScore(rule1, req);
}); });
let permission = ACL.DEFAULT; var permission = ACL.DEFAULT;
let score = 0; var score = 0;
for (let i = 0; i < acls.length; i++) { for (var i = 0; i < acls.length; i++) {
const candidate = acls[i]; var candidate = acls[i];
score = ACL.getMatchingScore(candidate, req); score = ACL.getMatchingScore(candidate, req);
if (score < 0) { if (score < 0) {
// the highest scored ACL did not match // the highest scored ACL did not match
@ -239,8 +239,8 @@ module.exports = function(ACL) {
break; break;
} }
// For wildcard match, find the strongest permission // For wildcard match, find the strongest permission
const candidateOrder = AccessContext.permissionOrder[candidate.permission]; var candidateOrder = AccessContext.permissionOrder[candidate.permission];
const permissionOrder = AccessContext.permissionOrder[permission]; var permissionOrder = AccessContext.permissionOrder[permission];
if (candidateOrder > permissionOrder) { if (candidateOrder > permissionOrder) {
permission = candidate.permission; permission = candidate.permission;
break; break;
@ -255,7 +255,7 @@ module.exports = function(ACL) {
debug('with score:', acl.score(req)); debug('with score:', acl.score(req));
}); });
} }
const res = new AccessRequest({ var res = new AccessRequest({
model: req.model, model: req.model,
property: req.property, property: req.property,
accessType: req.accessType, accessType: req.accessType,
@ -276,11 +276,11 @@ module.exports = function(ACL) {
* @return {Object[]} An array of ACLs * @return {Object[]} An array of ACLs
*/ */
ACL.getStaticACLs = function getStaticACLs(model, property) { ACL.getStaticACLs = function getStaticACLs(model, property) {
const modelClass = this.registry.findModel(model); var modelClass = this.registry.findModel(model);
const staticACLs = []; var staticACLs = [];
if (modelClass && modelClass.settings.acls) { if (modelClass && modelClass.settings.acls) {
modelClass.settings.acls.forEach(function(acl) { modelClass.settings.acls.forEach(function(acl) {
let prop = acl.property; var prop = acl.property;
// We support static ACL property with array of string values. // We support static ACL property with array of string values.
if (Array.isArray(prop) && prop.indexOf(property) >= 0) if (Array.isArray(prop) && prop.indexOf(property) >= 0)
prop = property; prop = property;
@ -296,7 +296,7 @@ module.exports = function(ACL) {
} }
}); });
} }
const prop = modelClass && ( var prop = modelClass && (
// regular property // regular property
modelClass.definition.properties[property] || modelClass.definition.properties[property] ||
// relation/scope // relation/scope
@ -339,17 +339,17 @@ module.exports = function(ACL) {
principalId = principalId.toString(); principalId = principalId.toString();
} }
property = property || ACL.ALL; property = property || ACL.ALL;
const propertyQuery = (property === ACL.ALL) ? undefined : {inq: [property, ACL.ALL]}; var propertyQuery = (property === ACL.ALL) ? undefined : {inq: [property, ACL.ALL]};
accessType = accessType || ACL.ALL; accessType = accessType || ACL.ALL;
const accessTypeQuery = (accessType === ACL.ALL) ? undefined : var accessTypeQuery = (accessType === ACL.ALL) ? undefined :
{inq: [accessType, ACL.ALL, ACL.EXECUTE]}; {inq: [accessType, ACL.ALL, ACL.EXECUTE]};
const req = new AccessRequest({model, property, accessType, registry: this.registry}); var req = new AccessRequest({model, property, accessType, registry: this.registry});
let acls = this.getStaticACLs(model, property); var acls = this.getStaticACLs(model, property);
// resolved is an instance of AccessRequest // resolved is an instance of AccessRequest
let resolved = this.resolvePermission(acls, req); var resolved = this.resolvePermission(acls, req);
if (resolved && resolved.permission === ACL.DENY) { if (resolved && resolved.permission === ACL.DENY) {
debug('Permission denied by statically resolved permission'); debug('Permission denied by statically resolved permission');
@ -360,7 +360,7 @@ module.exports = function(ACL) {
return callback.promise; return callback.promise;
} }
const self = this; var self = this;
this.find({where: {principalType: principalType, principalId: principalId, this.find({where: {principalType: principalType, principalId: principalId,
model: model, property: propertyQuery, accessType: accessTypeQuery}}, model: model, property: propertyQuery, accessType: accessTypeQuery}},
function(err, dynACLs) { function(err, dynACLs) {
@ -431,33 +431,33 @@ module.exports = function(ACL) {
*/ */
ACL.checkAccessForContext = function(context, callback) { ACL.checkAccessForContext = function(context, callback) {
if (!callback) callback = utils.createPromiseCallback(); if (!callback) callback = utils.createPromiseCallback();
const self = this; var self = this;
self.resolveRelatedModels(); self.resolveRelatedModels();
const roleModel = self.roleModel; var roleModel = self.roleModel;
if (!(context instanceof AccessContext)) { if (!(context instanceof AccessContext)) {
context.registry = this.registry; context.registry = this.registry;
context = new AccessContext(context); context = new AccessContext(context);
} }
let authorizedRoles = {}; var authorizedRoles = {};
const remotingContext = context.remotingContext; var remotingContext = context.remotingContext;
const model = context.model; var model = context.model;
const modelDefaultPermission = model && model.settings.defaultPermission; var modelDefaultPermission = model && model.settings.defaultPermission;
const property = context.property; var property = context.property;
const accessType = context.accessType; var accessType = context.accessType;
const modelName = context.modelName; var modelName = context.modelName;
const methodNames = context.methodNames; var methodNames = context.methodNames;
const propertyQuery = (property === ACL.ALL) ? undefined : {inq: methodNames.concat([ACL.ALL])}; var propertyQuery = (property === ACL.ALL) ? undefined : {inq: methodNames.concat([ACL.ALL])};
const accessTypeQuery = (accessType === ACL.ALL) ? var accessTypeQuery = (accessType === ACL.ALL) ?
undefined : undefined :
(accessType === ACL.REPLICATE) ? (accessType === ACL.REPLICATE) ?
{inq: [ACL.REPLICATE, ACL.WRITE, ACL.ALL]} : {inq: [ACL.REPLICATE, ACL.WRITE, ACL.ALL]} :
{inq: [accessType, ACL.ALL]}; {inq: [accessType, ACL.ALL]};
const req = new AccessRequest({ var req = new AccessRequest({
model: modelName, model: modelName,
property, property,
accessType, accessType,
@ -475,29 +475,22 @@ module.exports = function(ACL) {
return callback.promise; return callback.promise;
} }
const effectiveACLs = []; var effectiveACLs = [];
const staticACLs = self.getStaticACLs(model.modelName, property); var staticACLs = self.getStaticACLs(model.modelName, property);
const query = { this.find({where: {model: model.modelName, property: propertyQuery,
where: { accessType: accessTypeQuery}}, function(err, acls) {
model: {inq: [model.modelName, ACL.ALL]},
property: propertyQuery,
accessType: accessTypeQuery,
},
};
this.find(query, function(err, acls) {
if (err) return callback(err); if (err) return callback(err);
const inRoleTasks = []; var inRoleTasks = [];
acls = acls.concat(staticACLs); acls = acls.concat(staticACLs);
acls.forEach(function(acl) { acls.forEach(function(acl) {
// Check exact matches // Check exact matches
for (let i = 0; i < context.principals.length; i++) { for (var i = 0; i < context.principals.length; i++) {
const p = context.principals[i]; var p = context.principals[i];
const typeMatch = p.type === acl.principalType; var typeMatch = p.type === acl.principalType;
const idMatch = String(p.id) === String(acl.principalId); var idMatch = String(p.id) === String(acl.principalId);
if (typeMatch && idMatch) { if (typeMatch && idMatch) {
effectiveACLs.push(acl); effectiveACLs.push(acl);
return; return;
@ -525,7 +518,7 @@ module.exports = function(ACL) {
if (err) return callback(err, null); if (err) return callback(err, null);
// resolved is an instance of AccessRequest // resolved is an instance of AccessRequest
const resolved = self.resolvePermission(effectiveACLs, req); var resolved = self.resolvePermission(effectiveACLs, req);
debug('---Resolved---'); debug('---Resolved---');
resolved.debug(); resolved.debug();
@ -562,7 +555,7 @@ module.exports = function(ACL) {
ACL.checkAccessForToken = function(token, model, modelId, method, callback) { ACL.checkAccessForToken = function(token, model, modelId, method, callback) {
assert(token, 'Access token is required'); assert(token, 'Access token is required');
if (!callback) callback = utils.createPromiseCallback(); if (!callback) callback = utils.createPromiseCallback();
const context = new AccessContext({ var context = new AccessContext({
registry: this.registry, registry: this.registry,
accessToken: token, accessToken: token,
model: model, model: model,
@ -580,7 +573,7 @@ module.exports = function(ACL) {
ACL.resolveRelatedModels = function() { ACL.resolveRelatedModels = function() {
if (!this.roleModel) { if (!this.roleModel) {
const reg = this.registry; var reg = this.registry;
this.roleModel = reg.getModelByType('Role'); this.roleModel = reg.getModelByType('Role');
this.roleMappingModel = reg.getModelByType('RoleMapping'); this.roleMappingModel = reg.getModelByType('RoleMapping');
this.userModel = reg.getModelByType('User'); this.userModel = reg.getModelByType('User');
@ -607,25 +600,25 @@ module.exports = function(ACL) {
break; break;
case ACL.USER: case ACL.USER:
this.userModel.findOne( this.userModel.findOne(
{where: {or: [{username: id}, {email: id}, {id: id}]}}, cb, {where: {or: [{username: id}, {email: id}, {id: id}]}}, cb
); );
break; break;
case ACL.APP: case ACL.APP:
this.applicationModel.findOne( this.applicationModel.findOne(
{where: {or: [{name: id}, {email: id}, {id: id}]}}, cb, {where: {or: [{name: id}, {email: id}, {id: id}]}}, cb
); );
break; break;
default: default:
// try resolving a user model with a name matching the principalType // try resolving a user model with a name matching the principalType
const userModel = this.registry.findModel(type); var userModel = this.registry.findModel(type);
if (userModel) { if (userModel) {
userModel.findOne( userModel.findOne(
{where: {or: [{username: id}, {email: id}, {id: id}]}}, {where: {or: [{username: id}, {email: id}, {id: id}]}},
cb, cb
); );
} else { } else {
process.nextTick(function() { process.nextTick(function() {
const err = new Error(g.f('Invalid principal type: %s', type)); var err = new Error(g.f('Invalid principal type: %s', type));
err.statusCode = 400; err.statusCode = 400;
err.code = 'INVALID_PRINCIPAL_TYPE'; err.code = 'INVALID_PRINCIPAL_TYPE';
cb(err); cb(err);
@ -646,7 +639,7 @@ module.exports = function(ACL) {
*/ */
ACL.isMappedToRole = function(principalType, principalId, role, cb) { ACL.isMappedToRole = function(principalType, principalId, role, cb) {
cb = cb || utils.createPromiseCallback(); cb = cb || utils.createPromiseCallback();
const self = this; var self = this;
this.resolvePrincipal(principalType, principalId, this.resolvePrincipal(principalType, principalId,
function(err, principal) { function(err, principal) {
if (err) return cb(err); if (err) return cb(err);

View File

@ -1,26 +1,26 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const assert = require('assert'); var assert = require('assert');
const utils = require('../../lib/utils'); var utils = require('../../lib/utils');
/*! /*!
* Application management functions * Application management functions
*/ */
const crypto = require('crypto'); var crypto = require('crypto');
function generateKey(hmacKey, algorithm, encoding) { function generateKey(hmacKey, algorithm, encoding) {
hmacKey = hmacKey || 'loopback'; hmacKey = hmacKey || 'loopback';
algorithm = algorithm || 'sha1'; algorithm = algorithm || 'sha1';
encoding = encoding || 'hex'; encoding = encoding || 'hex';
const hmac = crypto.createHmac(algorithm, hmacKey); var hmac = crypto.createHmac(algorithm, hmacKey);
const buf = crypto.randomBytes(32); var buf = crypto.randomBytes(32);
hmac.update(buf); hmac.update(buf);
const key = hmac.digest(encoding); var key = hmac.digest(encoding);
return key; return key;
} }
@ -83,7 +83,7 @@ module.exports = function(Application) {
return next(); return next();
} }
const app = ctx.instance; var app = ctx.instance;
app.created = app.modified = new Date(); app.created = app.modified = new Date();
if (!app.id) { if (!app.id) {
app.id = generateKey('id', 'md5'); app.id = generateKey('id', 'md5');
@ -115,8 +115,8 @@ module.exports = function(Application) {
} }
cb = cb || utils.createPromiseCallback(); cb = cb || utils.createPromiseCallback();
const props = {owner: owner, name: name}; var props = {owner: owner, name: name};
for (const p in options) { for (var p in options) {
if (!(p in props)) { if (!(p in props)) {
props[p] = options[p]; props[p] = options[p];
} }
@ -182,9 +182,9 @@ module.exports = function(Application) {
cb(err, null); cb(err, null);
return cb.promise; return cb.promise;
} }
let result = null; var result = null;
const keyNames = ['clientKey', 'javaScriptKey', 'restApiKey', 'windowsKey', 'masterKey']; var keyNames = ['clientKey', 'javaScriptKey', 'restApiKey', 'windowsKey', 'masterKey'];
for (let i = 0; i < keyNames.length; i++) { for (var i = 0; i < keyNames.length; i++) {
if (app[keyNames[i]] === key) { if (app[keyNames[i]] === key) {
result = { result = {
application: app, application: app,

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -8,15 +8,15 @@
*/ */
'use strict'; 'use strict';
const g = require('../../lib/globalize'); var g = require('../../lib/globalize');
const PersistedModel = require('../../lib/loopback').PersistedModel; var PersistedModel = require('../../lib/loopback').PersistedModel;
const loopback = require('../../lib/loopback'); var loopback = require('../../lib/loopback');
const utils = require('../../lib/utils'); var utils = require('../../lib/utils');
const crypto = require('crypto'); var crypto = require('crypto');
const CJSON = {stringify: require('canonical-json')}; var CJSON = {stringify: require('canonical-json')};
const async = require('async'); var async = require('async');
const assert = require('assert'); var assert = require('assert');
const debug = require('debug')('loopback:change'); var debug = require('debug')('loopback:change');
/** /**
* Change list entry. * Change list entry.
@ -58,10 +58,10 @@ module.exports = function(Change) {
Change.setup = function() { Change.setup = function() {
PersistedModel.setup.call(this); PersistedModel.setup.call(this);
const Change = this; var Change = this;
Change.getter.id = function() { Change.getter.id = function() {
const hasModel = this.modelName && this.modelId; var hasModel = this.modelName && this.modelId;
if (!hasModel) return null; if (!hasModel) return null;
return Change.idForModel(this.modelName, this.modelId); return Change.idForModel(this.modelName, this.modelId);
@ -80,12 +80,12 @@ module.exports = function(Change) {
*/ */
Change.rectifyModelChanges = function(modelName, modelIds, callback) { Change.rectifyModelChanges = function(modelName, modelIds, callback) {
const Change = this; var Change = this;
const errors = []; var errors = [];
callback = callback || utils.createPromiseCallback(); callback = callback || utils.createPromiseCallback();
const tasks = modelIds.map(function(id) { var tasks = modelIds.map(function(id) {
return function(cb) { return function(cb) {
Change.findOrCreateChange(modelName, id, function(err, change) { Change.findOrCreateChange(modelName, id, function(err, change) {
if (err) return next(err); if (err) return next(err);
@ -106,13 +106,13 @@ module.exports = function(Change) {
async.parallel(tasks, function(err) { async.parallel(tasks, function(err) {
if (err) return callback(err); if (err) return callback(err);
if (errors.length) { if (errors.length) {
const desc = errors var desc = errors
.map(function(e) { .map(function(e) {
return '#' + e.modelId + ' - ' + e.toString(); return '#' + e.modelId + ' - ' + e.toString();
}) })
.join('\n'); .join('\n');
const msg = g.f('Cannot rectify %s changes:\n%s', modelName, desc); var msg = g.f('Cannot rectify %s changes:\n%s', modelName, desc);
err = new Error(msg); err = new Error(msg);
err.details = {errors: errors}; err.details = {errors: errors};
return callback(err); return callback(err);
@ -148,15 +148,15 @@ module.exports = function(Change) {
Change.findOrCreateChange = function(modelName, modelId, callback) { Change.findOrCreateChange = function(modelName, modelId, callback) {
assert(this.registry.findModel(modelName), modelName + ' does not exist'); assert(this.registry.findModel(modelName), modelName + ' does not exist');
callback = callback || utils.createPromiseCallback(); callback = callback || utils.createPromiseCallback();
const id = this.idForModel(modelName, modelId); var id = this.idForModel(modelName, modelId);
const Change = this; var Change = this;
this.findById(id, function(err, change) { this.findById(id, function(err, change) {
if (err) return callback(err); if (err) return callback(err);
if (change) { if (change) {
callback(null, change); callback(null, change);
} else { } else {
const ch = new Change({ var ch = new Change({
id: id, id: id,
modelName: modelName, modelName: modelName,
modelId: modelId, modelId: modelId,
@ -177,8 +177,8 @@ module.exports = function(Change) {
*/ */
Change.prototype.rectify = function(cb) { Change.prototype.rectify = function(cb) {
const change = this; var change = this;
const currentRev = this.rev; var currentRev = this.rev;
change.debug('rectify change'); change.debug('rectify change');
@ -216,7 +216,7 @@ module.exports = function(Change) {
function(err, checkpoint) { function(err, checkpoint) {
if (err) return cb(err); if (err) return cb(err);
doRectify(checkpoint, rev); doRectify(checkpoint, rev);
}, }
); );
} }
@ -274,8 +274,8 @@ module.exports = function(Change) {
Change.prototype.currentRevision = function(cb) { Change.prototype.currentRevision = function(cb) {
cb = cb || utils.createPromiseCallback(); cb = cb || utils.createPromiseCallback();
const model = this.getModelCtor(); var model = this.getModelCtor();
const id = this.getModelId(); var id = this.getModelId();
model.findById(id, function(err, inst) { model.findById(id, function(err, inst) {
if (err) return cb(err); if (err) return cb(err);
if (inst) { if (inst) {
@ -345,8 +345,8 @@ module.exports = function(Change) {
Change.prototype.equals = function(change) { Change.prototype.equals = function(change) {
if (!change) return false; if (!change) return false;
const thisRev = this.rev || null; var thisRev = this.rev || null;
const thatRev = change.rev || null; var thatRev = change.rev || null;
return thisRev === thatRev; return thisRev === thatRev;
}; };
@ -423,8 +423,8 @@ module.exports = function(Change) {
callback(null, {deltas: [], conflicts: []}); callback(null, {deltas: [], conflicts: []});
return callback.promise; return callback.promise;
} }
const remoteChangeIndex = {}; var remoteChangeIndex = {};
const modelIds = []; var modelIds = [];
remoteChanges.forEach(function(ch) { remoteChanges.forEach(function(ch) {
modelIds.push(ch.modelId); modelIds.push(ch.modelId);
remoteChangeIndex[ch.modelId] = new Change(ch); remoteChangeIndex[ch.modelId] = new Change(ch);
@ -439,18 +439,18 @@ module.exports = function(Change) {
}, },
}, function(err, allLocalChanges) { }, function(err, allLocalChanges) {
if (err) return callback(err); if (err) return callback(err);
const deltas = []; var deltas = [];
const conflicts = []; var conflicts = [];
const localModelIds = []; var localModelIds = [];
const localChanges = allLocalChanges.filter(function(c) { var localChanges = allLocalChanges.filter(function(c) {
return c.checkpoint >= since; return c.checkpoint >= since;
}); });
localChanges.forEach(function(localChange) { localChanges.forEach(function(localChange) {
localChange = new Change(localChange); localChange = new Change(localChange);
localModelIds.push(localChange.modelId); localModelIds.push(localChange.modelId);
const remoteChange = remoteChangeIndex[localChange.modelId]; var remoteChange = remoteChangeIndex[localChange.modelId];
if (remoteChange && !localChange.equals(remoteChange)) { if (remoteChange && !localChange.equals(remoteChange)) {
if (remoteChange.conflictsWith(localChange)) { if (remoteChange.conflictsWith(localChange)) {
remoteChange.debug('remote conflict'); remoteChange.debug('remote conflict');
@ -466,8 +466,8 @@ module.exports = function(Change) {
modelIds.forEach(function(id) { modelIds.forEach(function(id) {
if (localModelIds.indexOf(id) !== -1) return; if (localModelIds.indexOf(id) !== -1) return;
const d = remoteChangeIndex[id]; var d = remoteChangeIndex[id];
const oldChange = allLocalChanges.filter(function(c) { var oldChange = allLocalChanges.filter(function(c) {
return c.modelId === id; return c.modelId === id;
})[0]; })[0];
@ -495,14 +495,14 @@ module.exports = function(Change) {
Change.rectifyAll = function(cb) { Change.rectifyAll = function(cb) {
debug('rectify all'); debug('rectify all');
const Change = this; var Change = this;
// this should be optimized // this should be optimized
this.find(function(err, changes) { this.find(function(err, changes) {
if (err) return cb(err); if (err) return cb(err);
async.each( async.each(
changes, changes,
function(c, next) { c.rectify(next); }, function(c, next) { c.rectify(next); },
cb, cb
); );
}); });
}; };
@ -513,7 +513,7 @@ module.exports = function(Change) {
*/ */
Change.getCheckpointModel = function() { Change.getCheckpointModel = function() {
let checkpointModel = this.Checkpoint; var checkpointModel = this.Checkpoint;
if (checkpointModel) return checkpointModel; if (checkpointModel) return checkpointModel;
// FIXME(bajtos) This code creates multiple different models with the same // FIXME(bajtos) This code creates multiple different models with the same
// model name, which is not a valid supported usage of juggler's API. // model name, which is not a valid supported usage of juggler's API.
@ -526,7 +526,7 @@ module.exports = function(Change) {
Change.prototype.debug = function() { Change.prototype.debug = function() {
if (debug.enabled) { if (debug.enabled) {
const args = Array.prototype.slice.call(arguments); var args = Array.prototype.slice.call(arguments);
args[0] = args[0] + ' %s'; args[0] = args[0] + ' %s';
args.push(this.modelName); args.push(this.modelName);
debug.apply(this, args); debug.apply(this, args);
@ -551,16 +551,16 @@ module.exports = function(Change) {
Change.prototype.getModelId = function() { Change.prototype.getModelId = function() {
// TODO(ritch) get rid of the need to create an instance // TODO(ritch) get rid of the need to create an instance
const Model = this.getModelCtor(); var Model = this.getModelCtor();
const id = this.modelId; var id = this.modelId;
const m = new Model(); var m = new Model();
m.setId(id); m.setId(id);
return m.getId(); return m.getId();
}; };
Change.prototype.getModel = function(callback) { Change.prototype.getModel = function(callback) {
const Model = this.constructor.settings.trackModel; var Model = this.constructor.settings.trackModel;
const id = this.getModelId(); var id = this.getModelId();
Model.findById(id, callback); Model.findById(id, callback);
}; };
@ -595,10 +595,10 @@ module.exports = function(Change) {
*/ */
Conflict.prototype.models = function(cb) { Conflict.prototype.models = function(cb) {
const conflict = this; var conflict = this;
const SourceModel = this.SourceModel; var SourceModel = this.SourceModel;
const TargetModel = this.TargetModel; var TargetModel = this.TargetModel;
let source, target; var source, target;
async.parallel([ async.parallel([
getSourceModel, getSourceModel,
@ -637,8 +637,8 @@ module.exports = function(Change) {
*/ */
Conflict.prototype.changes = function(cb) { Conflict.prototype.changes = function(cb) {
const conflict = this; var conflict = this;
let sourceChange, targetChange; var sourceChange, targetChange;
async.parallel([ async.parallel([
getSourceChange, getSourceChange,
@ -646,7 +646,7 @@ module.exports = function(Change) {
], done); ], done);
function getSourceChange(cb) { function getSourceChange(cb) {
const SourceModel = conflict.SourceModel; var SourceModel = conflict.SourceModel;
SourceModel.findLastChange(conflict.modelId, function(err, change) { SourceModel.findLastChange(conflict.modelId, function(err, change) {
if (err) return cb(err); if (err) return cb(err);
sourceChange = change; sourceChange = change;
@ -655,7 +655,7 @@ module.exports = function(Change) {
} }
function getTargetChange(cb) { function getTargetChange(cb) {
const TargetModel = conflict.TargetModel; var TargetModel = conflict.TargetModel;
TargetModel.findLastChange(conflict.modelId, function(err, change) { TargetModel.findLastChange(conflict.modelId, function(err, change) {
if (err) return cb(err); if (err) return cb(err);
targetChange = change; targetChange = change;
@ -684,7 +684,7 @@ module.exports = function(Change) {
*/ */
Conflict.prototype.resolve = function(cb) { Conflict.prototype.resolve = function(cb) {
const conflict = this; var conflict = this;
conflict.TargetModel.findLastChange( conflict.TargetModel.findLastChange(
this.modelId, this.modelId,
function(err, targetChange) { function(err, targetChange) {
@ -692,9 +692,9 @@ module.exports = function(Change) {
conflict.SourceModel.updateLastChange( conflict.SourceModel.updateLastChange(
conflict.modelId, conflict.modelId,
{prev: targetChange.rev}, {prev: targetChange.rev},
cb, cb
); );
}, }
); );
}; };
@ -718,16 +718,16 @@ module.exports = function(Change) {
* @param {Error} err * @param {Error} err
*/ */
Conflict.prototype.resolveUsingTarget = function(cb) { Conflict.prototype.resolveUsingTarget = function(cb) {
const conflict = this; var conflict = this;
conflict.models(function(err, source, target) { conflict.models(function(err, source, target) {
if (err) return done(err); if (err) return done(err);
if (target === null) { if (target === null) {
return conflict.SourceModel.deleteById(conflict.modelId, done); return conflict.SourceModel.deleteById(conflict.modelId, done);
} }
const inst = new conflict.SourceModel( var inst = new conflict.SourceModel(
target.toObject(), target.toObject(),
{persisted: true}, {persisted: true}
); );
inst.save(done); inst.save(done);
}); });
@ -751,7 +751,7 @@ module.exports = function(Change) {
* @returns {Conflict} A new Conflict instance. * @returns {Conflict} A new Conflict instance.
*/ */
Conflict.prototype.swapParties = function() { Conflict.prototype.swapParties = function() {
const Ctor = this.constructor; var Ctor = this.constructor;
return new Ctor(this.modelId, this.TargetModel, this.SourceModel); return new Ctor(this.modelId, this.TargetModel, this.SourceModel);
}; };
@ -765,14 +765,14 @@ module.exports = function(Change) {
*/ */
Conflict.prototype.resolveManually = function(data, cb) { Conflict.prototype.resolveManually = function(data, cb) {
const conflict = this; var conflict = this;
if (!data) { if (!data) {
return conflict.SourceModel.deleteById(conflict.modelId, done); return conflict.SourceModel.deleteById(conflict.modelId, done);
} }
conflict.models(function(err, source, target) { conflict.models(function(err, source, target) {
if (err) return done(err); if (err) return done(err);
const inst = source || new conflict.SourceModel(target); var inst = source || new conflict.SourceModel(target);
inst.setAttributes(data); inst.setAttributes(data);
inst.save(function(err) { inst.save(function(err) {
if (err) return done(err); if (err) return done(err);
@ -801,11 +801,11 @@ module.exports = function(Change) {
*/ */
Conflict.prototype.type = function(cb) { Conflict.prototype.type = function(cb) {
const conflict = this; var conflict = this;
this.changes(function(err, sourceChange, targetChange) { this.changes(function(err, sourceChange, targetChange) {
if (err) return cb(err); if (err) return cb(err);
const sourceChangeType = sourceChange.type(); var sourceChangeType = sourceChange.type();
const targetChangeType = targetChange.type(); var targetChangeType = targetChange.type();
if (sourceChangeType === Change.UPDATE && targetChangeType === Change.UPDATE) { if (sourceChangeType === Change.UPDATE && targetChangeType === Change.UPDATE) {
return cb(null, Change.UPDATE); return cb(null, Change.UPDATE);
} }

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -8,7 +8,7 @@
*/ */
'use strict'; 'use strict';
const assert = require('assert'); var assert = require('assert');
/** /**
* Checkpoint list entry. * Checkpoint list entry.
@ -35,15 +35,15 @@ module.exports = function(Checkpoint) {
* @param {Number} checkpoint The current checkpoint seq * @param {Number} checkpoint The current checkpoint seq
*/ */
Checkpoint.current = function(cb) { Checkpoint.current = function(cb) {
const Checkpoint = this; var Checkpoint = this;
Checkpoint._getSingleton(function(err, cp) { Checkpoint._getSingleton(function(err, cp) {
cb(err, cp.seq); cb(err, cp.seq);
}); });
}; };
Checkpoint._getSingleton = function(cb) { Checkpoint._getSingleton = function(cb) {
const query = {limit: 1}; // match all instances, return only one var query = {limit: 1}; // match all instances, return only one
const initialData = {seq: 1}; var initialData = {seq: 1};
this.findOrCreate(query, initialData, cb); this.findOrCreate(query, initialData, cb);
}; };
@ -54,10 +54,10 @@ module.exports = function(Checkpoint) {
* @param {Object} checkpoint The current checkpoint * @param {Object} checkpoint The current checkpoint
*/ */
Checkpoint.bumpLastSeq = function(cb) { Checkpoint.bumpLastSeq = function(cb) {
const Checkpoint = this; var Checkpoint = this;
Checkpoint._getSingleton(function(err, cp) { Checkpoint._getSingleton(function(err, cp) {
if (err) return cb(err); if (err) return cb(err);
const originalSeq = cp.seq; var originalSeq = cp.seq;
cp.seq++; cp.seq++;
// Update the checkpoint but only if it was not changed under our hands // Update the checkpoint but only if it was not changed under our hands
Checkpoint.updateAll({id: cp.id, seq: originalSeq}, {seq: cp.seq}, function(err, info) { Checkpoint.updateAll({id: cp.id, seq: originalSeq}, {seq: cp.seq}, function(err, info) {

View File

@ -1,10 +1,10 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const g = require('../../lib/globalize'); var g = require('../../lib/globalize');
/** /**
* Email model. Extends LoopBack base [Model](#model-new-model). * Email model. Extends LoopBack base [Model](#model-new-model).

View File

@ -1,10 +1,10 @@
// Copyright IBM Corp. 2016,2019. All Rights Reserved. // Copyright IBM Corp. 2016,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const g = require('../../lib/globalize'); var g = require('../../lib/globalize');
/** /**
* Data model for key-value databases. * Data model for key-value databases.
@ -226,17 +226,17 @@ function throwNotAttached(modelName, methodName) {
'The %s method has not been setup. ' + 'The %s method has not been setup. ' +
'The {{KeyValueModel}} has not been correctly attached ' + 'The {{KeyValueModel}} has not been correctly attached ' +
'to a {{DataSource}}!', 'to a {{DataSource}}!',
modelName, methodName, methodName, modelName, methodName, methodName
)); ));
} }
function convertNullToNotFoundError(ctx, cb) { function convertNullToNotFoundError(ctx, cb) {
if (ctx.result !== null) return cb(); if (ctx.result !== null) return cb();
const modelName = ctx.method.sharedClass.name; var modelName = ctx.method.sharedClass.name;
const id = ctx.getArgByName('id'); var id = ctx.getArgByName('id');
const msg = g.f('Unknown "%s" {{key}} "%s".', modelName, id); var msg = g.f('Unknown "%s" {{key}} "%s".', modelName, id);
const error = new Error(msg); var error = new Error(msg);
error.statusCode = error.status = 404; error.statusCode = error.status = 404;
error.code = 'KEY_NOT_FOUND'; error.code = 'KEY_NOT_FOUND';
cb(error); cb(error);

View File

@ -1,11 +1,11 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const loopback = require('../../lib/loopback'); var loopback = require('../../lib/loopback');
const utils = require('../../lib/utils'); var utils = require('../../lib/utils');
/** /**
* The `RoleMapping` model extends from the built in `loopback.Model` type. * The `RoleMapping` model extends from the built in `loopback.Model` type.
@ -26,7 +26,7 @@ module.exports = function(RoleMapping) {
RoleMapping.resolveRelatedModels = function() { RoleMapping.resolveRelatedModels = function() {
if (!this.userModel) { if (!this.userModel) {
const reg = this.registry; var reg = this.registry;
this.roleModel = reg.getModelByType('Role'); this.roleModel = reg.getModelByType('Role');
this.userModel = reg.getModelByType('User'); this.userModel = reg.getModelByType('User');
this.applicationModel = reg.getModelByType('Application'); this.applicationModel = reg.getModelByType('Application');
@ -44,7 +44,7 @@ module.exports = function(RoleMapping) {
this.constructor.resolveRelatedModels(); this.constructor.resolveRelatedModels();
if (this.principalType === RoleMapping.APPLICATION) { if (this.principalType === RoleMapping.APPLICATION) {
const applicationModel = this.constructor.applicationModel; var applicationModel = this.constructor.applicationModel;
applicationModel.findById(this.principalId, callback); applicationModel.findById(this.principalId, callback);
} else { } else {
process.nextTick(function() { process.nextTick(function() {
@ -63,7 +63,7 @@ module.exports = function(RoleMapping) {
RoleMapping.prototype.user = function(callback) { RoleMapping.prototype.user = function(callback) {
callback = callback || utils.createPromiseCallback(); callback = callback || utils.createPromiseCallback();
this.constructor.resolveRelatedModels(); this.constructor.resolveRelatedModels();
let userModel; var userModel;
if (this.principalType === RoleMapping.USER) { if (this.principalType === RoleMapping.USER) {
userModel = this.constructor.userModel; userModel = this.constructor.userModel;
@ -94,7 +94,7 @@ module.exports = function(RoleMapping) {
this.constructor.resolveRelatedModels(); this.constructor.resolveRelatedModels();
if (this.principalType === RoleMapping.ROLE) { if (this.principalType === RoleMapping.ROLE) {
const roleModel = this.constructor.roleModel; var roleModel = this.constructor.roleModel;
roleModel.findById(this.principalId, callback); roleModel.findById(this.principalId, callback);
} else { } else {
process.nextTick(function() { process.nextTick(function() {

View File

@ -1,18 +1,18 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const loopback = require('../../lib/loopback'); var loopback = require('../../lib/loopback');
const debug = require('debug')('loopback:security:role'); var debug = require('debug')('loopback:security:role');
const assert = require('assert'); var assert = require('assert');
const async = require('async'); var async = require('async');
const utils = require('../../lib/utils'); var utils = require('../../lib/utils');
const ctx = require('../../lib/access-context'); var ctx = require('../../lib/access-context');
const AccessContext = ctx.AccessContext; var AccessContext = ctx.AccessContext;
const Principal = ctx.Principal; var Principal = ctx.Principal;
const RoleMapping = loopback.RoleMapping; var RoleMapping = loopback.RoleMapping;
assert(RoleMapping, 'RoleMapping model must be defined before Role model'); assert(RoleMapping, 'RoleMapping model must be defined before Role model');
@ -24,7 +24,7 @@ assert(RoleMapping, 'RoleMapping model must be defined before Role model');
module.exports = function(Role) { module.exports = function(Role) {
Role.resolveRelatedModels = function() { Role.resolveRelatedModels = function() {
if (!this.userModel) { if (!this.userModel) {
const reg = this.registry; var reg = this.registry;
this.roleMappingModel = reg.getModelByType('RoleMapping'); this.roleMappingModel = reg.getModelByType('RoleMapping');
this.userModel = reg.getModelByType('User'); this.userModel = reg.getModelByType('User');
this.applicationModel = reg.getModelByType('Application'); this.applicationModel = reg.getModelByType('Application');
@ -74,29 +74,29 @@ module.exports = function(Role) {
query.where = query.where || {}; query.where = query.where || {};
roleModel.resolveRelatedModels(); roleModel.resolveRelatedModels();
const relsToModels = { var relsToModels = {
users: roleModel.userModel, users: roleModel.userModel,
applications: roleModel.applicationModel, applications: roleModel.applicationModel,
roles: roleModel, roles: roleModel,
}; };
const ACL = loopback.ACL; var ACL = loopback.ACL;
const relsToTypes = { var relsToTypes = {
users: ACL.USER, users: ACL.USER,
applications: ACL.APP, applications: ACL.APP,
roles: ACL.ROLE, roles: ACL.ROLE,
}; };
let principalModel = relsToModels[rel]; var principalModel = relsToModels[rel];
let principalType = relsToTypes[rel]; var principalType = relsToTypes[rel];
// redefine user model and user type if user principalType is custom (available and not "USER") // redefine user model and user type if user principalType is custom (available and not "USER")
const isCustomUserPrincipalType = rel === 'users' && var isCustomUserPrincipalType = rel === 'users' &&
query.where.principalType && query.where.principalType &&
query.where.principalType !== RoleMapping.USER; query.where.principalType !== RoleMapping.USER;
if (isCustomUserPrincipalType) { if (isCustomUserPrincipalType) {
const registry = this.constructor.registry; var registry = this.constructor.registry;
principalModel = registry.findModel(query.where.principalType); principalModel = registry.findModel(query.where.principalType);
principalType = query.where.principalType; principalType = query.where.principalType;
} }
@ -133,10 +133,11 @@ module.exports = function(Role) {
roleModel.roleMappingModel.find({ roleModel.roleMappingModel.find({
where: {roleId: context.id, principalType: principalType}, where: {roleId: context.id, principalType: principalType},
}, function(err, mappings) { }, function(err, mappings) {
var ids;
if (err) { if (err) {
return callback(err); return callback(err);
} }
const ids = mappings.map(function(m) { ids = mappings.map(function(m) {
return m.principalId; return m.principalId;
}); });
query.where = query.where || {}; query.where = query.where || {};
@ -176,18 +177,18 @@ module.exports = function(Role) {
}); });
return; return;
} }
const modelClass = context.model; var modelClass = context.model;
const modelId = context.modelId; var modelId = context.modelId;
const user = context.getUser(); var user = context.getUser();
const userId = user && user.id; var userId = user && user.id;
const principalType = user && user.principalType; var principalType = user && user.principalType;
const opts = {accessToken: context.accessToken}; var opts = {accessToken: context.accessToken};
Role.isOwner(modelClass, modelId, userId, principalType, opts, callback); Role.isOwner(modelClass, modelId, userId, principalType, opts, callback);
}); });
function isUserClass(modelClass) { function isUserClass(modelClass) {
if (!modelClass) return false; if (!modelClass) return false;
const User = modelClass.modelBuilder.models.User; var User = modelClass.modelBuilder.models.User;
if (!User) return false; if (!User) return false;
return modelClass == User || modelClass.prototype instanceof User; return modelClass == User || modelClass.prototype instanceof User;
} }
@ -221,7 +222,7 @@ module.exports = function(Role) {
* @promise * @promise
*/ */
Role.isOwner = function isOwner(modelClass, modelId, userId, principalType, options, callback) { Role.isOwner = function isOwner(modelClass, modelId, userId, principalType, options, callback) {
const _this = this; var _this = this;
if (!callback && typeof options === 'function') { if (!callback && typeof options === 'function') {
callback = options; callback = options;
@ -252,8 +253,8 @@ module.exports = function(Role) {
// 1. the app has a single user model and principalType is 'USER' // 1. the app has a single user model and principalType is 'USER'
// 2. the app has multiple user models and principalType is not 'USER' // 2. the app has multiple user models and principalType is not 'USER'
// multiple user models // multiple user models
const isMultipleUsers = _isMultipleUsers(); var isMultipleUsers = _isMultipleUsers();
const isPrincipalTypeValid = var isPrincipalTypeValid =
(!isMultipleUsers && principalType === Principal.USER) || (!isMultipleUsers && principalType === Principal.USER) ||
(isMultipleUsers && principalType !== Principal.USER); (isMultipleUsers && principalType !== Principal.USER);
@ -270,7 +271,7 @@ module.exports = function(Role) {
// Is the modelClass User or a subclass of User? // Is the modelClass User or a subclass of User?
if (isUserClass(modelClass)) { if (isUserClass(modelClass)) {
const userModelName = modelClass.modelName; var userModelName = modelClass.modelName;
// matching ids is enough if principalType is USER or matches given user model name // matching ids is enough if principalType is USER or matches given user model name
if (principalType === Principal.USER || principalType === userModelName) { if (principalType === Principal.USER || principalType === userModelName) {
process.nextTick(function() { process.nextTick(function() {
@ -288,7 +289,7 @@ module.exports = function(Role) {
} }
debug('Model found: %j', inst); debug('Model found: %j', inst);
const ownerRelations = modelClass.settings.ownerRelations; var ownerRelations = modelClass.settings.ownerRelations;
if (!ownerRelations) { if (!ownerRelations) {
return legacyOwnershipCheck(inst); return legacyOwnershipCheck(inst);
} else { } else {
@ -307,24 +308,24 @@ module.exports = function(Role) {
// ownership check induced the whole isOwner() to resolve as false. // ownership check induced the whole isOwner() to resolve as false.
// This behaviour will be pruned at next LoopBack major release. // This behaviour will be pruned at next LoopBack major release.
function legacyOwnershipCheck(inst) { function legacyOwnershipCheck(inst) {
const ownerId = inst.userId || inst.owner; var ownerId = inst.userId || inst.owner;
if (principalType === Principal.USER && ownerId && 'function' !== typeof ownerId) { if (principalType === Principal.USER && ownerId && 'function' !== typeof ownerId) {
return callback(null, matches(ownerId, userId)); return callback(null, matches(ownerId, userId));
} }
// Try to follow belongsTo // Try to follow belongsTo
for (const r in modelClass.relations) { for (var r in modelClass.relations) {
const rel = modelClass.relations[r]; var rel = modelClass.relations[r];
// relation should be belongsTo and target a User based class // relation should be belongsTo and target a User based class
const belongsToUser = rel.type === 'belongsTo' && isUserClass(rel.modelTo); var belongsToUser = rel.type === 'belongsTo' && isUserClass(rel.modelTo);
if (!belongsToUser) { if (!belongsToUser) {
continue; continue;
} }
// checking related user // checking related user
const relatedUser = rel.modelTo; var relatedUser = rel.modelTo;
const userModelName = relatedUser.modelName; var userModelName = relatedUser.modelName;
const isMultipleUsers = _isMultipleUsers(relatedUser); var isMultipleUsers = _isMultipleUsers(relatedUser);
// a relation can be considered for isOwner resolution if: // a relation can be considered for isOwner resolution if:
// 1. the app has a single user model and principalType is 'USER' // 1. the app has a single user model and principalType is 'USER'
// 2. the app has multiple user models and principalType is the related user model name // 2. the app has multiple user models and principalType is the related user model name
@ -348,20 +349,20 @@ module.exports = function(Role) {
} }
function checkOwnership(inst) { function checkOwnership(inst) {
const ownerRelations = inst.constructor.settings.ownerRelations; var ownerRelations = inst.constructor.settings.ownerRelations;
// collecting related users // collecting related users
const relWithUsers = []; var relWithUsers = [];
for (const r in modelClass.relations) { for (var r in modelClass.relations) {
const rel = modelClass.relations[r]; var rel = modelClass.relations[r];
// relation should be belongsTo and target a User based class // relation should be belongsTo and target a User based class
if (rel.type !== 'belongsTo' || !isUserClass(rel.modelTo)) { if (rel.type !== 'belongsTo' || !isUserClass(rel.modelTo)) {
continue; continue;
} }
// checking related user // checking related user
const relatedUser = rel.modelTo; var relatedUser = rel.modelTo;
const userModelName = relatedUser.modelName; var userModelName = relatedUser.modelName;
const isMultipleUsers = _isMultipleUsers(relatedUser); var isMultipleUsers = _isMultipleUsers(relatedUser);
// a relation can be considered for isOwner resolution if: // a relation can be considered for isOwner resolution if:
// 1. the app has a single user model and principalType is 'USER' // 1. the app has a single user model and principalType is 'USER'
// 2. the app has multiple user models and principalType is the related user model name // 2. the app has multiple user models and principalType is the related user model name
@ -402,8 +403,8 @@ module.exports = function(Role) {
// user model by type. The relation with AccessToken is used to check // user model by type. The relation with AccessToken is used to check
// if polymorphism is used, and thus if multiple users. // if polymorphism is used, and thus if multiple users.
function _isMultipleUsers(userModel) { function _isMultipleUsers(userModel) {
const oneOfUserModels = userModel || _this.registry.getModelByType('User'); var oneOfUserModels = userModel || _this.registry.getModelByType('User');
const accessTokensRel = oneOfUserModels.relations.accessTokens; var accessTokensRel = oneOfUserModels.relations.accessTokens;
return !!(accessTokensRel && accessTokensRel.polymorphic); return !!(accessTokensRel && accessTokensRel.polymorphic);
} }
}; };
@ -479,15 +480,15 @@ module.exports = function(Role) {
debug('isInRole(): %s', role); debug('isInRole(): %s', role);
context.debug(); context.debug();
const resolver = Role.resolvers[role]; var resolver = Role.resolvers[role];
if (resolver) { if (resolver) {
debug('Custom resolver found for role %s', role); debug('Custom resolver found for role %s', role);
const promise = resolver(role, context, callback); var promise = resolver(role, context, callback);
if (promise && typeof promise.then === 'function') { if (promise && typeof promise.then === 'function') {
promise.then( promise.then(
function(result) { callback(null, result); }, function(result) { callback(null, result); },
callback, callback
); );
} }
return callback.promise; return callback.promise;
@ -501,9 +502,9 @@ module.exports = function(Role) {
return callback.promise; return callback.promise;
} }
const inRole = context.principals.some(function(p) { var inRole = context.principals.some(function(p) {
const principalType = p.type || undefined; var principalType = p.type || undefined;
const principalId = p.id || undefined; var principalId = p.id || undefined;
// Check if it's the same role // Check if it's the same role
return principalType === RoleMapping.ROLE && principalId === role; return principalType === RoleMapping.ROLE && principalId === role;
@ -517,7 +518,7 @@ module.exports = function(Role) {
return callback.promise; return callback.promise;
} }
const roleMappingModel = this.roleMappingModel; var roleMappingModel = this.roleMappingModel;
this.findOne({where: {name: role}}, function(err, result) { this.findOne({where: {name: role}}, function(err, result) {
if (err) { if (err) {
if (callback) callback(err); if (callback) callback(err);
@ -531,10 +532,10 @@ module.exports = function(Role) {
// Iterate through the list of principals // Iterate through the list of principals
async.some(context.principals, function(p, done) { async.some(context.principals, function(p, done) {
const principalType = p.type || undefined; var principalType = p.type || undefined;
let principalId = p.id || undefined; var principalId = p.id || undefined;
const roleId = result.id.toString(); var roleId = result.id.toString();
const principalIdIsString = typeof principalId === 'string'; var principalIdIsString = typeof principalId === 'string';
if (principalId !== null && principalId !== undefined && !principalIdIsString) { if (principalId !== null && principalId !== undefined && !principalIdIsString) {
principalId = principalId.toString(); principalId = principalId.toString();
@ -584,18 +585,18 @@ module.exports = function(Role) {
if (!(context instanceof AccessContext)) { if (!(context instanceof AccessContext)) {
context = new AccessContext(context); context = new AccessContext(context);
} }
const roles = []; var roles = [];
this.resolveRelatedModels(); this.resolveRelatedModels();
const addRole = function(role) { var addRole = function(role) {
if (role && roles.indexOf(role) === -1) { if (role && roles.indexOf(role) === -1) {
roles.push(role); roles.push(role);
} }
}; };
const self = this; var self = this;
// Check against the smart roles // Check against the smart roles
const inRoleTasks = []; var inRoleTasks = [];
Object.keys(Role.resolvers).forEach(function(role) { Object.keys(Role.resolvers).forEach(function(role) {
inRoleTasks.push(function(done) { inRoleTasks.push(function(done) {
self.isInRole(role, context, function(err, inRole) { self.isInRole(role, context, function(err, inRole) {
@ -612,11 +613,11 @@ module.exports = function(Role) {
}); });
}); });
const roleMappingModel = this.roleMappingModel; var roleMappingModel = this.roleMappingModel;
context.principals.forEach(function(p) { context.principals.forEach(function(p) {
// Check against the role mappings // Check against the role mappings
const principalType = p.type || undefined; var principalType = p.type || undefined;
let principalId = p.id == null ? undefined : p.id; var principalId = p.id == null ? undefined : p.id;
if (typeof principalId !== 'string' && principalId != null) { if (typeof principalId !== 'string' && principalId != null) {
principalId = principalId.toString(); principalId = principalId.toString();
@ -630,7 +631,7 @@ module.exports = function(Role) {
if (principalType && principalId) { if (principalType && principalId) {
// Please find() treat undefined matches all values // Please find() treat undefined matches all values
inRoleTasks.push(function(done) { inRoleTasks.push(function(done) {
const filter = {where: {principalType: principalType, principalId: principalId}}; var filter = {where: {principalType: principalType, principalId: principalId}};
if (options.returnOnlyRoleNames === true) { if (options.returnOnlyRoleNames === true) {
filter.include = ['role']; filter.include = ['role'];
} }
@ -641,7 +642,7 @@ module.exports = function(Role) {
return; return;
} }
mappings.forEach(function(m) { mappings.forEach(function(m) {
let role; var role;
if (options.returnOnlyRoleNames === true) { if (options.returnOnlyRoleNames === true) {
role = m.toJSON().role.name; role = m.toJSON().role.name;
} else { } else {

View File

@ -1,11 +1,11 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const assert = require('assert'); var assert = require('assert');
const loopback = require('../../lib/loopback'); var loopback = require('../../lib/loopback');
/** /**
* Resource owner grants/delegates permissions to client applications * Resource owner grants/delegates permissions to client applications
@ -21,7 +21,7 @@ const loopback = require('../../lib/loopback');
module.exports = function(Scope) { module.exports = function(Scope) {
Scope.resolveRelatedModels = function() { Scope.resolveRelatedModels = function() {
if (!this.aclModel) { if (!this.aclModel) {
const reg = this.registry; var reg = this.registry;
this.aclModel = reg.getModelByType(loopback.ACL); this.aclModel = reg.getModelByType(loopback.ACL);
} }
}; };
@ -38,7 +38,7 @@ module.exports = function(Scope) {
*/ */
Scope.checkPermission = function(scope, model, property, accessType, callback) { Scope.checkPermission = function(scope, model, property, accessType, callback) {
this.resolveRelatedModels(); this.resolveRelatedModels();
const aclModel = this.aclModel; var aclModel = this.aclModel;
assert(aclModel, assert(aclModel,
'ACL model must be defined before Scope.checkPermission is called'); 'ACL model must be defined before Scope.checkPermission is called');
@ -47,7 +47,7 @@ module.exports = function(Scope) {
if (callback) callback(err); if (callback) callback(err);
} else { } else {
aclModel.checkPermission( aclModel.checkPermission(
aclModel.SCOPE, scope.id, model, property, accessType, callback, aclModel.SCOPE, scope.id, model, property, accessType, callback
); );
} }
}); });

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -8,18 +8,18 @@
*/ */
'use strict'; 'use strict';
const g = require('../../lib/globalize'); var g = require('../../lib/globalize');
const isEmail = require('isemail'); var isEmail = require('isemail');
const loopback = require('../../lib/loopback'); var loopback = require('../../lib/loopback');
const utils = require('../../lib/utils'); var utils = require('../../lib/utils');
const path = require('path'); var path = require('path');
const qs = require('querystring'); var qs = require('querystring');
const SALT_WORK_FACTOR = 10; var SALT_WORK_FACTOR = 10;
const crypto = require('crypto'); var crypto = require('crypto');
// bcrypt's max length is 72 bytes; // bcrypt's max length is 72 bytes;
// See https://github.com/kelektiv/node.bcrypt.js/blob/45f498ef6dc6e8234e58e07834ce06a50ff16352/src/node_blf.h#L59 // See https://github.com/kelektiv/node.bcrypt.js/blob/45f498ef6dc6e8234e58e07834ce06a50ff16352/src/node_blf.h#L59
const MAX_PASSWORD_LENGTH = 72; var MAX_PASSWORD_LENGTH = 72;
let bcrypt; var bcrypt;
try { try {
// Try the native module first // Try the native module first
bcrypt = require('bcrypt'); bcrypt = require('bcrypt');
@ -32,12 +32,12 @@ try {
bcrypt = require('bcryptjs'); bcrypt = require('bcryptjs');
} }
const DEFAULT_TTL = 1209600; // 2 weeks in seconds var DEFAULT_TTL = 1209600; // 2 weeks in seconds
const DEFAULT_RESET_PW_TTL = 15 * 60; // 15 mins in seconds var DEFAULT_RESET_PW_TTL = 15 * 60; // 15 mins in seconds
const DEFAULT_MAX_TTL = 31556926; // 1 year in seconds var DEFAULT_MAX_TTL = 31556926; // 1 year in seconds
const assert = require('assert'); var assert = require('assert');
const debug = require('debug')('loopback:user'); var debug = require('debug')('loopback:user');
/** /**
* Built-in User model. * Built-in User model.
@ -123,18 +123,18 @@ module.exports = function(User) {
tokenData = {}; tokenData = {};
} }
const userSettings = this.constructor.settings; var userSettings = this.constructor.settings;
tokenData.ttl = Math.min(tokenData.ttl || userSettings.ttl, userSettings.maxTTL); tokenData.ttl = Math.min(tokenData.ttl || userSettings.ttl, userSettings.maxTTL);
this.accessTokens.create(tokenData, options, cb); this.accessTokens.create(tokenData, options, cb);
return cb.promise; return cb.promise;
}; };
function splitPrincipal(name, realmDelimiter) { function splitPrincipal(name, realmDelimiter) {
const parts = [null, name]; var parts = [null, name];
if (!realmDelimiter) { if (!realmDelimiter) {
return parts; return parts;
} }
const index = name.indexOf(realmDelimiter); var index = name.indexOf(realmDelimiter);
if (index !== -1) { if (index !== -1) {
parts[0] = name.substring(0, index); parts[0] = name.substring(0, index);
parts[1] = name.substring(index + realmDelimiter.length); parts[1] = name.substring(index + realmDelimiter.length);
@ -150,7 +150,7 @@ module.exports = function(User) {
* @returns {Object} The normalized credential object * @returns {Object} The normalized credential object
*/ */
User.normalizeCredentials = function(credentials, realmRequired, realmDelimiter) { User.normalizeCredentials = function(credentials, realmRequired, realmDelimiter) {
const query = {}; var query = {};
credentials = credentials || {}; credentials = credentials || {};
if (!realmRequired) { if (!realmRequired) {
if (credentials.email) { if (credentials.email) {
@ -162,7 +162,7 @@ module.exports = function(User) {
if (credentials.realm) { if (credentials.realm) {
query.realm = credentials.realm; query.realm = credentials.realm;
} }
let parts; var parts;
if (credentials.email) { if (credentials.email) {
parts = splitPrincipal(credentials.email, realmDelimiter); parts = splitPrincipal(credentials.email, realmDelimiter);
query.email = parts[1]; query.email = parts[1];
@ -205,7 +205,7 @@ module.exports = function(User) {
*/ */
User.login = function(credentials, include, fn) { User.login = function(credentials, include, fn) {
const self = this; var self = this;
if (typeof include === 'function') { if (typeof include === 'function') {
fn = include; fn = include;
include = undefined; include = undefined;
@ -222,54 +222,33 @@ module.exports = function(User) {
include = include.toLowerCase(); include = include.toLowerCase();
} }
let realmDelimiter; var realmDelimiter;
// Check if realm is required // Check if realm is required
const realmRequired = !!(self.settings.realmRequired || var realmRequired = !!(self.settings.realmRequired ||
self.settings.realmDelimiter); self.settings.realmDelimiter);
if (realmRequired) { if (realmRequired) {
realmDelimiter = self.settings.realmDelimiter; realmDelimiter = self.settings.realmDelimiter;
} }
const query = self.normalizeCredentials(credentials, realmRequired, var query = self.normalizeCredentials(credentials, realmRequired,
realmDelimiter); realmDelimiter);
if (realmRequired) { if (realmRequired && !query.realm) {
if (!query.realm) { var err1 = new Error(g.f('{{realm}} is required'));
const err1 = new Error(g.f('{{realm}} is required'));
err1.statusCode = 400; err1.statusCode = 400;
err1.code = 'REALM_REQUIRED'; err1.code = 'REALM_REQUIRED';
fn(err1); fn(err1);
return fn.promise; return fn.promise;
} else if (typeof query.realm !== 'string') {
const err5 = new Error(g.f('Invalid realm'));
err5.statusCode = 400;
err5.code = 'INVALID_REALM';
fn(err5);
return fn.promise;
}
} }
if (!query.email && !query.username) { if (!query.email && !query.username) {
const err2 = new Error(g.f('{{username}} or {{email}} is required')); var err2 = new Error(g.f('{{username}} or {{email}} is required'));
err2.statusCode = 400; err2.statusCode = 400;
err2.code = 'USERNAME_EMAIL_REQUIRED'; err2.code = 'USERNAME_EMAIL_REQUIRED';
fn(err2); fn(err2);
return fn.promise; return fn.promise;
} }
if (query.username && typeof query.username !== 'string') {
const err3 = new Error(g.f('Invalid username'));
err3.statusCode = 400;
err3.code = 'INVALID_USERNAME';
fn(err3);
return fn.promise;
} else if (query.email && typeof query.email !== 'string') {
const err4 = new Error(g.f('Invalid email'));
err4.statusCode = 400;
err4.code = 'INVALID_EMAIL';
fn(err4);
return fn.promise;
}
self.findOne({where: query}, function(err, user) { self.findOne({where: query}, function(err, user) {
const defaultError = new Error(g.f('login failed')); var defaultError = new Error(g.f('login failed'));
defaultError.statusCode = 401; defaultError.statusCode = 401;
defaultError.code = 'LOGIN_FAILED'; defaultError.code = 'LOGIN_FAILED';
@ -344,7 +323,7 @@ module.exports = function(User) {
User.logout = function(tokenId, fn) { User.logout = function(tokenId, fn) {
fn = fn || utils.createPromiseCallback(); fn = fn || utils.createPromiseCallback();
let err; var err;
if (!tokenId) { if (!tokenId) {
err = new Error(g.f('{{accessToken}} is required to logout')); err = new Error(g.f('{{accessToken}} is required to logout'));
err.statusCode = 401; err.statusCode = 401;
@ -370,12 +349,12 @@ module.exports = function(User) {
// Do nothing when the access control was disabled for this user model. // Do nothing when the access control was disabled for this user model.
if (!ctx.Model.relations.accessTokens) return next(); if (!ctx.Model.relations.accessTokens) return next();
const AccessToken = ctx.Model.relations.accessTokens.modelTo; var AccessToken = ctx.Model.relations.accessTokens.modelTo;
const pkName = ctx.Model.definition.idName() || 'id'; var pkName = ctx.Model.definition.idName() || 'id';
ctx.Model.find({where: ctx.where, fields: [pkName]}, function(err, list) { ctx.Model.find({where: ctx.where, fields: [pkName]}, function(err, list) {
if (err) return next(err); if (err) return next(err);
const ids = list.map(function(u) { return u[pkName]; }); var ids = list.map(function(u) { return u[pkName]; });
ctx.where = {}; ctx.where = {};
ctx.where[pkName] = {inq: ids}; ctx.where[pkName] = {inq: ids};
@ -720,9 +699,9 @@ module.exports = function(User) {
} }
cb = cb || utils.createPromiseCallback(); cb = cb || utils.createPromiseCallback();
const user = this; var user = this;
const userModel = this.constructor; var userModel = this.constructor;
const registry = userModel.registry; var registry = userModel.registry;
verifyOptions = Object.assign({}, verifyOptions); verifyOptions = Object.assign({}, verifyOptions);
// final assertion is performed once all options are assigned // final assertion is performed once all options are assigned
assert(typeof verifyOptions === 'object', assert(typeof verifyOptions === 'object',
@ -743,19 +722,19 @@ module.exports = function(User) {
verifyOptions.mailer = verifyOptions.mailer || userModel.email || verifyOptions.mailer = verifyOptions.mailer || userModel.email ||
registry.getModelByType(loopback.Email); registry.getModelByType(loopback.Email);
const pkName = userModel.definition.idName() || 'id'; var pkName = userModel.definition.idName() || 'id';
verifyOptions.redirect = verifyOptions.redirect || '/'; verifyOptions.redirect = verifyOptions.redirect || '/';
const defaultTemplate = path.join(__dirname, '..', '..', 'templates', 'verify.ejs'); var defaultTemplate = path.join(__dirname, '..', '..', 'templates', 'verify.ejs');
verifyOptions.template = path.resolve(verifyOptions.template || defaultTemplate); verifyOptions.template = path.resolve(verifyOptions.template || defaultTemplate);
verifyOptions.user = user; verifyOptions.user = user;
verifyOptions.protocol = verifyOptions.protocol || 'http'; verifyOptions.protocol = verifyOptions.protocol || 'http';
const app = userModel.app; var app = userModel.app;
verifyOptions.host = verifyOptions.host || (app && app.get('host')) || 'localhost'; verifyOptions.host = verifyOptions.host || (app && app.get('host')) || 'localhost';
verifyOptions.port = verifyOptions.port || (app && app.get('port')) || 3000; verifyOptions.port = verifyOptions.port || (app && app.get('port')) || 3000;
verifyOptions.restApiRoot = verifyOptions.restApiRoot || (app && app.get('restApiRoot')) || '/api'; verifyOptions.restApiRoot = verifyOptions.restApiRoot || (app && app.get('restApiRoot')) || '/api';
const displayPort = ( var displayPort = (
(verifyOptions.protocol === 'http' && verifyOptions.port == '80') || (verifyOptions.protocol === 'http' && verifyOptions.port == '80') ||
(verifyOptions.protocol === 'https' && verifyOptions.port == '443') (verifyOptions.protocol === 'https' && verifyOptions.port == '443')
) ? '' : ':' + verifyOptions.port; ) ? '' : ':' + verifyOptions.port;
@ -766,14 +745,14 @@ module.exports = function(User) {
throw new Error( throw new Error(
'Cannot build user verification URL, ' + 'Cannot build user verification URL, ' +
'the default confirm method is not public. ' + 'the default confirm method is not public. ' +
'Please provide the URL in verifyOptions.verifyHref.', 'Please provide the URL in verifyOptions.verifyHref.'
); );
} }
const urlPath = joinUrlPath( const urlPath = joinUrlPath(
verifyOptions.restApiRoot, verifyOptions.restApiRoot,
userModel.http.path, userModel.http.path,
confirmMethod.http.path, confirmMethod.http.path
); );
verifyOptions.verifyHref = verifyOptions.verifyHref =
@ -796,7 +775,7 @@ module.exports = function(User) {
assertVerifyOptions(verifyOptions); assertVerifyOptions(verifyOptions);
// argument "options" is passed depending on verifyOptions.generateVerificationToken function requirements // argument "options" is passed depending on verifyOptions.generateVerificationToken function requirements
const tokenGenerator = verifyOptions.generateVerificationToken; var tokenGenerator = verifyOptions.generateVerificationToken;
if (tokenGenerator.length == 3) { if (tokenGenerator.length == 3) {
tokenGenerator(user, options, addTokenToUserAndSave); tokenGenerator(user, options, addTokenToUserAndSave);
} else { } else {
@ -824,7 +803,7 @@ module.exports = function(User) {
verifyOptions.text = verifyOptions.text.replace(/\{href\}/g, verifyOptions.verifyHref); verifyOptions.text = verifyOptions.text.replace(/\{href\}/g, verifyOptions.verifyHref);
// argument "options" is passed depending on templateFn function requirements // argument "options" is passed depending on templateFn function requirements
const templateFn = verifyOptions.templateFn; var templateFn = verifyOptions.templateFn;
if (templateFn.length == 3) { if (templateFn.length == 3) {
templateFn(verifyOptions, options, setHtmlContentAndSend); templateFn(verifyOptions, options, setHtmlContentAndSend);
} else { } else {
@ -841,7 +820,7 @@ module.exports = function(User) {
delete verifyOptions.template; delete verifyOptions.template;
// argument "options" is passed depending on Email.send function requirements // argument "options" is passed depending on Email.send function requirements
const Email = verifyOptions.mailer; var Email = verifyOptions.mailer;
if (Email.send.length == 3) { if (Email.send.length == 3) {
Email.send(verifyOptions, options, handleAfterSend); Email.send(verifyOptions, options, handleAfterSend);
} else { } else {
@ -873,8 +852,8 @@ module.exports = function(User) {
} }
function createVerificationEmailBody(verifyOptions, options, cb) { function createVerificationEmailBody(verifyOptions, options, cb) {
const template = loopback.template(verifyOptions.template); var template = loopback.template(verifyOptions.template);
const body = template(verifyOptions); var body = template(verifyOptions);
cb(null, body); cb(null, body);
} }
@ -952,11 +931,11 @@ module.exports = function(User) {
User.resetPassword = function(options, cb) { User.resetPassword = function(options, cb) {
cb = cb || utils.createPromiseCallback(); cb = cb || utils.createPromiseCallback();
const UserModel = this; var UserModel = this;
const ttl = UserModel.settings.resetPasswordTokenTTL || DEFAULT_RESET_PW_TTL; var ttl = UserModel.settings.resetPasswordTokenTTL || DEFAULT_RESET_PW_TTL;
options = options || {}; options = options || {};
if (typeof options.email !== 'string') { if (typeof options.email !== 'string') {
const err = new Error(g.f('Email is required')); var err = new Error(g.f('Email is required'));
err.statusCode = 400; err.statusCode = 400;
err.code = 'EMAIL_REQUIRED'; err.code = 'EMAIL_REQUIRED';
cb(err); cb(err);
@ -970,7 +949,7 @@ module.exports = function(User) {
} catch (err) { } catch (err) {
return cb(err); return cb(err);
} }
const where = { var where = {
email: options.email, email: options.email,
}; };
if (options.realm) { if (options.realm) {
@ -1031,12 +1010,12 @@ module.exports = function(User) {
*/ */
User.hashPassword = function(plain) { User.hashPassword = function(plain) {
this.validatePassword(plain); this.validatePassword(plain);
const salt = bcrypt.genSaltSync(this.settings.saltWorkFactor || SALT_WORK_FACTOR); var salt = bcrypt.genSaltSync(this.settings.saltWorkFactor || SALT_WORK_FACTOR);
return bcrypt.hashSync(plain, salt); return bcrypt.hashSync(plain, salt);
}; };
User.validatePassword = function(plain) { User.validatePassword = function(plain) {
let err; var err;
if (!plain || typeof plain !== 'string') { if (!plain || typeof plain !== 'string') {
err = new Error(g.f('Invalid password.')); err = new Error(g.f('Invalid password.'));
err.code = 'INVALID_PASSWORD'; err.code = 'INVALID_PASSWORD';
@ -1045,7 +1024,7 @@ module.exports = function(User) {
} }
// Bcrypt only supports up to 72 bytes; the rest is silently dropped. // Bcrypt only supports up to 72 bytes; the rest is silently dropped.
const len = Buffer.byteLength(plain, 'utf8'); var len = Buffer.byteLength(plain, 'utf8');
if (len > MAX_PASSWORD_LENGTH) { if (len > MAX_PASSWORD_LENGTH) {
err = new Error(g.f('The password entered was too long. Max length is %d (entered %d)', err = new Error(g.f('The password entered was too long. Max length is %d (entered %d)',
MAX_PASSWORD_LENGTH, len)); MAX_PASSWORD_LENGTH, len));
@ -1064,20 +1043,20 @@ module.exports = function(User) {
if (!Array.isArray(userIds) || !userIds.length) if (!Array.isArray(userIds) || !userIds.length)
return process.nextTick(cb); return process.nextTick(cb);
const accessTokenRelation = this.relations.accessTokens; var accessTokenRelation = this.relations.accessTokens;
if (!accessTokenRelation) if (!accessTokenRelation)
return process.nextTick(cb); return process.nextTick(cb);
const AccessToken = accessTokenRelation.modelTo; var AccessToken = accessTokenRelation.modelTo;
const query = {userId: {inq: userIds}}; var query = {userId: {inq: userIds}};
const tokenPK = AccessToken.definition.idName() || 'id'; var tokenPK = AccessToken.definition.idName() || 'id';
if (options.accessToken && tokenPK in options.accessToken) { if (options.accessToken && tokenPK in options.accessToken) {
query[tokenPK] = {neq: options.accessToken[tokenPK]}; query[tokenPK] = {neq: options.accessToken[tokenPK]};
} }
// add principalType in AccessToken.query if using polymorphic relations // add principalType in AccessToken.query if using polymorphic relations
// between AccessToken and User // between AccessToken and User
const relatedUser = AccessToken.relations.user; var relatedUser = AccessToken.relations.user;
const isRelationPolymorphic = relatedUser && relatedUser.polymorphic && var isRelationPolymorphic = relatedUser && relatedUser.polymorphic &&
!relatedUser.modelTo; !relatedUser.modelTo;
if (isRelationPolymorphic) { if (isRelationPolymorphic) {
query.principalType = this.modelName; query.principalType = this.modelName;
@ -1092,7 +1071,7 @@ module.exports = function(User) {
User.setup = function() { User.setup = function() {
// We need to call the base class's setup method // We need to call the base class's setup method
User.base.setup.call(this); User.base.setup.call(this);
const UserModel = this; var UserModel = this;
// max ttl // max ttl
this.settings.maxTTL = this.settings.maxTTL || DEFAULT_MAX_TTL; this.settings.maxTTL = this.settings.maxTTL || DEFAULT_MAX_TTL;
@ -1110,7 +1089,7 @@ module.exports = function(User) {
if (typeof plain !== 'string') { if (typeof plain !== 'string') {
return; return;
} }
if ((plain.indexOf('$2a$') === 0 || plain.indexOf('$2b$') === 0) && plain.length === 60) { if (plain.indexOf('$2a$') === 0 && plain.length === 60) {
// The password is already hashed. It can be the case // The password is already hashed. It can be the case
// when the instance is loaded from DB // when the instance is loaded from DB
this.$password = plain; this.$password = plain;
@ -1121,7 +1100,7 @@ module.exports = function(User) {
// Make sure emailVerified is not set by creation // Make sure emailVerified is not set by creation
UserModel.beforeRemote('create', function(ctx, user, next) { UserModel.beforeRemote('create', function(ctx, user, next) {
const body = ctx.req.body; var body = ctx.req.body;
if (body && body.emailVerified) { if (body && body.emailVerified) {
body.emailVerified = false; body.emailVerified = false;
} }
@ -1148,7 +1127,7 @@ module.exports = function(User) {
'{{(`include=user`)}}\n\n'), '{{(`include=user`)}}\n\n'),
}, },
http: {verb: 'post'}, http: {verb: 'post'},
}, }
); );
UserModel.remoteMethod( UserModel.remoteMethod(
@ -1157,9 +1136,9 @@ module.exports = function(User) {
description: 'Logout a user with access token.', description: 'Logout a user with access token.',
accepts: [ accepts: [
{arg: 'access_token', type: 'string', http: function(ctx) { {arg: 'access_token', type: 'string', http: function(ctx) {
const req = ctx && ctx.req; var req = ctx && ctx.req;
const accessToken = req && req.accessToken; var accessToken = req && req.accessToken;
const tokenID = accessToken ? accessToken.id : undefined; var tokenID = accessToken ? accessToken.id : undefined;
return tokenID; return tokenID;
}, description: 'Do not supply this argument, it is automatically extracted ' + }, description: 'Do not supply this argument, it is automatically extracted ' +
@ -1167,7 +1146,7 @@ module.exports = function(User) {
}, },
], ],
http: {verb: 'all'}, http: {verb: 'all'},
}, }
); );
UserModel.remoteMethod( UserModel.remoteMethod(
@ -1179,7 +1158,7 @@ module.exports = function(User) {
{arg: 'options', type: 'object', http: 'optionsFromRequest'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'},
], ],
http: {verb: 'post'}, http: {verb: 'post'},
}, }
); );
UserModel.remoteMethod( UserModel.remoteMethod(
@ -1192,7 +1171,7 @@ module.exports = function(User) {
{arg: 'redirect', type: 'string'}, {arg: 'redirect', type: 'string'},
], ],
http: {verb: 'get', path: '/confirm'}, http: {verb: 'get', path: '/confirm'},
}, }
); );
UserModel.remoteMethod( UserModel.remoteMethod(
@ -1203,7 +1182,7 @@ module.exports = function(User) {
{arg: 'options', type: 'object', required: true, http: {source: 'body'}}, {arg: 'options', type: 'object', required: true, http: {source: 'body'}},
], ],
http: {verb: 'post', path: '/reset'}, http: {verb: 'post', path: '/reset'},
}, }
); );
UserModel.remoteMethod( UserModel.remoteMethod(
@ -1217,7 +1196,7 @@ module.exports = function(User) {
{arg: 'options', type: 'object', http: 'optionsFromRequest'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'},
], ],
http: {verb: 'POST', path: '/change-password'}, http: {verb: 'POST', path: '/change-password'},
}, }
); );
const setPasswordScopes = UserModel.settings.restrictResetPasswordTokenScope ? const setPasswordScopes = UserModel.settings.restrictResetPasswordTokenScope ?
@ -1234,7 +1213,7 @@ module.exports = function(User) {
], ],
accessScopes: setPasswordScopes, accessScopes: setPasswordScopes,
http: {verb: 'POST', path: '/reset-password'}, http: {verb: 'POST', path: '/reset-password'},
}, }
); );
function getUserIdFromRequestContext(ctx) { function getUserIdFromRequestContext(ctx) {
@ -1337,7 +1316,7 @@ module.exports = function(User) {
// This is a programmer's error, use the default status code 500 // This is a programmer's error, use the default status code 500
return next(new Error( return next(new Error(
'Invalid use of "options.setPassword". Only "password" can be ' + 'Invalid use of "options.setPassword". Only "password" can be ' +
'changed when using this option.', 'changed when using this option.'
)); ));
} }
@ -1350,7 +1329,7 @@ module.exports = function(User) {
const err = new Error( const err = new Error(
'Changing user password via patch/replace API is not allowed. ' + 'Changing user password via patch/replace API is not allowed. ' +
'Use changePassword() or setPassword() instead.', 'Use changePassword() or setPassword() instead.'
); );
err.statusCode = 401; err.statusCode = 401;
err.code = 'PASSWORD_CHANGE_NOT_ALLOWED'; err.code = 'PASSWORD_CHANGE_NOT_ALLOWED';
@ -1361,8 +1340,8 @@ module.exports = function(User) {
if (ctx.isNewInstance) return next(); if (ctx.isNewInstance) return next();
if (!ctx.where && !ctx.instance) return next(); if (!ctx.where && !ctx.instance) return next();
const pkName = ctx.Model.definition.idName() || 'id'; var pkName = ctx.Model.definition.idName() || 'id';
let where = ctx.where; var where = ctx.where;
if (!where) { if (!where) {
where = {}; where = {};
where[pkName] = ctx.instance[pkName]; where[pkName] = ctx.instance[pkName];
@ -1371,22 +1350,15 @@ module.exports = function(User) {
ctx.Model.find({where: where}, ctx.options, function(err, userInstances) { ctx.Model.find({where: where}, ctx.options, function(err, userInstances) {
if (err) return next(err); if (err) return next(err);
ctx.hookState.originalUserData = userInstances.map(function(u) { ctx.hookState.originalUserData = userInstances.map(function(u) {
const user = {}; var user = {};
user[pkName] = u[pkName]; user[pkName] = u[pkName];
user.email = u.email; user.email = u.email;
user.password = u.password; user.password = u.password;
return user; return user;
}); });
let emailChanged; var emailChanged;
if (ctx.instance) { if (ctx.instance) {
// Check if map does not return an empty array
// Fix server crashes when try to PUT a non existent id
if (ctx.hookState.originalUserData.length > 0) {
emailChanged = ctx.instance.email !== ctx.hookState.originalUserData[0].email; emailChanged = ctx.instance.email !== ctx.hookState.originalUserData[0].email;
} else {
emailChanged = true;
}
if (emailChanged && ctx.Model.settings.emailVerificationRequired) { if (emailChanged && ctx.Model.settings.emailVerificationRequired) {
ctx.instance.emailVerified = false; ctx.instance.emailVerified = false;
} }
@ -1407,15 +1379,13 @@ module.exports = function(User) {
if (!ctx.instance && !ctx.data) return next(); if (!ctx.instance && !ctx.data) return next();
if (!ctx.hookState.originalUserData) return next(); if (!ctx.hookState.originalUserData) return next();
const pkName = ctx.Model.definition.idName() || 'id'; var pkName = ctx.Model.definition.idName() || 'id';
const newEmail = (ctx.instance || ctx.data).email; var newEmail = (ctx.instance || ctx.data).email;
const newPassword = (ctx.instance || ctx.data).password; var newPassword = (ctx.instance || ctx.data).password;
if (!newEmail && !newPassword) return next(); if (!newEmail && !newPassword) return next();
if (ctx.options.preserveAccessTokens) return next(); var userIdsToExpire = ctx.hookState.originalUserData.filter(function(u) {
const userIdsToExpire = ctx.hookState.originalUserData.filter(function(u) {
return (newEmail && u.email !== newEmail) || return (newEmail && u.email !== newEmail) ||
(newPassword && u.password !== newPassword); (newPassword && u.password !== newPassword);
}).map(function(u) { }).map(function(u) {
@ -1426,7 +1396,7 @@ module.exports = function(User) {
}; };
function emailValidator(err, done) { function emailValidator(err, done) {
const value = this.email; var value = this.email;
if (value == null) if (value == null)
return; return;
if (typeof value !== 'string') if (typeof value !== 'string')
@ -1437,9 +1407,9 @@ function emailValidator(err, done) {
} }
function joinUrlPath(args) { function joinUrlPath(args) {
let result = arguments[0]; var result = arguments[0];
for (let ix = 1; ix < arguments.length; ix++) { for (var ix = 1; ix < arguments.length; ix++) {
const next = arguments[ix]; var next = arguments[ix];
result += result[result.length - 1] === '/' && next[0] === '/' ? result += result[result.length - 1] === '/' && next[0] === '/' ?
next.slice(1) : next; next.slice(1) : next;
} }

View File

@ -1,14 +1,14 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const g = require('../../lib/globalize'); var g = require('../../lib/globalize');
const loopback = require('../../'); var loopback = require('../../');
const client = loopback(); var client = loopback();
const CartItem = require('./models').CartItem; var CartItem = require('./models').CartItem;
const remote = loopback.createDataSource({ var remote = loopback.createDataSource({
connector: loopback.Remote, connector: loopback.Remote,
url: 'http://localhost:3000', url: 'http://localhost:3000',
}); });

View File

@ -1,12 +1,12 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const loopback = require('../../'); var loopback = require('../../');
const CartItem = exports.CartItem = loopback.PersistedModel.extend('CartItem', { var CartItem = exports.CartItem = loopback.PersistedModel.extend('CartItem', {
tax: {type: Number, default: 0.1}, tax: {type: Number, default: 0.1},
price: Number, price: Number,
item: String, item: String,
@ -17,7 +17,7 @@ const CartItem = exports.CartItem = loopback.PersistedModel.extend('CartItem', {
CartItem.sum = function(cartId, callback) { CartItem.sum = function(cartId, callback) {
this.find({where: {cartId: cartId}}, function(err, items) { this.find({where: {cartId: cartId}}, function(err, items) {
if (err) return callback(err); if (err) return callback(err);
const total = items var total = items
.map(function(item) { .map(function(item) {
return item.total(); return item.total();
}) })
@ -33,7 +33,8 @@ CartItem.remoteMethod('sum',
{ {
accepts: {arg: 'cartId', type: 'number'}, accepts: {arg: 'cartId', type: 'number'},
returns: {arg: 'total', type: 'number'}, returns: {arg: 'total', type: 'number'},
}); }
);
CartItem.prototype.total = function() { CartItem.prototype.total = function() {
return this.price * this.qty * (1 + this.tax); return this.price * this.qty * (1 + this.tax);

View File

@ -1,13 +1,13 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const loopback = require('../../'); var loopback = require('../../');
const server = module.exports = loopback(); var server = module.exports = loopback();
const CartItem = require('./models').CartItem; var CartItem = require('./models').CartItem;
const memory = loopback.createDataSource({ var memory = loopback.createDataSource({
connector: loopback.Memory, connector: loopback.Memory,
}); });

View File

@ -1,21 +1,21 @@
// Copyright IBM Corp. 2013,2019. All Rights Reserved. // Copyright IBM Corp. 2013,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const g = require('../../lib/globalize'); var g = require('../../lib/globalize');
const loopback = require('../../'); var loopback = require('../../');
const app = loopback(); var app = loopback();
app.use(loopback.rest()); app.use(loopback.rest());
const schema = { var schema = {
name: String, name: String,
}; };
app.dataSource('db', {connector: 'memory'}); app.dataSource('db', {connector: 'memory'});
const Color = app.registry.createModel('color', schema); var Color = app.registry.createModel('color', schema);
app.model(Color, {dataSource: 'db'}); app.model(Color, {dataSource: 'db'});
Color.create({name: 'red'}); Color.create({name: 'red'});

View File

@ -1,23 +1,23 @@
// Copyright IBM Corp. 2013,2019. All Rights Reserved. // Copyright IBM Corp. 2013,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const g = require('../../lib/globalize'); var g = require('../../lib/globalize');
const models = require('../../lib/models'); var models = require('../../lib/models');
const loopback = require('../../'); var loopback = require('../../');
const app = loopback(); var app = loopback();
app.use(loopback.rest()); app.use(loopback.rest());
const dataSource = loopback.createDataSource('db', {connector: loopback.Memory}); var dataSource = loopback.createDataSource('db', {connector: loopback.Memory});
const Application = models.Application(dataSource); var Application = models.Application(dataSource);
app.model(Application); app.model(Application);
const data = { var data = {
pushSettings: [{ pushSettings: [{
'platform': 'apns', 'platform': 'apns',
'apns': { 'apns': {

View File

@ -1,22 +1,22 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const loopback = require('../../'); var loopback = require('../../');
const app = loopback(); var app = loopback();
const db = app.dataSource('db', {connector: 'memory'}); var db = app.dataSource('db', {connector: 'memory'});
const Color = app.registry.createModel('color', {}, {trackChanges: true}); var Color = app.registry.createModel('color', {}, {trackChanges: true});
app.model(Color, {dataSource: 'db'}); app.model(Color, {dataSource: 'db'});
const Color2 = app.registry.createModel('color2', {}, {trackChanges: true}); var Color2 = app.registry.createModel('color2', {}, {trackChanges: true});
app.model(Color2, {dataSource: 'db'}); app.model(Color2, {dataSource: 'db'});
const target = Color2; var target = Color2;
const source = Color; var source = Color;
const SPEED = process.env.SPEED || 100; var SPEED = process.env.SPEED || 100;
let conflicts; var conflicts;
const steps = [ var steps = [
createSomeInitialSourceData, createSomeInitialSourceData,
@ -137,7 +137,7 @@ function list(model, msg) {
function run(steps) { function run(steps) {
setInterval(function() { setInterval(function() {
const step = steps.shift(); var step = steps.shift();
if (step) { if (step) {
console.log(step.name); console.log(step.name);
step(); step();

View File

@ -1,18 +1,18 @@
// Copyright IBM Corp. 2013,2019. All Rights Reserved. // Copyright IBM Corp. 2013,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const g = require('../../lib/globalize'); var g = require('../../lib/globalize');
const loopback = require('../../'); var loopback = require('../../');
const app = loopback(); var app = loopback();
app.use(loopback.rest()); app.use(loopback.rest());
const dataSource = app.dataSource('db', {adapter: 'memory'}); var dataSource = app.dataSource('db', {adapter: 'memory'});
const Color = dataSource.define('color', { var Color = dataSource.define('color', {
'name': String, 'name': String,
}); });

View File

@ -7,7 +7,7 @@
"0e21aad369dd09e1965c11949303cefd": "Les métadonnées remoting pour {0}.{1} {{\"isStatic\"}} ne correspondent pas au style basé sur le nom de la nouvelle méthode.", "0e21aad369dd09e1965c11949303cefd": "Les métadonnées remoting pour {0}.{1} {{\"isStatic\"}} ne correspondent pas au style basé sur le nom de la nouvelle méthode.",
"10e01c895dc0b2fecc385f9f462f1ca6": "une liste de couleurs est disponible sur {{http://localhost:3000/colors}}", "10e01c895dc0b2fecc385f9f462f1ca6": "une liste de couleurs est disponible sur {{http://localhost:3000/colors}}",
"1b2a6076dccbe91a56f1672eb3b8598c": "Le corps de réponse contient les propriétés de {{AccessToken}} créées lors de la connexion.\nEn fonction de la valeur du paramètre `include`, le corps peut contenir des propriétés supplémentaires :\n\n - `user` - `U+007BUserU+007D` - Données de l'utilisateur connecté. {{(`include=user`)}}\n\n", "1b2a6076dccbe91a56f1672eb3b8598c": "Le corps de réponse contient les propriétés de {{AccessToken}} créées lors de la connexion.\nEn fonction de la valeur du paramètre `include`, le corps peut contenir des propriétés supplémentaires :\n\n - `user` - `U+007BUserU+007D` - Données de l'utilisateur connecté. {{(`include=user`)}}\n\n",
"1d7833c3ca2f05fdad8fad7537531c40": "\t OBJET :{0}", "1d7833c3ca2f05fdad8fad7537531c40": "\t SUJET :{0}",
"1e85f822b547a75d7d385048030e4ecb": "Création de : {0}", "1e85f822b547a75d7d385048030e4ecb": "Création de : {0}",
"22fe62fa8d595b72c62208beddaa2a56": "Mettez à jour un élément lié par id pour {0}.", "22fe62fa8d595b72c62208beddaa2a56": "Mettez à jour un élément lié par id pour {0}.",
"275f22ab95671f095640ca99194b7635": "\t DE :{0}", "275f22ab95671f095640ca99194b7635": "\t DE :{0}",

View File

@ -1,12 +1,12 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const assert = require('assert'); var assert = require('assert');
const loopback = require('./loopback'); var loopback = require('./loopback');
const debug = require('debug')('loopback:security:access-context'); var debug = require('debug')('loopback:security:access-context');
const DEFAULT_SCOPES = ['DEFAULT']; const DEFAULT_SCOPES = ['DEFAULT'];
@ -50,7 +50,7 @@ function AccessContext(context) {
'Application registry is mandatory in AccessContext but missing in provided context'); 'Application registry is mandatory in AccessContext but missing in provided context');
this.registry = context.registry; this.registry = context.registry;
this.principals = context.principals || []; this.principals = context.principals || [];
let model = context.model; var model = context.model;
model = ('string' === typeof model) ? this.registry.getModel(model) : model; model = ('string' === typeof model) ? this.registry.getModel(model) : model;
this.model = model; this.model = model;
this.modelName = model && model.modelName; this.modelName = model && model.modelName;
@ -77,9 +77,9 @@ function AccessContext(context) {
'AccessToken model must be defined before AccessContext model'); 'AccessToken model must be defined before AccessContext model');
this.accessToken = context.accessToken || loopback.AccessToken.ANONYMOUS; this.accessToken = context.accessToken || loopback.AccessToken.ANONYMOUS;
const principalType = context.principalType || Principal.USER; var principalType = context.principalType || Principal.USER;
const principalId = context.principalId || undefined; var principalId = context.principalId || undefined;
const principalName = context.principalName || undefined; var principalName = context.principalName || undefined;
if (principalId != null) { if (principalId != null) {
this.addPrincipal(principalType, principalId, principalName); this.addPrincipal(principalType, principalId, principalName);
} }
@ -126,9 +126,9 @@ AccessContext.permissionOrder = {
* @returns {boolean} * @returns {boolean}
*/ */
AccessContext.prototype.addPrincipal = function(principalType, principalId, principalName) { AccessContext.prototype.addPrincipal = function(principalType, principalId, principalName) {
const principal = new Principal(principalType, principalId, principalName); var principal = new Principal(principalType, principalId, principalName);
for (let i = 0; i < this.principals.length; i++) { for (var i = 0; i < this.principals.length; i++) {
const p = this.principals[i]; var p = this.principals[i];
if (p.equals(principal)) { if (p.equals(principal)) {
return false; return false;
} }
@ -142,7 +142,7 @@ AccessContext.prototype.addPrincipal = function(principalType, principalId, prin
* @returns {*} * @returns {*}
*/ */
AccessContext.prototype.getUserId = function() { AccessContext.prototype.getUserId = function() {
const user = this.getUser(); var user = this.getUser();
return user && user.id; return user && user.id;
}; };
@ -151,10 +151,10 @@ AccessContext.prototype.getUserId = function() {
* @returns {*} * @returns {*}
*/ */
AccessContext.prototype.getUser = function() { AccessContext.prototype.getUser = function() {
const BaseUser = this.registry.getModel('User'); var BaseUser = this.registry.getModel('User');
for (let i = 0; i < this.principals.length; i++) { for (var i = 0; i < this.principals.length; i++) {
const p = this.principals[i]; var p = this.principals[i];
const isBuiltinPrincipal = p.type === Principal.APP || var isBuiltinPrincipal = p.type === Principal.APP ||
p.type === Principal.ROLE || p.type === Principal.ROLE ||
p.type == Principal.SCOPE; p.type == Principal.SCOPE;
if (isBuiltinPrincipal) continue; if (isBuiltinPrincipal) continue;
@ -165,7 +165,7 @@ AccessContext.prototype.getUser = function() {
} }
// or permit to resolve a valid user model // or permit to resolve a valid user model
const userModel = this.registry.findModel(p.type); var userModel = this.registry.findModel(p.type);
if (!userModel) continue; if (!userModel) continue;
if (userModel.prototype instanceof BaseUser) { if (userModel.prototype instanceof BaseUser) {
return {id: p.id, principalType: p.type}; return {id: p.id, principalType: p.type};
@ -178,8 +178,8 @@ AccessContext.prototype.getUser = function() {
* @returns {*} * @returns {*}
*/ */
AccessContext.prototype.getAppId = function() { AccessContext.prototype.getAppId = function() {
for (let i = 0; i < this.principals.length; i++) { for (var i = 0; i < this.principals.length; i++) {
const p = this.principals[i]; var p = this.principals[i];
if (p.type === Principal.APPLICATION) { if (p.type === Principal.APPLICATION) {
return p.id; return p.id;
} }
@ -325,7 +325,7 @@ function AccessRequest(model, property, accessType, permission, methodNames, reg
} }
if (arguments.length === 1 && typeof model === 'object') { if (arguments.length === 1 && typeof model === 'object') {
// The argument is an object that contains all required properties // The argument is an object that contains all required properties
const obj = model || {}; var obj = model || {};
this.model = obj.model || AccessContext.ALL; this.model = obj.model || AccessContext.ALL;
this.property = obj.property || AccessContext.ALL; this.property = obj.property || AccessContext.ALL;
this.accessType = obj.accessType || AccessContext.ALL; this.accessType = obj.accessType || AccessContext.ALL;
@ -363,10 +363,10 @@ AccessRequest.prototype.isWildcard = function() {
*/ */
AccessRequest.prototype.exactlyMatches = function(acl) { AccessRequest.prototype.exactlyMatches = function(acl) {
const matchesModel = acl.model === this.model; var matchesModel = acl.model === this.model;
const matchesProperty = acl.property === this.property; var matchesProperty = acl.property === this.property;
const matchesMethodName = this.methodNames.indexOf(acl.property) !== -1; var matchesMethodName = this.methodNames.indexOf(acl.property) !== -1;
const matchesAccessType = acl.accessType === this.accessType; var matchesAccessType = acl.accessType === this.accessType;
if (matchesModel && matchesAccessType) { if (matchesModel && matchesAccessType) {
return matchesProperty || matchesMethodName; return matchesProperty || matchesMethodName;
@ -388,9 +388,9 @@ AccessRequest.prototype.settleDefaultPermission = function(defaultPermission) {
if (this.permission !== 'DEFAULT') if (this.permission !== 'DEFAULT')
return; return;
const modelName = this.model; var modelName = this.model;
if (!defaultPermission) { if (!defaultPermission) {
const modelClass = this.registry.findModel(modelName); var modelClass = this.registry.findModel(modelName);
defaultPermission = modelClass && modelClass.settings.defaultPermission; defaultPermission = modelClass && modelClass.settings.defaultPermission;
} }

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2013,2019. All Rights Reserved. // Copyright IBM Corp. 2013,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -8,17 +8,17 @@
*/ */
'use strict'; 'use strict';
const g = require('./globalize'); var g = require('./globalize');
const DataSource = require('loopback-datasource-juggler').DataSource; var DataSource = require('loopback-datasource-juggler').DataSource;
const Registry = require('./registry'); var Registry = require('./registry');
const assert = require('assert'); var assert = require('assert');
const fs = require('fs'); var fs = require('fs');
const extend = require('util')._extend; var extend = require('util')._extend;
const RemoteObjects = require('strong-remoting'); var RemoteObjects = require('strong-remoting');
const classify = require('underscore.string/classify'); var classify = require('underscore.string/classify');
const camelize = require('underscore.string/camelize'); var camelize = require('underscore.string/camelize');
const path = require('path'); var path = require('path');
const util = require('util'); var util = require('util');
/** /**
* The `App` object represents a Loopback application. * The `App` object represents a Loopback application.
@ -49,7 +49,7 @@ function App() {
* Export the app prototype. * Export the app prototype.
*/ */
const app = module.exports = {}; var app = module.exports = {};
/** /**
* Lazily load a set of [remote objects](http://apidocs.strongloop.com/strong-remoting/#remoteobjectsoptions). * Lazily load a set of [remote objects](http://apidocs.strongloop.com/strong-remoting/#remoteobjectsoptions).
@ -62,7 +62,7 @@ app.remotes = function() {
if (this._remotes) { if (this._remotes) {
return this._remotes; return this._remotes;
} else { } else {
let options = {}; var options = {};
if (this.get) { if (this.get) {
options = this.get('remoting'); options = this.get('remoting');
@ -78,7 +78,7 @@ app.remotes = function() {
app.disuse = function(route) { app.disuse = function(route) {
if (this.stack) { if (this.stack) {
for (let i = 0; i < this.stack.length; i++) { for (var i = 0; i < this.stack.length; i++) {
if (this.stack[i].route === route) { if (this.stack[i].route === route) {
this.stack.splice(i, 1); this.stack.splice(i, 1);
} }
@ -111,11 +111,11 @@ app.disuse = function(route) {
*/ */
app.model = function(Model, config) { app.model = function(Model, config) {
let isPublic = true; var isPublic = true;
const registry = this.registry; var registry = this.registry;
if (typeof Model === 'string') { if (typeof Model === 'string') {
const msg = 'app.model(modelName, settings) is no longer supported. ' + var msg = 'app.model(modelName, settings) is no longer supported. ' +
'Use app.registry.createModel(modelName, definition) and ' + 'Use app.registry.createModel(modelName, definition) and ' +
'app.model(ModelCtor, config) instead.'; 'app.model(ModelCtor, config) instead.';
throw new Error(msg); throw new Error(msg);
@ -130,7 +130,7 @@ app.model = function(Model, config) {
Model.modelName + ' must be a descendant of loopback.Model'); Model.modelName + ' must be a descendant of loopback.Model');
} }
const modelName = Model.modelName; var modelName = Model.modelName;
this.models[modelName] = this.models[modelName] =
this.models[classify(modelName)] = this.models[classify(modelName)] =
this.models[camelize(modelName)] = Model; this.models[camelize(modelName)] = Model;
@ -149,13 +149,11 @@ app.model = function(Model, config) {
this.emit('modelRemoted', Model.sharedClass); this.emit('modelRemoted', Model.sharedClass);
} }
const self = this; var self = this;
Model.on('remoteMethodDisabled', function(model, methodName) { Model.on('remoteMethodDisabled', function(model, methodName) {
clearHandlerCache(self);
self.emit('remoteMethodDisabled', model, methodName); self.emit('remoteMethodDisabled', model, methodName);
}); });
Model.on('remoteMethodAdded', function(model) { Model.on('remoteMethodAdded', function(model) {
clearHandlerCache(self);
self.emit('remoteMethodAdded', model); self.emit('remoteMethodAdded', model);
}); });
@ -266,7 +264,7 @@ app.models = function() {
*/ */
app.dataSource = function(name, config) { app.dataSource = function(name, config) {
try { try {
const ds = dataSourcesFromConfig(name, config, this.connectors, this.registry); var ds = dataSourcesFromConfig(name, config, this.connectors, this.registry);
this.dataSources[name] = this.dataSources[name] =
this.dataSources[classify(name)] = this.dataSources[classify(name)] =
this.dataSources[camelize(name)] = ds; this.dataSources[camelize(name)] = ds;
@ -307,7 +305,7 @@ app.connector = function(name, connector) {
*/ */
app.remoteObjects = function() { app.remoteObjects = function() {
const result = {}; var result = {};
this.remotes().classes().forEach(function(sharedClass) { this.remotes().classes().forEach(function(sharedClass) {
result[sharedClass.name] = sharedClass.ctor; result[sharedClass.name] = sharedClass.ctor;
@ -322,13 +320,13 @@ app.remoteObjects = function() {
*/ */
app.handler = function(type, options) { app.handler = function(type, options) {
const handlers = this._handlers || (this._handlers = {}); var handlers = this._handlers || (this._handlers = {});
if (handlers[type]) { if (handlers[type]) {
return handlers[type]; return handlers[type];
} }
const remotes = this.remotes(); var remotes = this.remotes();
const handler = this._handlers[type] = remotes.handler(type, options); var handler = this._handlers[type] = remotes.handler(type, options);
remotes.classes().forEach(function(sharedClass) { remotes.classes().forEach(function(sharedClass) {
sharedClass.ctor.emit('mounted', app, sharedClass, remotes); sharedClass.ctor.emit('mounted', app, sharedClass, remotes);
@ -348,18 +346,18 @@ app.dataSources = app.datasources = {};
*/ */
app.enableAuth = function(options) { app.enableAuth = function(options) {
const AUTH_MODELS = ['User', 'AccessToken', 'ACL', 'Role', 'RoleMapping']; var AUTH_MODELS = ['User', 'AccessToken', 'ACL', 'Role', 'RoleMapping'];
const remotes = this.remotes(); var remotes = this.remotes();
const app = this; var app = this;
if (options && options.dataSource) { if (options && options.dataSource) {
const appModels = app.registry.modelBuilder.models; var appModels = app.registry.modelBuilder.models;
AUTH_MODELS.forEach(function(m) { AUTH_MODELS.forEach(function(m) {
const Model = app.registry.findModel(m); var Model = app.registry.findModel(m);
if (!Model) { if (!Model) {
throw new Error( throw new Error(
g.f('Authentication requires model %s to be defined.', m), g.f('Authentication requires model %s to be defined.', m)
); );
} }
@ -367,10 +365,10 @@ app.enableAuth = function(options) {
// Find descendants of Model that are attached, // Find descendants of Model that are attached,
// for example "Customer" extending "User" model // for example "Customer" extending "User" model
for (const name in appModels) { for (var name in appModels) {
const candidate = appModels[name]; var candidate = appModels[name];
const isSubclass = candidate.prototype instanceof Model; var isSubclass = candidate.prototype instanceof Model;
const isAttached = !!candidate.dataSource || !!candidate.app; var isAttached = !!candidate.dataSource || !!candidate.app;
if (isSubclass && isAttached) return; if (isSubclass && isAttached) return;
} }
@ -382,22 +380,22 @@ app.enableAuth = function(options) {
} }
remotes.authorization = function(ctx, next) { remotes.authorization = function(ctx, next) {
const method = ctx.method; var method = ctx.method;
const req = ctx.req; var req = ctx.req;
const Model = method.ctor; var Model = method.ctor;
const modelInstance = ctx.instance; var modelInstance = ctx.instance;
const modelId = modelInstance && modelInstance.id || var modelId = modelInstance && modelInstance.id ||
// replacement for deprecated req.param() // replacement for deprecated req.param()
(req.params && req.params.id !== undefined ? req.params.id : (req.params && req.params.id !== undefined ? req.params.id :
req.body && req.body.id !== undefined ? req.body.id : req.body && req.body.id !== undefined ? req.body.id :
req.query && req.query.id !== undefined ? req.query.id : req.query && req.query.id !== undefined ? req.query.id :
undefined); undefined);
const modelName = Model.modelName; var modelName = Model.modelName;
const modelSettings = Model.settings || {}; var modelSettings = Model.settings || {};
let errStatusCode = modelSettings.aclErrorStatus || app.get('aclErrorStatus') || 401; var errStatusCode = modelSettings.aclErrorStatus || app.get('aclErrorStatus') || 401;
if (!req.accessToken) { if (!req.accessToken) {
errStatusCode = 401; errStatusCode = 401;
} }
@ -415,7 +413,7 @@ app.enableAuth = function(options) {
} else if (allowed) { } else if (allowed) {
next(); next();
} else { } else {
const messages = { var messages = {
403: { 403: {
message: g.f('Access Denied'), message: g.f('Access Denied'),
code: 'ACCESS_DENIED', code: 'ACCESS_DENIED',
@ -430,12 +428,12 @@ app.enableAuth = function(options) {
}, },
}; };
const e = new Error(messages[errStatusCode].message || messages[403].message); var e = new Error(messages[errStatusCode].message || messages[403].message);
e.statusCode = errStatusCode; e.statusCode = errStatusCode;
e.code = messages[errStatusCode].code || messages[403].code; e.code = messages[errStatusCode].code || messages[403].code;
next(e); next(e);
} }
}, }
); );
} else { } else {
next(); next();
@ -486,7 +484,7 @@ app._verifyAuthModelRelations = function() {
'custom User subclass, but does not fix AccessToken relations ' + 'custom User subclass, but does not fix AccessToken relations ' +
'to use this new model.\n' + 'to use this new model.\n' +
'Learn more at http://ibm.biz/setup-loopback-auth', 'Learn more at http://ibm.biz/setup-loopback-auth',
Model.modelName, userName, userName, Model.modelName, userName, userName
); );
return; return;
} }
@ -495,7 +493,7 @@ app._verifyAuthModelRelations = function() {
'The model %j does not have "belongsTo User-like model" relation ' + 'The model %j does not have "belongsTo User-like model" relation ' +
'configured.\n' + 'configured.\n' +
'Learn more at http://ibm.biz/setup-loopback-auth', 'Learn more at http://ibm.biz/setup-loopback-auth',
Model.modelName, Model.modelName
); );
} }
@ -509,7 +507,7 @@ app._verifyAuthModelRelations = function() {
'The app configuration follows the multiple user models setup ' + 'The app configuration follows the multiple user models setup ' +
'as described in http://ibm.biz/setup-loopback-auth', 'as described in http://ibm.biz/setup-loopback-auth',
'The built-in role resolver $owner is not currently compatible ' + 'The built-in role resolver $owner is not currently compatible ' +
'with this configuration and should not be used in production.', 'with this configuration and should not be used in production.'
); );
} }
return; return;
@ -526,7 +524,7 @@ app._verifyAuthModelRelations = function() {
'AccessToken subclass, but does not fix User relations to use this ' + 'AccessToken subclass, but does not fix User relations to use this ' +
'new model.\n' + 'new model.\n' +
'Learn more at http://ibm.biz/setup-loopback-auth', 'Learn more at http://ibm.biz/setup-loopback-auth',
Model.modelName, accessTokenName, accessTokenName, Model.modelName, accessTokenName, accessTokenName
); );
return; return;
} }
@ -535,19 +533,19 @@ app._verifyAuthModelRelations = function() {
'The model %j does not have "hasMany AccessToken-like models" relation ' + 'The model %j does not have "hasMany AccessToken-like models" relation ' +
'configured.\n' + 'configured.\n' +
'Learn more at http://ibm.biz/setup-loopback-auth', 'Learn more at http://ibm.biz/setup-loopback-auth',
Model.modelName, Model.modelName
); );
} }
}; };
app.boot = function(options) { app.boot = function(options) {
throw new Error( throw new Error(
g.f('{{`app.boot`}} was removed, use the new module {{loopback-boot}} instead'), g.f('{{`app.boot`}} was removed, use the new module {{loopback-boot}} instead')
); );
}; };
function dataSourcesFromConfig(name, config, connectorRegistry, registry) { function dataSourcesFromConfig(name, config, connectorRegistry, registry) {
let connectorPath; var connectorPath;
assert(typeof config === 'object', assert(typeof config === 'object',
'can not create data source without config object'); 'can not create data source without config object');
@ -574,7 +572,7 @@ function configureModel(ModelCtor, config, app) {
assert(ModelCtor.prototype instanceof ModelCtor.registry.getModel('Model'), assert(ModelCtor.prototype instanceof ModelCtor.registry.getModel('Model'),
ModelCtor.modelName + ' must be a descendant of loopback.Model'); ModelCtor.modelName + ' must be a descendant of loopback.Model');
let dataSource = config.dataSource; var dataSource = config.dataSource;
if (dataSource) { if (dataSource) {
if (typeof dataSource === 'string') { if (typeof dataSource === 'string') {
@ -584,7 +582,7 @@ function configureModel(ModelCtor, config, app) {
assert( assert(
dataSource instanceof DataSource, dataSource instanceof DataSource,
ModelCtor.modelName + ' is referencing a dataSource that does not exist: "' + ModelCtor.modelName + ' is referencing a dataSource that does not exist: "' +
config.dataSource + '"', config.dataSource + '"'
); );
} }
@ -627,15 +625,15 @@ function clearHandlerCache(app) {
* as the request handler. * as the request handler.
*/ */
app.listen = function(cb) { app.listen = function(cb) {
const self = this; var self = this;
const server = require('http').createServer(this); var server = require('http').createServer(this);
server.on('listening', function() { server.on('listening', function() {
self.set('port', this.address().port); self.set('port', this.address().port);
let listeningOnAll = false; var listeningOnAll = false;
let host = self.get('host'); var host = self.get('host');
if (!host) { if (!host) {
listeningOnAll = true; listeningOnAll = true;
host = this.address().address; host = this.address().address;
@ -650,17 +648,17 @@ app.listen = function(cb) {
// that can be copied and pasted into the browser. // that can be copied and pasted into the browser.
host = 'localhost'; host = 'localhost';
} }
const url = 'http://' + host + ':' + self.get('port') + '/'; var url = 'http://' + host + ':' + self.get('port') + '/';
self.set('url', url); self.set('url', url);
} }
}); });
const useAppConfig = var useAppConfig =
arguments.length === 0 || arguments.length === 0 ||
(arguments.length == 1 && typeof arguments[0] == 'function'); (arguments.length == 1 && typeof arguments[0] == 'function');
if (useAppConfig) { if (useAppConfig) {
let port = this.get('port'); var port = this.get('port');
// NOTE(bajtos) port:undefined no longer works on node@6, // NOTE(bajtos) port:undefined no longer works on node@6,
// we must pass port:0 explicitly // we must pass port:0 explicitly
if (port === undefined) port = 0; if (port === undefined) port = 0;

View File

@ -1,11 +1,11 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const EventEmitter = require('events').EventEmitter; var EventEmitter = require('events').EventEmitter;
const util = require('util'); var util = require('util');
module.exports = browserExpress; module.exports = browserExpress;

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -12,57 +12,57 @@ module.exports = function(registry) {
registry.KeyValueModel = createModel( registry.KeyValueModel = createModel(
require('../common/models/key-value-model.json'), require('../common/models/key-value-model.json'),
require('../common/models/key-value-model.js'), require('../common/models/key-value-model.js')
); );
registry.Email = createModel( registry.Email = createModel(
require('../common/models/email.json'), require('../common/models/email.json'),
require('../common/models/email.js'), require('../common/models/email.js')
); );
registry.Application = createModel( registry.Application = createModel(
require('../common/models/application.json'), require('../common/models/application.json'),
require('../common/models/application.js'), require('../common/models/application.js')
); );
registry.AccessToken = createModel( registry.AccessToken = createModel(
require('../common/models/access-token.json'), require('../common/models/access-token.json'),
require('../common/models/access-token.js'), require('../common/models/access-token.js')
); );
registry.User = createModel( registry.User = createModel(
require('../common/models/user.json'), require('../common/models/user.json'),
require('../common/models/user.js'), require('../common/models/user.js')
); );
registry.RoleMapping = createModel( registry.RoleMapping = createModel(
require('../common/models/role-mapping.json'), require('../common/models/role-mapping.json'),
require('../common/models/role-mapping.js'), require('../common/models/role-mapping.js')
); );
registry.Role = createModel( registry.Role = createModel(
require('../common/models/role.json'), require('../common/models/role.json'),
require('../common/models/role.js'), require('../common/models/role.js')
); );
registry.ACL = createModel( registry.ACL = createModel(
require('../common/models/acl.json'), require('../common/models/acl.json'),
require('../common/models/acl.js'), require('../common/models/acl.js')
); );
registry.Scope = createModel( registry.Scope = createModel(
require('../common/models/scope.json'), require('../common/models/scope.json'),
require('../common/models/scope.js'), require('../common/models/scope.js')
); );
registry.Change = createModel( registry.Change = createModel(
require('../common/models/change.json'), require('../common/models/change.json'),
require('../common/models/change.js'), require('../common/models/change.js')
); );
registry.Checkpoint = createModel( registry.Checkpoint = createModel(
require('../common/models/checkpoint.json'), require('../common/models/checkpoint.json'),
require('../common/models/checkpoint.js'), require('../common/models/checkpoint.js')
); );
function createModel(definitionJson, customizeFn) { function createModel(definitionJson, customizeFn) {
@ -74,7 +74,7 @@ module.exports = function(registry) {
// object instance it loaded during the first call. // object instance it loaded during the first call.
definitionJson = cloneDeepJson(definitionJson); definitionJson = cloneDeepJson(definitionJson);
const Model = registry.createModel(definitionJson); var Model = registry.createModel(definitionJson);
customizeFn(Model); customizeFn(Model);
return Model; return Model;
} }

View File

@ -1,19 +1,19 @@
// Copyright IBM Corp. 2017,2019. All Rights Reserved. // Copyright IBM Corp. 2017,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const util = require('util'); var util = require('util');
const extend = require('util')._extend; var extend = require('util')._extend;
const g = require('./globalize'); var g = require('./globalize');
module.exports = function(modelCtor, remotingConfig, modelConfig) { module.exports = function(modelCtor, remotingConfig, modelConfig) {
const settings = {}; var settings = {};
// apply config.json settings // apply config.json settings
const configHasSharedMethodsSettings = remotingConfig && var configHasSharedMethodsSettings = remotingConfig &&
remotingConfig.sharedMethods && remotingConfig.sharedMethods &&
typeof remotingConfig.sharedMethods === 'object'; typeof remotingConfig.sharedMethods === 'object';
if (configHasSharedMethodsSettings) if (configHasSharedMethodsSettings)
@ -21,7 +21,7 @@ module.exports = function(modelCtor, remotingConfig, modelConfig) {
// apply model-config.json settings // apply model-config.json settings
const options = modelConfig.options; const options = modelConfig.options;
const modelConfigHasSharedMethodsSettings = options && var modelConfigHasSharedMethodsSettings = options &&
options.remoting && options.remoting &&
options.remoting.sharedMethods && options.remoting.sharedMethods &&
typeof options.remoting.sharedMethods === 'object'; typeof options.remoting.sharedMethods === 'object';
@ -30,30 +30,30 @@ module.exports = function(modelCtor, remotingConfig, modelConfig) {
// validate setting values // validate setting values
Object.keys(settings).forEach(function(setting) { Object.keys(settings).forEach(function(setting) {
const settingValue = settings[setting]; var settingValue = settings[setting];
const settingValueType = typeof settingValue; var settingValueType = typeof settingValue;
if (settingValueType !== 'boolean') if (settingValueType !== 'boolean')
throw new TypeError(g.f('Expected boolean, got %s', settingValueType)); throw new TypeError(g.f('Expected boolean, got %s', settingValueType));
}); });
// set sharedMethod.shared using the merged settings // set sharedMethod.shared using the merged settings
const sharedMethods = modelCtor.sharedClass.methods({includeDisabled: true}); var sharedMethods = modelCtor.sharedClass.methods({includeDisabled: true});
// re-map glob style values to regular expressions // re-map glob style values to regular expressions
const tests = Object var tests = Object
.keys(settings) .keys(settings)
.filter(function(setting) { .filter(function(setting) {
return settings.hasOwnProperty(setting) && setting.indexOf('*') >= 0; return settings.hasOwnProperty(setting) && setting.indexOf('*') >= 0;
}) })
.map(function(setting) { .map(function(setting) {
// Turn * into an testable regexp string // Turn * into an testable regexp string
const glob = escapeRegExp(setting).replace(/\*/g, '(.)*'); var glob = escapeRegExp(setting).replace(/\*/g, '(.)*');
return {regex: new RegExp(glob), setting: settings[setting]}; return {regex: new RegExp(glob), setting: settings[setting]};
}) || []; }) || [];
sharedMethods.forEach(function(sharedMethod) { sharedMethods.forEach(function(sharedMethod) {
// use the specific setting if it exists // use the specific setting if it exists
const methodName = sharedMethod.isStatic ? sharedMethod.name : 'prototype.' + sharedMethod.name; var methodName = sharedMethod.isStatic ? sharedMethod.name : 'prototype.' + sharedMethod.name;
const hasSpecificSetting = settings.hasOwnProperty(methodName); var hasSpecificSetting = settings.hasOwnProperty(methodName);
if (hasSpecificSetting) { if (hasSpecificSetting) {
if (settings[methodName] === false) { if (settings[methodName] === false) {
sharedMethod.sharedClass.disableMethodByName(methodName); sharedMethod.sharedClass.disableMethodByName(methodName);

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2013,2019. All Rights Reserved. // Copyright IBM Corp. 2013,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -14,11 +14,11 @@ module.exports = Connector;
* Module dependencies. * Module dependencies.
*/ */
const EventEmitter = require('events').EventEmitter; var EventEmitter = require('events').EventEmitter;
const debug = require('debug')('connector'); var debug = require('debug')('connector');
const util = require('util'); var util = require('util');
const inherits = util.inherits; var inherits = util.inherits;
const assert = require('assert'); var assert = require('assert');
/** /**
* Create a new `Connector` with the given `options`. * Create a new `Connector` with the given `options`.
@ -45,7 +45,7 @@ inherits(Connector, EventEmitter);
*/ */
Connector._createJDBAdapter = function(jdbModule) { Connector._createJDBAdapter = function(jdbModule) {
const fauxSchema = {}; var fauxSchema = {};
jdbModule.initialize(fauxSchema, function() { jdbModule.initialize(fauxSchema, function() {
// connected // connected
}); });

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2013,2019. All Rights Reserved. // Copyright IBM Corp. 2013,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -8,11 +8,11 @@
*/ */
'use strict'; 'use strict';
const g = require('../globalize'); var g = require('../globalize');
const mailer = require('nodemailer'); var mailer = require('nodemailer');
const assert = require('assert'); var assert = require('assert');
const debug = require('debug')('loopback:connector:mail'); var debug = require('debug')('loopback:connector:mail');
const loopback = require('../loopback'); var loopback = require('../loopback');
/** /**
* Export the MailConnector class. * Export the MailConnector class.
@ -27,7 +27,7 @@ module.exports = MailConnector;
function MailConnector(settings) { function MailConnector(settings) {
assert(typeof settings === 'object', 'cannot initialize MailConnector without a settings object'); assert(typeof settings === 'object', 'cannot initialize MailConnector without a settings object');
let transports = settings.transports; var transports = settings.transports;
// if transports is not in settings object AND settings.transport exists // if transports is not in settings object AND settings.transport exists
if (!transports && settings.transport) { if (!transports && settings.transport) {
@ -74,17 +74,17 @@ MailConnector.prototype.DataAccessObject = Mailer;
*/ */
MailConnector.prototype.setupTransport = function(setting) { MailConnector.prototype.setupTransport = function(setting) {
const connector = this; var connector = this;
connector.transports = connector.transports || []; connector.transports = connector.transports || [];
connector.transportsIndex = connector.transportsIndex || {}; connector.transportsIndex = connector.transportsIndex || {};
let transport; var transport;
const transportType = (setting.type || 'STUB').toLowerCase(); var transportType = (setting.type || 'STUB').toLowerCase();
if (transportType === 'smtp') { if (transportType === 'smtp') {
transport = mailer.createTransport(setting); transport = mailer.createTransport(setting);
} else { } else {
const transportModuleName = 'nodemailer-' + transportType + '-transport'; var transportModuleName = 'nodemailer-' + transportType + '-transport';
const transportModule = require(transportModuleName); var transportModule = require(transportModuleName);
transport = mailer.createTransport(transportModule(setting)); transport = mailer.createTransport(transportModule(setting));
} }
@ -138,12 +138,12 @@ MailConnector.prototype.defaultTransport = function() {
*/ */
Mailer.send = function(options, fn) { Mailer.send = function(options, fn) {
const dataSource = this.dataSource; var dataSource = this.dataSource;
const settings = dataSource && dataSource.settings; var settings = dataSource && dataSource.settings;
const connector = dataSource.connector; var connector = dataSource.connector;
assert(connector, 'Cannot send mail without a connector!'); assert(connector, 'Cannot send mail without a connector!');
let transport = connector.transportForName(options.transport); var transport = connector.transportForName(options.transport);
if (!transport) { if (!transport) {
transport = connector.defaultTransport(); transport = connector.defaultTransport();

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2013,2019. All Rights Reserved. // Copyright IBM Corp. 2013,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -14,12 +14,12 @@ module.exports = Memory;
* Module dependencies. * Module dependencies.
*/ */
const Connector = require('./base-connector'); var Connector = require('./base-connector');
const debug = require('debug')('memory'); var debug = require('debug')('memory');
const util = require('util'); var util = require('util');
const inherits = util.inherits; var inherits = util.inherits;
const assert = require('assert'); var assert = require('assert');
const JdbMemory = require('loopback-datasource-juggler/lib/connectors/memory'); var JdbMemory = require('loopback-datasource-juggler/lib/connectors/memory');
/** /**
* Create a new `Memory` connector with the given `options`. * Create a new `Memory` connector with the given `options`.

View File

@ -1,12 +1,12 @@
// Copyright IBM Corp. 2016,2019. All Rights Reserved. // Copyright IBM Corp. 2016,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const g = require('./globalize'); var g = require('./globalize');
const juggler = require('loopback-datasource-juggler'); var juggler = require('loopback-datasource-juggler');
const remoting = require('strong-remoting'); var remoting = require('strong-remoting');
module.exports = function(loopback) { module.exports = function(loopback) {
juggler.getCurrentContext = juggler.getCurrentContext =
@ -15,7 +15,7 @@ module.exports = function(loopback) {
throw new Error(g.f( throw new Error(g.f(
'%s was removed in version 3.0. See %s for more details.', '%s was removed in version 3.0. See %s for more details.',
'loopback.getCurrentContext()', 'loopback.getCurrentContext()',
'http://loopback.io/doc/en/lb2/Using-current-context.html', 'http://loopback.io/doc/en/lb2/Using-current-context.html'
)); ));
}; };
@ -23,7 +23,7 @@ module.exports = function(loopback) {
throw new Error(g.f( throw new Error(g.f(
'%s was removed in version 3.0. See %s for more details.', '%s was removed in version 3.0. See %s for more details.',
'loopback.runInContext()', 'loopback.runInContext()',
'http://loopback.io/doc/en/lb2/Using-current-context.html', 'http://loopback.io/doc/en/lb2/Using-current-context.html'
)); ));
}; };
@ -31,7 +31,7 @@ module.exports = function(loopback) {
throw new Error(g.f( throw new Error(g.f(
'%s was removed in version 3.0. See %s for more details.', '%s was removed in version 3.0. See %s for more details.',
'loopback.createContext()', 'loopback.createContext()',
'http://loopback.io/doc/en/lb2/Using-current-context.html', 'http://loopback.io/doc/en/lb2/Using-current-context.html'
)); ));
}; };
}; };

View File

@ -1,11 +1,11 @@
// Copyright IBM Corp. 2016,2019. All Rights Reserved. // Copyright IBM Corp. 2016,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const path = require('path'); var path = require('path');
const SG = require('strong-globalize'); var SG = require('strong-globalize');
SG.SetRootDir(path.join(__dirname, '..'), {autonomousMsgLoading: 'all'}); SG.SetRootDir(path.join(__dirname, '..'), {autonomousMsgLoading: 'all'});
module.exports = SG(); module.exports = SG();

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2013,2019. All Rights Reserved. // Copyright IBM Corp. 2013,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -8,17 +8,17 @@
*/ */
'use strict'; 'use strict';
const express = require('express'); var express = require('express');
const loopbackExpress = require('./server-app'); var loopbackExpress = require('./server-app');
const proto = require('./application'); var proto = require('./application');
const fs = require('fs'); var fs = require('fs');
const ejs = require('ejs'); var ejs = require('ejs');
const path = require('path'); var path = require('path');
const merge = require('util')._extend; var merge = require('util')._extend;
const assert = require('assert'); var assert = require('assert');
const Registry = require('./registry'); var Registry = require('./registry');
const juggler = require('loopback-datasource-juggler'); var juggler = require('loopback-datasource-juggler');
const configureSharedMethods = require('./configure-shared-methods'); var configureSharedMethods = require('./configure-shared-methods');
/** /**
* LoopBack core module. It provides static properties and * LoopBack core module. It provides static properties and
@ -40,7 +40,7 @@ const configureSharedMethods = require('./configure-shared-methods');
* @header loopback * @header loopback
*/ */
const loopback = module.exports = createApplication; var loopback = module.exports = createApplication;
/*! /*!
* Framework version. * Framework version.
@ -73,7 +73,7 @@ Object.defineProperties(loopback, {
*/ */
function createApplication(options) { function createApplication(options) {
const app = loopbackExpress(); var app = loopbackExpress();
merge(app, proto); merge(app, proto);
@ -107,7 +107,7 @@ function createApplication(options) {
if (loopback.localRegistry || options && options.localRegistry === true) { if (loopback.localRegistry || options && options.localRegistry === true) {
// setup the app registry // setup the app registry
const registry = app.registry = new Registry(); var registry = app.registry = new Registry();
if (options && options.loadBuiltinModels === true) { if (options && options.loadBuiltinModels === true) {
require('./builtin-models')(registry); require('./builtin-models')(registry);
} }
@ -119,8 +119,8 @@ function createApplication(options) {
} }
function mixin(source) { function mixin(source) {
for (const key in source) { for (var key in source) {
const desc = Object.getOwnPropertyDescriptor(source, key); var desc = Object.getOwnPropertyDescriptor(source, key);
// Fix for legacy (pre-ES5) browsers like PhantomJS // Fix for legacy (pre-ES5) browsers like PhantomJS
if (!desc) continue; if (!desc) continue;
@ -204,8 +204,8 @@ loopback.remoteMethod = function(fn, options) {
*/ */
loopback.template = function(file) { loopback.template = function(file) {
const templates = this._templates || (this._templates = {}); var templates = this._templates || (this._templates = {});
const str = templates[file] || (templates[file] = fs.readFileSync(file, 'utf8')); var str = templates[file] || (templates[file] = fs.readFileSync(file, 'utf8'));
return ejs.compile(str, { return ejs.compile(str, {
filename: file, filename: file,
}); });

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -7,14 +7,14 @@
* Module Dependencies. * Module Dependencies.
*/ */
'use strict'; 'use strict';
const g = require('./globalize'); var g = require('./globalize');
const assert = require('assert'); var assert = require('assert');
const debug = require('debug')('loopback:model'); var debug = require('debug')('loopback:model');
const RemoteObjects = require('strong-remoting'); var RemoteObjects = require('strong-remoting');
const SharedClass = require('strong-remoting').SharedClass; var SharedClass = require('strong-remoting').SharedClass;
const extend = require('util')._extend; var extend = require('util')._extend;
const deprecated = require('depd')('loopback'); var deprecated = require('depd')('loopback');
module.exports = function(registry) { module.exports = function(registry) {
/** /**
@ -104,7 +104,7 @@ module.exports = function(registry) {
* @property {Array.<Object>} settings.acls Array of ACLs for the model. * @property {Array.<Object>} settings.acls Array of ACLs for the model.
* @class * @class
*/ */
const Model = registry.modelBuilder.define('Model'); var Model = registry.modelBuilder.define('Model');
Model.registry = registry; Model.registry = registry;
@ -115,23 +115,23 @@ module.exports = function(registry) {
*/ */
Model.setup = function() { Model.setup = function() {
const ModelCtor = this; var ModelCtor = this;
const Parent = this.super_; var Parent = this.super_;
if (!ModelCtor.registry && Parent && Parent.registry) { if (!ModelCtor.registry && Parent && Parent.registry) {
ModelCtor.registry = Parent.registry; ModelCtor.registry = Parent.registry;
} }
const options = this.settings; var options = this.settings;
const typeName = this.modelName; var typeName = this.modelName;
// support remoting prototype methods // support remoting prototype methods
// it's important to setup this function *before* calling `new SharedClass` // it's important to setup this function *before* calling `new SharedClass`
// otherwise remoting metadata from our base model is picked up // otherwise remoting metadata from our base model is picked up
ModelCtor.sharedCtor = function(data, id, options, fn) { ModelCtor.sharedCtor = function(data, id, options, fn) {
const ModelCtor = this; var ModelCtor = this;
const isRemoteInvocationWithOptions = typeof data !== 'object' && var isRemoteInvocationWithOptions = typeof data !== 'object' &&
typeof id === 'object' && typeof id === 'object' &&
typeof options === 'function'; typeof options === 'function';
if (isRemoteInvocationWithOptions) { if (isRemoteInvocationWithOptions) {
@ -161,13 +161,13 @@ module.exports = function(registry) {
} }
if (id != null && data) { if (id != null && data) {
const model = new ModelCtor(data); var model = new ModelCtor(data);
model.id = id; model.id = id;
fn(null, model); fn(null, model);
} else if (data) { } else if (data) {
fn(null, new ModelCtor(data)); fn(null, new ModelCtor(data));
} else if (id != null) { } else if (id != null) {
const filter = {}; var filter = {};
ModelCtor.findById(id, filter, options, function(err, model) { ModelCtor.findById(id, filter, options, function(err, model) {
if (err) { if (err) {
fn(err); fn(err);
@ -185,7 +185,7 @@ module.exports = function(registry) {
} }
}; };
const idDesc = ModelCtor.modelName + ' id'; var idDesc = ModelCtor.modelName + ' id';
ModelCtor.sharedCtor.accepts = [ ModelCtor.sharedCtor.accepts = [
{arg: 'id', type: 'any', required: true, http: {source: 'path'}, {arg: 'id', type: 'any', required: true, http: {source: 'path'},
description: idDesc}, description: idDesc},
@ -199,21 +199,21 @@ module.exports = function(registry) {
ModelCtor.sharedCtor.returns = {root: true}; ModelCtor.sharedCtor.returns = {root: true};
const remotingOptions = {}; var remotingOptions = {};
extend(remotingOptions, options.remoting || {}); extend(remotingOptions, options.remoting || {});
// create a sharedClass // create a sharedClass
const sharedClass = ModelCtor.sharedClass = new SharedClass( var sharedClass = ModelCtor.sharedClass = new SharedClass(
ModelCtor.modelName, ModelCtor.modelName,
ModelCtor, ModelCtor,
remotingOptions, remotingOptions
); );
// before remote hook // before remote hook
ModelCtor.beforeRemote = function(name, fn) { ModelCtor.beforeRemote = function(name, fn) {
const className = this.modelName; var className = this.modelName;
this._runWhenAttachedToApp(function(app) { this._runWhenAttachedToApp(function(app) {
const remotes = app.remotes(); var remotes = app.remotes();
remotes.before(className + '.' + name, function(ctx, next) { remotes.before(className + '.' + name, function(ctx, next) {
return fn(ctx, ctx.result, next); return fn(ctx, ctx.result, next);
}); });
@ -222,9 +222,9 @@ module.exports = function(registry) {
// after remote hook // after remote hook
ModelCtor.afterRemote = function(name, fn) { ModelCtor.afterRemote = function(name, fn) {
const className = this.modelName; var className = this.modelName;
this._runWhenAttachedToApp(function(app) { this._runWhenAttachedToApp(function(app) {
const remotes = app.remotes(); var remotes = app.remotes();
remotes.after(className + '.' + name, function(ctx, next) { remotes.after(className + '.' + name, function(ctx, next) {
return fn(ctx, ctx.result, next); return fn(ctx, ctx.result, next);
}); });
@ -232,16 +232,16 @@ module.exports = function(registry) {
}; };
ModelCtor.afterRemoteError = function(name, fn) { ModelCtor.afterRemoteError = function(name, fn) {
const className = this.modelName; var className = this.modelName;
this._runWhenAttachedToApp(function(app) { this._runWhenAttachedToApp(function(app) {
const remotes = app.remotes(); var remotes = app.remotes();
remotes.afterError(className + '.' + name, fn); remotes.afterError(className + '.' + name, fn);
}); });
}; };
ModelCtor._runWhenAttachedToApp = function(fn) { ModelCtor._runWhenAttachedToApp = function(fn) {
if (this.app) return fn(this.app); if (this.app) return fn(this.app);
const self = this; var self = this;
self.once('attached', function() { self.once('attached', function() {
fn(self.app); fn(self.app);
}); });
@ -250,19 +250,19 @@ module.exports = function(registry) {
if ('injectOptionsFromRemoteContext' in options) { if ('injectOptionsFromRemoteContext' in options) {
console.warn(g.f( console.warn(g.f(
'%s is using model setting %s which is no longer available.', '%s is using model setting %s which is no longer available.',
typeName, 'injectOptionsFromRemoteContext', typeName, 'injectOptionsFromRemoteContext'
)); ));
console.warn(g.f( console.warn(g.f(
'Please rework your app to use the offical solution for injecting ' + 'Please rework your app to use the offical solution for injecting ' +
'"options" argument from request context,\nsee %s', '"options" argument from request context,\nsee %s',
'http://loopback.io/doc/en/lb3/Using-current-context.html', 'http://loopback.io/doc/en/lb3/Using-current-context.html'
)); ));
} }
// resolve relation functions // resolve relation functions
sharedClass.resolve(function resolver(define) { sharedClass.resolve(function resolver(define) {
const relations = ModelCtor.relations || {}; var relations = ModelCtor.relations || {};
const defineRaw = define; var defineRaw = define;
define = function(name, options, fn) { define = function(name, options, fn) {
if (options.accepts) { if (options.accepts) {
options = extend({}, options); options = extend({}, options);
@ -272,8 +272,8 @@ module.exports = function(registry) {
}; };
// get the relations // get the relations
for (const relationName in relations) { for (var relationName in relations) {
const relation = relations[relationName]; var relation = relations[relationName];
if (relation.type === 'belongsTo') { if (relation.type === 'belongsTo') {
ModelCtor.belongsToRemoting(relationName, relation, define); ModelCtor.belongsToRemoting(relationName, relation, define);
} else if ( } else if (
@ -295,8 +295,8 @@ module.exports = function(registry) {
} }
// handle scopes // handle scopes
const scopes = ModelCtor.scopes || {}; var scopes = ModelCtor.scopes || {};
for (const scopeName in scopes) { for (var scopeName in scopes) {
ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define); ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
} }
}); });
@ -307,9 +307,9 @@ module.exports = function(registry) {
/*! /*!
* Get the reference to ACL in a lazy fashion to avoid race condition in require * Get the reference to ACL in a lazy fashion to avoid race condition in require
*/ */
let _aclModel = null; var _aclModel = null;
Model._ACL = function getACL(ACL) { Model._ACL = function getACL(ACL) {
const registry = this.registry; var registry = this.registry;
if (ACL !== undefined) { if (ACL !== undefined) {
// The function is used as a setter // The function is used as a setter
_aclModel = ACL; _aclModel = ACL;
@ -317,7 +317,7 @@ module.exports = function(registry) {
if (_aclModel) { if (_aclModel) {
return _aclModel; return _aclModel;
} }
const aclModel = registry.getModel('ACL'); var aclModel = registry.getModel('ACL');
_aclModel = registry.getModelByType(aclModel); _aclModel = registry.getModelByType(aclModel);
return _aclModel; return _aclModel;
}; };
@ -335,9 +335,9 @@ module.exports = function(registry) {
*/ */
Model.checkAccess = function(token, modelId, sharedMethod, ctx, callback) { Model.checkAccess = function(token, modelId, sharedMethod, ctx, callback) {
const ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS; var ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS;
token = token || ANONYMOUS; token = token || ANONYMOUS;
const aclModel = Model._ACL(); var aclModel = Model._ACL();
ctx = ctx || {}; ctx = ctx || {};
if (typeof ctx === 'function' && callback === undefined) { if (typeof ctx === 'function' && callback === undefined) {
@ -373,10 +373,10 @@ module.exports = function(registry) {
} }
assert( assert(
typeof method === 'object', typeof method === 'object',
'method is a required argument and must be a RemoteMethod object', 'method is a required argument and must be a RemoteMethod object'
); );
const ACL = Model._ACL(); var ACL = Model._ACL();
// Check the explicit setting of accessType // Check the explicit setting of accessType
if (method.accessType) { if (method.accessType) {
@ -390,7 +390,7 @@ module.exports = function(registry) {
} }
// Default GET requests to READ // Default GET requests to READ
let verb = method.http && method.http.verb; var verb = method.http && method.http.verb;
if (typeof verb === 'string') { if (typeof verb === 'string') {
verb = verb.toUpperCase(); verb = verb.toUpperCase();
} }
@ -438,7 +438,7 @@ module.exports = function(registry) {
*/ */
Model.getApp = function(callback) { Model.getApp = function(callback) {
const self = this; var self = this;
self._runWhenAttachedToApp(function(app) { self._runWhenAttachedToApp(function(app) {
assert(self.app); assert(self.app);
assert.equal(app, self.app); assert.equal(app, self.app);
@ -463,21 +463,21 @@ module.exports = function(registry) {
Model.remoteMethod = function(name, options) { Model.remoteMethod = function(name, options) {
if (options.isStatic === undefined) { if (options.isStatic === undefined) {
const m = name.match(/^prototype\.(.*)$/); var m = name.match(/^prototype\.(.*)$/);
options.isStatic = !m; options.isStatic = !m;
name = options.isStatic ? name : m[1]; name = options.isStatic ? name : m[1];
} }
if (options.accepts) { if (options.accepts) {
options = extend({}, options); options = extend({}, options);
options.accepts = setupOptionsArgs(options.accepts, this); options.accepts = setupOptionsArgs(options.accepts);
} }
this.sharedClass.defineMethod(name, options); this.sharedClass.defineMethod(name, options);
this.emit('remoteMethodAdded', this.sharedClass); this.emit('remoteMethodAdded', this.sharedClass);
}; };
function setupOptionsArgs(accepts, modelClass) { function setupOptionsArgs(accepts) {
if (!Array.isArray(accepts)) if (!Array.isArray(accepts))
accepts = [accepts]; accepts = [accepts];
@ -485,33 +485,21 @@ module.exports = function(registry) {
if (arg.http && arg.http === 'optionsFromRequest') { if (arg.http && arg.http === 'optionsFromRequest') {
// clone to preserve the input value // clone to preserve the input value
arg = extend({}, arg); arg = extend({}, arg);
arg.http = createOptionsViaModelMethod.bind(modelClass); arg.http = createOptionsViaModelMethod;
} }
return arg; return arg;
}); });
} }
function createOptionsViaModelMethod(ctx) { function createOptionsViaModelMethod(ctx) {
const ModelCtor = (ctx.method && ctx.method.ctor) || this; var EMPTY_OPTIONS = {};
var ModelCtor = ctx.method && ctx.method.ctor;
/** if (!ModelCtor)
* Configure default values for juggler settings to protect user-supplied return EMPTY_OPTIONS;
* input from attacking juggler
*/
const DEFAULT_OPTIONS = {
// Default to `true` so that hidden properties cannot be used in query
prohibitHiddenPropertiesInQuery: ModelCtor._getProhibitHiddenPropertiesInQuery({}, true),
// Default to `12` for the max depth of a query object
maxDepthOfQuery: ModelCtor._getMaxDepthOfQuery({}, 12),
// Default to `32` for the max depth of a data object
maxDepthOfData: ModelCtor._getMaxDepthOfData({}, 32),
};
if (typeof ModelCtor.createOptionsFromRemotingContext !== 'function') if (typeof ModelCtor.createOptionsFromRemotingContext !== 'function')
return DEFAULT_OPTIONS; return EMPTY_OPTIONS;
debug('createOptionsFromRemotingContext for %s', ctx.method.stringName); debug('createOptionsFromRemotingContext for %s', ctx.method.stringName);
return Object.assign(DEFAULT_OPTIONS, return ModelCtor.createOptionsFromRemotingContext(ctx);
ModelCtor.createOptionsFromRemotingContext(ctx));
} }
/** /**
@ -526,7 +514,7 @@ module.exports = function(registry) {
Model.disableRemoteMethod = function(name, isStatic) { Model.disableRemoteMethod = function(name, isStatic) {
deprecated('Model.disableRemoteMethod is deprecated. ' + deprecated('Model.disableRemoteMethod is deprecated. ' +
'Use Model.disableRemoteMethodByName instead.'); 'Use Model.disableRemoteMethodByName instead.');
const key = this.sharedClass.getKeyFromMethodNameAndTarget(name, isStatic); var key = this.sharedClass.getKeyFromMethodNameAndTarget(name, isStatic);
this.sharedClass.disableMethodByName(key); this.sharedClass.disableMethodByName(key);
this.emit('remoteMethodDisabled', this.sharedClass, key); this.emit('remoteMethodDisabled', this.sharedClass, key);
}; };
@ -544,10 +532,10 @@ module.exports = function(registry) {
}; };
Model.belongsToRemoting = function(relationName, relation, define) { Model.belongsToRemoting = function(relationName, relation, define) {
let modelName = relation.modelTo && relation.modelTo.modelName; var modelName = relation.modelTo && relation.modelTo.modelName;
modelName = modelName || 'PersistedModel'; modelName = modelName || 'PersistedModel';
const fn = this.prototype[relationName]; var fn = this.prototype[relationName];
const pathName = (relation.options.http && relation.options.http.path) || relationName; var pathName = (relation.options.http && relation.options.http.path) || relationName;
define('__get__' + relationName, { define('__get__' + relationName, {
isStatic: false, isStatic: false,
http: {verb: 'get', path: '/' + pathName}, http: {verb: 'get', path: '/' + pathName},
@ -564,19 +552,17 @@ module.exports = function(registry) {
function convertNullToNotFoundError(toModelName, ctx, cb) { function convertNullToNotFoundError(toModelName, ctx, cb) {
if (ctx.result !== null) return cb(); if (ctx.result !== null) return cb();
const fk = ctx.getArgByName('fk'); var fk = ctx.getArgByName('fk');
const msg = fk ? var msg = g.f('Unknown "%s" id "%s".', toModelName, fk);
g.f('Unknown "%s" id "%s".', toModelName, fk) : var error = new Error(msg);
g.f('No "%s" instance(s) found', toModelName);
const error = new Error(msg);
error.statusCode = error.status = 404; error.statusCode = error.status = 404;
error.code = 'MODEL_NOT_FOUND'; error.code = 'MODEL_NOT_FOUND';
cb(error); cb(error);
} }
Model.hasOneRemoting = function(relationName, relation, define) { Model.hasOneRemoting = function(relationName, relation, define) {
const pathName = (relation.options.http && relation.options.http.path) || relationName; var pathName = (relation.options.http && relation.options.http.path) || relationName;
const toModelName = relation.modelTo.modelName; var toModelName = relation.modelTo.modelName;
define('__get__' + relationName, { define('__get__' + relationName, {
isStatic: false, isStatic: false,
@ -633,10 +619,10 @@ module.exports = function(registry) {
}; };
Model.hasManyRemoting = function(relationName, relation, define) { Model.hasManyRemoting = function(relationName, relation, define) {
const pathName = (relation.options.http && relation.options.http.path) || relationName; var pathName = (relation.options.http && relation.options.http.path) || relationName;
const toModelName = relation.modelTo.modelName; var toModelName = relation.modelTo.modelName;
const findByIdFunc = this.prototype['__findById__' + relationName]; var findByIdFunc = this.prototype['__findById__' + relationName];
define('__findById__' + relationName, { define('__findById__' + relationName, {
isStatic: false, isStatic: false,
http: {verb: 'get', path: '/' + pathName + '/:fk'}, http: {verb: 'get', path: '/' + pathName + '/:fk'},
@ -655,7 +641,7 @@ module.exports = function(registry) {
rest: {after: convertNullToNotFoundError.bind(null, toModelName)}, rest: {after: convertNullToNotFoundError.bind(null, toModelName)},
}, findByIdFunc); }, findByIdFunc);
const destroyByIdFunc = this.prototype['__destroyById__' + relationName]; var destroyByIdFunc = this.prototype['__destroyById__' + relationName];
define('__destroyById__' + relationName, { define('__destroyById__' + relationName, {
isStatic: false, isStatic: false,
http: {verb: 'delete', path: '/' + pathName + '/:fk'}, http: {verb: 'delete', path: '/' + pathName + '/:fk'},
@ -673,7 +659,7 @@ module.exports = function(registry) {
returns: [], returns: [],
}, destroyByIdFunc); }, destroyByIdFunc);
const updateByIdFunc = this.prototype['__updateById__' + relationName]; var updateByIdFunc = this.prototype['__updateById__' + relationName];
define('__updateById__' + relationName, { define('__updateById__' + relationName, {
isStatic: false, isStatic: false,
http: {verb: 'put', path: '/' + pathName + '/:fk'}, http: {verb: 'put', path: '/' + pathName + '/:fk'},
@ -691,9 +677,9 @@ module.exports = function(registry) {
}, updateByIdFunc); }, updateByIdFunc);
if (relation.modelThrough || relation.type === 'referencesMany') { if (relation.modelThrough || relation.type === 'referencesMany') {
const modelThrough = relation.modelThrough || relation.modelTo; var modelThrough = relation.modelThrough || relation.modelTo;
const accepts = []; var accepts = [];
if (relation.type === 'hasMany' && relation.modelThrough) { if (relation.type === 'hasMany' && relation.modelThrough) {
// Restrict: only hasManyThrough relation can have additional properties // Restrict: only hasManyThrough relation can have additional properties
accepts.push({ accepts.push({
@ -702,7 +688,7 @@ module.exports = function(registry) {
}); });
} }
const addFunc = this.prototype['__link__' + relationName]; var addFunc = this.prototype['__link__' + relationName];
define('__link__' + relationName, { define('__link__' + relationName, {
isStatic: false, isStatic: false,
http: {verb: 'put', path: '/' + pathName + '/rel/:fk'}, http: {verb: 'put', path: '/' + pathName + '/rel/:fk'},
@ -718,7 +704,7 @@ module.exports = function(registry) {
returns: {arg: relationName, type: modelThrough.modelName, root: true}, returns: {arg: relationName, type: modelThrough.modelName, root: true},
}, addFunc); }, addFunc);
const removeFunc = this.prototype['__unlink__' + relationName]; var removeFunc = this.prototype['__unlink__' + relationName];
define('__unlink__' + relationName, { define('__unlink__' + relationName, {
isStatic: false, isStatic: false,
http: {verb: 'delete', path: '/' + pathName + '/rel/:fk'}, http: {verb: 'delete', path: '/' + pathName + '/rel/:fk'},
@ -738,7 +724,7 @@ module.exports = function(registry) {
// FIXME: [rfeng] How to map a function with callback(err, true|false) to HEAD? // FIXME: [rfeng] How to map a function with callback(err, true|false) to HEAD?
// true --> 200 and false --> 404? // true --> 200 and false --> 404?
const existsFunc = this.prototype['__exists__' + relationName]; var existsFunc = this.prototype['__exists__' + relationName];
define('__exists__' + relationName, { define('__exists__' + relationName, {
isStatic: false, isStatic: false,
http: {verb: 'head', path: '/' + pathName + '/rel/:fk'}, http: {verb: 'head', path: '/' + pathName + '/rel/:fk'},
@ -758,10 +744,10 @@ module.exports = function(registry) {
// After hook to map exists to 200/404 for HEAD // After hook to map exists to 200/404 for HEAD
after: function(ctx, cb) { after: function(ctx, cb) {
if (ctx.result === false) { if (ctx.result === false) {
const modelName = ctx.method.sharedClass.name; var modelName = ctx.method.sharedClass.name;
const id = ctx.getArgByName('id'); var id = ctx.getArgByName('id');
const msg = g.f('Unknown "%s" {{id}} "%s".', modelName, id); var msg = g.f('Unknown "%s" {{id}} "%s".', modelName, id);
const error = new Error(msg); var error = new Error(msg);
error.statusCode = error.status = 404; error.statusCode = error.status = 404;
error.code = 'MODEL_NOT_FOUND'; error.code = 'MODEL_NOT_FOUND';
cb(error); cb(error);
@ -775,17 +761,17 @@ module.exports = function(registry) {
}; };
Model.scopeRemoting = function(scopeName, scope, define) { Model.scopeRemoting = function(scopeName, scope, define) {
const pathName = var pathName =
(scope.options && scope.options.http && scope.options.http.path) || scopeName; (scope.options && scope.options.http && scope.options.http.path) || scopeName;
let modelTo = scope.modelTo; var modelTo = scope.modelTo;
const isStatic = scope.isStatic; var isStatic = scope.isStatic;
let toModelName = scope.modelTo.modelName; var toModelName = scope.modelTo.modelName;
// https://github.com/strongloop/loopback/issues/811 // https://github.com/strongloop/loopback/issues/811
// Check if the scope is for a hasMany relation // Check if the scope is for a hasMany relation
const relation = this.relations[scopeName]; var relation = this.relations[scopeName];
if (relation && relation.modelTo) { if (relation && relation.modelTo) {
// For a relation with through model, the toModelName should be the one // For a relation with through model, the toModelName should be the one
// from the target model // from the target model
@ -886,22 +872,22 @@ module.exports = function(registry) {
} }
options = options || {}; options = options || {};
const regExp = /^__([^_]+)__([^_]+)$/; var regExp = /^__([^_]+)__([^_]+)$/;
const relation = this.relations[relationName]; var relation = this.relations[relationName];
if (relation && relation._nestRemotingProcessed) { if (relation && relation._nestRemotingProcessed) {
return; // Prevent unwanted circular traversals! return; // Prevent unwanted circular traversals!
} else if (relation && relation.modelTo && relation.modelTo.sharedClass) { } else if (relation && relation.modelTo && relation.modelTo.sharedClass) {
relation._nestRemotingProcessed = true; relation._nestRemotingProcessed = true;
const self = this; var self = this;
const sharedClass = this.sharedClass; var sharedClass = this.sharedClass;
const sharedToClass = relation.modelTo.sharedClass; var sharedToClass = relation.modelTo.sharedClass;
const toModelName = relation.modelTo.modelName; var toModelName = relation.modelTo.modelName;
const pathName = options.pathName || relation.options.path || relationName; var pathName = options.pathName || relation.options.path || relationName;
const paramName = options.paramName || 'nk'; var paramName = options.paramName || 'nk';
const http = [].concat(sharedToClass.http || [])[0]; var http = [].concat(sharedToClass.http || [])[0];
let httpPath, acceptArgs; var httpPath, acceptArgs;
if (relation.multiple) { if (relation.multiple) {
httpPath = pathName + '/:' + paramName; httpPath = pathName + '/:' + paramName;
@ -923,30 +909,30 @@ module.exports = function(registry) {
// A method should return the method name to use, if it is to be // A method should return the method name to use, if it is to be
// included as a nested method - a falsy return value will skip. // included as a nested method - a falsy return value will skip.
const filter = filterCallback || options.filterMethod || function(method, relation) { var filter = filterCallback || options.filterMethod || function(method, relation) {
const matches = method.name.match(regExp); var matches = method.name.match(regExp);
if (matches) { if (matches) {
return '__' + matches[1] + '__' + relation.name + '__' + matches[2]; return '__' + matches[1] + '__' + relation.name + '__' + matches[2];
} }
}; };
sharedToClass.methods().forEach(function(method) { sharedToClass.methods().forEach(function(method) {
let methodName; var methodName;
if (!method.isStatic && (methodName = filter(method, relation))) { if (!method.isStatic && (methodName = filter(method, relation))) {
const prefix = relation.multiple ? '__findById__' : '__get__'; var prefix = relation.multiple ? '__findById__' : '__get__';
const getterName = options.getterName || (prefix + relationName); var getterName = options.getterName || (prefix + relationName);
const getterFn = relation.modelFrom.prototype[getterName]; var getterFn = relation.modelFrom.prototype[getterName];
if (typeof getterFn !== 'function') { if (typeof getterFn !== 'function') {
throw new Error(g.f('Invalid remote method: `%s`', getterName)); throw new Error(g.f('Invalid remote method: `%s`', getterName));
} }
const nestedFn = relation.modelTo.prototype[method.name]; var nestedFn = relation.modelTo.prototype[method.name];
if (typeof nestedFn !== 'function') { if (typeof nestedFn !== 'function') {
throw new Error(g.f('Invalid remote method: `%s`', method.name)); throw new Error(g.f('Invalid remote method: `%s`', method.name));
} }
const opts = {}; var opts = {};
opts.accepts = acceptArgs.concat(method.accepts || []); opts.accepts = acceptArgs.concat(method.accepts || []);
opts.returns = [].concat(method.returns || []); opts.returns = [].concat(method.returns || []);
@ -956,10 +942,10 @@ module.exports = function(registry) {
opts.rest.delegateTo = method; opts.rest.delegateTo = method;
opts.http = []; opts.http = [];
const routes = [].concat(method.http || []); var routes = [].concat(method.http || []);
routes.forEach(function(route) { routes.forEach(function(route) {
if (route.path) { if (route.path) {
const copy = extend({}, route); var copy = extend({}, route);
copy.path = httpPath + route.path; copy.path = httpPath + route.path;
opts.http.push(copy); opts.http.push(copy);
} }
@ -1017,19 +1003,19 @@ module.exports = function(registry) {
if (options.hooks === false) return; // don't inherit before/after hooks if (options.hooks === false) return; // don't inherit before/after hooks
self.once('mounted', function(app, sc, remotes) { self.once('mounted', function(app, sc, remotes) {
const listenerTree = extend({}, remotes.listenerTree || {}); var listenerTree = extend({}, remotes.listenerTree || {});
listenerTree.before = listenerTree.before || {}; listenerTree.before = listenerTree.before || {};
listenerTree.after = listenerTree.after || {}; listenerTree.after = listenerTree.after || {};
const beforeListeners = listenerTree.before[toModelName] || {}; var beforeListeners = listenerTree.before[toModelName] || {};
const afterListeners = listenerTree.after[toModelName] || {}; var afterListeners = listenerTree.after[toModelName] || {};
sharedClass.methods().forEach(function(method) { sharedClass.methods().forEach(function(method) {
const delegateTo = method.rest && method.rest.delegateTo; var delegateTo = method.rest && method.rest.delegateTo;
if (delegateTo && delegateTo.ctor == relation.modelTo) { if (delegateTo && delegateTo.ctor == relation.modelTo) {
const before = method.isStatic ? beforeListeners : beforeListeners['prototype']; var before = method.isStatic ? beforeListeners : beforeListeners['prototype'];
const after = method.isStatic ? afterListeners : afterListeners['prototype']; var after = method.isStatic ? afterListeners : afterListeners['prototype'];
const m = method.isStatic ? method.name : 'prototype.' + method.name; var m = method.isStatic ? method.name : 'prototype.' + method.name;
if (before && before[delegateTo.name]) { if (before && before[delegateTo.name]) {
self.beforeRemote(m, function(ctx, result, next) { self.beforeRemote(m, function(ctx, result, next) {
before[delegateTo.name]._listeners.call(null, ctx, next); before[delegateTo.name]._listeners.call(null, ctx, next);
@ -1044,7 +1030,7 @@ module.exports = function(registry) {
}); });
}); });
} else { } else {
const msg = g.f('Relation `%s` does not exist for model `%s`', relationName, this.modelName); var msg = g.f('Relation `%s` does not exist for model `%s`', relationName, this.modelName);
throw new Error(msg); throw new Error(msg);
} }
}; };

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -7,20 +7,20 @@
* Module Dependencies. * Module Dependencies.
*/ */
'use strict'; 'use strict';
const g = require('./globalize'); var g = require('./globalize');
const runtime = require('./runtime'); var runtime = require('./runtime');
const assert = require('assert'); var assert = require('assert');
const async = require('async'); var async = require('async');
const deprecated = require('depd')('loopback'); var deprecated = require('depd')('loopback');
const debug = require('debug')('loopback:persisted-model'); var debug = require('debug')('loopback:persisted-model');
const PassThrough = require('stream').PassThrough; var PassThrough = require('stream').PassThrough;
const utils = require('./utils'); var utils = require('./utils');
const filterNodes = require('loopback-filters'); var filterNodes = require('loopback-filters');
const REPLICATION_CHUNK_SIZE = -1; var REPLICATION_CHUNK_SIZE = -1;
module.exports = function(registry) { module.exports = function(registry) {
const Model = registry.getModel('Model'); var Model = registry.getModel('Model');
/** /**
* Extends Model with basic query and CRUD support. * Extends Model with basic query and CRUD support.
@ -38,7 +38,7 @@ module.exports = function(registry) {
* @class PersistedModel * @class PersistedModel
*/ */
const PersistedModel = Model.extend('PersistedModel'); var PersistedModel = Model.extend('PersistedModel');
/*! /*!
* Setup the `PersistedModel` constructor. * Setup the `PersistedModel` constructor.
@ -48,7 +48,7 @@ module.exports = function(registry) {
// call Model.setup first // call Model.setup first
Model.setup.call(this); Model.setup.call(this);
const PersistedModel = this; var PersistedModel = this;
// enable change tracking (usually for replication) // enable change tracking (usually for replication)
if (this.settings.trackChanges) { if (this.settings.trackChanges) {
@ -72,7 +72,7 @@ module.exports = function(registry) {
g.f('Cannot call %s.%s().' + g.f('Cannot call %s.%s().' +
' The %s method has not been setup.' + ' The %s method has not been setup.' +
' The {{PersistedModel}} has not been correctly attached to a {{DataSource}}!', ' The {{PersistedModel}} has not been correctly attached to a {{DataSource}}!',
modelName, methodName, methodName), modelName, methodName, methodName)
); );
} }
@ -85,10 +85,10 @@ module.exports = function(registry) {
function convertNullToNotFoundError(ctx, cb) { function convertNullToNotFoundError(ctx, cb) {
if (ctx.result !== null) return cb(); if (ctx.result !== null) return cb();
const modelName = ctx.method.sharedClass.name; var modelName = ctx.method.sharedClass.name;
const id = ctx.getArgByName('id'); var id = ctx.getArgByName('id');
const msg = g.f('Unknown "%s" {{id}} "%s".', modelName, id); var msg = g.f('Unknown "%s" {{id}} "%s".', modelName, id);
const error = new Error(msg); var error = new Error(msg);
error.statusCode = error.status = 404; error.statusCode = error.status = 404;
error.code = 'MODEL_NOT_FOUND'; error.code = 'MODEL_NOT_FOUND';
cb(error); cb(error);
@ -403,7 +403,7 @@ module.exports = function(registry) {
*/ */
PersistedModel.prototype.save = function(options, callback) { PersistedModel.prototype.save = function(options, callback) {
const Model = this.constructor; var Model = this.constructor;
if (typeof options == 'function') { if (typeof options == 'function') {
callback = options; callback = options;
@ -421,9 +421,9 @@ module.exports = function(registry) {
options.throws = false; options.throws = false;
} }
const inst = this; var inst = this;
const data = inst.toObject(true); var data = inst.toObject(true);
const id = this.getId(); var id = this.getId();
if (!id) { if (!id) {
return Model.create(this, callback); return Model.create(this, callback);
@ -438,7 +438,7 @@ module.exports = function(registry) {
if (valid) { if (valid) {
save(); save();
} else { } else {
const err = new Model.ValidationError(inst); var err = new Model.ValidationError(inst);
// throws option is dangerous for async usage // throws option is dangerous for async usage
if (options.throws) { if (options.throws) {
throw err; throw err;
@ -581,7 +581,7 @@ module.exports = function(registry) {
*/ */
PersistedModel.prototype.setId = function(val) { PersistedModel.prototype.setId = function(val) {
const ds = this.getDataSource(); var ds = this.getDataSource();
this[this.getIdName()] = val; this[this.getIdName()] = val;
}; };
@ -592,7 +592,7 @@ module.exports = function(registry) {
*/ */
PersistedModel.prototype.getId = function() { PersistedModel.prototype.getId = function() {
const data = this.toObject(); var data = this.toObject();
if (!data) return; if (!data) return;
return data[this.getIdName()]; return data[this.getIdName()];
}; };
@ -614,8 +614,8 @@ module.exports = function(registry) {
*/ */
PersistedModel.getIdName = function() { PersistedModel.getIdName = function() {
const Model = this; var Model = this;
const ds = Model.getDataSource(); var ds = Model.getDataSource();
if (ds.idName) { if (ds.idName) {
return ds.idName(Model.modelName); return ds.idName(Model.modelName);
@ -625,9 +625,9 @@ module.exports = function(registry) {
}; };
PersistedModel.setupRemoting = function() { PersistedModel.setupRemoting = function() {
const PersistedModel = this; var PersistedModel = this;
const typeName = PersistedModel.modelName; var typeName = PersistedModel.modelName;
const options = PersistedModel.settings; var options = PersistedModel.settings;
// if there is atleast one updateOnly property, then we set // if there is atleast one updateOnly property, then we set
// createOnlyInstance flag in __create__ to indicate loopback-swagger // createOnlyInstance flag in __create__ to indicate loopback-swagger
@ -640,7 +640,7 @@ module.exports = function(registry) {
options.replaceOnPUT = options.replaceOnPUT !== false; options.replaceOnPUT = options.replaceOnPUT !== false;
function setRemoting(scope, name, options) { function setRemoting(scope, name, options) {
const fn = scope[name]; var fn = scope[name];
fn._delegate = true; fn._delegate = true;
options.isStatic = scope === PersistedModel; options.isStatic = scope === PersistedModel;
PersistedModel.remoteMethod(name, options); PersistedModel.remoteMethod(name, options);
@ -662,7 +662,7 @@ module.exports = function(registry) {
http: {verb: 'post', path: '/'}, http: {verb: 'post', path: '/'},
}); });
const upsertOptions = { var upsertOptions = {
aliases: ['upsert', 'updateOrCreate'], aliases: ['upsert', 'updateOrCreate'],
description: 'Patch an existing model instance or insert a new one ' + description: 'Patch an existing model instance or insert a new one ' +
'into the data source.', 'into the data source.',
@ -683,7 +683,7 @@ module.exports = function(registry) {
} }
setRemoting(PersistedModel, 'patchOrCreate', upsertOptions); setRemoting(PersistedModel, 'patchOrCreate', upsertOptions);
const replaceOrCreateOptions = { var replaceOrCreateOptions = {
description: 'Replace an existing model instance or insert a new one into the data source.', description: 'Replace an existing model instance or insert a new one into the data source.',
accessType: 'WRITE', accessType: 'WRITE',
accepts: [ accepts: [
@ -724,8 +724,7 @@ module.exports = function(registry) {
description: 'Check whether a model instance exists in the data source.', description: 'Check whether a model instance exists in the data source.',
accessType: 'READ', accessType: 'READ',
accepts: [ accepts: [
{arg: 'id', type: 'any', description: 'Model id', required: true, {arg: 'id', type: 'any', description: 'Model id', required: true},
http: {source: 'path'}},
{arg: 'options', type: 'object', http: 'optionsFromRequest'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'},
], ],
returns: {arg: 'exists', type: 'boolean'}, returns: {arg: 'exists', type: 'boolean'},
@ -741,10 +740,10 @@ module.exports = function(registry) {
return cb(); return cb();
} }
if (!ctx.result.exists) { if (!ctx.result.exists) {
const modelName = ctx.method.sharedClass.name; var modelName = ctx.method.sharedClass.name;
const id = ctx.getArgByName('id'); var id = ctx.getArgByName('id');
const msg = 'Unknown "' + modelName + '" id "' + id + '".'; var msg = 'Unknown "' + modelName + '" id "' + id + '".';
const error = new Error(msg); var error = new Error(msg);
error.statusCode = error.status = 404; error.statusCode = error.status = 404;
error.code = 'MODEL_NOT_FOUND'; error.code = 'MODEL_NOT_FOUND';
cb(error); cb(error);
@ -772,7 +771,7 @@ module.exports = function(registry) {
rest: {after: convertNullToNotFoundError}, rest: {after: convertNullToNotFoundError},
}); });
const replaceByIdOptions = { var replaceByIdOptions = {
description: 'Replace attributes for a model instance and persist it into the data source.', description: 'Replace attributes for a model instance and persist it into the data source.',
accessType: 'WRITE', accessType: 'WRITE',
accepts: [ accepts: [
@ -798,9 +797,7 @@ module.exports = function(registry) {
accepts: [ accepts: [
{arg: 'filter', type: 'object', description: {arg: 'filter', type: 'object', description:
'Filter defining fields, where, include, order, offset, and limit - must be a ' + 'Filter defining fields, where, include, order, offset, and limit - must be a ' +
'JSON-encoded string (`{"where":{"something":"value"}}`). ' + 'JSON-encoded string ({"something":"value"})'},
'See https://loopback.io/doc/en/lb3/Querying-data.html#using-stringified-json-in-rest-queries ' +
'for more details.'},
{arg: 'options', type: 'object', http: 'optionsFromRequest'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'},
], ],
returns: {arg: 'data', type: [typeName], root: true}, returns: {arg: 'data', type: [typeName], root: true},
@ -813,9 +810,7 @@ module.exports = function(registry) {
accepts: [ accepts: [
{arg: 'filter', type: 'object', description: {arg: 'filter', type: 'object', description:
'Filter defining fields, where, include, order, offset, and limit - must be a ' + 'Filter defining fields, where, include, order, offset, and limit - must be a ' +
'JSON-encoded string (`{"where":{"something":"value"}}`). ' + 'JSON-encoded string ({"something":"value"})'},
'See https://loopback.io/doc/en/lb3/Querying-data.html#using-stringified-json-in-rest-queries ' +
'for more details.'},
{arg: 'options', type: 'object', http: 'optionsFromRequest'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'},
], ],
returns: {arg: 'data', type: typeName, root: true}, returns: {arg: 'data', type: typeName, root: true},
@ -889,7 +884,7 @@ module.exports = function(registry) {
http: {verb: 'get', path: '/count'}, http: {verb: 'get', path: '/count'},
}); });
const updateAttributesOptions = { var updateAttributesOptions = {
aliases: ['updateAttributes'], aliases: ['updateAttributes'],
description: 'Patch attributes for a model instance and persist it into ' + description: 'Patch attributes for a model instance and persist it into ' +
'the data source.', 'the data source.',
@ -1038,7 +1033,7 @@ module.exports = function(registry) {
*/ */
PersistedModel.diff = function(since, remoteChanges, callback) { PersistedModel.diff = function(since, remoteChanges, callback) {
const Change = this.getChangeModel(); var Change = this.getChangeModel();
Change.diff(this.modelName, since, remoteChanges, callback); Change.diff(this.modelName, since, remoteChanges, callback);
}; };
@ -1064,9 +1059,9 @@ module.exports = function(registry) {
filter = {}; filter = {};
} }
const idName = this.dataSource.idName(this.modelName); var idName = this.dataSource.idName(this.modelName);
const Change = this.getChangeModel(); var Change = this.getChangeModel();
const model = this; var model = this;
const changeFilter = this.createChangeFilter(since, filter); const changeFilter = this.createChangeFilter(since, filter);
filter = filter || {}; filter = filter || {};
@ -1078,13 +1073,13 @@ module.exports = function(registry) {
Change.find(changeFilter, function(err, changes) { Change.find(changeFilter, function(err, changes) {
if (err) return callback(err); if (err) return callback(err);
if (!Array.isArray(changes) || changes.length === 0) return callback(null, []); if (!Array.isArray(changes) || changes.length === 0) return callback(null, []);
const ids = changes.map(function(change) { var ids = changes.map(function(change) {
return change.getModelId(); return change.getModelId();
}); });
filter.where[idName] = {inq: ids}; filter.where[idName] = {inq: ids};
model.find(filter, function(err, models) { model.find(filter, function(err, models) {
if (err) return callback(err); if (err) return callback(err);
const modelIds = models.map(function(m) { var modelIds = models.map(function(m) {
return m[idName].toString(); return m[idName].toString();
}); });
callback(null, changes.filter(function(ch) { callback(null, changes.filter(function(ch) {
@ -1102,7 +1097,7 @@ module.exports = function(registry) {
*/ */
PersistedModel.checkpoint = function(cb) { PersistedModel.checkpoint = function(cb) {
const Checkpoint = this.getChangeModel().getCheckpointModel(); var Checkpoint = this.getChangeModel().getCheckpointModel();
Checkpoint.bumpLastSeq(cb); Checkpoint.bumpLastSeq(cb);
}; };
@ -1115,7 +1110,7 @@ module.exports = function(registry) {
*/ */
PersistedModel.currentCheckpoint = function(cb) { PersistedModel.currentCheckpoint = function(cb) {
const Checkpoint = this.getChangeModel().getCheckpointModel(); var Checkpoint = this.getChangeModel().getCheckpointModel();
Checkpoint.current(cb); Checkpoint.current(cb);
}; };
@ -1136,7 +1131,7 @@ module.exports = function(registry) {
*/ */
PersistedModel.replicate = function(since, targetModel, options, callback) { PersistedModel.replicate = function(since, targetModel, options, callback) {
const lastArg = arguments[arguments.length - 1]; var lastArg = arguments[arguments.length - 1];
if (typeof lastArg === 'function' && arguments.length > 1) { if (typeof lastArg === 'function' && arguments.length > 1) {
callback = lastArg; callback = lastArg;
@ -1157,7 +1152,7 @@ module.exports = function(registry) {
options = options || {}; options = options || {};
const sourceModel = this; var sourceModel = this;
callback = callback || utils.createPromiseCallback(); callback = callback || utils.createPromiseCallback();
debug('replicating %s since %s to %s since %s', debug('replicating %s since %s to %s since %s',
@ -1181,7 +1176,7 @@ module.exports = function(registry) {
// until no changes were replicated, but at most MAX_ATTEMPTS times // until no changes were replicated, but at most MAX_ATTEMPTS times
// to prevent starvation. In most cases, the second run will find no changes // to prevent starvation. In most cases, the second run will find no changes
// to replicate and we are done. // to replicate and we are done.
const MAX_ATTEMPTS = 3; var MAX_ATTEMPTS = 3;
run(1, since); run(1, since);
return callback.promise; return callback.promise;
@ -1191,7 +1186,7 @@ module.exports = function(registry) {
tryReplicate(sourceModel, targetModel, since, options, next); tryReplicate(sourceModel, targetModel, since, options, next);
function next(err, conflicts, cps, updates) { function next(err, conflicts, cps, updates) {
const finished = err || conflicts.length || var finished = err || conflicts.length ||
!updates || updates.length === 0 || !updates || updates.length === 0 ||
attempt >= MAX_ATTEMPTS; attempt >= MAX_ATTEMPTS;
@ -1203,10 +1198,10 @@ module.exports = function(registry) {
}; };
function tryReplicate(sourceModel, targetModel, since, options, callback) { function tryReplicate(sourceModel, targetModel, since, options, callback) {
const Change = sourceModel.getChangeModel(); var Change = sourceModel.getChangeModel();
const TargetChange = targetModel.getChangeModel(); var TargetChange = targetModel.getChangeModel();
const changeTrackingEnabled = Change && TargetChange; var changeTrackingEnabled = Change && TargetChange;
let replicationChunkSize = REPLICATION_CHUNK_SIZE; var replicationChunkSize = REPLICATION_CHUNK_SIZE;
if (sourceModel.settings && sourceModel.settings.replicationChunkSize) { if (sourceModel.settings && sourceModel.settings.replicationChunkSize) {
replicationChunkSize = sourceModel.settings.replicationChunkSize; replicationChunkSize = sourceModel.settings.replicationChunkSize;
@ -1214,12 +1209,12 @@ module.exports = function(registry) {
assert( assert(
changeTrackingEnabled, changeTrackingEnabled,
'You must enable change tracking before replicating', 'You must enable change tracking before replicating'
); );
let diff, updates, newSourceCp, newTargetCp; var diff, updates, newSourceCp, newTargetCp;
const tasks = [ var tasks = [
checkpoints, checkpoints,
getSourceChanges, getSourceChanges,
getDiffFromTarget, getDiffFromTarget,
@ -1236,7 +1231,7 @@ module.exports = function(registry) {
function(filter, pagingCallback) { function(filter, pagingCallback) {
sourceModel.changes(since.source, filter, pagingCallback); sourceModel.changes(since.source, filter, pagingCallback);
}, },
debug.enabled ? log : cb, debug.enabled ? log : cb
); );
function log(err, result) { function log(err, result) {
@ -1254,7 +1249,7 @@ module.exports = function(registry) {
function(smallArray, chunkCallback) { function(smallArray, chunkCallback) {
return targetModel.diff(since.target, smallArray, chunkCallback); return targetModel.diff(since.target, smallArray, chunkCallback);
}, },
debug.enabled ? log : cb, debug.enabled ? log : cb
); );
function log(err, result) { function log(err, result) {
@ -1283,7 +1278,7 @@ module.exports = function(registry) {
function(smallArray, chunkCallback) { function(smallArray, chunkCallback) {
return sourceModel.createUpdates(smallArray, chunkCallback); return sourceModel.createUpdates(smallArray, chunkCallback);
}, },
cb, cb
); );
} else { } else {
// nothing to replicate // nothing to replicate
@ -1304,7 +1299,7 @@ module.exports = function(registry) {
}); });
}, },
function(notUsed, err) { function(notUsed, err) {
const conflicts = err && err.details && err.details.conflicts; var conflicts = err && err.details && err.details.conflicts;
if (conflicts && err.statusCode == 409) { if (conflicts && err.statusCode == 409) {
diff.conflicts = conflicts; diff.conflicts = conflicts;
// filter out updates that were not applied // filter out updates that were not applied
@ -1316,12 +1311,12 @@ module.exports = function(registry) {
return cb(); return cb();
} }
cb(err); cb(err);
}, }
); );
} }
function checkpoints() { function checkpoints() {
const cb = arguments[arguments.length - 1]; var cb = arguments[arguments.length - 1];
sourceModel.checkpoint(function(err, source) { sourceModel.checkpoint(function(err, source) {
if (err) return cb(err); if (err) return cb(err);
newSourceCp = source.seq; newSourceCp = source.seq;
@ -1345,9 +1340,9 @@ module.exports = function(registry) {
debug('\t\tnew checkpoints: { source: %j, target: %j }', debug('\t\tnew checkpoints: { source: %j, target: %j }',
newSourceCp, newTargetCp); newSourceCp, newTargetCp);
const conflicts = diff.conflicts.map(function(change) { var conflicts = diff.conflicts.map(function(change) {
return new Change.Conflict( return new Change.Conflict(
change.modelId, sourceModel, targetModel, change.modelId, sourceModel, targetModel
); );
}); });
@ -1356,7 +1351,7 @@ module.exports = function(registry) {
} }
if (callback) { if (callback) {
const newCheckpoints = {source: newSourceCp, target: newTargetCp}; var newCheckpoints = {source: newSourceCp, target: newTargetCp};
callback(null, conflicts, newCheckpoints, updates); callback(null, conflicts, newCheckpoints, updates);
} }
} }
@ -1371,15 +1366,15 @@ module.exports = function(registry) {
*/ */
PersistedModel.createUpdates = function(deltas, cb) { PersistedModel.createUpdates = function(deltas, cb) {
const Change = this.getChangeModel(); var Change = this.getChangeModel();
const updates = []; var updates = [];
const Model = this; var Model = this;
const tasks = []; var tasks = [];
deltas.forEach(function(change) { deltas.forEach(function(change) {
change = new Change(change); change = new Change(change);
const type = change.type(); var type = change.type();
const update = {type: type, change: change}; var update = {type: type, change: change};
switch (type) { switch (type) {
case Change.CREATE: case Change.CREATE:
case Change.UPDATE: case Change.UPDATE:
@ -1423,12 +1418,12 @@ module.exports = function(registry) {
*/ */
PersistedModel.bulkUpdate = function(updates, options, callback) { PersistedModel.bulkUpdate = function(updates, options, callback) {
const tasks = []; var tasks = [];
const Model = this; var Model = this;
const Change = this.getChangeModel(); var Change = this.getChangeModel();
const conflicts = []; var conflicts = [];
const lastArg = arguments[arguments.length - 1]; var lastArg = arguments[arguments.length - 1];
if (typeof lastArg === 'function' && arguments.length > 1) { if (typeof lastArg === 'function' && arguments.length > 1) {
callback = lastArg; callback = lastArg;
@ -1444,8 +1439,8 @@ module.exports = function(registry) {
if (err) return callback(err); if (err) return callback(err);
updates.forEach(function(update) { updates.forEach(function(update) {
const id = update.change.modelId; var id = update.change.modelId;
const current = currentMap[id]; var current = currentMap[id];
switch (update.type) { switch (update.type) {
case Change.UPDATE: case Change.UPDATE:
tasks.push(function(cb) { tasks.push(function(cb) {
@ -1480,13 +1475,13 @@ module.exports = function(registry) {
}; };
function buildLookupOfAffectedModelData(Model, updates, callback) { function buildLookupOfAffectedModelData(Model, updates, callback) {
const idName = Model.dataSource.idName(Model.modelName); var idName = Model.dataSource.idName(Model.modelName);
const affectedIds = updates.map(function(u) { return u.change.modelId; }); var affectedIds = updates.map(function(u) { return u.change.modelId; });
const whereAffected = {}; var whereAffected = {};
whereAffected[idName] = {inq: affectedIds}; whereAffected[idName] = {inq: affectedIds};
Model.find({where: whereAffected}, function(err, affectedList) { Model.find({where: whereAffected}, function(err, affectedList) {
if (err) return callback(err); if (err) return callback(err);
const dataLookup = {}; var dataLookup = {};
affectedList.forEach(function(it) { affectedList.forEach(function(it) {
dataLookup[it[idName]] = it; dataLookup[it[idName]] = it;
}); });
@ -1495,8 +1490,8 @@ module.exports = function(registry) {
} }
function applyUpdate(Model, id, current, data, change, conflicts, options, cb) { function applyUpdate(Model, id, current, data, change, conflicts, options, cb) {
const Change = Model.getChangeModel(); var Change = Model.getChangeModel();
const rev = current ? Change.revisionForInst(current) : null; var rev = current ? Change.revisionForInst(current) : null;
if (rev !== change.prev) { if (rev !== change.prev) {
debug('Detected non-rectified change of %s %j', debug('Detected non-rectified change of %s %j',
@ -1515,7 +1510,7 @@ module.exports = function(registry) {
Model.updateAll(current.toObject(), data, options, function(err, result) { Model.updateAll(current.toObject(), data, options, function(err, result) {
if (err) return cb(err); if (err) return cb(err);
const count = result && result.count; var count = result && result.count;
switch (count) { switch (count) {
case 1: case 1:
// The happy path, exactly one record was updated // The happy path, exactly one record was updated
@ -1535,7 +1530,7 @@ module.exports = function(registry) {
return cb(new Error( return cb(new Error(
g.f('Cannot apply bulk updates, ' + g.f('Cannot apply bulk updates, ' +
'the connector does not correctly report ' + 'the connector does not correctly report ' +
'the number of updated records.'), 'the number of updated records.')
)); ));
default: default:
@ -1543,7 +1538,7 @@ module.exports = function(registry) {
Model.modelName, count); Model.modelName, count);
return cb(new Error( return cb(new Error(
g.f('Bulk update failed, the connector has modified unexpected ' + g.f('Bulk update failed, the connector has modified unexpected ' +
'number of records: %s', JSON.stringify(count)), 'number of records: %s', JSON.stringify(count))
)); ));
} }
}); });
@ -1573,7 +1568,7 @@ module.exports = function(registry) {
Model.modelName, id); Model.modelName, id);
conflicts.push(change); conflicts.push(change);
const Change = Model.getChangeModel(); var Change = Model.getChangeModel();
return Change.rectifyModelChanges(Model.modelName, [id], cb); return Change.rectifyModelChanges(Model.modelName, [id], cb);
} }
} }
@ -1585,8 +1580,8 @@ module.exports = function(registry) {
return cb(); return cb();
} }
const Change = Model.getChangeModel(); var Change = Model.getChangeModel();
const rev = Change.revisionForInst(current); var rev = Change.revisionForInst(current);
if (rev !== change.prev) { if (rev !== change.prev) {
debug('Detected non-rectified change of %s %j', debug('Detected non-rectified change of %s %j',
Model.modelName, id); Model.modelName, id);
@ -1599,7 +1594,7 @@ module.exports = function(registry) {
Model.deleteAll(current.toObject(), options, function(err, result) { Model.deleteAll(current.toObject(), options, function(err, result) {
if (err) return cb(err); if (err) return cb(err);
const count = result && result.count; var count = result && result.count;
switch (count) { switch (count) {
case 1: case 1:
// The happy path, exactly one record was updated // The happy path, exactly one record was updated
@ -1619,7 +1614,7 @@ module.exports = function(registry) {
return cb(new Error( return cb(new Error(
g.f('Cannot apply bulk updates, ' + g.f('Cannot apply bulk updates, ' +
'the connector does not correctly report ' + 'the connector does not correctly report ' +
'the number of deleted records.'), 'the number of deleted records.')
)); ));
default: default:
@ -1627,7 +1622,7 @@ module.exports = function(registry) {
Model.modelName, count); Model.modelName, count);
return cb(new Error( return cb(new Error(
g.f('Bulk update failed, the connector has deleted unexpected ' + g.f('Bulk update failed, the connector has deleted unexpected ' +
'number of records: %s', JSON.stringify(count)), 'number of records: %s', JSON.stringify(count))
)); ));
} }
}); });
@ -1640,8 +1635,8 @@ module.exports = function(registry) {
*/ */
PersistedModel.getChangeModel = function() { PersistedModel.getChangeModel = function() {
const changeModel = this.Change; var changeModel = this.Change;
const isSetup = changeModel && changeModel.dataSource; var isSetup = changeModel && changeModel.dataSource;
assert(isSetup, 'Cannot get a setup Change model for ' + this.modelName); assert(isSetup, 'Cannot get a setup Change model for ' + this.modelName);
@ -1657,15 +1652,15 @@ module.exports = function(registry) {
*/ */
PersistedModel.getSourceId = function(cb) { PersistedModel.getSourceId = function(cb) {
const dataSource = this.dataSource; var dataSource = this.dataSource;
if (!dataSource) { if (!dataSource) {
this.once('dataSourceAttached', this.getSourceId.bind(this, cb)); this.once('dataSourceAttached', this.getSourceId.bind(this, cb));
} }
assert( assert(
dataSource.connector.name, dataSource.connector.name,
'Model.getSourceId: cannot get id without dataSource.connector.name', 'Model.getSourceId: cannot get id without dataSource.connector.name'
); );
const id = [dataSource.connector.name, this.modelName].join('-'); var id = [dataSource.connector.name, this.modelName].join('-');
cb(null, id); cb(null, id);
}; };
@ -1674,17 +1669,17 @@ module.exports = function(registry) {
*/ */
PersistedModel.enableChangeTracking = function() { PersistedModel.enableChangeTracking = function() {
const Model = this; var Model = this;
const Change = this.Change || this._defineChangeModel(); var Change = this.Change || this._defineChangeModel();
const cleanupInterval = Model.settings.changeCleanupInterval || 30000; var cleanupInterval = Model.settings.changeCleanupInterval || 30000;
assert(this.dataSource, 'Cannot enableChangeTracking(): ' + this.modelName + assert(this.dataSource, 'Cannot enableChangeTracking(): ' + this.modelName +
' is not attached to a dataSource'); ' is not attached to a dataSource');
const idName = this.getIdName(); var idName = this.getIdName();
const idProp = this.definition.properties[idName]; var idProp = this.definition.properties[idName];
const idType = idProp && idProp.type; var idType = idProp && idProp.type;
const idDefn = idProp && idProp.defaultFn; var idDefn = idProp && idProp.defaultFn;
if (idType !== String || !(idDefn === 'uuid' || idDefn === 'guid')) { if (idType !== String || !(idDefn === 'uuid' || idDefn === 'guid')) {
deprecated('The model ' + this.modelName + ' is tracking changes, ' + deprecated('The model ' + this.modelName + ' is tracking changes, ' +
'which requires a string id with GUID/UUID default value.'); 'which requires a string id with GUID/UUID default value.');
@ -1714,8 +1709,8 @@ module.exports = function(registry) {
}; };
function rectifyOnSave(ctx, next) { function rectifyOnSave(ctx, next) {
const instance = ctx.instance || ctx.currentInstance; var instance = ctx.instance || ctx.currentInstance;
const id = instance ? instance.getId() : var id = instance ? instance.getId() :
getIdFromWhereByModelId(ctx.Model, ctx.where); getIdFromWhereByModelId(ctx.Model, ctx.where);
if (debug.enabled) { if (debug.enabled) {
@ -1740,7 +1735,7 @@ module.exports = function(registry) {
} }
function rectifyOnDelete(ctx, next) { function rectifyOnDelete(ctx, next) {
const id = ctx.instance ? ctx.instance.getId() : var id = ctx.instance ? ctx.instance.getId() :
getIdFromWhereByModelId(ctx.Model, ctx.where); getIdFromWhereByModelId(ctx.Model, ctx.where);
if (debug.enabled) { if (debug.enabled) {
@ -1764,10 +1759,10 @@ module.exports = function(registry) {
} }
function getIdFromWhereByModelId(Model, where) { function getIdFromWhereByModelId(Model, where) {
const idName = Model.getIdName(); var idName = Model.getIdName();
if (!(idName in where)) return undefined; if (!(idName in where)) return undefined;
const id = where[idName]; var id = where[idName];
// TODO(bajtos) support object values that are not LB conditions // TODO(bajtos) support object values that are not LB conditions
if (typeof id === 'string' || typeof id === 'number') { if (typeof id === 'string' || typeof id === 'number') {
return id; return id;
@ -1776,7 +1771,7 @@ module.exports = function(registry) {
} }
PersistedModel._defineChangeModel = function() { PersistedModel._defineChangeModel = function() {
const BaseChangeModel = this.registry.getModel('Change'); var BaseChangeModel = this.registry.getModel('Change');
assert(BaseChangeModel, assert(BaseChangeModel,
'Change model must be defined before enabling change replication'); 'Change model must be defined before enabling change replication');
@ -1786,7 +1781,7 @@ module.exports = function(registry) {
this.Change = BaseChangeModel.extend( this.Change = BaseChangeModel.extend(
this.modelName + '-change', this.modelName + '-change',
additionalChangeModelProperties, additionalChangeModelProperties,
{trackModel: this}, {trackModel: this}
); );
if (this.dataSource) { if (this.dataSource) {
@ -1794,7 +1789,7 @@ module.exports = function(registry) {
} }
// Re-attach related models whenever our datasource is changed. // Re-attach related models whenever our datasource is changed.
const self = this; var self = this;
this.on('dataSourceAttached', function() { this.on('dataSourceAttached', function() {
attachRelatedModels(self); attachRelatedModels(self);
}); });
@ -1832,17 +1827,17 @@ module.exports = function(registry) {
*/ */
PersistedModel.rectifyChange = function(id, callback) { PersistedModel.rectifyChange = function(id, callback) {
const Change = this.getChangeModel(); var Change = this.getChangeModel();
Change.rectifyModelChanges(this.modelName, [id], callback); Change.rectifyModelChanges(this.modelName, [id], callback);
}; };
PersistedModel.findLastChange = function(id, cb) { PersistedModel.findLastChange = function(id, cb) {
const Change = this.getChangeModel(); var Change = this.getChangeModel();
Change.findOne({where: {modelId: id}}, cb); Change.findOne({where: {modelId: id}}, cb);
}; };
PersistedModel.updateLastChange = function(id, data, cb) { PersistedModel.updateLastChange = function(id, data, cb) {
const self = this; var self = this;
this.findLastChange(id, function(err, inst) { this.findLastChange(id, function(err, inst) {
if (err) return cb(err); if (err) return cb(err);
if (!inst) { if (!inst) {
@ -1873,9 +1868,9 @@ module.exports = function(registry) {
} }
cb = cb || utils.createPromiseCallback(); cb = cb || utils.createPromiseCallback();
const idName = this.getIdName(); var idName = this.getIdName();
const Model = this; var Model = this;
const changes = new PassThrough({objectMode: true}); var changes = new PassThrough({objectMode: true});
changes._destroy = function() { changes._destroy = function() {
changes.end(); changes.end();
@ -1900,31 +1895,31 @@ module.exports = function(registry) {
return cb.promise; return cb.promise;
function changeHandler(ctx, next) { function changeHandler(ctx, next) {
const change = createChangeObject(ctx, 'save'); var change = createChangeObject(ctx, 'save');
if (change) { if (change) {
changes.write(change); changes.write(change);
} }
next(); next();
} };
function deleteHandler(ctx, next) { function deleteHandler(ctx, next) {
const change = createChangeObject(ctx, 'delete'); var change = createChangeObject(ctx, 'delete');
if (change) { if (change) {
changes.write(change); changes.write(change);
} }
next(); next();
} };
function createChangeObject(ctx, type) { function createChangeObject(ctx, type) {
const where = ctx.where; var where = ctx.where;
let data = ctx.instance || ctx.data; var data = ctx.instance || ctx.data;
const whereId = where && where[idName]; var whereId = where && where[idName];
// the data includes the id // the data includes the id
// or the where includes the id // or the where includes the id
let target; var target;
if (data && (data[idName] || data[idName] === 0)) { if (data && (data[idName] || data[idName] === 0)) {
target = data[idName]; target = data[idName];
@ -1932,18 +1927,18 @@ module.exports = function(registry) {
target = where[idName]; target = where[idName];
} }
const hasTarget = target === 0 || !!target; var hasTarget = target === 0 || !!target;
// apply filtering if options is set // apply filtering if options is set
if (options) { if (options) {
const filtered = filterNodes([data], options); var filtered = filterNodes([data], options);
if (filtered.length !== 1) { if (filtered.length !== 1) {
return null; return null;
} }
data = filtered[0]; data = filtered[0];
} }
const change = { var change = {
target: target, target: target,
where: where, where: where,
data: data, data: data,

View File

@ -1,17 +1,17 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const g = require('./globalize'); var g = require('./globalize');
const assert = require('assert'); var assert = require('assert');
const extend = require('util')._extend; var extend = require('util')._extend;
const juggler = require('loopback-datasource-juggler'); var juggler = require('loopback-datasource-juggler');
const debug = require('debug')('loopback:registry'); var debug = require('debug')('loopback:registry');
const DataSource = juggler.DataSource; var DataSource = juggler.DataSource;
const ModelBuilder = juggler.ModelBuilder; var ModelBuilder = juggler.ModelBuilder;
const deprecated = require('depd')('strong-remoting'); var deprecated = require('depd')('strong-remoting');
module.exports = Registry; module.exports = Registry;
@ -97,7 +97,7 @@ function Registry() {
Registry.prototype.createModel = function(name, properties, options) { Registry.prototype.createModel = function(name, properties, options) {
if (arguments.length === 1 && typeof name === 'object') { if (arguments.length === 1 && typeof name === 'object') {
const config = name; var config = name;
name = config.name; name = config.name;
properties = config.properties; properties = config.properties;
options = buildModelOptionsFromConfig(config); options = buildModelOptionsFromConfig(config);
@ -107,10 +107,10 @@ Registry.prototype.createModel = function(name, properties, options) {
} }
options = options || {}; options = options || {};
let BaseModel = options.base || options.super; var BaseModel = options.base || options.super;
if (typeof BaseModel === 'string') { if (typeof BaseModel === 'string') {
const baseName = BaseModel; var baseName = BaseModel;
BaseModel = this.findModel(BaseModel); BaseModel = this.findModel(BaseModel);
if (!BaseModel) { if (!BaseModel) {
throw new Error(g.f('Model not found: model `%s` is extending an unknown model `%s`.', throw new Error(g.f('Model not found: model `%s` is extending an unknown model `%s`.',
@ -119,7 +119,7 @@ Registry.prototype.createModel = function(name, properties, options) {
} }
BaseModel = BaseModel || this.getModel('PersistedModel'); BaseModel = BaseModel || this.getModel('PersistedModel');
const model = BaseModel.extend(name, properties, options); var model = BaseModel.extend(name, properties, options);
model.registry = this; model.registry = this;
this._defineRemoteMethods(model, model.settings.methods); this._defineRemoteMethods(model, model.settings.methods);
@ -128,8 +128,8 @@ Registry.prototype.createModel = function(name, properties, options) {
}; };
function buildModelOptionsFromConfig(config) { function buildModelOptionsFromConfig(config) {
const options = extend({}, config.options); var options = extend({}, config.options);
for (const key in config) { for (var key in config) {
if (['name', 'properties', 'options'].indexOf(key) !== -1) { if (['name', 'properties', 'options'].indexOf(key) !== -1) {
// Skip items which have special meaning // Skip items which have special meaning
continue; continue;
@ -152,7 +152,7 @@ function buildModelOptionsFromConfig(config) {
* @param {Object} acl * @param {Object} acl
*/ */
function addACL(acls, acl) { function addACL(acls, acl) {
for (let i = 0, n = acls.length; i < n; i++) { for (var i = 0, n = acls.length; i < n; i++) {
// Check if there is a matching acl to be overriden // Check if there is a matching acl to be overriden
if (acls[i].property === acl.property && if (acls[i].property === acl.property &&
acls[i].accessType === acl.accessType && acls[i].accessType === acl.accessType &&
@ -176,14 +176,14 @@ function addACL(acls, acl) {
*/ */
Registry.prototype.configureModel = function(ModelCtor, config) { Registry.prototype.configureModel = function(ModelCtor, config) {
const settings = ModelCtor.settings; var settings = ModelCtor.settings;
const modelName = ModelCtor.modelName; var modelName = ModelCtor.modelName;
ModelCtor.config = config; ModelCtor.config = config;
// Relations // Relations
if (typeof config.relations === 'object' && config.relations !== null) { if (typeof config.relations === 'object' && config.relations !== null) {
const relations = settings.relations = settings.relations || {}; var relations = settings.relations = settings.relations || {};
Object.keys(config.relations).forEach(function(key) { Object.keys(config.relations).forEach(function(key) {
// FIXME: [rfeng] We probably should check if the relation exists // FIXME: [rfeng] We probably should check if the relation exists
relations[key] = extend(relations[key] || {}, config.relations[key]); relations[key] = extend(relations[key] || {}, config.relations[key]);
@ -195,7 +195,7 @@ Registry.prototype.configureModel = function(ModelCtor, config) {
// ACLs // ACLs
if (Array.isArray(config.acls)) { if (Array.isArray(config.acls)) {
const acls = settings.acls = settings.acls || []; var acls = settings.acls = settings.acls || [];
config.acls.forEach(function(acl) { config.acls.forEach(function(acl) {
addACL(acls, acl); addACL(acls, acl);
}); });
@ -205,7 +205,7 @@ Registry.prototype.configureModel = function(ModelCtor, config) {
} }
// Settings // Settings
const excludedProperties = { var excludedProperties = {
base: true, base: true,
'super': true, 'super': true,
relations: true, relations: true,
@ -213,7 +213,7 @@ Registry.prototype.configureModel = function(ModelCtor, config) {
dataSource: true, dataSource: true,
}; };
if (typeof config.options === 'object' && config.options !== null) { if (typeof config.options === 'object' && config.options !== null) {
for (const p in config.options) { for (var p in config.options) {
if (!(p in excludedProperties)) { if (!(p in excludedProperties)) {
settings[p] = config.options[p]; settings[p] = config.options[p];
} else { } else {
@ -244,17 +244,17 @@ Registry.prototype.configureModel = function(ModelCtor, config) {
g.warn( g.warn(
'The configuration of `%s` is missing {{`dataSource`}} property.\n' + 'The configuration of `%s` is missing {{`dataSource`}} property.\n' +
'Use `null` or `false` to mark models not attached to any data source.', 'Use `null` or `false` to mark models not attached to any data source.',
modelName, modelName
); );
} }
const newMethodNames = config.methods && Object.keys(config.methods); var newMethodNames = config.methods && Object.keys(config.methods);
const hasNewMethods = newMethodNames && newMethodNames.length; var hasNewMethods = newMethodNames && newMethodNames.length;
const hasDescendants = this.getModelByType(ModelCtor) !== ModelCtor; var hasDescendants = this.getModelByType(ModelCtor) !== ModelCtor;
if (hasNewMethods && hasDescendants) { if (hasNewMethods && hasDescendants) {
g.warn( g.warn(
'Child models of `%s` will not inherit newly defined remote methods %s.', 'Child models of `%s` will not inherit newly defined remote methods %s.',
modelName, newMethodNames, modelName, newMethodNames
); );
} }
@ -271,9 +271,9 @@ Registry.prototype._defineRemoteMethods = function(ModelCtor, methods) {
} }
Object.keys(methods).forEach(function(key) { Object.keys(methods).forEach(function(key) {
let meta = methods[key]; var meta = methods[key];
const m = key.match(/^prototype\.(.*)$/); var m = key.match(/^prototype\.(.*)$/);
const isStatic = !m; var isStatic = !m;
if (typeof meta.isStatic !== 'boolean') { if (typeof meta.isStatic !== 'boolean') {
key = isStatic ? key : m[1]; key = isStatic ? key : m[1];
@ -313,7 +313,7 @@ Registry.prototype.findModel = function(modelName) {
* @header loopback.getModel(modelName) * @header loopback.getModel(modelName)
*/ */
Registry.prototype.getModel = function(modelName) { Registry.prototype.getModel = function(modelName) {
const model = this.findModel(modelName); var model = this.findModel(modelName);
if (model) return model; if (model) return model;
throw new Error(g.f('Model not found: %s', modelName)); throw new Error(g.f('Model not found: %s', modelName));
@ -329,8 +329,8 @@ Registry.prototype.getModel = function(modelName) {
* @header loopback.getModelByType(modelType) * @header loopback.getModelByType(modelType)
*/ */
Registry.prototype.getModelByType = function(modelType) { Registry.prototype.getModelByType = function(modelType) {
const type = typeof modelType; var type = typeof modelType;
const accepted = ['function', 'string']; var accepted = ['function', 'string'];
assert(accepted.indexOf(type) > -1, assert(accepted.indexOf(type) > -1,
'The model type must be a constructor or model name'); 'The model type must be a constructor or model name');
@ -339,8 +339,8 @@ Registry.prototype.getModelByType = function(modelType) {
modelType = this.getModel(modelType); modelType = this.getModel(modelType);
} }
const models = this.modelBuilder.models; var models = this.modelBuilder.models;
for (const m in models) { for (var m in models) {
if (models[m].prototype instanceof modelType) { if (models[m].prototype instanceof modelType) {
return models[m]; return models[m];
} }
@ -359,15 +359,15 @@ Registry.prototype.getModelByType = function(modelType) {
*/ */
Registry.prototype.createDataSource = function(name, options) { Registry.prototype.createDataSource = function(name, options) {
const self = this; var self = this;
const ds = new DataSource(name, options, self.modelBuilder); var ds = new DataSource(name, options, self.modelBuilder);
ds.createModel = function(name, properties, settings) { ds.createModel = function(name, properties, settings) {
settings = settings || {}; settings = settings || {};
let BaseModel = settings.base || settings.super; var BaseModel = settings.base || settings.super;
if (!BaseModel) { if (!BaseModel) {
// Check the connector types // Check the connector types
const connectorTypes = ds.getTypes(); var connectorTypes = ds.getTypes();
if (Array.isArray(connectorTypes) && connectorTypes.indexOf('db') !== -1) { if (Array.isArray(connectorTypes) && connectorTypes.indexOf('db') !== -1) {
// Only set up the base model to PersistedModel if the connector is DB // Only set up the base model to PersistedModel if the connector is DB
BaseModel = self.PersistedModel; BaseModel = self.PersistedModel;
@ -376,13 +376,13 @@ Registry.prototype.createDataSource = function(name, options) {
} }
settings.base = BaseModel; settings.base = BaseModel;
} }
const ModelCtor = self.createModel(name, properties, settings); var ModelCtor = self.createModel(name, properties, settings);
ModelCtor.attachTo(ds); ModelCtor.attachTo(ds);
return ModelCtor; return ModelCtor;
}; };
if (ds.settings && ds.settings.defaultForType) { if (ds.settings && ds.settings.defaultForType) {
const msg = g.f('{{DataSource}} option {{"defaultForType"}} is no longer supported'); var msg = g.f('{{DataSource}} option {{"defaultForType"}} is no longer supported');
throw new Error(msg); throw new Error(msg);
} }
@ -398,7 +398,7 @@ Registry.prototype.createDataSource = function(name, options) {
Registry.prototype.memory = function(name) { Registry.prototype.memory = function(name) {
name = name || 'default'; name = name || 'default';
let memory = ( var memory = (
this._memoryDataSources || (this._memoryDataSources = {}) this._memoryDataSources || (this._memoryDataSources = {})
)[name]; )[name];

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -10,7 +10,7 @@
*/ */
'use strict'; 'use strict';
const runtime = exports; var runtime = exports;
/** /**
* True if running in a browser environment; false otherwise. * True if running in a browser environment; false otherwise.

View File

@ -1,23 +1,23 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const g = require('./globalize'); var g = require('./globalize');
const assert = require('assert'); var assert = require('assert');
const express = require('express'); var express = require('express');
const merge = require('util')._extend; var merge = require('util')._extend;
const mergePhaseNameLists = require('loopback-phase').mergePhaseNameLists; var mergePhaseNameLists = require('loopback-phase').mergePhaseNameLists;
const debug = require('debug')('loopback:app'); var debug = require('debug')('loopback:app');
const stableSortInPlace = require('stable').inplace; var stableSortInPlace = require('stable').inplace;
const BUILTIN_MIDDLEWARE = {builtin: true}; var BUILTIN_MIDDLEWARE = {builtin: true};
const proto = {}; var proto = {};
module.exports = function loopbackExpress() { module.exports = function loopbackExpress() {
const app = express(); var app = express();
app.__expressLazyRouter = app.lazyrouter; app.__expressLazyRouter = app.lazyrouter;
merge(app, proto); merge(app, proto);
return app; return app;
@ -64,23 +64,23 @@ proto.middlewareFromConfig = function(factory, config) {
if (config.enabled === false) if (config.enabled === false)
return; return;
let params = config.params; var params = config.params;
if (params === undefined) { if (params === undefined) {
params = []; params = [];
} else if (!Array.isArray(params)) { } else if (!Array.isArray(params)) {
params = [params]; params = [params];
} }
let handler = factory.apply(null, params); var handler = factory.apply(null, params);
// Check if methods/verbs filter exists // Check if methods/verbs filter exists
let verbs = config.methods || config.verbs; var verbs = config.methods || config.verbs;
if (Array.isArray(verbs)) { if (Array.isArray(verbs)) {
verbs = verbs.map(function(verb) { verbs = verbs.map(function(verb) {
return verb && verb.toUpperCase(); return verb && verb.toUpperCase();
}); });
if (verbs.indexOf('ALL') === -1) { if (verbs.indexOf('ALL') === -1) {
const originalHandler = handler; var originalHandler = handler;
if (handler.length <= 3) { if (handler.length <= 3) {
// Regular handler // Regular handler
handler = function(req, res, next) { handler = function(req, res, next) {
@ -145,7 +145,7 @@ proto.defineMiddlewarePhases = function(nameOrArray) {
mergePhaseNameLists(this._requestHandlingPhases, nameOrArray); mergePhaseNameLists(this._requestHandlingPhases, nameOrArray);
} else { } else {
// add the new phase before 'routes' // add the new phase before 'routes'
const routesIx = this._requestHandlingPhases.indexOf('routes'); var routesIx = this._requestHandlingPhases.indexOf('routes');
this._requestHandlingPhases.splice(routesIx - 1, 0, nameOrArray); this._requestHandlingPhases.splice(routesIx - 1, 0, nameOrArray);
} }
@ -181,10 +181,10 @@ proto.middleware = function(name, paths, handler) {
paths = '/'; paths = '/';
} }
const fullPhaseName = name; var fullPhaseName = name;
const handlerName = handler.name || '<anonymous>'; var handlerName = handler.name || '<anonymous>';
const m = name.match(/^(.+):(before|after)$/); var m = name.match(/^(.+):(before|after)$/);
if (m) { if (m) {
name = m[1]; name = m[1];
} }
@ -197,7 +197,7 @@ proto.middleware = function(name, paths, handler) {
this._skipLayerSorting = true; this._skipLayerSorting = true;
this.use(paths, handler); this.use(paths, handler);
const layer = this._findLayerByHandler(handler); var layer = this._findLayerByHandler(handler);
if (layer) { if (layer) {
// Set the phase name for sorting // Set the phase name for sorting
layer.phase = fullPhaseName; layer.phase = fullPhaseName;
@ -221,20 +221,17 @@ proto.middleware = function(name, paths, handler) {
*/ */
proto._findLayerByHandler = function(handler) { proto._findLayerByHandler = function(handler) {
// Other handlers can be added to the stack, for example, // Other handlers can be added to the stack, for example,
// NewRelic adds sentinel handler, and AppDynamics adds // NewRelic adds sentinel handler. We need to search the stack
// some additional proxy info. We need to search the stack for (var k = this._router.stack.length - 1; k >= 0; k--) {
for (let k = this._router.stack.length - 1; k >= 0; k--) { if (this._router.stack[k].handle === handler ||
const isOriginal = this._router.stack[k].handle === handler; // NewRelic replaces the handle and keeps it as __NR_original
const isNewRelic = this._router.stack[k].handle['__NR_original'] === handler; this._router.stack[k].handle['__NR_original'] === handler
const isAppDynamics = this._router.stack[k].handle['__appdynamicsProxyInfo__'] && ) {
this._router.stack[k].handle['__appdynamicsProxyInfo__']['orig'] === handler;
if (isOriginal || isNewRelic || isAppDynamics) {
return this._router.stack[k]; return this._router.stack[k];
} else { } else {
// Aggressively check if the original handler has been wrapped // Aggressively check if the original handler has been wrapped
// into a new function with a property pointing to the original handler // into a new function with a property pointing to the original handler
for (const p in this._router.stack[k].handle) { for (var p in this._router.stack[k].handle) {
if (this._router.stack[k].handle[p] === handler) { if (this._router.stack[k].handle[p] === handler) {
return this._router.stack[k]; return this._router.stack[k];
} }
@ -246,12 +243,12 @@ proto._findLayerByHandler = function(handler) {
// Install our custom PhaseList-based handler into the app // Install our custom PhaseList-based handler into the app
proto.lazyrouter = function() { proto.lazyrouter = function() {
const self = this; var self = this;
if (self._router) return; if (self._router) return;
self.__expressLazyRouter(); self.__expressLazyRouter();
const router = self._router; var router = self._router;
// Mark all middleware added by Router ctor as builtin // Mark all middleware added by Router ctor as builtin
// The sorting algo will keep them at beginning of the list // The sorting algo will keep them at beginning of the list
@ -261,14 +258,14 @@ proto.lazyrouter = function() {
router.__expressUse = router.use; router.__expressUse = router.use;
router.use = function useAndSort() { router.use = function useAndSort() {
const retval = this.__expressUse.apply(this, arguments); var retval = this.__expressUse.apply(this, arguments);
self._sortLayersByPhase(); self._sortLayersByPhase();
return retval; return retval;
}; };
router.__expressRoute = router.route; router.__expressRoute = router.route;
router.route = function routeAndSort() { router.route = function routeAndSort() {
const retval = this.__expressRoute.apply(this, arguments); var retval = this.__expressRoute.apply(this, arguments);
self._sortLayersByPhase(); self._sortLayersByPhase();
return retval; return retval;
}; };
@ -282,19 +279,19 @@ proto.lazyrouter = function() {
proto._sortLayersByPhase = function() { proto._sortLayersByPhase = function() {
if (this._skipLayerSorting) return; if (this._skipLayerSorting) return;
const phaseOrder = {}; var phaseOrder = {};
this._requestHandlingPhases.forEach(function(name, ix) { this._requestHandlingPhases.forEach(function(name, ix) {
phaseOrder[name + ':before'] = ix * 3; phaseOrder[name + ':before'] = ix * 3;
phaseOrder[name] = ix * 3 + 1; phaseOrder[name] = ix * 3 + 1;
phaseOrder[name + ':after'] = ix * 3 + 2; phaseOrder[name + ':after'] = ix * 3 + 2;
}); });
const router = this._router; var router = this._router;
stableSortInPlace(router.stack, compareLayers); stableSortInPlace(router.stack, compareLayers);
function compareLayers(left, right) { function compareLayers(left, right) {
const leftPhase = left.phase; var leftPhase = left.phase;
const rightPhase = right.phase; var rightPhase = right.phase;
if (leftPhase === rightPhase) return 0; if (leftPhase === rightPhase) return 0;

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2015,2019. All Rights Reserved. // Copyright IBM Corp. 2015,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -10,12 +10,12 @@ exports.uploadInChunks = uploadInChunks;
exports.downloadInChunks = downloadInChunks; exports.downloadInChunks = downloadInChunks;
exports.concatResults = concatResults; exports.concatResults = concatResults;
const Promise = require('bluebird'); var Promise = require('bluebird');
const async = require('async'); var async = require('async');
function createPromiseCallback() { function createPromiseCallback() {
let cb; var cb;
const promise = new Promise(function(resolve, reject) { var promise = new Promise(function(resolve, reject) {
cb = function(err, data) { cb = function(err, data) {
if (err) return reject(err); if (err) return reject(err);
return resolve(data); return resolve(data);
@ -28,7 +28,7 @@ function createPromiseCallback() {
function throwPromiseNotDefined() { function throwPromiseNotDefined() {
throw new Error( throw new Error(
'Your Node runtime does support ES6 Promises. ' + 'Your Node runtime does support ES6 Promises. ' +
'Set "global.Promise" to your preferred implementation of promises.', 'Set "global.Promise" to your preferred implementation of promises.'
); );
} }
@ -40,23 +40,23 @@ function throwPromiseNotDefined() {
* @param {Function} cb - the callback * @param {Function} cb - the callback
*/ */
function uploadInChunks(largeArray, chunkSize, processFunction, cb) { function uploadInChunks(largeArray, chunkSize, processFunction, cb) {
const chunkArrays = []; var chunkArrays = [];
if (!chunkSize || chunkSize < 1 || largeArray.length <= chunkSize) { if (!chunkSize || chunkSize < 1 || largeArray.length <= chunkSize) {
// if chunking not required // if chunking not required
processFunction(largeArray, cb); processFunction(largeArray, cb);
} else { } else {
// copying so that the largeArray object does not get affected during splice // copying so that the largeArray object does not get affected during splice
const copyOfLargeArray = [].concat(largeArray); var copyOfLargeArray = [].concat(largeArray);
// chunking to smaller arrays // chunking to smaller arrays
while (copyOfLargeArray.length > 0) { while (copyOfLargeArray.length > 0) {
chunkArrays.push(copyOfLargeArray.splice(0, chunkSize)); chunkArrays.push(copyOfLargeArray.splice(0, chunkSize));
} }
const tasks = chunkArrays.map(function(chunkArray) { var tasks = chunkArrays.map(function(chunkArray) {
return function(previousResults, chunkCallback) { return function(previousResults, chunkCallback) {
const lastArg = arguments[arguments.length - 1]; var lastArg = arguments[arguments.length - 1];
if (typeof lastArg === 'function') { if (typeof lastArg === 'function') {
chunkCallback = lastArg; chunkCallback = lastArg;
@ -92,7 +92,7 @@ function uploadInChunks(largeArray, chunkSize, processFunction, cb) {
* @param {Function} cb - the callback * @param {Function} cb - the callback
*/ */
function downloadInChunks(filter, chunkSize, processFunction, cb) { function downloadInChunks(filter, chunkSize, processFunction, cb) {
let results = []; var results = [];
filter = filter ? JSON.parse(JSON.stringify(filter)) : {}; filter = filter ? JSON.parse(JSON.stringify(filter)) : {};
if (!chunkSize || chunkSize < 1) { if (!chunkSize || chunkSize < 1) {

View File

@ -1,6 +1,6 @@
{ {
"name": "loopback", "name": "loopback",
"version": "3.28.0", "version": "3.22.2",
"description": "LoopBack: Open Source Framework for Node.js", "description": "LoopBack: Open Source Framework for Node.js",
"homepage": "http://loopback.io", "homepage": "http://loopback.io",
"keywords": [ "keywords": [
@ -34,7 +34,7 @@
"test": "nyc grunt mocha-and-karma" "test": "nyc grunt mocha-and-karma"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=6"
}, },
"dependencies": { "dependencies": {
"async": "^2.0.1", "async": "^2.0.1",
@ -47,12 +47,12 @@
"ejs": "^2.3.1", "ejs": "^2.3.1",
"express": "^4.14.0", "express": "^4.14.0",
"inflection": "^1.6.0", "inflection": "^1.6.0",
"isemail": "^3.2.0", "isemail": "^2.2.1",
"loopback-connector-remote": "^3.0.0", "loopback-connector-remote": "^3.0.0",
"loopback-datasource-juggler": "^3.28.0", "loopback-datasource-juggler": "^3.18.0",
"loopback-filters": "^1.0.0", "loopback-filters": "^1.0.0",
"loopback-phase": "^3.0.0", "loopback-phase": "^3.0.0",
"nodemailer": "^6.4.16", "nodemailer": "^4.0.1",
"nodemailer-direct-transport": "^3.3.2", "nodemailer-direct-transport": "^3.3.2",
"nodemailer-stub-transport": "^1.1.0", "nodemailer-stub-transport": "^1.1.0",
"serve-favicon": "^2.2.0", "serve-favicon": "^2.2.0",
@ -60,45 +60,48 @@
"strong-globalize": "^4.1.1", "strong-globalize": "^4.1.1",
"strong-remoting": "^3.11.0", "strong-remoting": "^3.11.0",
"uid2": "0.0.3", "uid2": "0.0.3",
"underscore.string": "^3.3.5" "underscore.string": "^3.0.3"
}, },
"devDependencies": { "devDependencies": {
"browserify": "^16.5.0", "babel-preset-es2015": "^6.22.0",
"chai": "^4.2.0", "babelify": "^7.3.0",
"browserify": "^13.1.0",
"chai": "^3.5.0",
"cookie-parser": "^1.3.4", "cookie-parser": "^1.3.4",
"coveralls": "^3.0.2", "coveralls": "^3.0.2",
"dirty-chai": "^2.0.1", "dirty-chai": "^1.2.2",
"eslint": "^6.5.1", "eslint": "^5.3.0",
"eslint-config-loopback": "^13.1.0", "eslint-config-loopback": "^11.0.0",
"eslint-plugin-mocha": "^5.1.0",
"express-session": "^1.14.0", "express-session": "^1.14.0",
"grunt": "^1.0.1", "grunt": "^1.0.1",
"grunt-browserify": "^5.0.0", "grunt-browserify": "^5.0.0",
"grunt-cli": "^1.2.0", "grunt-cli": "^1.2.0",
"grunt-contrib-uglify": "^4.0.1", "grunt-contrib-uglify": "^3.4.0",
"grunt-contrib-watch": "^1.0.0", "grunt-contrib-watch": "^1.0.0",
"grunt-eslint": "^22.0.0", "grunt-eslint": "^21.0.0",
"grunt-karma": "^3.0.2", "grunt-karma": "^2.0.0",
"grunt-mocha-test": "^0.13.3", "grunt-mocha-test": "^0.13.3",
"is-docker": "^2.0.0", "karma": "^1.1.2",
"karma": "^4.1.0", "karma-browserify": "^5.1.1",
"karma-browserify": "^6.0.0", "karma-chrome-launcher": "^1.0.1",
"karma-chrome-launcher": "^3.1.0",
"karma-es6-shim": "^1.0.0", "karma-es6-shim": "^1.0.0",
"karma-firefox-launcher": "^1.0.0", "karma-firefox-launcher": "^1.0.0",
"karma-html2js-preprocessor": "^1.0.0", "karma-html2js-preprocessor": "^1.0.0",
"karma-junit-reporter": "^1.2.0", "karma-junit-reporter": "~1.0.0",
"karma-mocha": "^1.1.1", "karma-mocha": "^1.1.1",
"karma-phantomjs-launcher": "^1.0.0",
"karma-script-launcher": "^1.0.0", "karma-script-launcher": "^1.0.0",
"loopback-boot": "^2.7.0", "loopback-boot": "^2.7.0",
"loopback-context": "^1.0.0", "loopback-context": "^1.0.0",
"mocha": "^6.2.1", "mocha": "^5.2.0",
"nyc": "^14.1.1", "nyc": "^10.1.2",
"sinon": "^7.5.0", "phantomjs-prebuilt": "^2.1.7",
"sinon": "^6.1.4",
"sinon-chai": "^3.2.0", "sinon-chai": "^3.2.0",
"strong-error-handler": "^3.0.0", "strong-error-handler": "^3.0.0",
"strong-task-emitter": "^0.0.8", "strong-task-emitter": "^0.0.8",
"supertest": "^4.0.2", "supertest": "^3.0.0"
"which": "^2.0.1"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -119,13 +122,5 @@
} }
}, },
"copyright.owner": "IBM Corp.", "copyright.owner": "IBM Corp.",
"license": "MIT", "license": "MIT"
"author": "IBM Corp.",
"ci": {
"downstreamIgnoreList": [
"bluemix-service-broker",
"gateway-director-bluemix",
"plan-manager"
]
}
} }

View File

@ -1,15 +1,15 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const g = require('../../lib/globalize'); var g = require('../../lib/globalize');
module.exports = function() { module.exports = function() {
throw new Error(g.f( throw new Error(g.f(
'%s middleware was removed in version 3.0. See %s for more details.', '%s middleware was removed in version 3.0. See %s for more details.',
'loopback#context', 'loopback#context',
'http://loopback.io/doc/en/lb2/Using-current-context.html', 'http://loopback.io/doc/en/lb2/Using-current-context.html'
)); ));
}; };

View File

@ -1,11 +1,11 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const favicon = require('serve-favicon'); var favicon = require('serve-favicon');
const path = require('path'); var path = require('path');
/** /**
* Serve the LoopBack favicon. * Serve the LoopBack favicon.

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -8,9 +8,9 @@
*/ */
'use strict'; 'use strict';
const g = require('../../lib/globalize'); var g = require('../../lib/globalize');
const loopback = require('../../lib/loopback'); var loopback = require('../../lib/loopback');
const async = require('async'); var async = require('async');
/*! /*!
* Export the middleware. * Export the middleware.
@ -30,27 +30,27 @@ module.exports = rest;
*/ */
function rest() { function rest() {
let handlers; // Cached handlers var handlers; // Cached handlers
return function restApiHandler(req, res, next) { return function restApiHandler(req, res, next) {
const app = req.app; var app = req.app;
const registry = app.registry; var registry = app.registry;
if (!handlers) { if (!handlers) {
handlers = []; handlers = [];
const remotingOptions = app.get('remoting') || {}; var remotingOptions = app.get('remoting') || {};
const contextOptions = remotingOptions.context; var contextOptions = remotingOptions.context;
if (contextOptions !== undefined && contextOptions !== false) { if (contextOptions !== undefined && contextOptions !== false) {
throw new Error(g.f( throw new Error(g.f(
'%s was removed in version 3.0. See %s for more details.', '%s was removed in version 3.0. See %s for more details.',
'remoting.context option', 'remoting.context option',
'http://loopback.io/doc/en/lb2/Using-current-context.html', 'http://loopback.io/doc/en/lb2/Using-current-context.html'
)); ));
} }
if (app.isAuthEnabled) { if (app.isAuthEnabled) {
const AccessToken = registry.getModelByType('AccessToken'); var AccessToken = registry.getModelByType('AccessToken');
handlers.push(loopback.token({model: AccessToken, app: app})); handlers.push(loopback.token({model: AccessToken, app: app}));
} }

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -24,7 +24,7 @@ module.exports = status;
* @header loopback.status() * @header loopback.status()
*/ */
function status() { function status() {
const started = new Date(); var started = new Date();
return function(req, res) { return function(req, res) {
res.send({ res.send({

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -8,10 +8,10 @@
*/ */
'use strict'; 'use strict';
const g = require('../../lib/globalize'); var g = require('../../lib/globalize');
const loopback = require('../../lib/loopback'); var loopback = require('../../lib/loopback');
const assert = require('assert'); var assert = require('assert');
const debug = require('debug')('loopback:middleware:token'); var debug = require('debug')('loopback:middleware:token');
/*! /*!
* Export the middleware. * Export the middleware.
@ -28,7 +28,7 @@ function rewriteUserLiteral(req, currentUserLiteral, next) {
if (req.accessToken && req.accessToken.userId) { if (req.accessToken && req.accessToken.userId) {
// Replace /me/ with /current-user-id/ // Replace /me/ with /current-user-id/
const urlBeforeRewrite = req.url; var urlBeforeRewrite = req.url;
req.url = req.url.replace(literalRegExp, req.url = req.url.replace(literalRegExp,
'/' + req.accessToken.userId + '$1'); '/' + req.accessToken.userId + '$1');
@ -40,10 +40,10 @@ function rewriteUserLiteral(req, currentUserLiteral, next) {
debug( debug(
'URL %s matches current-user literal %s,' + 'URL %s matches current-user literal %s,' +
' but no (valid) access token was provided.', ' but no (valid) access token was provided.',
req.url, currentUserLiteral, req.url, currentUserLiteral
); );
const e = new Error(g.f('Authorization Required')); var e = new Error(g.f('Authorization Required'));
e.status = e.statusCode = 401; e.status = e.statusCode = 401;
e.code = 'AUTHORIZATION_REQUIRED'; e.code = 'AUTHORIZATION_REQUIRED';
return next(e); return next(e);
@ -97,9 +97,9 @@ function escapeRegExp(str) {
function token(options) { function token(options) {
options = options || {}; options = options || {};
let TokenModel; var TokenModel;
let currentUserLiteral = options.currentUserLiteral; var currentUserLiteral = options.currentUserLiteral;
if (currentUserLiteral && (typeof currentUserLiteral !== 'string')) { if (currentUserLiteral && (typeof currentUserLiteral !== 'string')) {
debug('Set currentUserLiteral to \'me\' as the value is not a string.'); debug('Set currentUserLiteral to \'me\' as the value is not a string.');
currentUserLiteral = 'me'; currentUserLiteral = 'me';
@ -111,12 +111,12 @@ function token(options) {
if (options.bearerTokenBase64Encoded === undefined) { if (options.bearerTokenBase64Encoded === undefined) {
options.bearerTokenBase64Encoded = true; options.bearerTokenBase64Encoded = true;
} }
const enableDoublecheck = !!options.enableDoublecheck; var enableDoublecheck = !!options.enableDoublecheck;
const overwriteExistingToken = !!options.overwriteExistingToken; var overwriteExistingToken = !!options.overwriteExistingToken;
return function(req, res, next) { return function(req, res, next) {
const app = req.app; var app = req.app;
const registry = app.registry; var registry = app.registry;
if (!TokenModel) { if (!TokenModel) {
TokenModel = registry.getModel(options.model || 'AccessToken'); TokenModel = registry.getModel(options.model || 'AccessToken');
} }
@ -141,7 +141,7 @@ function token(options) {
TokenModel.findForRequest(req, options, function(err, token) { TokenModel.findForRequest(req, options, function(err, token) {
req.accessToken = token || null; req.accessToken = token || null;
const ctx = req.loopbackContext; var ctx = req.loopbackContext;
if (ctx && ctx.active) ctx.set('accessToken', token); if (ctx && ctx.active) ctx.set('accessToken', token);
if (err) return next(err); if (err) return next(err);

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -18,7 +18,7 @@ module.exports = urlNotFound;
*/ */
function urlNotFound() { function urlNotFound() {
return function raiseUrlNotFoundError(req, res, next) { return function raiseUrlNotFoundError(req, res, next) {
const error = new Error('Cannot ' + req.method + ' ' + req.url); var error = new Error('Cannot ' + req.method + ' ' + req.url);
error.status = 404; error.status = 404;
next(error); next(error);
}; };

View File

@ -1,18 +1,18 @@
// Copyright IBM Corp. 2013,2019. All Rights Reserved. // Copyright IBM Corp. 2013,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const loopback = require('../'); var loopback = require('../');
const lt = require('./helpers/loopback-testing-helper'); var lt = require('./helpers/loopback-testing-helper');
const path = require('path'); var path = require('path');
const ACCESS_CONTROL_APP = path.join(__dirname, 'fixtures', 'access-control'); var ACCESS_CONTROL_APP = path.join(__dirname, 'fixtures', 'access-control');
const app = require(path.join(ACCESS_CONTROL_APP, 'server/server.js')); var app = require(path.join(ACCESS_CONTROL_APP, 'server/server.js'));
const assert = require('assert'); var assert = require('assert');
const USER = {email: 'test@test.test', password: 'test'}; var USER = {email: 'test@test.test', password: 'test'};
const CURRENT_USER = {email: 'current@test.test', password: 'test'}; var CURRENT_USER = {email: 'current@test.test', password: 'test'};
const debug = require('debug')('loopback:test:access-control.integration'); var debug = require('debug')('loopback:test:access-control.integration');
describe('access control - integration', function() { describe('access control - integration', function() {
lt.beforeEach.withApp(app); lt.beforeEach.withApp(app);
@ -75,11 +75,11 @@ describe('access control - integration', function() {
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'GET', urlForUser); lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'GET', urlForUser);
lt.it.shouldBeAllowedWhenCalledAnonymously( lt.it.shouldBeAllowedWhenCalledAnonymously(
'POST', '/api/users', newUserData(), 'POST', '/api/users', newUserData()
); );
lt.it.shouldBeAllowedWhenCalledByUser( lt.it.shouldBeAllowedWhenCalledByUser(
CURRENT_USER, 'POST', '/api/users', newUserData(), CURRENT_USER, 'POST', '/api/users', newUserData()
); );
lt.it.shouldBeAllowedWhenCalledByUser(CURRENT_USER, 'POST', '/api/users/logout'); lt.it.shouldBeAllowedWhenCalledByUser(CURRENT_USER, 'POST', '/api/users/logout');
@ -110,7 +110,7 @@ describe('access control - integration', function() {
this.res.statusCode, this.res.statusCode,
this.res.headers, this.res.headers,
this.res.text); this.res.text);
const user = this.res.body; var user = this.res.body;
assert.equal(user.password, undefined); assert.equal(user.password, undefined);
}); });
}); });
@ -137,7 +137,7 @@ describe('access control - integration', function() {
return '/api/users/' + this.randomUser.id; return '/api/users/' + this.randomUser.id;
} }
var userCounter; // eslint-disable-line no-var var userCounter;
function newUserData() { function newUserData() {
userCounter = userCounter ? ++userCounter : 1; userCounter = userCounter ? ++userCounter : 1;
@ -149,14 +149,14 @@ describe('access control - integration', function() {
}); });
describe('/banks', function() { describe('/banks', function() {
const SPECIAL_USER = {email: 'special@test.test', password: 'test'}; var SPECIAL_USER = {email: 'special@test.test', password: 'test'};
// define dynamic role that would only grant access when the authenticated user's email is equal to // define dynamic role that would only grant access when the authenticated user's email is equal to
// SPECIAL_USER's email // SPECIAL_USER's email
before(function() { before(function() {
const roleModel = app.registry.getModel('Role'); var roleModel = app.registry.getModel('Role');
const userModel = app.registry.getModel('user'); var userModel = app.registry.getModel('user');
roleModel.registerResolver('$dynamic-role', function(role, context, callback) { roleModel.registerResolver('$dynamic-role', function(role, context, callback) {
if (!(context && context.accessToken && context.accessToken.userId)) { if (!(context && context.accessToken && context.accessToken.userId)) {
@ -164,7 +164,7 @@ describe('access control - integration', function() {
if (callback) callback(null, false); if (callback) callback(null, false);
}); });
} }
const accessToken = context.accessToken; var accessToken = context.accessToken;
userModel.findById(accessToken.userId, function(err, user) { userModel.findById(accessToken.userId, function(err, user) {
if (err) { if (err) {
return callback(err, false); return callback(err, false);
@ -210,9 +210,9 @@ describe('access control - integration', function() {
}); });
describe('/accounts with replaceOnPUT true', function() { describe('/accounts with replaceOnPUT true', function() {
let count = 0; var count = 0;
before(function() { before(function() {
const roleModel = loopback.getModelByType(loopback.Role); var roleModel = loopback.getModelByType(loopback.Role);
roleModel.registerResolver('$dummy', function(role, context, callback) { roleModel.registerResolver('$dummy', function(role, context, callback) {
process.nextTick(function() { process.nextTick(function() {
if (context.remotingContext) { if (context.remotingContext) {
@ -250,9 +250,9 @@ describe('access control - integration', function() {
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PATCH', urlForAccount); lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PATCH', urlForAccount);
lt.describe.whenLoggedInAsUser(CURRENT_USER, function() { lt.describe.whenLoggedInAsUser(CURRENT_USER, function() {
let actId; var actId;
beforeEach(function(done) { beforeEach(function(done) {
const self = this; var self = this;
// Create an account under the given user // Create an account under the given user
app.models.accountWithReplaceOnPUTtrue.create({ app.models.accountWithReplaceOnPUTtrue.create({
userId: self.user.id, userId: self.user.id,
@ -314,9 +314,9 @@ describe('access control - integration', function() {
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PATCH', urlForAccount); lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PATCH', urlForAccount);
lt.describe.whenLoggedInAsUser(CURRENT_USER, function() { lt.describe.whenLoggedInAsUser(CURRENT_USER, function() {
let actId; var actId;
beforeEach(function(done) { beforeEach(function(done) {
const self = this; var self = this;
// Create an account under the given user // Create an account under the given user
app.models.accountWithReplaceOnPUTfalse.create({ app.models.accountWithReplaceOnPUTfalse.create({
userId: self.user.id, userId: self.user.id,

View File

@ -1,23 +1,23 @@
// Copyright IBM Corp. 2013,2019. All Rights Reserved. // Copyright IBM Corp. 2013,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const assert = require('assert'); var assert = require('assert');
const expect = require('./helpers/expect'); var expect = require('./helpers/expect');
const cookieParser = require('cookie-parser'); var cookieParser = require('cookie-parser');
const LoopBackContext = require('loopback-context'); var LoopBackContext = require('loopback-context');
const contextMiddleware = require('loopback-context').perRequest; var contextMiddleware = require('loopback-context').perRequest;
const loopback = require('../'); var loopback = require('../');
const extend = require('util')._extend; var extend = require('util')._extend;
const session = require('express-session'); var session = require('express-session');
const request = require('supertest'); var request = require('supertest');
let Token, ACL, User, TestModel; var Token, ACL, User, TestModel;
describe('loopback.token(options)', function() { describe('loopback.token(options)', function() {
let app; var app;
beforeEach(function(done) { beforeEach(function(done) {
app = loopback({localRegistry: true, loadBuiltinModels: true}); app = loopback({localRegistry: true, loadBuiltinModels: true});
app.dataSource('db', {connector: 'memory'}); app.dataSource('db', {connector: 'memory'});
@ -52,7 +52,7 @@ describe('loopback.token(options)', function() {
}); });
it('defaults to built-in AccessToken model', function() { it('defaults to built-in AccessToken model', function() {
const BuiltInToken = app.registry.getModel('AccessToken'); var BuiltInToken = app.registry.getModel('AccessToken');
app.model(BuiltInToken, {dataSource: 'db'}); app.model(BuiltInToken, {dataSource: 'db'});
app.enableAuth({dataSource: 'db'}); app.enableAuth({dataSource: 'db'});
@ -140,13 +140,13 @@ describe('loopback.token(options)', function() {
it('does not search default keys when searchDefaultTokenKeys is false', it('does not search default keys when searchDefaultTokenKeys is false',
function(done) { function(done) {
const tokenId = this.token.id; var tokenId = this.token.id;
const app = createTestApp( var app = createTestApp(
this.token, this.token,
{token: {searchDefaultTokenKeys: false}}, {token: {searchDefaultTokenKeys: false}},
done, done
); );
const agent = request.agent(app); var agent = request.agent(app);
// Set the token cookie // Set the token cookie
agent.get('/token').expect(200).end(function(err, res) { agent.get('/token').expect(200).end(function(err, res) {
@ -165,7 +165,7 @@ describe('loopback.token(options)', function() {
it('populates req.token from an authorization header with bearer token with base64', it('populates req.token from an authorization header with bearer token with base64',
function(done) { function(done) {
let token = this.token.id; var token = this.token.id;
token = 'Bearer ' + new Buffer(token).toString('base64'); token = 'Bearer ' + new Buffer(token).toString('base64');
createTestAppAndRequest(this.token, done) createTestAppAndRequest(this.token, done)
.get('/') .get('/')
@ -175,7 +175,7 @@ describe('loopback.token(options)', function() {
}); });
it('populates req.token from an authorization header with bearer token', function(done) { it('populates req.token from an authorization header with bearer token', function(done) {
let token = this.token.id; var token = this.token.id;
token = 'Bearer ' + token; token = 'Bearer ' + token;
createTestAppAndRequest(this.token, {token: {bearerTokenBase64Encoded: false}}, done) createTestAppAndRequest(this.token, {token: {bearerTokenBase64Encoded: false}}, done)
.get('/') .get('/')
@ -186,7 +186,7 @@ describe('loopback.token(options)', function() {
describe('populating req.token from HTTP Basic Auth formatted authorization header', function() { describe('populating req.token from HTTP Basic Auth formatted authorization header', function() {
it('parses "standalone-token"', function(done) { it('parses "standalone-token"', function(done) {
let token = this.token.id; var token = this.token.id;
token = 'Basic ' + new Buffer(token).toString('base64'); token = 'Basic ' + new Buffer(token).toString('base64');
createTestAppAndRequest(this.token, done) createTestAppAndRequest(this.token, done)
.get('/') .get('/')
@ -196,7 +196,7 @@ describe('loopback.token(options)', function() {
}); });
it('parses "token-and-empty-password:"', function(done) { it('parses "token-and-empty-password:"', function(done) {
let token = this.token.id + ':'; var token = this.token.id + ':';
token = 'Basic ' + new Buffer(token).toString('base64'); token = 'Basic ' + new Buffer(token).toString('base64');
createTestAppAndRequest(this.token, done) createTestAppAndRequest(this.token, done)
.get('/') .get('/')
@ -206,7 +206,7 @@ describe('loopback.token(options)', function() {
}); });
it('parses "ignored-user:token-is-password"', function(done) { it('parses "ignored-user:token-is-password"', function(done) {
let token = 'username:' + this.token.id; var token = 'username:' + this.token.id;
token = 'Basic ' + new Buffer(token).toString('base64'); token = 'Basic ' + new Buffer(token).toString('base64');
createTestAppAndRequest(this.token, done) createTestAppAndRequest(this.token, done)
.get('/') .get('/')
@ -216,7 +216,7 @@ describe('loopback.token(options)', function() {
}); });
it('parses "token-is-username:ignored-password"', function(done) { it('parses "token-is-username:ignored-password"', function(done) {
let token = this.token.id + ':password'; var token = this.token.id + ':password';
token = 'Basic ' + new Buffer(token).toString('base64'); token = 'Basic ' + new Buffer(token).toString('base64');
createTestAppAndRequest(this.token, done) createTestAppAndRequest(this.token, done)
.get('/') .get('/')
@ -227,7 +227,7 @@ describe('loopback.token(options)', function() {
}); });
it('populates req.token from a secure cookie', function(done) { it('populates req.token from a secure cookie', function(done) {
const app = createTestApp(this.token, done); var app = createTestApp(this.token, done);
request(app) request(app)
.get('/token') .get('/token')
@ -240,8 +240,8 @@ describe('loopback.token(options)', function() {
}); });
it('populates req.token from a header or a secure cookie', function(done) { it('populates req.token from a header or a secure cookie', function(done) {
const app = createTestApp(this.token, done); var app = createTestApp(this.token, done);
const id = this.token.id; var id = this.token.id;
request(app) request(app)
.get('/token') .get('/token')
.end(function(err, res) { .end(function(err, res) {
@ -255,9 +255,9 @@ describe('loopback.token(options)', function() {
it('rewrites url for the current user literal at the end without query', it('rewrites url for the current user literal at the end without query',
function(done) { function(done) {
const app = createTestApp(this.token, done); var app = createTestApp(this.token, done);
const id = this.token.id; var id = this.token.id;
const userId = this.token.userId; var userId = this.token.userId;
request(app) request(app)
.get('/users/me') .get('/users/me')
.set('authorization', id) .set('authorization', id)
@ -271,9 +271,9 @@ describe('loopback.token(options)', function() {
it('rewrites url for the current user literal at the end with query', it('rewrites url for the current user literal at the end with query',
function(done) { function(done) {
const app = createTestApp(this.token, done); var app = createTestApp(this.token, done);
const id = this.token.id; var id = this.token.id;
const userId = this.token.userId; var userId = this.token.userId;
request(app) request(app)
.get('/users/me?state=1') .get('/users/me?state=1')
.set('authorization', id) .set('authorization', id)
@ -287,9 +287,9 @@ describe('loopback.token(options)', function() {
it('rewrites url for the current user literal in the middle', it('rewrites url for the current user literal in the middle',
function(done) { function(done) {
const app = createTestApp(this.token, done); var app = createTestApp(this.token, done);
const id = this.token.id; var id = this.token.id;
const userId = this.token.userId; var userId = this.token.userId;
request(app) request(app)
.get('/users/me/1') .get('/users/me/1')
.set('authorization', id) .set('authorization', id)
@ -303,7 +303,7 @@ describe('loopback.token(options)', function() {
it('generates a 401 on a current user literal route without an authToken', it('generates a 401 on a current user literal route without an authToken',
function(done) { function(done) {
const app = createTestApp(null, done); var app = createTestApp(null, done);
request(app) request(app)
.get('/users/me') .get('/users/me')
.set('authorization', null) .set('authorization', null)
@ -311,19 +311,9 @@ describe('loopback.token(options)', function() {
.end(done); .end(done);
}); });
it('generates a 401 on a current user literal route with empty authToken',
function(done) {
const app = createTestApp(null, done);
request(app)
.get('/users/me')
.set('authorization', '')
.expect(401)
.end(done);
});
it('generates a 401 on a current user literal route with invalid authToken', it('generates a 401 on a current user literal route with invalid authToken',
function(done) { function(done) {
const app = createTestApp(this.token, done); var app = createTestApp(this.token, done);
request(app) request(app)
.get('/users/me') .get('/users/me')
.set('Authorization', 'invald-token-id') .set('Authorization', 'invald-token-id')
@ -332,7 +322,7 @@ describe('loopback.token(options)', function() {
}); });
it('skips when req.token is already present', function(done) { it('skips when req.token is already present', function(done) {
const tokenStub = {id: 'stub id'}; var tokenStub = {id: 'stub id'};
app.use(function(req, res, next) { app.use(function(req, res, next) {
req.accessToken = tokenStub; req.accessToken = tokenStub;
@ -358,7 +348,7 @@ describe('loopback.token(options)', function() {
describe('loading multiple instances of token middleware', function() { describe('loading multiple instances of token middleware', function() {
it('skips when req.token is already present and no further options are set', it('skips when req.token is already present and no further options are set',
function(done) { function(done) {
const tokenStub = {id: 'stub id'}; var tokenStub = {id: 'stub id'};
app.use(function(req, res, next) { app.use(function(req, res, next) {
req.accessToken = tokenStub; req.accessToken = tokenStub;
@ -384,7 +374,7 @@ describe('loopback.token(options)', function() {
it('does not overwrite valid existing token (has "id" property) ' + it('does not overwrite valid existing token (has "id" property) ' +
' when overwriteExistingToken is falsy', ' when overwriteExistingToken is falsy',
function(done) { function(done) {
const tokenStub = {id: 'stub id'}; var tokenStub = {id: 'stub id'};
app.use(function(req, res, next) { app.use(function(req, res, next) {
req.accessToken = tokenStub; req.accessToken = tokenStub;
@ -413,7 +403,7 @@ describe('loopback.token(options)', function() {
it('overwrites invalid existing token (is !== undefined and has no "id" property) ' + it('overwrites invalid existing token (is !== undefined and has no "id" property) ' +
' when enableDoublecheck is true', ' when enableDoublecheck is true',
function(done) { function(done) {
const token = this.token; var token = this.token;
app.use(function(req, res, next) { app.use(function(req, res, next) {
req.accessToken = null; req.accessToken = null;
next(); next();
@ -446,8 +436,8 @@ describe('loopback.token(options)', function() {
it('overwrites existing token when enableDoublecheck ' + it('overwrites existing token when enableDoublecheck ' +
'and overwriteExistingToken options are truthy', 'and overwriteExistingToken options are truthy',
function(done) { function(done) {
const token = this.token; var token = this.token;
const tokenStub = {id: 'stub id'}; var tokenStub = {id: 'stub id'};
app.use(function(req, res, next) { app.use(function(req, res, next) {
req.accessToken = tokenStub; req.accessToken = tokenStub;
@ -521,7 +511,7 @@ describe('AccessToken', function() {
it('allows eternal tokens when enabled by User.allowEternalTokens', it('allows eternal tokens when enabled by User.allowEternalTokens',
function(done) { function(done) {
const Token = givenLocalTokenModel(); var Token = givenLocalTokenModel();
// Overwrite User settings - enable eternal tokens // Overwrite User settings - enable eternal tokens
Token.app.models.User.settings.allowEternalTokens = true; Token.app.models.User.settings.allowEternalTokens = true;
@ -541,8 +531,8 @@ describe('AccessToken', function() {
beforeEach(createTestingToken); beforeEach(createTestingToken);
it('supports two-arg variant with no options', function(done) { it('supports two-arg variant with no options', function(done) {
const expectedTokenId = this.token.id; var expectedTokenId = this.token.id;
const req = mockRequest({ var req = mockRequest({
headers: {'authorization': expectedTokenId}, headers: {'authorization': expectedTokenId},
}); });
@ -556,14 +546,14 @@ describe('AccessToken', function() {
}); });
it('allows getIdForRequest() to be overridden', function(done) { it('allows getIdForRequest() to be overridden', function(done) {
const expectedTokenId = this.token.id; var expectedTokenId = this.token.id;
const current = Token.getIdForRequest; var current = Token.getIdForRequest;
let called = false; var called = false;
Token.getIdForRequest = function(req, options) { Token.getIdForRequest = function(req, options) {
called = true; called = true;
return expectedTokenId; return expectedTokenId;
}; };
const req = mockRequest({ var req = mockRequest({
headers: {'authorization': 'dummy'}, headers: {'authorization': 'dummy'},
}); });
@ -579,16 +569,16 @@ describe('AccessToken', function() {
}); });
it('allows resolve() to be overridden', function(done) { it('allows resolve() to be overridden', function(done) {
const expectedTokenId = this.token.id; var expectedTokenId = this.token.id;
const current = Token.resolve; var current = Token.resolve;
let called = false; var called = false;
Token.resolve = function(id, cb) { Token.resolve = function(id, cb) {
called = true; called = true;
process.nextTick(function() { process.nextTick(function() {
cb(null, {id: expectedTokenId}); cb(null, {id: expectedTokenId});
}); });
}; };
const req = mockRequest({ var req = mockRequest({
headers: {'authorization': expectedTokenId}, headers: {'authorization': expectedTokenId},
}); });
@ -615,14 +605,14 @@ describe('AccessToken', function() {
param: function(name) { return this._params[name]; }, param: function(name) { return this._params[name]; },
header: function(name) { return this.headers[name]; }, header: function(name) { return this.headers[name]; },
}, },
opts, opts
); );
} }
}); });
}); });
describe('app.enableAuth()', function() { describe('app.enableAuth()', function() {
let app; var app;
beforeEach(function setupAuthWithModels() { beforeEach(function setupAuthWithModels() {
app = loopback({localRegistry: true, loadBuiltinModels: true}); app = loopback({localRegistry: true, loadBuiltinModels: true});
app.dataSource('db', {connector: 'memory'}); app.dataSource('db', {connector: 'memory'});
@ -653,7 +643,7 @@ describe('app.enableAuth()', function() {
return done(err); return done(err);
} }
const errorResponse = res.body.error; var errorResponse = res.body.error;
assert(errorResponse); assert(errorResponse);
assert.equal(errorResponse.code, 'AUTHORIZATION_REQUIRED'); assert.equal(errorResponse.code, 'AUTHORIZATION_REQUIRED');
@ -671,7 +661,7 @@ describe('app.enableAuth()', function() {
return done(err); return done(err);
} }
const errorResponse = res.body.error; var errorResponse = res.body.error;
assert(errorResponse); assert(errorResponse);
assert.equal(errorResponse.code, 'ACCESS_DENIED'); assert.equal(errorResponse.code, 'ACCESS_DENIED');
@ -689,7 +679,7 @@ describe('app.enableAuth()', function() {
return done(err); return done(err);
} }
const errorResponse = res.body.error; var errorResponse = res.body.error;
assert(errorResponse); assert(errorResponse);
assert.equal(errorResponse.code, 'MODEL_NOT_FOUND'); assert.equal(errorResponse.code, 'MODEL_NOT_FOUND');
@ -707,7 +697,7 @@ describe('app.enableAuth()', function() {
return done(err); return done(err);
} }
const errorResponse = res.body.error; var errorResponse = res.body.error;
assert(errorResponse); assert(errorResponse);
assert.equal(errorResponse.code, 'AUTHORIZATION_REQUIRED'); assert.equal(errorResponse.code, 'AUTHORIZATION_REQUIRED');
@ -716,9 +706,9 @@ describe('app.enableAuth()', function() {
}); });
it('stores token in the context', function(done) { it('stores token in the context', function(done) {
const TestModel = app.registry.createModel('TestModel', {base: 'Model'}); var TestModel = app.registry.createModel('TestModel', {base: 'Model'});
TestModel.getToken = function(cb) { TestModel.getToken = function(cb) {
const ctx = LoopBackContext.getCurrentContext(); var ctx = LoopBackContext.getCurrentContext();
cb(null, ctx && ctx.get('accessToken') || null); cb(null, ctx && ctx.get('accessToken') || null);
}; };
TestModel.remoteMethod('getToken', { TestModel.remoteMethod('getToken', {
@ -733,7 +723,7 @@ describe('app.enableAuth()', function() {
app.use(loopback.token({model: Token})); app.use(loopback.token({model: Token}));
app.use(loopback.rest()); app.use(loopback.rest());
const token = this.token; var token = this.token;
request(app) request(app)
.get('/TestModels/token?_format=json') .get('/TestModels/token?_format=json')
.set('authorization', token.id) .set('authorization', token.id)
@ -772,7 +762,7 @@ describe('app.enableAuth()', function() {
}); });
function createTestingToken(done) { function createTestingToken(done) {
const test = this; var test = this;
Token.create({userId: '123'}, function(err, token) { Token.create({userId: '123'}, function(err, token) {
if (err) return done(err); if (err) return done(err);
@ -783,7 +773,7 @@ function createTestingToken(done) {
} }
function createTestAppAndRequest(testToken, settings, done) { function createTestAppAndRequest(testToken, settings, done) {
const app = createTestApp(testToken, settings, done); var app = createTestApp(testToken, settings, done);
return request(app); return request(app);
} }
@ -793,14 +783,14 @@ function createTestApp(testToken, settings, done) {
settings = {}; settings = {};
} }
const appSettings = settings.app || {}; var appSettings = settings.app || {};
const modelSettings = settings.model || {}; var modelSettings = settings.model || {};
const tokenSettings = extend({ var tokenSettings = extend({
model: Token, model: Token,
currentUserLiteral: 'me', currentUserLiteral: 'me',
}, settings.token); }, settings.token);
const app = loopback({localRegistry: true, loadBuiltinModels: true}); var app = loopback({localRegistry: true, loadBuiltinModels: true});
app.dataSource('db', {connector: 'memory'}); app.dataSource('db', {connector: 'memory'});
app.use(cookieParser('secret')); app.use(cookieParser('secret'));
@ -824,7 +814,7 @@ function createTestApp(testToken, settings, done) {
res.status(req.accessToken ? 200 : 401).end(); res.status(req.accessToken ? 200 : 401).end();
}); });
app.use('/users/:uid', function(req, res) { app.use('/users/:uid', function(req, res) {
const result = {userId: req.params.uid}; var result = {userId: req.params.uid};
if (req.query.state) { if (req.query.state) {
result.state = req.query.state; result.state = req.query.state;
} else if (req.url !== '/') { } else if (req.url !== '/') {
@ -839,7 +829,7 @@ function createTestApp(testToken, settings, done) {
app.set(key, appSettings[key]); app.set(key, appSettings[key]);
}); });
const modelOptions = { var modelOptions = {
acls: [ acls: [
{ {
principalType: 'ROLE', principalType: 'ROLE',
@ -855,20 +845,20 @@ function createTestApp(testToken, settings, done) {
modelOptions[key] = modelSettings[key]; modelOptions[key] = modelSettings[key];
}); });
const TestModel = app.registry.createModel('test', {}, modelOptions); var TestModel = app.registry.createModel('test', {}, modelOptions);
app.model(TestModel, {dataSource: 'db'}); app.model(TestModel, {dataSource: 'db'});
return app; return app;
} }
function givenLocalTokenModel() { function givenLocalTokenModel() {
const app = loopback({localRegistry: true, loadBuiltinModels: true}); var app = loopback({localRegistry: true, loadBuiltinModels: true});
app.dataSource('db', {connector: 'memory'}); app.dataSource('db', {connector: 'memory'});
const User = app.registry.getModel('User'); var User = app.registry.getModel('User');
app.model(User, {dataSource: 'db'}); app.model(User, {dataSource: 'db'});
const Token = app.registry.getModel('AccessToken'); var Token = app.registry.getModel('AccessToken');
app.model(Token, {dataSource: 'db'}); app.model(Token, {dataSource: 'db'});
return Token; return Token;

View File

@ -1,27 +1,34 @@
// Copyright IBM Corp. 2013,2019. All Rights Reserved. // Copyright IBM Corp. 2013,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const assert = require('assert'); var assert = require('assert');
const expect = require('./helpers/expect'); var expect = require('./helpers/expect');
const loopback = require('../index'); var loopback = require('../index');
const Scope = loopback.Scope; var Scope = loopback.Scope;
const ACL = loopback.ACL; var ACL = loopback.ACL;
const request = require('supertest'); var request = require('supertest');
const Promise = require('bluebird'); var Promise = require('bluebird');
const supertest = require('supertest'); var supertest = require('supertest');
const Role = loopback.Role; var Role = loopback.Role;
const RoleMapping = loopback.RoleMapping; var RoleMapping = loopback.RoleMapping;
const User = loopback.User; var User = loopback.User;
const async = require('async'); var testModel;
// Speed up the password hashing algorithm for tests // Speed up the password hashing algorithm for tests
User.settings.saltWorkFactor = 4; User.settings.saltWorkFactor = 4;
let ds = null; function checkResult(err, result) {
let testModel; // console.log(err, result);
assert(!err);
}
var ds = null;
before(function() {
ds = loopback.createDataSource({connector: loopback.Memory});
});
describe('ACL model', function() { describe('ACL model', function() {
it('provides DEFAULT_SCOPE constant', () => { it('provides DEFAULT_SCOPE constant', () => {
@ -30,9 +37,18 @@ describe('ACL model', function() {
}); });
describe('security scopes', function() { describe('security scopes', function() {
beforeEach(setupTestModels); beforeEach(function() {
var ds = this.ds = loopback.createDataSource({connector: loopback.Memory});
testModel = loopback.PersistedModel.extend('testModel');
ACL.attachTo(ds);
Role.attachTo(ds);
RoleMapping.attachTo(ds);
User.attachTo(ds);
Scope.attachTo(ds);
testModel.attachTo(ds);
});
it('should allow access to models for the given scope by wildcard', function(done) { it('should allow access to models for the given scope by wildcard', function() {
Scope.create({name: 'userScope', description: 'access user information'}, Scope.create({name: 'userScope', description: 'access user information'},
function(err, scope) { function(err, scope) {
ACL.create({ ACL.create({
@ -40,19 +56,14 @@ describe('security scopes', function() {
model: 'User', property: ACL.ALL, model: 'User', property: ACL.ALL,
accessType: ACL.ALL, permission: ACL.ALLOW, accessType: ACL.ALL, permission: ACL.ALLOW,
}, function(err, resource) { }, function(err, resource) {
async.parallel([ Scope.checkPermission('userScope', 'User', ACL.ALL, ACL.ALL, checkResult);
cb => Scope.checkPermission('userScope', 'User', ACL.ALL, ACL.ALL, cb), Scope.checkPermission('userScope', 'User', 'name', ACL.ALL, checkResult);
cb => Scope.checkPermission('userScope', 'User', 'name', ACL.ALL, cb), Scope.checkPermission('userScope', 'User', 'name', ACL.READ, checkResult);
cb => Scope.checkPermission('userScope', 'User', 'name', ACL.READ, cb),
], (err) => {
assert.ifError(err);
done();
});
}); });
}); });
}); });
it('should allow access to models for the given scope', function(done) { it('should allow access to models for the given scope', function() {
Scope.create({name: 'testModelScope', description: 'access testModel information'}, Scope.create({name: 'testModelScope', description: 'access testModel information'},
function(err, scope) { function(err, scope) {
ACL.create({ ACL.create({
@ -64,20 +75,22 @@ describe('security scopes', function() {
model: 'testModel', property: 'name', model: 'testModel', property: 'name',
accessType: ACL.WRITE, permission: ACL.DENY, accessType: ACL.WRITE, permission: ACL.DENY,
}, function(err, resource) { }, function(err, resource) {
async.parallel([ // console.log(resource);
cb => Scope.checkPermission('testModelScope', 'testModel', ACL.ALL, ACL.ALL, cb), Scope.checkPermission('testModelScope', 'testModel', ACL.ALL, ACL.ALL,
cb => Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.ALL, cb), function(err, perm) {
cb => Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.READ, cb), assert(perm.permission === ACL.DENY); // because name.WRITE == DENY
cb => Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.WRITE, cb), });
], (err, perms) => { Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.ALL,
if (err) return done(err); function(err, perm) {
assert.deepEqual(perms.map(p => p.permission), [ assert(perm.permission === ACL.DENY); // because name.WRITE == DENY
ACL.DENY, });
ACL.DENY, Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.READ,
ACL.ALLOW, function(err, perm) {
ACL.DENY, assert(perm.permission === ACL.ALLOW);
]); });
done(); Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.WRITE,
function(err, perm) {
assert(perm.permission === ACL.DENY);
}); });
}); });
}); });
@ -86,8 +99,6 @@ describe('security scopes', function() {
}); });
describe('security ACLs', function() { describe('security ACLs', function() {
beforeEach(setupTestModels);
it('supports checkPermission() returning a promise', function() { it('supports checkPermission() returning a promise', function() {
return ACL.create({ return ACL.create({
principalType: ACL.USER, principalType: ACL.USER,
@ -105,46 +116,8 @@ describe('security ACLs', function() {
}); });
}); });
it('supports ACL rules with a wildcard for models', function() {
const A_USER_ID = 'a-test-user';
// By default, access is allowed to all users
return assertPermission(ACL.ALLOW, 'initial state')
// An ACL rule applying to all models denies access to everybody
.then(() => ACL.create({
model: '*',
property: '*',
accessType: '*',
principalType: 'ROLE',
principalId: '$everyone',
permission: 'DENY',
}))
.then(() => assertPermission(ACL.DENY, 'all denied'))
// A rule for a specific model overrides the rule matching all models
.then(() => ACL.create({
model: testModel.modelName,
property: '*',
accessType: '*',
principalType: ACL.USER,
principalId: A_USER_ID,
permission: ACL.ALLOW,
}))
.then(() => assertPermission(ACL.ALLOW, 'only a single model allowed'));
function assertPermission(expectedPermission, msg) {
return ACL.checkAccessForContext({
principals: [{type: ACL.USER, id: A_USER_ID}],
model: testModel.modelName,
accessType: ACL.ALL,
}).then(accessContext => {
const actual = accessContext.isAllowed() ? ACL.ALLOW : ACL.DENY;
expect(actual, msg).to.equal(expectedPermission);
});
}
});
it('supports checkAccessForContext() returning a promise', function() { it('supports checkAccessForContext() returning a promise', function() {
const testModel = ds.createModel('testModel', { var testModel = ds.createModel('testModel', {
acls: [ acls: [
{principalType: ACL.USER, principalId: 'u001', {principalType: ACL.USER, principalId: 'u001',
accessType: ACL.ALL, permission: ACL.ALLOW}, accessType: ACL.ALL, permission: ACL.ALLOW},
@ -162,7 +135,7 @@ describe('security ACLs', function() {
}); });
it('should order ACL entries based on the matching score', function() { it('should order ACL entries based on the matching score', function() {
let acls = [ var acls = [
{ {
'model': 'account', 'model': 'account',
'accessType': '*', 'accessType': '*',
@ -184,7 +157,7 @@ describe('security ACLs', function() {
'principalType': 'ROLE', 'principalType': 'ROLE',
'principalId': '$everyone', 'principalId': '$everyone',
}]; }];
const req = { var req = {
model: 'account', model: 'account',
property: 'find', property: 'find',
accessType: 'WRITE', accessType: 'WRITE',
@ -192,7 +165,7 @@ describe('security ACLs', function() {
acls = acls.map(function(a) { return new ACL(a); }); acls = acls.map(function(a) { return new ACL(a); });
const perm = ACL.resolvePermission(acls, req); var perm = ACL.resolvePermission(acls, req);
// remove the registry from AccessRequest instance to ease asserting // remove the registry from AccessRequest instance to ease asserting
delete perm.registry; delete perm.registry;
assert.deepEqual(perm, {model: 'account', assert.deepEqual(perm, {model: 'account',
@ -214,7 +187,7 @@ describe('security ACLs', function() {
}); });
it('should order ACL entries based on the matching score even with wildcard req', function() { it('should order ACL entries based on the matching score even with wildcard req', function() {
let acls = [ var acls = [
{ {
'model': 'account', 'model': 'account',
'accessType': '*', 'accessType': '*',
@ -229,7 +202,7 @@ describe('security ACLs', function() {
'principalType': 'ROLE', 'principalType': 'ROLE',
'principalId': '$owner', 'principalId': '$owner',
}]; }];
const req = { var req = {
model: 'account', model: 'account',
property: '*', property: '*',
accessType: 'WRITE', accessType: 'WRITE',
@ -237,7 +210,7 @@ describe('security ACLs', function() {
acls = acls.map(function(a) { return new ACL(a); }); acls = acls.map(function(a) { return new ACL(a); });
const perm = ACL.resolvePermission(acls, req); var perm = ACL.resolvePermission(acls, req);
// remove the registry from AccessRequest instance to ease asserting. // remove the registry from AccessRequest instance to ease asserting.
// Check the above test case for more info. // Check the above test case for more info.
delete perm.registry; delete perm.registry;
@ -248,7 +221,7 @@ describe('security ACLs', function() {
methodNames: []}); methodNames: []});
}); });
it('should allow access to models for the given principal by wildcard', function(done) { it('should allow access to models for the given principal by wildcard', function() {
// jscs:disable validateIndentation // jscs:disable validateIndentation
ACL.create({ ACL.create({
principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL, principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL,
@ -258,22 +231,18 @@ describe('security ACLs', function() {
principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL, principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL,
accessType: ACL.READ, permission: ACL.DENY, accessType: ACL.READ, permission: ACL.DENY,
}, function(err, acl) { }, function(err, acl) {
async.parallel([ ACL.checkPermission(ACL.USER, 'u001', 'User', 'name', ACL.READ, function(err, perm) {
cb => ACL.checkPermission(ACL.USER, 'u001', 'User', 'name', ACL.READ, cb), assert(perm.permission === ACL.DENY);
cb => ACL.checkPermission(ACL.USER, 'u001', 'User', 'name', ACL.ALL, cb), });
], (err, perms) => {
if (err) return done(err); ACL.checkPermission(ACL.USER, 'u001', 'User', 'name', ACL.ALL, function(err, perm) {
assert.deepEqual(perms.map(p => p.permission), [ assert(perm.permission === ACL.DENY);
ACL.DENY,
ACL.DENY,
]);
done();
}); });
}); });
}); });
}); });
it('should allow access to models by exception', function(done) { it('should allow access to models by exception', function() {
ACL.create({ ACL.create({
principalType: ACL.USER, principalId: 'u001', model: 'testModel', property: ACL.ALL, principalType: ACL.USER, principalId: 'u001', model: 'testModel', property: ACL.ALL,
accessType: ACL.ALL, permission: ACL.DENY, accessType: ACL.ALL, permission: ACL.DENY,
@ -286,32 +255,42 @@ describe('security ACLs', function() {
principalType: ACL.USER, principalId: 'u002', model: 'testModel', property: ACL.ALL, principalType: ACL.USER, principalId: 'u002', model: 'testModel', property: ACL.ALL,
accessType: ACL.EXECUTE, permission: ACL.ALLOW, accessType: ACL.EXECUTE, permission: ACL.ALLOW,
}, function(err, acl) { }, function(err, acl) {
async.parallel([ ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.READ,
cb => ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.READ, cb), function(err, perm) {
cb => ACL.checkPermission(ACL.USER, 'u001', 'testModel', ACL.ALL, ACL.READ, cb), assert(perm.permission === ACL.ALLOW);
cb => ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.WRITE, cb), });
cb => ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.ALL, cb),
cb => ACL.checkPermission(ACL.USER, 'u002', 'testModel', 'name', ACL.WRITE, cb), ACL.checkPermission(ACL.USER, 'u001', 'testModel', ACL.ALL, ACL.READ,
cb => ACL.checkPermission(ACL.USER, 'u002', 'testModel', 'name', ACL.READ, cb), function(err, perm) {
], (err, perms) => { assert(perm.permission === ACL.ALLOW);
if (err) return done(err); });
assert.deepEqual(perms.map(p => p.permission), [
ACL.ALLOW, ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.WRITE,
ACL.ALLOW, function(err, perm) {
ACL.DENY, assert(perm.permission === ACL.DENY);
ACL.DENY, });
ACL.ALLOW,
ACL.ALLOW, ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.ALL,
]); function(err, perm) {
done(); assert(perm.permission === ACL.DENY);
});
ACL.checkPermission(ACL.USER, 'u002', 'testModel', 'name', ACL.WRITE,
function(err, perm) {
assert(perm.permission === ACL.ALLOW);
});
ACL.checkPermission(ACL.USER, 'u002', 'testModel', 'name', ACL.READ,
function(err, perm) {
assert(perm.permission === ACL.ALLOW);
}); });
}); });
}); });
}); });
}); });
it('should honor defaultPermission from the model', function(done) { it('should honor defaultPermission from the model', function() {
const Customer = ds.createModel('Customer', { var Customer = ds.createModel('Customer', {
name: { name: {
type: String, type: String,
acls: [ acls: [
@ -331,23 +310,22 @@ describe('security ACLs', function() {
// ACL default permission is to DENY for model Customer // ACL default permission is to DENY for model Customer
Customer.settings.defaultPermission = ACL.DENY; Customer.settings.defaultPermission = ACL.DENY;
async.parallel([ ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.WRITE,
cb => ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.WRITE, cb), function(err, perm) {
cb => ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.READ, cb), assert(perm.permission === ACL.DENY);
cb => ACL.checkPermission(ACL.USER, 'u002', 'Customer', 'name', ACL.WRITE, cb), });
], (err, perms) => {
if (err) return done(err); ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.READ, function(err, perm) {
assert.deepEqual(perms.map(p => p.permission), [ assert(perm.permission === ACL.ALLOW);
ACL.DENY, });
ACL.ALLOW,
ACL.DENY, ACL.checkPermission(ACL.USER, 'u002', 'Customer', 'name', ACL.WRITE, function(err, perm) {
]); assert(perm.permission === ACL.DENY);
done();
}); });
}); });
it('should honor static ACLs from the model', function(done) { it('should honor static ACLs from the model', function() {
const Customer = ds.createModel('Customer', { var Customer = ds.createModel('Customer', {
name: { name: {
type: String, type: String,
acls: [ acls: [
@ -374,27 +352,34 @@ describe('security ACLs', function() {
]; ];
*/ */
async.parallel([ ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.WRITE,
cb => ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.WRITE, cb), function(err, perm) {
cb => ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.READ, cb), assert(perm.permission === ACL.DENY);
cb => ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.ALL, cb), });
cb => ACL.checkPermission(ACL.USER, 'u002', 'Customer', 'name', ACL.READ, cb),
cb => ACL.checkPermission(ACL.USER, 'u003', 'Customer', 'name', ACL.WRITE, cb), ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.READ,
], (err, perms) => { function(err, perm) {
if (err) return done(err); assert(perm.permission === ACL.ALLOW);
assert.deepEqual(perms.map(p => p.permission), [ });
ACL.DENY,
ACL.ALLOW, ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.ALL,
ACL.ALLOW, function(err, perm) {
ACL.ALLOW, assert(perm.permission === ACL.ALLOW);
ACL.DENY, });
]);
done(); ACL.checkPermission(ACL.USER, 'u002', 'Customer', 'name', ACL.READ,
function(err, perm) {
assert(perm.permission === ACL.ALLOW);
});
ACL.checkPermission(ACL.USER, 'u003', 'Customer', 'name', ACL.WRITE,
function(err, perm) {
assert(perm.permission === ACL.DENY);
}); });
}); });
it('should filter static ACLs by model/property', function() { it('should filter static ACLs by model/property', function() {
const Model1 = ds.createModel('Model1', { var Model1 = ds.createModel('Model1', {
name: { name: {
type: String, type: String,
acls: [ acls: [
@ -415,7 +400,7 @@ describe('security ACLs', function() {
], ],
}); });
let staticACLs = ACL.getStaticACLs('Model1', 'name'); var staticACLs = ACL.getStaticACLs('Model1', 'name');
assert(staticACLs.length === 3); assert(staticACLs.length === 3);
staticACLs = ACL.getStaticACLs('Model1', 'findOne'); staticACLs = ACL.getStaticACLs('Model1', 'findOne');
@ -426,17 +411,18 @@ describe('security ACLs', function() {
assert(staticACLs[0].property === 'findById'); assert(staticACLs[0].property === 'findById');
}); });
it('should check access against LDL, ACL, and Role', function(done) { it('should check access against LDL, ACL, and Role', function() {
const log = function() {}; // var log = console.log;
var log = function() {};
// Create // Create
User.create({name: 'Raymond', email: 'x@y.com', password: 'foobar'}, function(err, user) { User.create({name: 'Raymond', email: 'x@y.com', password: 'foobar'}, function(err, user) {
log('User: ', user.toObject()); log('User: ', user.toObject());
const userId = user.id; var userId = user.id;
// Define a model with static ACLs // Define a model with static ACLs
const Customer = ds.createModel('Customer', { var Customer = ds.createModel('Customer', {
name: { name: {
type: String, type: String,
acls: [ acls: [
@ -475,8 +461,6 @@ describe('security ACLs', function() {
}, function(err, acl) { }, function(err, acl) {
log('ACL 2: ', acl.toObject()); log('ACL 2: ', acl.toObject());
async.parallel([
cb => {
ACL.checkAccessForContext({ ACL.checkAccessForContext({
principals: [ principals: [
{type: ACL.USER, id: userId}, {type: ACL.USER, id: userId},
@ -485,12 +469,9 @@ describe('security ACLs', function() {
property: 'name', property: 'name',
accessType: ACL.READ, accessType: ACL.READ,
}, function(err, access) { }, function(err, access) {
assert.ifError(err); assert(!err && access.permission === ACL.ALLOW);
assert.equal(access.permission, ACL.ALLOW);
cb();
}); });
},
cb => {
ACL.checkAccessForContext({ ACL.checkAccessForContext({
principals: [ principals: [
{type: ACL.ROLE, id: Role.EVERYONE}, {type: ACL.ROLE, id: Role.EVERYONE},
@ -499,11 +480,8 @@ describe('security ACLs', function() {
property: 'name', property: 'name',
accessType: ACL.READ, accessType: ACL.READ,
}, function(err, access) { }, function(err, access) {
assert.ifError(err); assert(!err && access.permission === ACL.DENY);
assert.equal(access.permission, ACL.DENY);
cb();
}); });
}], done);
}); });
}); });
}); });
@ -514,10 +492,10 @@ describe('security ACLs', function() {
describe('access check', function() { describe('access check', function() {
it('should occur before other remote hooks', function(done) { it('should occur before other remote hooks', function(done) {
const app = loopback(); var app = loopback();
const MyTestModel = app.registry.createModel('MyTestModel'); var MyTestModel = app.registry.createModel('MyTestModel');
let checkAccessCalled = false; var checkAccessCalled = false;
let beforeHookCalled = false; var beforeHookCalled = false;
app.use(loopback.rest()); app.use(loopback.rest());
app.set('remoting', {errorHandler: {debug: true, log: false}}); app.set('remoting', {errorHandler: {debug: true, log: false}});
@ -527,9 +505,9 @@ describe('access check', function() {
// fake / spy on the checkAccess method // fake / spy on the checkAccess method
MyTestModel.checkAccess = function() { MyTestModel.checkAccess = function() {
const cb = arguments[arguments.length - 1]; var cb = arguments[arguments.length - 1];
checkAccessCalled = true; checkAccessCalled = true;
const allowed = true; var allowed = true;
cb(null, allowed); cb(null, allowed);
}; };
@ -554,8 +532,8 @@ describe('access check', function() {
}); });
describe('authorized roles propagation in RemotingContext', function() { describe('authorized roles propagation in RemotingContext', function() {
let app, request, accessToken; var app, request, accessToken;
let models = {}; var models = {};
beforeEach(setupAppAndRequest); beforeEach(setupAppAndRequest);
@ -567,13 +545,13 @@ describe('authorized roles propagation in RemotingContext', function() {
]) ])
.then(makeAuthorizedHttpRequestOnMyTestModel) .then(makeAuthorizedHttpRequestOnMyTestModel)
.then(function() { .then(function() {
const ctx = models.MyTestModel.lastRemotingContext; var ctx = models.MyTestModel.lastRemotingContext;
expect(ctx.args.options.authorizedRoles).to.eql( expect(ctx.args.options.authorizedRoles).to.eql(
{ {
$everyone: true, $everyone: true,
$authenticated: true, $authenticated: true,
myRole: true, myRole: true,
}, }
); );
}); });
}); });
@ -586,12 +564,12 @@ describe('authorized roles propagation in RemotingContext', function() {
]) ])
.then(makeAuthorizedHttpRequestOnMyTestModel) .then(makeAuthorizedHttpRequestOnMyTestModel)
.then(function() { .then(function() {
const ctx = models.MyTestModel.lastRemotingContext; var ctx = models.MyTestModel.lastRemotingContext;
expect(ctx.args.options.authorizedRoles).to.eql( expect(ctx.args.options.authorizedRoles).to.eql(
{ {
$everyone: true, $everyone: true,
myRole: true, myRole: true,
}, }
); );
}); });
}); });
@ -607,10 +585,10 @@ describe('authorized roles propagation in RemotingContext', function() {
]) ])
.then(makeAuthorizedHttpRequestOnMyTestModel) .then(makeAuthorizedHttpRequestOnMyTestModel)
.then(function() { .then(function() {
const ctx = models.MyTestModel.lastRemotingContext; var ctx = models.MyTestModel.lastRemotingContext;
expect(ctx.args.options.authorizedRoles).to.eql( expect(ctx.args.options.authorizedRoles).to.eql(
// '$everyone' is not expected as default permission is DENY // '$everyone' is not expected as default permission is DENY
{myRole: true}, {myRole: true}
); );
}); });
}); });
@ -626,9 +604,6 @@ describe('authorized roles propagation in RemotingContext', function() {
app.enableAuth({dataSource: 'db'}); app.enableAuth({dataSource: 'db'});
models = app.models; models = app.models;
// Speed up the password hashing algorithm for tests
models.User.settings.saltWorkFactor = 4;
// creating a custom model // creating a custom model
const MyTestModel = app.registry.createModel('MyTestModel'); const MyTestModel = app.registry.createModel('MyTestModel');
app.model(MyTestModel, {dataSource: 'db'}); app.model(MyTestModel, {dataSource: 'db'});
@ -667,7 +642,7 @@ describe('authorized roles propagation in RemotingContext', function() {
}); });
}); });
return Promise.all(acls); return Promise.all(acls);
} };
function makeAuthorizedHttpRequestOnMyTestModel() { function makeAuthorizedHttpRequestOnMyTestModel() {
return request.get('/MyTestModels') return request.get('/MyTestModels')
@ -675,14 +650,3 @@ describe('authorized roles propagation in RemotingContext', function() {
.expect(200); .expect(200);
} }
}); });
function setupTestModels() {
ds = this.ds = loopback.createDataSource({connector: loopback.Memory});
testModel = loopback.PersistedModel.extend('testModel');
ACL.attachTo(ds);
Role.attachTo(ds);
RoleMapping.attachTo(ds);
User.attachTo(ds);
Scope.attachTo(ds);
testModel.attachTo(ds);
}

View File

@ -1,39 +1,39 @@
// Copyright IBM Corp. 2013,2019. All Rights Reserved. // Copyright IBM Corp. 2013,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const assert = require('assert'); var assert = require('assert');
const async = require('async'); var async = require('async');
const path = require('path'); var path = require('path');
const http = require('http'); var http = require('http');
const express = require('express'); var express = require('express');
const loopback = require('../'); var loopback = require('../');
const PersistedModel = loopback.PersistedModel; var PersistedModel = loopback.PersistedModel;
const describe = require('./util/describe'); var describe = require('./util/describe');
const expect = require('./helpers/expect'); var expect = require('./helpers/expect');
const it = require('./util/it'); var it = require('./util/it');
const request = require('supertest'); var request = require('supertest');
const sinon = require('sinon'); const sinon = require('sinon');
describe('app', function() { describe('app', function() {
let app; var app;
beforeEach(function() { beforeEach(function() {
app = loopback({localRegistry: true, loadBuiltinModels: true}); app = loopback({localRegistry: true, loadBuiltinModels: true});
}); });
describe.onServer('.middleware(phase, handler)', function() { describe.onServer('.middleware(phase, handler)', function() {
let steps; var steps;
beforeEach(function setup() { beforeEach(function setup() {
steps = []; steps = [];
}); });
it('runs middleware in phases', function(done) { it('runs middleware in phases', function(done) {
const PHASES = [ var PHASES = [
'initial', 'session', 'auth', 'parse', 'initial', 'session', 'auth', 'parse',
'routes', 'files', 'final', 'routes', 'files', 'final',
]; ];
@ -88,10 +88,10 @@ describe('app', function() {
return namedHandler(name); return namedHandler(name);
} }
let myHandler; var myHandler;
app.middleware('routes:before', app.middleware('routes:before',
myHandler = handlerThatAddsHandler('my-handler')); myHandler = handlerThatAddsHandler('my-handler'));
const found = app._findLayerByHandler(myHandler); var found = app._findLayerByHandler(myHandler);
expect(found).to.be.an('object'); expect(found).to.be.an('object');
expect(myHandler).to.equal(found.handle); expect(myHandler).to.equal(found.handle);
expect(found).have.property('phase', 'routes:before'); expect(found).have.property('phase', 'routes:before');
@ -106,35 +106,13 @@ describe('app', function() {
it('allows handlers to be wrapped as __NR_handler on express stack', it('allows handlers to be wrapped as __NR_handler on express stack',
function(done) { function(done) {
const myHandler = namedHandler('my-handler'); var myHandler = namedHandler('my-handler');
const wrappedHandler = function(req, res, next) { var wrappedHandler = function(req, res, next) {
myHandler(req, res, next); myHandler(req, res, next);
}; };
wrappedHandler['__NR_handler'] = myHandler; wrappedHandler['__NR_handler'] = myHandler;
app.middleware('routes:before', wrappedHandler); app.middleware('routes:before', wrappedHandler);
const found = app._findLayerByHandler(myHandler); var found = app._findLayerByHandler(myHandler);
expect(found).to.be.an('object');
expect(found).have.property('phase', 'routes:before');
executeMiddlewareHandlers(app, function(err) {
if (err) return done(err);
expect(steps).to.eql(['my-handler']);
done();
});
});
it('allows handlers to be wrapped as __appdynamicsProxyInfo__ on express stack',
function(done) {
const myHandler = namedHandler('my-handler');
const wrappedHandler = function(req, res, next) {
myHandler(req, res, next);
};
wrappedHandler['__appdynamicsProxyInfo__'] = {
orig: myHandler,
};
app.middleware('routes:before', wrappedHandler);
const found = app._findLayerByHandler(myHandler);
expect(found).to.be.an('object'); expect(found).to.be.an('object');
expect(found).have.property('phase', 'routes:before'); expect(found).have.property('phase', 'routes:before');
executeMiddlewareHandlers(app, function(err) { executeMiddlewareHandlers(app, function(err) {
@ -148,13 +126,13 @@ describe('app', function() {
it('allows handlers to be wrapped as a property on express stack', it('allows handlers to be wrapped as a property on express stack',
function(done) { function(done) {
const myHandler = namedHandler('my-handler'); var myHandler = namedHandler('my-handler');
const wrappedHandler = function(req, res, next) { var wrappedHandler = function(req, res, next) {
myHandler(req, res, next); myHandler(req, res, next);
}; };
wrappedHandler['__handler'] = myHandler; wrappedHandler['__handler'] = myHandler;
app.middleware('routes:before', wrappedHandler); app.middleware('routes:before', wrappedHandler);
const found = app._findLayerByHandler(myHandler); var found = app._findLayerByHandler(myHandler);
expect(found).to.be.an('object'); expect(found).to.be.an('object');
expect(found).have.property('phase', 'routes:before'); expect(found).have.property('phase', 'routes:before');
executeMiddlewareHandlers(app, function(err) { executeMiddlewareHandlers(app, function(err) {
@ -167,7 +145,7 @@ describe('app', function() {
}); });
it('injects error from previous phases into the router', function(done) { it('injects error from previous phases into the router', function(done) {
const expectedError = new Error('expected error'); var expectedError = new Error('expected error');
app.middleware('initial', function(req, res, next) { app.middleware('initial', function(req, res, next) {
steps.push('initial'); steps.push('initial');
@ -193,7 +171,7 @@ describe('app', function() {
}); });
it('passes unhandled error to callback', function(done) { it('passes unhandled error to callback', function(done) {
const expectedError = new Error('expected error'); var expectedError = new Error('expected error');
app.middleware('initial', function(req, res, next) { app.middleware('initial', function(req, res, next) {
next(expectedError); next(expectedError);
@ -207,8 +185,8 @@ describe('app', function() {
}); });
it('passes errors to error handlers in the same phase', function(done) { it('passes errors to error handlers in the same phase', function(done) {
const expectedError = new Error('this should be handled by middleware'); var expectedError = new Error('this should be handled by middleware');
let handledError; var handledError;
app.middleware('initial', function(req, res, next) { app.middleware('initial', function(req, res, next) {
// continue in the next tick, this verifies that the next // continue in the next tick, this verifies that the next
@ -245,7 +223,7 @@ describe('app', function() {
expect(steps).to.eql(['/scope', '/scope/item']); expect(steps).to.eql(['/scope', '/scope/item']);
done(); done();
}, }
); );
}); });
@ -261,7 +239,7 @@ describe('app', function() {
expect(steps).to.eql(['/a', '/b']); expect(steps).to.eql(['/a', '/b']);
done(); done();
}, }
); );
}); });
@ -277,7 +255,7 @@ describe('app', function() {
expect(steps).to.eql(['/a', '/b', '/scope']); expect(steps).to.eql(['/a', '/b', '/scope']);
done(); done();
}, }
); );
}); });
@ -298,7 +276,7 @@ describe('app', function() {
}); });
it('exposes express helpers on req and res objects', function(done) { it('exposes express helpers on req and res objects', function(done) {
let req, res; var req, res;
app.middleware('initial', function(rq, rs, next) { app.middleware('initial', function(rq, rs, next) {
req = rq; req = rq;
@ -336,7 +314,7 @@ describe('app', function() {
}); });
it('sets req.baseUrl and req.originalUrl', function(done) { it('sets req.baseUrl and req.originalUrl', function(done) {
let reqProps; var reqProps;
app.middleware('initial', function(req, res, next) { app.middleware('initial', function(req, res, next) {
reqProps = {baseUrl: req.baseUrl, originalUrl: req.originalUrl}; reqProps = {baseUrl: req.baseUrl, originalUrl: req.originalUrl};
@ -374,7 +352,7 @@ describe('app', function() {
// we need at least 9 elements to expose non-stability // we need at least 9 elements to expose non-stability
// of the built-in sort function // of the built-in sort function
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]; var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
numbers.forEach(function(n) { numbers.forEach(function(n) {
app.middleware('routes', namedHandler(n)); app.middleware('routes', namedHandler(n));
}); });
@ -389,8 +367,8 @@ describe('app', function() {
}); });
it('correctly mounts express apps', function(done) { it('correctly mounts express apps', function(done) {
let data, mountWasEmitted; var data, mountWasEmitted;
const subapp = express(); var subapp = express();
subapp.use(function(req, res, next) { subapp.use(function(req, res, next) {
data = { data = {
mountpath: req.app.mountpath, mountpath: req.app.mountpath,
@ -417,10 +395,10 @@ describe('app', function() {
}); });
it('restores req & res on return from mounted express app', function(done) { it('restores req & res on return from mounted express app', function(done) {
const expected = {}; var expected = {};
const actual = {}; var actual = {};
const subapp = express(); var subapp = express();
subapp.use(function verifyTestAssumptions(req, res, next) { subapp.use(function verifyTestAssumptions(req, res, next) {
expect(req.__proto__).to.not.equal(expected.req); expect(req.__proto__).to.not.equal(expected.req);
expect(res.__proto__).to.not.equal(expected.res); expect(res.__proto__).to.not.equal(expected.res);
@ -468,8 +446,8 @@ describe('app', function() {
} }
function getObjectAndPrototypeKeys(obj) { function getObjectAndPrototypeKeys(obj) {
const result = []; var result = [];
for (const k in obj) { for (var k in obj) {
result.push(k); result.push(k);
} }
result.sort(); result.sort();
@ -479,11 +457,11 @@ describe('app', function() {
describe.onServer('.middlewareFromConfig', function() { describe.onServer('.middlewareFromConfig', function() {
it('provides API for loading middleware from JSON config', function(done) { it('provides API for loading middleware from JSON config', function(done) {
const steps = []; var steps = [];
const expectedConfig = {key: 'value'}; var expectedConfig = {key: 'value'};
const handlerFactory = function() { var handlerFactory = function() {
const args = Array.prototype.slice.apply(arguments); var args = Array.prototype.slice.apply(arguments);
return function(req, res, next) { return function(req, res, next) {
steps.push(args); steps.push(args);
@ -550,7 +528,7 @@ describe('app', function() {
}); });
it('scopes middleware from config to a list of scopes', function(done) { it('scopes middleware from config to a list of scopes', function(done) {
const steps = []; var steps = [];
app.middlewareFromConfig( app.middlewareFromConfig(
function factory() { function factory() {
return function(req, res, next) { return function(req, res, next) {
@ -562,7 +540,7 @@ describe('app', function() {
{ {
phase: 'initial', phase: 'initial',
paths: ['/scope', /^\/(a|b)/], paths: ['/scope', /^\/(a|b)/],
}, }
); );
async.eachSeries( async.eachSeries(
@ -574,13 +552,13 @@ describe('app', function() {
expect(steps).to.eql(['/a', '/b', '/scope']); expect(steps).to.eql(['/a', '/b', '/scope']);
done(); done();
}, }
); );
}); });
}); });
describe.onServer('.defineMiddlewarePhases(nameOrArray)', function() { describe.onServer('.defineMiddlewarePhases(nameOrArray)', function() {
let app; var app;
beforeEach(function() { beforeEach(function() {
app = loopback(); app = loopback();
}); });
@ -626,7 +604,7 @@ describe('app', function() {
}); });
function verifyMiddlewarePhases(names, done) { function verifyMiddlewarePhases(names, done) {
const steps = []; var steps = [];
names.forEach(function(it) { names.forEach(function(it) {
app.middleware(it, function(req, res, next) { app.middleware(it, function(req, res, next) {
steps.push(it); steps.push(it);
@ -646,7 +624,7 @@ describe('app', function() {
}); });
describe('app.model(Model)', function() { describe('app.model(Model)', function() {
let app, db, MyTestModel; var app, db, MyTestModel;
beforeEach(function() { beforeEach(function() {
app = loopback(); app = loopback();
app.set('remoting', {errorHandler: {debug: true, log: false}}); app.set('remoting', {errorHandler: {debug: true, log: false}});
@ -655,7 +633,7 @@ describe('app', function() {
}); });
it('Expose a `Model` to remote clients', function() { it('Expose a `Model` to remote clients', function() {
const Color = PersistedModel.extend('color', {name: String}); var Color = PersistedModel.extend('color', {name: String});
app.model(Color); app.model(Color);
Color.attachTo(db); Color.attachTo(db);
@ -663,22 +641,22 @@ describe('app', function() {
}); });
it('uses singular name as app.remoteObjects() key', function() { it('uses singular name as app.remoteObjects() key', function() {
const Color = PersistedModel.extend('color', {name: String}); var Color = PersistedModel.extend('color', {name: String});
app.model(Color); app.model(Color);
Color.attachTo(db); Color.attachTo(db);
expect(app.remoteObjects()).to.eql({color: Color}); expect(app.remoteObjects()).to.eql({color: Color});
}); });
it('uses singular name as shared class name', function() { it('uses singular name as shared class name', function() {
const Color = PersistedModel.extend('color', {name: String}); var Color = PersistedModel.extend('color', {name: String});
app.model(Color); app.model(Color);
Color.attachTo(db); Color.attachTo(db);
const classes = app.remotes().classes().map(function(c) { return c.name; }); var classes = app.remotes().classes().map(function(c) { return c.name; });
expect(classes).to.contain('color'); expect(classes).to.contain('color');
}); });
it('registers existing models to app.models', function() { it('registers existing models to app.models', function() {
const Color = db.createModel('color', {name: String}); var Color = db.createModel('color', {name: String});
app.model(Color); app.model(Color);
expect(Color.app).to.be.equal(app); expect(Color.app).to.be.equal(app);
expect(Color.shared).to.equal(true); expect(Color.shared).to.equal(true);
@ -687,9 +665,9 @@ describe('app', function() {
}); });
it('emits a `modelRemoted` event', function() { it('emits a `modelRemoted` event', function() {
const Color = PersistedModel.extend('color', {name: String}); var Color = PersistedModel.extend('color', {name: String});
Color.shared = true; Color.shared = true;
let remotedClass; var remotedClass;
app.on('modelRemoted', function(sharedClass) { app.on('modelRemoted', function(sharedClass) {
remotedClass = sharedClass; remotedClass = sharedClass;
}); });
@ -699,9 +677,9 @@ describe('app', function() {
}); });
it('emits a `remoteMethodDisabled` event', function() { it('emits a `remoteMethodDisabled` event', function() {
const Color = PersistedModel.extend('color', {name: String}); var Color = PersistedModel.extend('color', {name: String});
Color.shared = true; Color.shared = true;
let remoteMethodDisabledClass, disabledRemoteMethod; var remoteMethodDisabledClass, disabledRemoteMethod;
app.on('remoteMethodDisabled', function(sharedClass, methodName) { app.on('remoteMethodDisabled', function(sharedClass, methodName) {
remoteMethodDisabledClass = sharedClass; remoteMethodDisabledClass = sharedClass;
disabledRemoteMethod = methodName; disabledRemoteMethod = methodName;
@ -716,23 +694,23 @@ describe('app', function() {
it('emits a `remoteMethodAdded` event', function() { it('emits a `remoteMethodAdded` event', function() {
app.dataSource('db', {connector: 'memory'}); app.dataSource('db', {connector: 'memory'});
const Book = app.registry.createModel( var Book = app.registry.createModel(
'Book', 'Book',
{name: 'string'}, {name: 'string'},
{plural: 'books'}, {plural: 'books'}
); );
app.model(Book, {dataSource: 'db'}); app.model(Book, {dataSource: 'db'});
const Page = app.registry.createModel( var Page = app.registry.createModel(
'Page', 'Page',
{name: 'string'}, {name: 'string'},
{plural: 'pages'}, {plural: 'pages'}
); );
app.model(Page, {dataSource: 'db'}); app.model(Page, {dataSource: 'db'});
Book.hasMany(Page); Book.hasMany(Page);
let remoteMethodAddedClass; var remoteMethodAddedClass;
app.on('remoteMethodAdded', function(sharedClass) { app.on('remoteMethodAdded', function(sharedClass) {
remoteMethodAddedClass = sharedClass; remoteMethodAddedClass = sharedClass;
}); });
@ -741,6 +719,17 @@ describe('app', function() {
expect(remoteMethodAddedClass).to.eql(Book.sharedClass); expect(remoteMethodAddedClass).to.eql(Book.sharedClass);
}); });
it.onServer('updates REST API when a new model is added', function(done) {
app.use(loopback.rest());
request(app).get('/colors').expect(404, function(err, res) {
if (err) return done(err);
var Color = PersistedModel.extend('color', {name: String});
app.model(Color);
Color.attachTo(db);
request(app).get('/colors').expect(200, done);
});
});
it('accepts null dataSource', function(done) { it('accepts null dataSource', function(done) {
app.model(MyTestModel, {dataSource: null}); app.model(MyTestModel, {dataSource: null});
expect(MyTestModel.dataSource).to.eql(null); expect(MyTestModel.dataSource).to.eql(null);
@ -759,14 +748,14 @@ describe('app', function() {
}); });
it('throws error if model typeof string is passed', function() { it('throws error if model typeof string is passed', function() {
const fn = function() { app.model('MyTestModel'); }; var fn = function() { app.model('MyTestModel'); };
expect(fn).to.throw(/app(\.model|\.registry)/); expect(fn).to.throw(/app(\.model|\.registry)/);
}); });
}); });
describe('app.model(ModelCtor, config)', function() { describe('app.model(ModelCtor, config)', function() {
it('attaches the model to a datasource', function() { it('attaches the model to a datasource', function() {
const previousModel = loopback.registry.findModel('TestModel'); var previousModel = loopback.registry.findModel('TestModel');
app.dataSource('db', {connector: 'memory'}); app.dataSource('db', {connector: 'memory'});
if (previousModel) { if (previousModel) {
@ -774,7 +763,7 @@ describe('app', function() {
} }
assert(!previousModel || !previousModel.dataSource); assert(!previousModel || !previousModel.dataSource);
const TestModel = app.registry.createModel('TestModel'); var TestModel = app.registry.createModel('TestModel');
app.model(TestModel, {dataSource: 'db'}); app.model(TestModel, {dataSource: 'db'});
expect(app.models.TestModel.dataSource).to.equal(app.dataSources.db); expect(app.models.TestModel.dataSource).to.equal(app.dataSources.db);
}); });
@ -843,10 +832,10 @@ describe('app', function() {
describe('app.models', function() { describe('app.models', function() {
it('is unique per app instance', function() { it('is unique per app instance', function() {
app.dataSource('db', {connector: 'memory'}); app.dataSource('db', {connector: 'memory'});
const Color = app.registry.createModel('Color'); var Color = app.registry.createModel('Color');
app.model(Color, {dataSource: 'db'}); app.model(Color, {dataSource: 'db'});
expect(app.models.Color).to.equal(Color); expect(app.models.Color).to.equal(Color);
const anotherApp = loopback(); var anotherApp = loopback();
expect(anotherApp.models.Color).to.equal(undefined); expect(anotherApp.models.Color).to.equal(undefined);
}); });
}); });
@ -855,7 +844,7 @@ describe('app', function() {
it('is unique per app instance', function() { it('is unique per app instance', function() {
app.dataSource('ds', {connector: 'memory'}); app.dataSource('ds', {connector: 'memory'});
expect(app.datasources.ds).to.not.equal(undefined); expect(app.datasources.ds).to.not.equal(undefined);
const anotherApp = loopback(); var anotherApp = loopback();
expect(anotherApp.datasources.ds).to.equal(undefined); expect(anotherApp.datasources.ds).to.equal(undefined);
}); });
}); });
@ -886,11 +875,11 @@ describe('app', function() {
describe.onServer('listen()', function() { describe.onServer('listen()', function() {
it('starts http server', function(done) { it('starts http server', function(done) {
const app = loopback(); var app = loopback();
app.set('port', 0); app.set('port', 0);
app.get('/', function(req, res) { res.status(200).send('OK'); }); app.get('/', function(req, res) { res.status(200).send('OK'); });
const server = app.listen(); var server = app.listen();
expect(server).to.be.an.instanceOf(require('http').Server); expect(server).to.be.an.instanceOf(require('http').Server);
@ -900,7 +889,7 @@ describe('app', function() {
}); });
it('updates port on `listening` event', function(done) { it('updates port on `listening` event', function(done) {
const app = loopback(); var app = loopback();
app.set('port', 0); app.set('port', 0);
app.listen(function() { app.listen(function() {
@ -911,12 +900,12 @@ describe('app', function() {
}); });
it('updates `url` on `listening` event', function(done) { it('updates `url` on `listening` event', function(done) {
const app = loopback(); var app = loopback();
app.set('port', 0); app.set('port', 0);
app.set('host', undefined); app.set('host', undefined);
app.listen(function() { app.listen(function() {
const expectedUrl = 'http://localhost:' + app.get('port') + '/'; var expectedUrl = 'http://localhost:' + app.get('port') + '/';
expect(app.get('url'), 'url').to.equal(expectedUrl); expect(app.get('url'), 'url').to.equal(expectedUrl);
done(); done();
@ -924,7 +913,7 @@ describe('app', function() {
}); });
it('forwards to http.Server.listen on more than one arg', function(done) { it('forwards to http.Server.listen on more than one arg', function(done) {
const app = loopback(); var app = loopback();
app.set('port', 1); app.set('port', 1);
app.listen(0, '127.0.0.1', function() { app.listen(0, '127.0.0.1', function() {
expect(app.get('port'), 'port').to.not.equal(0).and.not.equal(1); expect(app.get('port'), 'port').to.not.equal(0).and.not.equal(1);
@ -935,7 +924,7 @@ describe('app', function() {
}); });
it('forwards to http.Server.listen when the single arg is not a function', function(done) { it('forwards to http.Server.listen when the single arg is not a function', function(done) {
const app = loopback(); var app = loopback();
app.set('port', 1); app.set('port', 1);
app.listen(0).on('listening', function() { app.listen(0).on('listening', function() {
expect(app.get('port'), 'port') .to.not.equal(0).and.not.equal(1); expect(app.get('port'), 'port') .to.not.equal(0).and.not.equal(1);
@ -945,7 +934,7 @@ describe('app', function() {
}); });
it('uses app config when no parameter is supplied', function(done) { it('uses app config when no parameter is supplied', function(done) {
const app = loopback(); var app = loopback();
// Http listens on all interfaces by default // Http listens on all interfaces by default
// Custom host serves as an indicator whether // Custom host serves as an indicator whether
// the value was used by app.listen // the value was used by app.listen
@ -967,26 +956,26 @@ describe('app', function() {
}); });
it('auto-configures required models to provided dataSource', function() { it('auto-configures required models to provided dataSource', function() {
const AUTH_MODELS = ['User', 'ACL', 'AccessToken', 'Role', 'RoleMapping']; var AUTH_MODELS = ['User', 'ACL', 'AccessToken', 'Role', 'RoleMapping'];
const app = loopback({localRegistry: true, loadBuiltinModels: true}); var app = loopback({localRegistry: true, loadBuiltinModels: true});
require('../lib/builtin-models')(app.registry); require('../lib/builtin-models')(app.registry);
const db = app.dataSource('db', {connector: 'memory'}); var db = app.dataSource('db', {connector: 'memory'});
app.enableAuth({dataSource: 'db'}); app.enableAuth({dataSource: 'db'});
expect(Object.keys(app.models)).to.include.members(AUTH_MODELS); expect(Object.keys(app.models)).to.include.members(AUTH_MODELS);
AUTH_MODELS.forEach(function(m) { AUTH_MODELS.forEach(function(m) {
const Model = app.models[m]; var Model = app.models[m];
expect(Model.dataSource, m + '.dataSource').to.equal(db); expect(Model.dataSource, m + '.dataSource').to.equal(db);
expect(Model.shared, m + '.shared').to.equal(m === 'User'); expect(Model.shared, m + '.shared').to.equal(m === 'User');
}); });
}); });
it('detects already configured subclass of a required model', function() { it('detects already configured subclass of a required model', function() {
const app = loopback({localRegistry: true, loadBuiltinModels: true}); var app = loopback({localRegistry: true, loadBuiltinModels: true});
const db = app.dataSource('db', {connector: 'memory'}); var db = app.dataSource('db', {connector: 'memory'});
const Customer = app.registry.createModel('Customer', {}, {base: 'User'}); var Customer = app.registry.createModel('Customer', {}, {base: 'User'});
app.model(Customer, {dataSource: 'db'}); app.model(Customer, {dataSource: 'db'});
// Fix AccessToken's "belongsTo user" relation to use our new Customer model // Fix AccessToken's "belongsTo user" relation to use our new Customer model
@ -1001,7 +990,7 @@ describe('app', function() {
describe.onServer('app.get(\'/\', loopback.status())', function() { describe.onServer('app.get(\'/\', loopback.status())', function() {
it('should return the status of the application', function(done) { it('should return the status of the application', function(done) {
const app = loopback(); var app = loopback();
app.get('/', loopback.status()); app.get('/', loopback.status());
request(app) request(app)
.get('/') .get('/')
@ -1013,7 +1002,7 @@ describe('app', function() {
expect(res.body).to.have.property('started'); expect(res.body).to.have.property('started');
expect(res.body.uptime, 'uptime').to.be.gte(0); expect(res.body.uptime, 'uptime').to.be.gte(0);
const elapsed = Date.now() - Number(new Date(res.body.started)); var elapsed = Date.now() - Number(new Date(res.body.started));
// elapsed should be a small positive number... // elapsed should be a small positive number...
expect(elapsed, 'elapsed').to.be.within(0, 300); expect(elapsed, 'elapsed').to.be.within(0, 300);
@ -1026,7 +1015,7 @@ describe('app', function() {
describe('app.connectors', function() { describe('app.connectors', function() {
it('is unique per app instance', function() { it('is unique per app instance', function() {
app.connectors.foo = 'bar'; app.connectors.foo = 'bar';
const anotherApp = loopback(); var anotherApp = loopback();
expect(anotherApp.connectors.foo).to.equal(undefined); expect(anotherApp.connectors.foo).to.equal(undefined);
}); });
@ -1069,8 +1058,8 @@ describe('app', function() {
}); });
it('is unique per app instance', function() { it('is unique per app instance', function() {
const app1 = loopback(); var app1 = loopback();
const app2 = loopback(); var app2 = loopback();
expect(app1.settings).to.not.equal(app2.settings); expect(app1.settings).to.not.equal(app2.settings);
@ -1080,20 +1069,20 @@ describe('app', function() {
}); });
it('exposes loopback as a property', function() { it('exposes loopback as a property', function() {
const app = loopback(); var app = loopback();
expect(app.loopback).to.equal(loopback); expect(app.loopback).to.equal(loopback);
}); });
function setupUserModels(app, options, done) { function setupUserModels(app, options, done) {
app.dataSource('db', {connector: 'memory'}); app.dataSource('db', {connector: 'memory'});
const UserAccount = app.registry.createModel( var UserAccount = app.registry.createModel(
'UserAccount', 'UserAccount',
{name: 'string'}, {name: 'string'},
options, options
); );
const UserRole = app.registry.createModel( var UserRole = app.registry.createModel(
'UserRole', 'UserRole',
{name: 'string'}, {name: 'string'}
); );
app.model(UserAccount, {dataSource: 'db'}); app.model(UserAccount, {dataSource: 'db'});
app.model(UserRole, {dataSource: 'db'}); app.model(UserRole, {dataSource: 'db'});
@ -1108,7 +1097,7 @@ describe('app', function() {
} }
describe('Model-level normalizeHttpPath option', function() { describe('Model-level normalizeHttpPath option', function() {
let app; var app;
beforeEach(function() { beforeEach(function() {
app = loopback(); app = loopback();
}); });
@ -1144,7 +1133,7 @@ describe('app', function() {
}); });
}); });
describe('app-level normalizeHttpPath option', function() { describe('app-level normalizeHttpPath option', function() {
let app; var app;
beforeEach(function() { beforeEach(function() {
app = loopback(); app = loopback();
}); });
@ -1179,7 +1168,7 @@ describe('app', function() {
}); });
describe('Model-level and app-level normalizeHttpPath options', function() { describe('Model-level and app-level normalizeHttpPath options', function() {
let app; var app;
beforeEach(function() { beforeEach(function() {
app = loopback(); app = loopback();
}); });
@ -1203,8 +1192,8 @@ describe('app', function() {
}); });
function executeMiddlewareHandlers(app, urlPath, callback) { function executeMiddlewareHandlers(app, urlPath, callback) {
let handlerError = undefined; var handlerError = undefined;
const server = http.createServer(function(req, res) { var server = http.createServer(function(req, res) {
app.handle(req, res, function(err) { app.handle(req, res, function(err) {
if (err) { if (err) {
handlerError = err; handlerError = err;

View File

@ -1,20 +1,20 @@
// Copyright IBM Corp. 2015,2019. All Rights Reserved. // Copyright IBM Corp. 2015,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const expect = require('./helpers/expect'); var expect = require('./helpers/expect');
const sinon = require('sinon'); var sinon = require('sinon');
const loopback = require('../'); var loopback = require('../');
describe('PersistedModel.createChangeStream()', function() { describe('PersistedModel.createChangeStream()', function() {
describe('configured to source changes locally', function() { describe('configured to source changes locally', function() {
before(function() { before(function() {
const test = this; var test = this;
const app = loopback({localRegistry: true}); var app = loopback({localRegistry: true});
const ds = app.dataSource('ds', {connector: 'memory'}); var ds = app.dataSource('ds', {connector: 'memory'});
const Score = app.registry.createModel('Score'); var Score = app.registry.createModel('Score');
this.Score = app.model(Score, { this.Score = app.model(Score, {
dataSource: 'ds', dataSource: 'ds',
changeDataSource: false, // use only local observers changeDataSource: false, // use only local observers
@ -24,7 +24,7 @@ describe('PersistedModel.createChangeStream()', function() {
afterEach(verifyObserversRemoval); afterEach(verifyObserversRemoval);
it('should detect create', function(done) { it('should detect create', function(done) {
const Score = this.Score; var Score = this.Score;
Score.createChangeStream(function(err, changes) { Score.createChangeStream(function(err, changes) {
changes.on('data', function(change) { changes.on('data', function(change) {
@ -38,7 +38,7 @@ describe('PersistedModel.createChangeStream()', function() {
}); });
it('should detect update', function(done) { it('should detect update', function(done) {
const Score = this.Score; var Score = this.Score;
Score.create({team: 'foo'}, function(err, newScore) { Score.create({team: 'foo'}, function(err, newScore) {
Score.createChangeStream(function(err, changes) { Score.createChangeStream(function(err, changes) {
changes.on('data', function(change) { changes.on('data', function(change) {
@ -55,7 +55,7 @@ describe('PersistedModel.createChangeStream()', function() {
}); });
it('should detect delete', function(done) { it('should detect delete', function(done) {
const Score = this.Score; var Score = this.Score;
Score.create({team: 'foo'}, function(err, newScore) { Score.create({team: 'foo'}, function(err, newScore) {
Score.createChangeStream(function(err, changes) { Score.createChangeStream(function(err, changes) {
changes.on('data', function(change) { changes.on('data', function(change) {
@ -103,9 +103,9 @@ describe('PersistedModel.createChangeStream()', function() {
}); });
it('should not emit changes after destroy', function(done) { it('should not emit changes after destroy', function(done) {
const Score = this.Score; var Score = this.Score;
const spy = sinon.spy(); var spy = sinon.spy();
Score.createChangeStream(function(err, changes) { Score.createChangeStream(function(err, changes) {
changes.on('data', function() { changes.on('data', function() {
@ -123,7 +123,7 @@ describe('PersistedModel.createChangeStream()', function() {
}); });
function verifyObserversRemoval() { function verifyObserversRemoval() {
const Score = this.Score; var Score = this.Score;
expect(Score._observers['after save']).to.be.empty(); expect(Score._observers['after save']).to.be.empty();
expect(Score._observers['after delete']).to.be.empty(); expect(Score._observers['after delete']).to.be.empty();
} }
@ -132,10 +132,10 @@ describe('PersistedModel.createChangeStream()', function() {
// TODO(ritch) implement multi-server support // TODO(ritch) implement multi-server support
describe.skip('configured to source changes using pubsub', function() { describe.skip('configured to source changes using pubsub', function() {
before(function() { before(function() {
const test = this; var test = this;
const app = loopback({localRegistry: true}); var app = loopback({localRegistry: true});
const db = app.dataSource('ds', {connector: 'memory'}); var db = app.dataSource('ds', {connector: 'memory'});
const ps = app.dataSource('ps', { var ps = app.dataSource('ps', {
host: 'localhost', host: 'localhost',
port: '12345', port: '12345',
connector: 'pubsub', connector: 'pubsub',
@ -148,7 +148,7 @@ describe('PersistedModel.createChangeStream()', function() {
}); });
it('should detect a change', function(done) { it('should detect a change', function(done) {
const Score = this.Score; var Score = this.Score;
Score.createChangeStream(function(err, changes) { Score.createChangeStream(function(err, changes) {
changes.on('data', function(change) { changes.on('data', function(change) {

View File

@ -1,19 +1,19 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const assert = require('assert'); var assert = require('assert');
const async = require('async'); var async = require('async');
const expect = require('./helpers/expect'); var expect = require('./helpers/expect');
const loopback = require('../'); var loopback = require('../');
describe('Change', function() { describe('Change', function() {
let Change, TestModel; let Change, TestModel;
beforeEach(function() { beforeEach(function() {
const memory = loopback.createDataSource({ var memory = loopback.createDataSource({
connector: loopback.Memory, connector: loopback.Memory,
}); });
TestModel = loopback.PersistedModel.extend('ChangeTestModel', TestModel = loopback.PersistedModel.extend('ChangeTestModel',
@ -29,7 +29,7 @@ describe('Change', function() {
}); });
beforeEach(function(done) { beforeEach(function(done) {
const test = this; var test = this;
test.data = { test.data = {
foo: 'bar', foo: 'bar',
}; };
@ -52,13 +52,13 @@ describe('Change', function() {
describe('change.id', function() { describe('change.id', function() {
it('should be a hash of the modelName and modelId', function() { it('should be a hash of the modelName and modelId', function() {
const change = new Change({ var change = new Change({
rev: 'abc', rev: 'abc',
modelName: 'foo', modelName: 'foo',
modelId: 'bar', modelId: 'bar',
}); });
const hash = Change.hash([change.modelName, change.modelId].join('-')); var hash = Change.hash([change.modelName, change.modelId].join('-'));
assert.equal(change.id, hash); assert.equal(change.id, hash);
}); });
@ -67,7 +67,7 @@ describe('Change', function() {
describe('Change.rectifyModelChanges(modelName, modelIds, callback)', function() { describe('Change.rectifyModelChanges(modelName, modelIds, callback)', function() {
describe('using an existing untracked model', function() { describe('using an existing untracked model', function() {
beforeEach(function(done) { beforeEach(function(done) {
const test = this; var test = this;
Change.rectifyModelChanges(this.modelName, [this.modelId], function(err, trackedChanges) { Change.rectifyModelChanges(this.modelName, [this.modelId], function(err, trackedChanges) {
if (err) return done(err); if (err) return done(err);
@ -76,7 +76,7 @@ describe('Change', function() {
}); });
it('should create an entry', function(done) { it('should create an entry', function(done) {
const test = this; var test = this;
Change.find(function(err, trackedChanges) { Change.find(function(err, trackedChanges) {
assert.equal(trackedChanges[0].modelId, test.modelId.toString()); assert.equal(trackedChanges[0].modelId, test.modelId.toString());
@ -97,7 +97,7 @@ describe('Change', function() {
describe('Change.rectifyModelChanges - promise variant', function() { describe('Change.rectifyModelChanges - promise variant', function() {
describe('using an existing untracked model', function() { describe('using an existing untracked model', function() {
beforeEach(function(done) { beforeEach(function(done) {
const test = this; var test = this;
Change.rectifyModelChanges(this.modelName, [this.modelId]) Change.rectifyModelChanges(this.modelName, [this.modelId])
.then(function(trackedChanges) { .then(function(trackedChanges) {
done(); done();
@ -106,7 +106,7 @@ describe('Change', function() {
}); });
it('should create an entry', function(done) { it('should create an entry', function(done) {
const test = this; var test = this;
Change.find() Change.find()
.then(function(trackedChanges) { .then(function(trackedChanges) {
assert.equal(trackedChanges[0].modelId, test.modelId.toString()); assert.equal(trackedChanges[0].modelId, test.modelId.toString());
@ -131,7 +131,7 @@ describe('Change', function() {
describe('Change.findOrCreateChange(modelName, modelId, callback)', function() { describe('Change.findOrCreateChange(modelName, modelId, callback)', function() {
describe('when a change doesnt exist', function() { describe('when a change doesnt exist', function() {
beforeEach(function(done) { beforeEach(function(done) {
const test = this; var test = this;
Change.findOrCreateChange(this.modelName, this.modelId, function(err, result) { Change.findOrCreateChange(this.modelName, this.modelId, function(err, result) {
if (err) return done(err); if (err) return done(err);
@ -142,7 +142,7 @@ describe('Change', function() {
}); });
it('should create an entry', function(done) { it('should create an entry', function(done) {
const test = this; var test = this;
Change.findById(this.result.id, function(err, change) { Change.findById(this.result.id, function(err, change) {
if (err) return done(err); if (err) return done(err);
@ -155,7 +155,7 @@ describe('Change', function() {
describe('when a change doesnt exist - promise variant', function() { describe('when a change doesnt exist - promise variant', function() {
beforeEach(function(done) { beforeEach(function(done) {
const test = this; var test = this;
Change.findOrCreateChange(this.modelName, this.modelId) Change.findOrCreateChange(this.modelName, this.modelId)
.then(function(result) { .then(function(result) {
test.result = result; test.result = result;
@ -166,7 +166,7 @@ describe('Change', function() {
}); });
it('should create an entry', function(done) { it('should create an entry', function(done) {
const test = this; var test = this;
Change.findById(this.result.id, function(err, change) { Change.findById(this.result.id, function(err, change) {
if (err) return done(err); if (err) return done(err);
@ -179,7 +179,7 @@ describe('Change', function() {
describe('when a change does exist', function() { describe('when a change does exist', function() {
beforeEach(function(done) { beforeEach(function(done) {
const test = this; var test = this;
Change.create({ Change.create({
modelName: test.modelName, modelName: test.modelName,
modelId: test.modelId, modelId: test.modelId,
@ -191,7 +191,7 @@ describe('Change', function() {
}); });
beforeEach(function(done) { beforeEach(function(done) {
const test = this; var test = this;
Change.findOrCreateChange(this.modelName, this.modelId, function(err, result) { Change.findOrCreateChange(this.modelName, this.modelId, function(err, result) {
if (err) return done(err); if (err) return done(err);
@ -202,7 +202,7 @@ describe('Change', function() {
}); });
it('should find the entry', function(done) { it('should find the entry', function(done) {
const test = this; var test = this;
assert.equal(test.existingChange.id, test.result.id); assert.equal(test.existingChange.id, test.result.id);
done(); done();
@ -211,7 +211,7 @@ describe('Change', function() {
}); });
describe('change.rectify(callback)', function() { describe('change.rectify(callback)', function() {
let change; var change;
beforeEach(function(done) { beforeEach(function(done) {
Change.findOrCreate( Change.findOrCreate(
{ {
@ -222,12 +222,12 @@ describe('Change', function() {
change = ch; change = ch;
done(err); done(err);
}, }
); );
}); });
it('should create a new change with the correct revision', function(done) { it('should create a new change with the correct revision', function(done) {
const test = this; var test = this;
change.rectify(function(err, ch) { change.rectify(function(err, ch) {
assert.equal(ch.rev, test.revisionForModel); assert.equal(ch.rev, test.revisionForModel);
@ -238,9 +238,9 @@ describe('Change', function() {
// This test is a low-level equivalent of the test in replication.test.js // This test is a low-level equivalent of the test in replication.test.js
// called "replicates multiple updates within the same CP" // called "replicates multiple updates within the same CP"
it('should merge updates within the same checkpoint', function(done) { it('should merge updates within the same checkpoint', function(done) {
const test = this; var test = this;
const originalRev = this.revisionForModel; var originalRev = this.revisionForModel;
let cp; var cp;
async.series([ async.series([
rectify, rectify,
@ -274,7 +274,7 @@ describe('Change', function() {
} }
function update(next) { function update(next) {
const model = test.model; var model = test.model;
model.name += 'updated'; model.name += 'updated';
model.save(function(err) { model.save(function(err) {
@ -286,9 +286,9 @@ describe('Change', function() {
}); });
it('should not change checkpoint when rev is the same', function(done) { it('should not change checkpoint when rev is the same', function(done) {
const test = this; var test = this;
const originalCheckpoint = change.checkpoint; var originalCheckpoint = change.checkpoint;
const originalRev = change.rev; var originalRev = change.rev;
TestModel.checkpoint(function(err, inst) { TestModel.checkpoint(function(err, inst) {
if (err) return done(err); if (err) return done(err);
@ -306,7 +306,7 @@ describe('Change', function() {
}); });
describe('change.rectify - promise variant', function() { describe('change.rectify - promise variant', function() {
let change; var change;
beforeEach(function(done) { beforeEach(function(done) {
Change.findOrCreateChange(this.modelName, this.modelId) Change.findOrCreateChange(this.modelName, this.modelId)
.then(function(ch) { .then(function(ch) {
@ -318,7 +318,7 @@ describe('Change', function() {
}); });
it('should create a new change with the correct revision', function(done) { it('should create a new change with the correct revision', function(done) {
const test = this; var test = this;
change.rectify() change.rectify()
.then(function(ch) { .then(function(ch) {
assert.equal(ch.rev, test.revisionForModel); assert.equal(ch.rev, test.revisionForModel);
@ -330,8 +330,8 @@ describe('Change', function() {
describe('change.currentRevision(callback)', function() { describe('change.currentRevision(callback)', function() {
it('should get the correct revision', function(done) { it('should get the correct revision', function(done) {
const test = this; var test = this;
const change = new Change({ var change = new Change({
modelName: this.modelName, modelName: this.modelName,
modelId: this.modelId, modelId: this.modelId,
}); });
@ -346,8 +346,8 @@ describe('Change', function() {
describe('change.currentRevision - promise variant', function() { describe('change.currentRevision - promise variant', function() {
it('should get the correct revision', function(done) { it('should get the correct revision', function(done) {
const test = this; var test = this;
const change = new Change({ var change = new Change({
modelName: this.modelName, modelName: this.modelName,
modelId: this.modelId, modelId: this.modelId,
}); });
@ -365,8 +365,8 @@ describe('Change', function() {
describe('Change.hash(str)', function() { describe('Change.hash(str)', function() {
// todo(ritch) test other hashing algorithms // todo(ritch) test other hashing algorithms
it('should hash the given string', function() { it('should hash the given string', function() {
const str = 'foo'; var str = 'foo';
const hash = Change.hash(str); var hash = Change.hash(str);
assert(hash !== str); assert(hash !== str);
assert(typeof hash === 'string'); assert(typeof hash === 'string');
}); });
@ -374,54 +374,54 @@ describe('Change', function() {
describe('Change.revisionForInst(inst)', function() { describe('Change.revisionForInst(inst)', function() {
it('should return the same revision for the same data', function() { it('should return the same revision for the same data', function() {
const a = { var a = {
b: { b: {
b: ['c', 'd'], b: ['c', 'd'],
c: ['d', 'e'], c: ['d', 'e'],
}, },
}; };
const b = { var b = {
b: { b: {
c: ['d', 'e'], c: ['d', 'e'],
b: ['c', 'd'], b: ['c', 'd'],
}, },
}; };
const aRev = Change.revisionForInst(a); var aRev = Change.revisionForInst(a);
const bRev = Change.revisionForInst(b); var bRev = Change.revisionForInst(b);
assert.equal(aRev, bRev); assert.equal(aRev, bRev);
}); });
}); });
describe('change.type()', function() { describe('change.type()', function() {
it('CREATE', function() { it('CREATE', function() {
const change = new Change({ var change = new Change({
rev: this.revisionForModel, rev: this.revisionForModel,
}); });
assert.equal(Change.CREATE, change.type()); assert.equal(Change.CREATE, change.type());
}); });
it('UPDATE', function() { it('UPDATE', function() {
const change = new Change({ var change = new Change({
rev: this.revisionForModel, rev: this.revisionForModel,
prev: this.revisionForModel, prev: this.revisionForModel,
}); });
assert.equal(Change.UPDATE, change.type()); assert.equal(Change.UPDATE, change.type());
}); });
it('DELETE', function() { it('DELETE', function() {
const change = new Change({ var change = new Change({
prev: this.revisionForModel, prev: this.revisionForModel,
}); });
assert.equal(Change.DELETE, change.type()); assert.equal(Change.DELETE, change.type());
}); });
it('UNKNOWN', function() { it('UNKNOWN', function() {
const change = new Change(); var change = new Change();
assert.equal(Change.UNKNOWN, change.type()); assert.equal(Change.UNKNOWN, change.type());
}); });
}); });
describe('change.getModelCtor()', function() { describe('change.getModelCtor()', function() {
it('should get the correct model class', function() { it('should get the correct model class', function() {
const change = new Change({ var change = new Change({
modelName: this.modelName, modelName: this.modelName,
}); });
@ -431,11 +431,11 @@ describe('Change', function() {
describe('change.equals(otherChange)', function() { describe('change.equals(otherChange)', function() {
it('should return true when the change is equal', function() { it('should return true when the change is equal', function() {
const change = new Change({ var change = new Change({
rev: this.revisionForModel, rev: this.revisionForModel,
}); });
const otherChange = new Change({ var otherChange = new Change({
rev: this.revisionForModel, rev: this.revisionForModel,
}); });
@ -443,13 +443,13 @@ describe('Change', function() {
}); });
it('should return true when both changes are deletes', function() { it('should return true when both changes are deletes', function() {
const REV = 'foo'; var REV = 'foo';
const change = new Change({ var change = new Change({
rev: null, rev: null,
prev: REV, prev: REV,
}); });
const otherChange = new Change({ var otherChange = new Change({
rev: undefined, rev: undefined,
prev: REV, prev: REV,
}); });
@ -463,11 +463,11 @@ describe('Change', function() {
describe('change.isBasedOn(otherChange)', function() { describe('change.isBasedOn(otherChange)', function() {
it('should return true when the change is based on the other', function() { it('should return true when the change is based on the other', function() {
const change = new Change({ var change = new Change({
prev: this.revisionForModel, prev: this.revisionForModel,
}); });
const otherChange = new Change({ var otherChange = new Change({
rev: this.revisionForModel, rev: this.revisionForModel,
}); });
@ -485,7 +485,7 @@ describe('Change', function() {
}); });
it('should return delta and conflict lists', function(done) { it('should return delta and conflict lists', function(done) {
const remoteChanges = [ var remoteChanges = [
// an update => should result in a delta // an update => should result in a delta
{rev: 'foo2', prev: 'foo', modelName: this.modelName, modelId: 9, checkpoint: 1}, {rev: 'foo2', prev: 'foo', modelName: this.modelName, modelId: 9, checkpoint: 1},
// no change => should not result in a delta / conflict // no change => should not result in a delta / conflict
@ -505,7 +505,7 @@ describe('Change', function() {
}); });
it('should return delta and conflict lists - promise variant', function(done) { it('should return delta and conflict lists - promise variant', function(done) {
const remoteChanges = [ var remoteChanges = [
// an update => should result in a delta // an update => should result in a delta
{rev: 'foo2', prev: 'foo', modelName: this.modelName, modelId: 9, checkpoint: 1}, {rev: 'foo2', prev: 'foo', modelName: this.modelName, modelId: 9, checkpoint: 1},
// no change => should not result in a delta / conflict // no change => should not result in a delta / conflict
@ -525,7 +525,7 @@ describe('Change', function() {
}); });
it('should set "prev" to local revision in non-conflicting delta', function(done) { it('should set "prev" to local revision in non-conflicting delta', function(done) {
const updateRecord = { var updateRecord = {
rev: 'foo-new', rev: 'foo-new',
prev: 'foo', prev: 'foo',
modelName: this.modelName, modelName: this.modelName,
@ -537,7 +537,7 @@ describe('Change', function() {
expect(diff.conflicts, 'conflicts').to.have.length(0); expect(diff.conflicts, 'conflicts').to.have.length(0);
expect(diff.deltas, 'deltas').to.have.length(1); expect(diff.deltas, 'deltas').to.have.length(1);
const actual = diff.deltas[0].toObject(); var actual = diff.deltas[0].toObject();
delete actual.id; delete actual.id;
expect(actual).to.eql({ expect(actual).to.eql({
checkpoint: 2, checkpoint: 2,
@ -552,7 +552,7 @@ describe('Change', function() {
}); });
it('should set "prev" to local revision in remote-only delta', function(done) { it('should set "prev" to local revision in remote-only delta', function(done) {
const updateRecord = { var updateRecord = {
rev: 'foo-new', rev: 'foo-new',
prev: 'foo-prev', prev: 'foo-prev',
modelName: this.modelName, modelName: this.modelName,
@ -566,7 +566,7 @@ describe('Change', function() {
expect(diff.conflicts, 'conflicts').to.have.length(0); expect(diff.conflicts, 'conflicts').to.have.length(0);
expect(diff.deltas, 'deltas').to.have.length(1); expect(diff.deltas, 'deltas').to.have.length(1);
const actual = diff.deltas[0].toObject(); var actual = diff.deltas[0].toObject();
delete actual.id; delete actual.id;
expect(actual).to.eql({ expect(actual).to.eql({
checkpoint: 2, checkpoint: 2,
@ -581,7 +581,7 @@ describe('Change', function() {
}); });
it('should set "prev" to null for a new instance', function(done) { it('should set "prev" to null for a new instance', function(done) {
const updateRecord = { var updateRecord = {
rev: 'new-rev', rev: 'new-rev',
prev: 'new-prev', prev: 'new-prev',
modelName: this.modelName, modelName: this.modelName,
@ -594,7 +594,7 @@ describe('Change', function() {
expect(diff.conflicts).to.have.length(0); expect(diff.conflicts).to.have.length(0);
expect(diff.deltas).to.have.length(1); expect(diff.deltas).to.have.length(1);
const actual = diff.deltas[0].toObject(); var actual = diff.deltas[0].toObject();
delete actual.id; delete actual.id;
expect(actual).to.eql({ expect(actual).to.eql({
checkpoint: 2, checkpoint: 2,
@ -614,7 +614,7 @@ describe('Change with with custom properties', function() {
let Change, TestModel; let Change, TestModel;
beforeEach(function() { beforeEach(function() {
const memory = loopback.createDataSource({ let memory = loopback.createDataSource({
connector: loopback.Memory, connector: loopback.Memory,
}); });
@ -630,7 +630,7 @@ describe('Change with with custom properties', function() {
this.modelName = TestModel.modelName; this.modelName = TestModel.modelName;
TestModel.prototype.fillCustomChangeProperties = function(change, cb) { TestModel.prototype.fillCustomChangeProperties = function(change, cb) {
const inst = this; var inst = this;
if (inst && inst.tenantId) { if (inst && inst.tenantId) {
change.tenantId = inst.tenantId; change.tenantId = inst.tenantId;

View File

@ -1,19 +1,19 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const async = require('async'); var async = require('async');
const loopback = require('../'); var loopback = require('../');
const expect = require('./helpers/expect'); var expect = require('./helpers/expect');
const Checkpoint = loopback.Checkpoint.extend('TestCheckpoint'); var Checkpoint = loopback.Checkpoint.extend('TestCheckpoint');
describe('Checkpoint', function() { describe('Checkpoint', function() {
describe('bumpLastSeq() and current()', function() { describe('bumpLastSeq() and current()', function() {
beforeEach(function() { beforeEach(function() {
const memory = loopback.createDataSource({ var memory = loopback.createDataSource({
connector: loopback.Memory, connector: loopback.Memory,
}); });
Checkpoint.attachTo(memory); Checkpoint.attachTo(memory);

View File

@ -1,16 +1,16 @@
// Copyright IBM Corp. 2016,2019. All Rights Reserved. // Copyright IBM Corp. 2016,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const expect = require('chai').expect; var expect = require('chai').expect;
const loopback = require('..'); var loopback = require('..');
const supertest = require('supertest'); var supertest = require('supertest');
describe('OptionsFromRemotingContext', function() { describe('OptionsFromRemotingContext', function() {
let app, request, accessToken, userId, Product, actualOptions; var app, request, accessToken, userId, Product, actualOptions;
beforeEach(setupAppAndRequest); beforeEach(setupAppAndRequest);
beforeEach(resetActualOptions); beforeEach(resetActualOptions);
@ -133,7 +133,7 @@ describe('OptionsFromRemotingContext', function() {
// despite the fact that under the hood a method on "modelTo" is called. // despite the fact that under the hood a method on "modelTo" is called.
context('hasManyThrough', function() { context('hasManyThrough', function() {
let Category, ThroughModel; var Category, ThroughModel;
beforeEach(givenCategoryHasManyProductsThroughAnotherModel); beforeEach(givenCategoryHasManyProductsThroughAnotherModel);
beforeEach(givenCategoryAndProduct); beforeEach(givenCategoryAndProduct);
@ -215,7 +215,7 @@ describe('OptionsFromRemotingContext', function() {
Category = app.registry.createModel( Category = app.registry.createModel(
'Category', 'Category',
{name: String}, {name: String},
{forceId: false, replaceOnPUT: true}, {forceId: false, replaceOnPUT: true}
); );
app.model(Category, {dataSource: 'db'}); app.model(Category, {dataSource: 'db'});
@ -242,7 +242,7 @@ describe('OptionsFromRemotingContext', function() {
}); });
context('hasOne', function() { context('hasOne', function() {
let Category; var Category;
beforeEach(givenCategoryHasOneProduct); beforeEach(givenCategoryHasOneProduct);
beforeEach(givenCategoryId1); beforeEach(givenCategoryId1);
@ -288,7 +288,7 @@ describe('OptionsFromRemotingContext', function() {
Category = app.registry.createModel( Category = app.registry.createModel(
'Category', 'Category',
{name: String}, {name: String},
{forceId: false, replaceOnPUT: true}, {forceId: false, replaceOnPUT: true}
); );
app.model(Category, {dataSource: 'db'}); app.model(Category, {dataSource: 'db'});
@ -313,7 +313,7 @@ describe('OptionsFromRemotingContext', function() {
}); });
context('belongsTo', function() { context('belongsTo', function() {
let Category; var Category;
beforeEach(givenCategoryBelongsToProduct); beforeEach(givenCategoryBelongsToProduct);
@ -333,7 +333,7 @@ describe('OptionsFromRemotingContext', function() {
Category = app.registry.createModel( Category = app.registry.createModel(
'Category', 'Category',
{name: String}, {name: String},
{forceId: false, replaceOnPUT: true}, {forceId: false, replaceOnPUT: true}
); );
app.model(Category, {dataSource: 'db'}); app.model(Category, {dataSource: 'db'});
@ -364,7 +364,7 @@ describe('OptionsFromRemotingContext', function() {
Product = app.registry.createModel( Product = app.registry.createModel(
'Product', 'Product',
{name: String}, {name: String},
{forceId: false, replaceOnPUT: true}, {forceId: false, replaceOnPUT: true}
); );
Product.createOptionsFromRemotingContext = function(ctx) { Product.createOptionsFromRemotingContext = function(ctx) {
@ -382,7 +382,7 @@ describe('OptionsFromRemotingContext', function() {
} }
function observeOptionsBeforeSave() { function observeOptionsBeforeSave() {
const Model = arguments[0] || Product; var Model = arguments[0] || Product;
Model.observe('before save', function(ctx, next) { Model.observe('before save', function(ctx, next) {
actualOptions = ctx.options; actualOptions = ctx.options;
next(); next();
@ -390,7 +390,7 @@ describe('OptionsFromRemotingContext', function() {
} }
function observeOptionsBeforeDelete() { function observeOptionsBeforeDelete() {
const Model = arguments[0] || Product; var Model = arguments[0] || Product;
Model.observe('before delete', function(ctx, next) { Model.observe('before delete', function(ctx, next) {
actualOptions = ctx.options; actualOptions = ctx.options;
next(); next();
@ -398,7 +398,7 @@ describe('OptionsFromRemotingContext', function() {
} }
function observeOptionsOnAccess() { function observeOptionsOnAccess() {
const Model = arguments[0] || Product; var Model = arguments[0] || Product;
Model.observe('access', function(ctx, next) { Model.observe('access', function(ctx, next) {
actualOptions = ctx.options; actualOptions = ctx.options;
next(); next();

View File

@ -1,14 +1,14 @@
// Copyright IBM Corp. 2013,2019. All Rights Reserved. // Copyright IBM Corp. 2013,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const assert = require('assert'); var assert = require('assert');
const loopback = require('../'); var loopback = require('../');
describe('DataSource', function() { describe('DataSource', function() {
let memory; var memory;
beforeEach(function() { beforeEach(function() {
memory = loopback.createDataSource({ memory = loopback.createDataSource({
@ -20,7 +20,7 @@ describe('DataSource', function() {
describe('dataSource.createModel(name, properties, settings)', function() { describe('dataSource.createModel(name, properties, settings)', function() {
it('Define a model and attach it to a `DataSource`', function() { it('Define a model and attach it to a `DataSource`', function() {
const Color = memory.createModel('color', {name: String}); var Color = memory.createModel('color', {name: String});
assert.isFunc(Color, 'find'); assert.isFunc(Color, 'find');
assert.isFunc(Color, 'findById'); assert.isFunc(Color, 'findById');
assert.isFunc(Color, 'findOne'); assert.isFunc(Color, 'findOne');
@ -45,31 +45,31 @@ describe('DataSource', function() {
}); });
it('should honor settings.base', function() { it('should honor settings.base', function() {
const Base = memory.createModel('base'); var Base = memory.createModel('base');
const Color = memory.createModel('color', {name: String}, {base: Base}); var Color = memory.createModel('color', {name: String}, {base: Base});
assert(Color.prototype instanceof Base); assert(Color.prototype instanceof Base);
assert.equal(Color.base, Base); assert.equal(Color.base, Base);
}); });
it('should use loopback.PersistedModel as the base for DBs', function() { it('should use loopback.PersistedModel as the base for DBs', function() {
const Color = memory.createModel('color', {name: String}); var Color = memory.createModel('color', {name: String});
assert(Color.prototype instanceof loopback.PersistedModel); assert(Color.prototype instanceof loopback.PersistedModel);
assert.equal(Color.base, loopback.PersistedModel); assert.equal(Color.base, loopback.PersistedModel);
}); });
it('should use loopback.Model as the base for non DBs', function() { it('should use loopback.Model as the base for non DBs', function() {
// Mock up a non-DB connector // Mock up a non-DB connector
const Connector = function() { var Connector = function() {
}; };
Connector.prototype.getTypes = function() { Connector.prototype.getTypes = function() {
return ['rest']; return ['rest'];
}; };
const ds = loopback.createDataSource({ var ds = loopback.createDataSource({
connector: new Connector(), connector: new Connector(),
}); });
const Color = ds.createModel('color', {name: String}); var Color = ds.createModel('color', {name: String});
assert(Color.prototype instanceof Color.registry.getModel('Model')); assert(Color.prototype instanceof Color.registry.getModel('Model'));
assert.equal(Color.base.modelName, 'PersistedModel'); assert.equal(Color.base.modelName, 'PersistedModel');
}); });
@ -77,7 +77,7 @@ describe('DataSource', function() {
describe.skip('PersistedModel Methods', function() { describe.skip('PersistedModel Methods', function() {
it('List the enabled and disabled methods', function() { it('List the enabled and disabled methods', function() {
const TestModel = loopback.PersistedModel.extend('TestPersistedModel'); var TestModel = loopback.PersistedModel.extend('TestPersistedModel');
TestModel.attachTo(loopback.memory()); TestModel.attachTo(loopback.memory());
// assert the defaults // assert the defaults
@ -109,9 +109,9 @@ describe('DataSource', function() {
existsAndShared('reload', false); existsAndShared('reload', false);
function existsAndShared(Model, name, isRemoteEnabled, isProto) { function existsAndShared(Model, name, isRemoteEnabled, isProto) {
const scope = isProto ? Model.prototype : Model; var scope = isProto ? Model.prototype : Model;
const fn = scope[name]; var fn = scope[name];
const actuallyEnabled = Model.getRemoteMethod(name); var actuallyEnabled = Model.getRemoteMethod(name);
assert(fn, name + ' should be defined!'); assert(fn, name + ' should be defined!');
assert(actuallyEnabled === isRemoteEnabled, assert(actuallyEnabled === isRemoteEnabled,
name + ' ' + (isRemoteEnabled ? 'should' : 'should not') + name + ' ' + (isRemoteEnabled ? 'should' : 'should not') +
@ -121,7 +121,7 @@ describe('DataSource', function() {
}); });
}); });
function assertValidDataSource(dataSource) { var assertValidDataSource = function(dataSource) {
// has methods // has methods
assert.isFunc(dataSource, 'createModel'); assert.isFunc(dataSource, 'createModel');
assert.isFunc(dataSource, 'discoverModelDefinitions'); assert.isFunc(dataSource, 'discoverModelDefinitions');
@ -130,7 +130,7 @@ function assertValidDataSource(dataSource) {
assert.isFunc(dataSource, 'disableRemote'); assert.isFunc(dataSource, 'disableRemote');
assert.isFunc(dataSource, 'defineOperation'); assert.isFunc(dataSource, 'defineOperation');
assert.isFunc(dataSource, 'operations'); assert.isFunc(dataSource, 'operations');
} };
assert.isFunc = function(obj, name) { assert.isFunc = function(obj, name) {
assert(obj, 'cannot assert function ' + name + ' on object that doesnt exist'); assert(obj, 'cannot assert function ' + name + ' on object that doesnt exist');

View File

@ -1,19 +1,19 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const path = require('path'); var path = require('path');
const loopback = require('../../'); var loopback = require('../../');
const models = require('../fixtures/e2e/models'); var models = require('../fixtures/e2e/models');
const TestModel = models.TestModel; var TestModel = models.TestModel;
const assert = require('assert'); var assert = require('assert');
describe('RemoteConnector', function() { describe('RemoteConnector', function() {
before(function() { before(function() {
// setup the remote connector // setup the remote connector
const ds = loopback.createDataSource({ var ds = loopback.createDataSource({
url: 'http://127.0.0.1:3000/api', url: 'http://127.0.0.1:3000/api',
connector: loopback.Remote, connector: loopback.Remote,
}); });
@ -33,7 +33,7 @@ describe('RemoteConnector', function() {
}); });
it('should be able to call save', function(done) { it('should be able to call save', function(done) {
const m = new TestModel({ var m = new TestModel({
foo: 'bar', foo: 'bar',
}); });
m.save(function(err, data) { m.save(function(err, data) {

View File

@ -1,32 +1,32 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const path = require('path'); var path = require('path');
const loopback = require('../../'); var loopback = require('../../');
const models = require('../fixtures/e2e/models'); var models = require('../fixtures/e2e/models');
const TestModel = models.TestModel; var TestModel = models.TestModel;
const LocalTestModel = TestModel.extend('LocalTestModel', {}, { var LocalTestModel = TestModel.extend('LocalTestModel', {}, {
trackChanges: true, trackChanges: true,
}); });
const assert = require('assert'); var assert = require('assert');
describe('Replication', function() { describe('Replication', function() {
before(function() { before(function() {
// setup the remote connector // setup the remote connector
const ds = loopback.createDataSource({ var ds = loopback.createDataSource({
url: 'http://127.0.0.1:3000/api', url: 'http://127.0.0.1:3000/api',
connector: loopback.Remote, connector: loopback.Remote,
}); });
TestModel.attachTo(ds); TestModel.attachTo(ds);
const memory = loopback.memory(); var memory = loopback.memory();
LocalTestModel.attachTo(memory); LocalTestModel.attachTo(memory);
}); });
it('should replicate local data to the remote', function(done) { it('should replicate local data to the remote', function(done) {
const RANDOM = Math.random(); var RANDOM = Math.random();
LocalTestModel.create({ LocalTestModel.create({
n: RANDOM, n: RANDOM,

View File

@ -1,38 +1,38 @@
// Copyright IBM Corp. 2013,2019. All Rights Reserved. // Copyright IBM Corp. 2013,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const loopback = require('../'); var loopback = require('../');
let MyEmail; var MyEmail;
const assert = require('assert'); var assert = require('assert');
const MailConnector = require('../lib/connectors/mail'); var MailConnector = require('../lib/connectors/mail');
describe('Email connector', function() { describe('Email connector', function() {
it('should set up SMTP', function() { it('should set up SMTP', function() {
const connector = new MailConnector({transports: [ var connector = new MailConnector({transports: [
{type: 'smtp', service: 'gmail'}, {type: 'smtp', service: 'gmail'},
]}); ]});
assert(connector.transportForName('smtp')); assert(connector.transportForName('smtp'));
}); });
it('should set up DIRECT', function() { it('should set up DIRECT', function() {
const connector = new MailConnector({transports: [ var connector = new MailConnector({transports: [
{type: 'direct', name: 'localhost'}, {type: 'direct', name: 'localhost'},
]}); ]});
assert(connector.transportForName('direct')); assert(connector.transportForName('direct'));
}); });
it('should set up STUB', function() { it('should set up STUB', function() {
const connector = new MailConnector({transports: [ var connector = new MailConnector({transports: [
{type: 'stub', service: 'gmail'}, {type: 'stub', service: 'gmail'},
]}); ]});
assert(connector.transportForName('stub')); assert(connector.transportForName('stub'));
}); });
it('should set up a single transport for SMTP', function() { it('should set up a single transport for SMTP', function() {
const connector = new MailConnector({transport: var connector = new MailConnector({transport:
{type: 'smtp', service: 'gmail'}, {type: 'smtp', service: 'gmail'},
}); });
@ -40,7 +40,7 @@ describe('Email connector', function() {
}); });
it('should set up a aliased transport for SMTP', function() { it('should set up a aliased transport for SMTP', function() {
const connector = new MailConnector({transport: var connector = new MailConnector({transport:
{type: 'smtp', service: 'ses-us-east-1', alias: 'ses-smtp'}, {type: 'smtp', service: 'ses-us-east-1', alias: 'ses-smtp'},
}); });
@ -51,7 +51,7 @@ describe('Email connector', function() {
describe('Email and SMTP', function() { describe('Email and SMTP', function() {
beforeEach(function() { beforeEach(function() {
MyEmail = loopback.Email.extend('my-email'); MyEmail = loopback.Email.extend('my-email');
const ds = loopback.createDataSource('email', { var ds = loopback.createDataSource('email', {
connector: loopback.Mail, connector: loopback.Mail,
transports: [{type: 'STUB'}], transports: [{type: 'STUB'}],
}); });
@ -65,7 +65,7 @@ describe('Email and SMTP', function() {
describe('MyEmail', function() { describe('MyEmail', function() {
it('MyEmail.send(options, callback)', function(done) { it('MyEmail.send(options, callback)', function(done) {
const options = { var options = {
to: 'to@to.com', to: 'to@to.com',
from: 'from@from.com', from: 'from@from.com',
subject: 'subject', subject: 'subject',
@ -84,7 +84,7 @@ describe('Email and SMTP', function() {
}); });
it('myEmail.send(callback)', function(done) { it('myEmail.send(callback)', function(done) {
const message = new MyEmail({ var message = new MyEmail({
to: 'to@to.com', to: 'to@to.com',
from: 'from@from.com', from: 'from@from.com',
subject: 'subject', subject: 'subject',

View File

@ -1,14 +1,14 @@
// Copyright IBM Corp. 2015,2019. All Rights Reserved. // Copyright IBM Corp. 2015,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const loopback = require('../'); var loopback = require('../');
let app; var app;
const assert = require('assert'); var assert = require('assert');
const request = require('supertest'); var request = require('supertest');
const expect = require('./helpers/expect'); var expect = require('./helpers/expect');
describe('loopback.errorHandler(options)', function() { describe('loopback.errorHandler(options)', function() {
it('should throw a descriptive error', function() { it('should throw a descriptive error', function() {

View File

@ -1,20 +1,20 @@
// Copyright IBM Corp. 2015,2019. All Rights Reserved. // Copyright IBM Corp. 2015,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const loopback = require('../../../..'); var loopback = require('../../../..');
const boot = require('loopback-boot'); var boot = require('loopback-boot');
const app = module.exports = loopback({ var app = module.exports = loopback({
localRegistry: true, localRegistry: true,
loadBuiltinModels: true, loadBuiltinModels: true,
}); });
const errorHandler = require('strong-error-handler'); var errorHandler = require('strong-error-handler');
boot(app, __dirname); boot(app, __dirname);
const apiPath = '/api'; var apiPath = '/api';
app.use(loopback.token({model: app.models.accessToken})); app.use(loopback.token({model: app.models.accessToken}));
app.use(apiPath, loopback.rest()); app.use(apiPath, loopback.rest());

View File

@ -1,11 +1,11 @@
// Copyright IBM Corp. 2015,2019. All Rights Reserved. // Copyright IBM Corp. 2015,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const loopback = require('../../../../index'); var loopback = require('../../../../index');
const PersistedModel = loopback.PersistedModel; var PersistedModel = loopback.PersistedModel;
exports.TestModel = PersistedModel.extend('TestModel', {}, { exports.TestModel = PersistedModel.extend('TestModel', {}, {
trackChanges: true, trackChanges: true,

View File

@ -1,15 +1,15 @@
// Copyright IBM Corp. 2015,2019. All Rights Reserved. // Copyright IBM Corp. 2015,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const loopback = require('../../../../index'); var loopback = require('../../../../index');
const app = module.exports = loopback({localRegistry: true}); var app = module.exports = loopback({localRegistry: true});
const models = require('./models'); var models = require('./models');
const TestModel = models.TestModel; var TestModel = models.TestModel;
const apiPath = '/api'; var apiPath = '/api';
app.use(apiPath, loopback.rest()); app.use(apiPath, loopback.rest());
TestModel.attachTo(loopback.memory()); TestModel.attachTo(loopback.memory());

View File

@ -1,12 +1,12 @@
// Copyright IBM Corp. 2015,2019. All Rights Reserved. // Copyright IBM Corp. 2015,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const boot = require('loopback-boot'); var boot = require('loopback-boot');
const loopback = require('../../../../../index'); var loopback = require('../../../../../index');
const app = module.exports = loopback(); var app = module.exports = loopback();
boot(app, __dirname); boot(app, __dirname);
app.use(loopback.rest()); app.use(loopback.rest());

View File

@ -1,12 +1,12 @@
// Copyright IBM Corp. 2015,2019. All Rights Reserved. // Copyright IBM Corp. 2015,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const boot = require('loopback-boot'); var boot = require('loopback-boot');
const loopback = require('../../../../../index'); var loopback = require('../../../../../index');
const app = module.exports = loopback(); var app = module.exports = loopback();
boot(app, __dirname); boot(app, __dirname);
app.use(loopback.rest()); app.use(loopback.rest());

View File

@ -1,12 +1,12 @@
// Copyright IBM Corp. 2015,2019. All Rights Reserved. // Copyright IBM Corp. 2015,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const boot = require('loopback-boot'); var boot = require('loopback-boot');
const loopback = require('../../../../../index'); var loopback = require('../../../../../index');
const app = module.exports = loopback(); var app = module.exports = loopback();
boot(app, __dirname); boot(app, __dirname);
app.use(loopback.rest()); app.use(loopback.rest());

View File

@ -1,12 +1,12 @@
// Copyright IBM Corp. 2015,2019. All Rights Reserved. // Copyright IBM Corp. 2015,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const boot = require('loopback-boot'); var boot = require('loopback-boot');
const loopback = require('../../../../../index'); var loopback = require('../../../../../index');
const app = module.exports = loopback(); var app = module.exports = loopback();
boot(app, __dirname); boot(app, __dirname);
app.use(loopback.rest()); app.use(loopback.rest());

View File

@ -1,12 +1,12 @@
// Copyright IBM Corp. 2015,2019. All Rights Reserved. // Copyright IBM Corp. 2015,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const boot = require('loopback-boot'); var boot = require('loopback-boot');
const loopback = require('../../../../../index'); var loopback = require('../../../../../index');
const app = module.exports = loopback(); var app = module.exports = loopback();
boot(app, __dirname); boot(app, __dirname);
app.use(loopback.rest()); app.use(loopback.rest());

View File

@ -1,12 +1,12 @@
// Copyright IBM Corp. 2015,2019. All Rights Reserved. // Copyright IBM Corp. 2015,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const boot = require('loopback-boot'); var boot = require('loopback-boot');
const loopback = require('../../../../../index'); var loopback = require('../../../../../index');
const app = module.exports = loopback(); var app = module.exports = loopback();
boot(app, __dirname); boot(app, __dirname);
app.use(loopback.rest()); app.use(loopback.rest());

View File

@ -1,12 +1,12 @@
// Copyright IBM Corp. 2015,2019. All Rights Reserved. // Copyright IBM Corp. 2015,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const boot = require('loopback-boot'); var boot = require('loopback-boot');
const loopback = require('../../../../../index'); var loopback = require('../../../../../index');
const app = module.exports = loopback(); var app = module.exports = loopback();
boot(app, __dirname); boot(app, __dirname);
app.use(loopback.rest()); app.use(loopback.rest());

View File

@ -1,12 +1,12 @@
// Copyright IBM Corp. 2015,2019. All Rights Reserved. // Copyright IBM Corp. 2015,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const boot = require('loopback-boot'); var boot = require('loopback-boot');
const loopback = require('../../../../../index'); var loopback = require('../../../../../index');
const app = module.exports = loopback(); var app = module.exports = loopback();
boot(app, __dirname); boot(app, __dirname);
app.use(loopback.rest()); app.use(loopback.rest());

View File

@ -1,12 +1,12 @@
// Copyright IBM Corp. 2015,2019. All Rights Reserved. // Copyright IBM Corp. 2015,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const boot = require('loopback-boot'); var boot = require('loopback-boot');
const loopback = require('../../../../../index'); var loopback = require('../../../../../index');
const app = module.exports = loopback(); var app = module.exports = loopback();
boot(app, __dirname); boot(app, __dirname);
app.use(loopback.rest()); app.use(loopback.rest());

View File

@ -1,16 +1,16 @@
// Copyright IBM Corp. 2015,2019. All Rights Reserved. // Copyright IBM Corp. 2015,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const loopback = require('../../../../index'); var loopback = require('../../../../index');
const boot = require('loopback-boot'); var boot = require('loopback-boot');
const app = module.exports = loopback({localRegistry: true}); var app = module.exports = loopback({localRegistry: true});
const errorHandler = require('strong-error-handler'); var errorHandler = require('strong-error-handler');
boot(app, __dirname); boot(app, __dirname);
const apiPath = '/api'; var apiPath = '/api';
app.use(apiPath, loopback.rest()); app.use(apiPath, loopback.rest());
app.use(loopback.urlNotFound()); app.use(loopback.urlNotFound());
app.use(errorHandler()); app.use(errorHandler());

View File

@ -1,21 +1,21 @@
// Copyright IBM Corp. 2015,2019. All Rights Reserved. // Copyright IBM Corp. 2015,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const loopback = require('../../../../index'); var loopback = require('../../../../index');
const boot = require('loopback-boot'); var boot = require('loopback-boot');
const app = module.exports = loopback({ var app = module.exports = loopback({
localRegistry: true, localRegistry: true,
loadBuiltinModels: true, loadBuiltinModels: true,
}); });
const errorHandler = require('strong-error-handler'); var errorHandler = require('strong-error-handler');
app.enableAuth(); app.enableAuth();
boot(app, __dirname); boot(app, __dirname);
app.use(loopback.token({model: app.models.AccessToken})); app.use(loopback.token({model: app.models.AccessToken}));
const apiPath = '/api'; var apiPath = '/api';
app.use(apiPath, loopback.rest()); app.use(apiPath, loopback.rest());
app.use(loopback.urlNotFound()); app.use(loopback.urlNotFound());
app.use(errorHandler()); app.use(errorHandler());

View File

@ -1,19 +1,19 @@
// Copyright IBM Corp. 2013,2019. All Rights Reserved. // Copyright IBM Corp. 2013,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const assert = require('assert'); var assert = require('assert');
const loopback = require('../'); var loopback = require('../');
const GeoPoint = loopback.GeoPoint; var GeoPoint = loopback.GeoPoint;
describe('GeoPoint', function() { describe('GeoPoint', function() {
describe('geoPoint.distanceTo(geoPoint, options)', function() { describe('geoPoint.distanceTo(geoPoint, options)', function() {
it('Get the distance to another `GeoPoint`', function() { it('Get the distance to another `GeoPoint`', function() {
const here = new GeoPoint({lat: 10, lng: 10}); var here = new GeoPoint({lat: 10, lng: 10});
const there = new GeoPoint({lat: 5, lng: 5}); var there = new GeoPoint({lat: 5, lng: 5});
const distance = here.distanceTo(there, {type: 'meters'}); var distance = here.distanceTo(there, {type: 'meters'});
assert.equal(Math.floor(distance), 782777); assert.equal(Math.floor(distance), 782777);
}); });
@ -21,9 +21,9 @@ describe('GeoPoint', function() {
describe('GeoPoint.distanceBetween(a, b, options)', function() { describe('GeoPoint.distanceBetween(a, b, options)', function() {
it('Get the distance between two points', function() { it('Get the distance between two points', function() {
const here = new GeoPoint({lat: 10, lng: 10}); var here = new GeoPoint({lat: 10, lng: 10});
const there = new GeoPoint({lat: 5, lng: 5}); var there = new GeoPoint({lat: 5, lng: 5});
const distance = GeoPoint.distanceBetween(here, there, {type: 'feet'}); var distance = GeoPoint.distanceBetween(here, there, {type: 'feet'});
assert.equal(Math.floor(distance), 2568169); assert.equal(Math.floor(distance), 2568169);
}); });
@ -31,32 +31,32 @@ describe('GeoPoint', function() {
describe('GeoPoint()', function() { describe('GeoPoint()', function() {
it('Create from string', function() { it('Create from string', function() {
const point = new GeoPoint('1.234,5.678'); var point = new GeoPoint('1.234,5.678');
assert.equal(point.lat, 1.234); assert.equal(point.lat, 1.234);
assert.equal(point.lng, 5.678); assert.equal(point.lng, 5.678);
const point2 = new GeoPoint('1.222, 5.333'); var point2 = new GeoPoint('1.222, 5.333');
assert.equal(point2.lat, 1.222); assert.equal(point2.lat, 1.222);
assert.equal(point2.lng, 5.333); assert.equal(point2.lng, 5.333);
const point3 = new GeoPoint('1.333, 5.111'); var point3 = new GeoPoint('1.333, 5.111');
assert.equal(point3.lat, 1.333); assert.equal(point3.lat, 1.333);
assert.equal(point3.lng, 5.111); assert.equal(point3.lng, 5.111);
}); });
it('Serialize as string', function() { it('Serialize as string', function() {
const str = '1.234,5.678'; var str = '1.234,5.678';
const point = new GeoPoint(str); var point = new GeoPoint(str);
assert.equal(point.toString(), str); assert.equal(point.toString(), str);
}); });
it('Create from array', function() { it('Create from array', function() {
const point = new GeoPoint([5.555, 6.777]); var point = new GeoPoint([5.555, 6.777]);
assert.equal(point.lat, 5.555); assert.equal(point.lat, 5.555);
assert.equal(point.lng, 6.777); assert.equal(point.lng, 6.777);
}); });
it('Create as Model property', function() { it('Create as Model property', function() {
const Model = loopback.createModel('geo-model', { var Model = loopback.createModel('geo-model', {
geo: {type: 'GeoPoint'}, geo: {type: 'GeoPoint'},
}); });
const m = new Model({ var m = new Model({
geo: '1.222,3.444', geo: '1.222,3.444',
}); });

View File

@ -1,11 +1,11 @@
// Copyright IBM Corp. 2016,2019. All Rights Reserved. // Copyright IBM Corp. 2016,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const chai = require('chai'); var chai = require('chai');
chai.use(require('dirty-chai')); chai.use(require('dirty-chai'));
chai.use(require('sinon-chai')); chai.use(require('sinon-chai'));

View File

@ -1,24 +1,24 @@
// Copyright IBM Corp. 2015,2019. All Rights Reserved. // Copyright IBM Corp. 2015,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const _describe = {}; var _describe = {};
const _it = {}; var _it = {};
const _beforeEach = {}; var _beforeEach = {};
const helpers = { var helpers = {
describe: _describe, describe: _describe,
it: _it, it: _it,
beforeEach: _beforeEach, beforeEach: _beforeEach,
}; };
module.exports = helpers; module.exports = helpers;
const assert = require('assert'); var assert = require('assert');
const request = require('supertest'); var request = require('supertest');
const chai = require('chai'); var chai = require('chai');
const expect = chai.expect; var expect = chai.expect;
const sinon = require('sinon'); var sinon = require('sinon');
chai.use(require('sinon-chai')); chai.use(require('sinon-chai'));
_beforeEach.withApp = function(app) { _beforeEach.withApp = function(app) {
@ -29,7 +29,7 @@ _beforeEach.withApp = function(app) {
beforeEach(function(done) { beforeEach(function(done) {
this.app = app; this.app = app;
const _request = this.request = request(app); var _request = this.request = request(app);
this.post = _request.post; this.post = _request.post;
this.get = _request.get; this.get = _request.get;
this.put = _request.put; this.put = _request.put;
@ -45,14 +45,14 @@ _beforeEach.withApp = function(app) {
}; };
_beforeEach.withArgs = function() { _beforeEach.withArgs = function() {
const args = Array.prototype.slice.call(arguments, 0); var args = Array.prototype.slice.call(arguments, 0);
beforeEach(function() { beforeEach(function() {
this.args = args; this.args = args;
}); });
}; };
_beforeEach.givenModel = function(modelName, attrs, optionalHandler) { _beforeEach.givenModel = function(modelName, attrs, optionalHandler) {
let modelKey = modelName; var modelKey = modelName;
if (typeof attrs === 'function') { if (typeof attrs === 'function') {
optionalHandler = attrs; optionalHandler = attrs;
@ -66,9 +66,9 @@ _beforeEach.givenModel = function(modelName, attrs, optionalHandler) {
attrs = attrs || {}; attrs = attrs || {};
beforeEach(function(done) { beforeEach(function(done) {
const test = this; var test = this;
const app = this.app; var app = this.app;
const model = app.models[modelName]; var model = app.models[modelName];
app.set('remoting', {errorHandler: {debug: true, log: false}}); app.set('remoting', {errorHandler: {debug: true, log: false}});
assert(model, 'cannot get model of name ' + modelName + ' from app.models'); assert(model, 'cannot get model of name ' + modelName + ' from app.models');
@ -76,7 +76,7 @@ _beforeEach.givenModel = function(modelName, attrs, optionalHandler) {
' without attached dataSource'); ' without attached dataSource');
assert( assert(
typeof model.create === 'function', typeof model.create === 'function',
modelName + ' does not have a create method', modelName + ' does not have a create method'
); );
model.create(attrs, function(err, result) { model.create(attrs, function(err, result) {
@ -108,7 +108,7 @@ _beforeEach.givenUser = function(attrs, optionalHandler) {
_beforeEach.givenLoggedInUser = function(credentials, optionalHandler) { _beforeEach.givenLoggedInUser = function(credentials, optionalHandler) {
_beforeEach.givenUser(credentials, function(done) { _beforeEach.givenUser(credentials, function(done) {
const test = this; var test = this;
this.user.constructor.login(credentials, function(err, token) { this.user.constructor.login(credentials, function(err, token) {
if (err) { if (err) {
done(err); done(err);
@ -121,7 +121,7 @@ _beforeEach.givenLoggedInUser = function(credentials, optionalHandler) {
}); });
afterEach(function(done) { afterEach(function(done) {
const test = this; var test = this;
this.loggedInAccessToken.destroy(function(err) { this.loggedInAccessToken.destroy(function(err) {
if (err) return done(err); if (err) return done(err);
@ -146,7 +146,7 @@ _describe.whenCalledRemotely = function(verb, url, data, cb) {
data = null; data = null;
} }
let urlStr = url; var urlStr = url;
if (typeof url === 'function') { if (typeof url === 'function') {
urlStr = '/<dynamic>'; urlStr = '/<dynamic>';
} }
@ -159,11 +159,11 @@ _describe.whenCalledRemotely = function(verb, url, data, cb) {
this.remotely = true; this.remotely = true;
this.verb = verb.toUpperCase(); this.verb = verb.toUpperCase();
this.url = this.url || url; this.url = this.url || url;
let methodForVerb = verb.toLowerCase(); var methodForVerb = verb.toLowerCase();
if (methodForVerb === 'delete') methodForVerb = 'del'; if (methodForVerb === 'delete') methodForVerb = 'del';
if (this.request === undefined) { if (this.request === undefined) {
const msg = 'App is not specified. ' + var msg = 'App is not specified. ' +
'Please use lt.beforeEach.withApp to specify the app.'; 'Please use lt.beforeEach.withApp to specify the app.';
throw new Error(msg); throw new Error(msg);
} }
@ -175,13 +175,13 @@ _describe.whenCalledRemotely = function(verb, url, data, cb) {
this.http.set('authorization', this.loggedInAccessToken.id); this.http.set('authorization', this.loggedInAccessToken.id);
} }
if (data) { if (data) {
let payload = data; var payload = data;
if (typeof data === 'function') if (typeof data === 'function')
payload = data.call(this); payload = data.call(this);
this.http.send(payload); this.http.send(payload);
} }
this.req = this.http.req; this.req = this.http.req;
const test = this; var test = this;
this.http.end(function(err) { this.http.end(function(err) {
test.req = test.http.req; test.req = test.http.req;
test.res = test.http.response; test.res = test.http.response;
@ -236,7 +236,7 @@ _it.shouldBeAllowed = function() {
_it.shouldBeDenied = function() { _it.shouldBeDenied = function() {
it('should not be allowed', function() { it('should not be allowed', function() {
assert(this.res); assert(this.res);
const expectedStatus = this.aclErrorStatus || var expectedStatus = this.aclErrorStatus ||
this.app && this.app.get('aclErrorStatus') || this.app && this.app.get('aclErrorStatus') ||
401; 401;
expect(this.res.statusCode).to.equal(expectedStatus); expect(this.res.statusCode).to.equal(expectedStatus);

View File

@ -1,11 +1,11 @@
// Copyright IBM Corp. 2017,2019. All Rights Reserved. // Copyright IBM Corp. 2017,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const env = process.env; var env = process.env;
// delete any user-provided language settings // delete any user-provided language settings
delete env.LC_ALL; delete env.LC_ALL;

View File

@ -1,24 +1,24 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const assert = require('assert'); var assert = require('assert');
const loopback = require('../'); var loopback = require('../');
const request = require('supertest'); var request = require('supertest');
describe('hidden properties', function() { describe('hidden properties', function() {
beforeEach(function(done) { beforeEach(function(done) {
const app = this.app = loopback(); var app = this.app = loopback();
const Product = this.Product = loopback.PersistedModel.extend( var Product = this.Product = loopback.PersistedModel.extend(
'product', 'product',
{}, {},
{hidden: ['secret']}, {hidden: ['secret']}
); );
Product.attachTo(loopback.memory()); Product.attachTo(loopback.memory());
const Category = this.Category = loopback.PersistedModel.extend('category'); var Category = this.Category = loopback.PersistedModel.extend('category');
Category.attachTo(loopback.memory()); Category.attachTo(loopback.memory());
Category.hasMany(Product); Category.hasMany(Product);
@ -37,7 +37,7 @@ describe('hidden properties', function() {
}); });
afterEach(function(done) { afterEach(function(done) {
const Product = this.Product; var Product = this.Product;
this.Category.destroyAll(function() { this.Category.destroyAll(function() {
Product.destroyAll(done); Product.destroyAll(done);
}); });
@ -51,7 +51,7 @@ describe('hidden properties', function() {
.end(function(err, res) { .end(function(err, res) {
if (err) return done(err); if (err) return done(err);
const product = res.body[0]; var product = res.body[0];
assert.equal(product.secret, undefined); assert.equal(product.secret, undefined);
done(); done();
@ -59,7 +59,7 @@ describe('hidden properties', function() {
}); });
it('should hide a property of nested models', function(done) { it('should hide a property of nested models', function(done) {
const app = this.app; var app = this.app;
request(app) request(app)
.get('/categories?filter[include]=products') .get('/categories?filter[include]=products')
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
@ -67,8 +67,8 @@ describe('hidden properties', function() {
.end(function(err, res) { .end(function(err, res) {
if (err) return done(err); if (err) return done(err);
const category = res.body[0]; var category = res.body[0];
const product = category.products[0]; var product = category.products[0];
assert.equal(product.secret, undefined); assert.equal(product.secret, undefined);
done(); done();

View File

@ -1,18 +1,18 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const expect = require('./helpers/expect'); var expect = require('./helpers/expect');
const loopback = require('../'); var loopback = require('../');
const net = require('net'); var net = require('net');
describe('loopback application', function() { describe('loopback application', function() {
it('pauses request stream during authentication', function(done) { it('pauses request stream during authentication', function(done) {
// This test reproduces the issue reported in // This test reproduces the issue reported in
// https://github.com/strongloop/loopback-storage-service/issues/7 // https://github.com/strongloop/loopback-storage-service/issues/7
const app = loopback(); var app = loopback();
setupAppWithStreamingMethod(); setupAppWithStreamingMethod();
app.listen(0, function() { app.listen(0, function() {
@ -29,7 +29,7 @@ describe('loopback application', function() {
expect(res).to.match(/\nX$/); expect(res).to.match(/\nX$/);
done(); done();
}, }
); );
}); });
@ -37,7 +37,7 @@ describe('loopback application', function() {
app.dataSource('db', { app.dataSource('db', {
connector: loopback.Memory, connector: loopback.Memory,
}); });
const db = app.datasources.db; var db = app.datasources.db;
loopback.User.attachTo(db); loopback.User.attachTo(db);
loopback.AccessToken.attachTo(db); loopback.AccessToken.attachTo(db);
@ -45,10 +45,10 @@ describe('loopback application', function() {
loopback.ACL.attachTo(db); loopback.ACL.attachTo(db);
loopback.User.hasMany(loopback.AccessToken, {as: 'accessTokens'}); loopback.User.hasMany(loopback.AccessToken, {as: 'accessTokens'});
const Streamer = app.registry.createModel('Streamer'); var Streamer = app.registry.createModel('Streamer');
app.model(Streamer, {dataSource: 'db'}); app.model(Streamer, {dataSource: 'db'});
Streamer.read = function(req, res, cb) { Streamer.read = function(req, res, cb) {
let body = new Buffer(0); var body = new Buffer(0);
req.on('data', function(chunk) { req.on('data', function(chunk) {
body += chunk; body += chunk;
}); });
@ -75,8 +75,8 @@ describe('loopback application', function() {
} }
function sendHttpRequestInOnePacket(port, reqString, cb) { function sendHttpRequestInOnePacket(port, reqString, cb) {
const socket = net.createConnection(port); var socket = net.createConnection(port);
let response = new Buffer(0); var response = new Buffer(0);
socket.on('data', function(chunk) { socket.on('data', function(chunk) {
response += chunk; response += chunk;

View File

@ -1,39 +1,14 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict';
const isDocker = require('is-docker');
const which = require('which');
// Karma configuration // Karma configuration
// http://karma-runner.github.io/0.12/config/configuration-file.html // http://karma-runner.github.io/0.12/config/configuration-file.html
'use strict';
module.exports = function(config) { module.exports = function(config) {
// see https://github.com/docker/for-linux/issues/496
const disableChromeSandbox = isDocker() && !process.env.TRAVIS;
if (disableChromeSandbox) {
console.log('!! Disabling Chrome sandbox to support un-privileged Docker !!');
}
const hasChromium =
which.sync('chromium-browser', {nothrow: true}) ||
which.sync('chromium', {nothrow: true});
config.set({ config.set({
customLaunchers: {
ChromeDocker: {
// cis-jenkins build server does not provide Chrome, only Chromium
base: hasChromium ? 'ChromiumHeadless' : 'ChromeHeadless',
// We must disable the Chrome sandbox when running Chrome inside Docker
// (Chrome's sandbox needs more permissions than Docker allows by default)
// See https://github.com/docker/for-linux/issues/496
flags: disableChromeSandbox ? ['--no-sandbox'] : [],
},
},
// enable / disable watching file and executing tests whenever any file changes // enable / disable watching file and executing tests whenever any file changes
autoWatch: true, autoWatch: true,
@ -89,6 +64,7 @@ module.exports = function(config) {
'karma-browserify', 'karma-browserify',
'karma-es6-shim', 'karma-es6-shim',
'karma-mocha', 'karma-mocha',
'karma-phantomjs-launcher',
'karma-chrome-launcher', 'karma-chrome-launcher',
'karma-junit-reporter', 'karma-junit-reporter',
], ],
@ -128,18 +104,27 @@ module.exports = function(config) {
'superagent', 'superagent',
'supertest', 'supertest',
], ],
packageFilter: function(pkg, dir) { transform: [
// async@3 (used e.g. by loopback-connector) is specifying custom ['babelify', {
// browserify config, in particular it wants to apply transformation presets: [
// `babelify`. We don't have `babelify` installed because we are ['es2015', {
// testing using latest Chrome and thus don't need any transpilation. // Disable transform-es2015-modules-commonjs which adds
// Let's remove the browserify config from the package and force // "use strict" to all files, even those that don't work
// browserify to use our config instead. // in strict mode
if (pkg.name === 'async') { // (e.g. chai, loopback-datasource-juggler, etc.)
delete pkg.browserify; modules: false,
} }],
return pkg; ],
}, // By default, browserify does not transform node_modules
// As a result, our dependencies like strong-remoting and juggler
// are kept in original ES6 form that does not work in PhantomJS
global: true,
// Prevent SyntaxError in strong-task-emitter:
// strong-task-emitter/lib/task.js (83:4):
// arguments is a reserved word in strict mode
ignore: /node_modules\/(strong-task-emitter)\//,
}],
],
debug: true, debug: true,
// noParse: ['jquery'], // noParse: ['jquery'],
watch: true, watch: true,

View File

@ -1,18 +1,18 @@
// Copyright IBM Corp. 2016,2019. All Rights Reserved. // Copyright IBM Corp. 2016,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const expect = require('./helpers/expect'); var expect = require('./helpers/expect');
const http = require('http'); var http = require('http');
const loopback = require('..'); var loopback = require('..');
const supertest = require('supertest'); var supertest = require('supertest');
const AN_OBJECT_VALUE = {name: 'an-object'}; var AN_OBJECT_VALUE = {name: 'an-object'};
describe('KeyValueModel', function() { describe('KeyValueModel', function() {
let request, app, CacheItem; var request, app, CacheItem;
beforeEach(setupAppAndCacheItem); beforeEach(setupAppAndCacheItem);
describe('REST API', function() { describe('REST API', function() {
@ -156,7 +156,7 @@ describe('KeyValueModel', function() {
app.model(CacheItem, {dataSource: 'kv'}); app.model(CacheItem, {dataSource: 'kv'});
} }
let _server, _requestHandler; // eslint-disable-line one-var var _server, _requestHandler; // eslint-disable-line one-var
function setupSharedHttpServer(done) { function setupSharedHttpServer(done) {
_server = http.createServer(function(req, res) { _server = http.createServer(function(req, res) {
app(req, res); app(req, res);

View File

@ -1,20 +1,20 @@
// Copyright IBM Corp. 2013,2019. All Rights Reserved. // Copyright IBM Corp. 2013,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const it = require('./util/it'); var it = require('./util/it');
const describe = require('./util/describe'); var describe = require('./util/describe');
const Domain = require('domain'); var Domain = require('domain');
const EventEmitter = require('events').EventEmitter; var EventEmitter = require('events').EventEmitter;
const loopback = require('../'); var loopback = require('../');
const expect = require('./helpers/expect'); var expect = require('./helpers/expect');
const assert = require('assert'); var assert = require('assert');
describe('loopback', function() { describe('loopback', function() {
let nameCounter = 0; var nameCounter = 0;
let uniqueModelName; var uniqueModelName;
beforeEach(function() { beforeEach(function() {
uniqueModelName = 'TestModel-' + (++nameCounter); uniqueModelName = 'TestModel-' + (++nameCounter);
@ -27,7 +27,7 @@ describe('loopback', function() {
}); });
it.onServer('includes `faviconFile`', function() { it.onServer('includes `faviconFile`', function() {
const file = loopback.faviconFile; var file = loopback.faviconFile;
expect(file, 'faviconFile').to.not.equal(undefined); expect(file, 'faviconFile').to.not.equal(undefined);
expect(require('fs').existsSync(loopback.faviconFile), 'file exists') expect(require('fs').existsSync(loopback.faviconFile), 'file exists')
.to.equal(true); .to.equal(true);
@ -38,7 +38,7 @@ describe('loopback', function() {
}); });
it.onServer('exports all expected properties', function() { it.onServer('exports all expected properties', function() {
const EXPECTED = [ var EXPECTED = [
'ACL', 'ACL',
'AccessToken', 'AccessToken',
'Application', 'Application',
@ -98,7 +98,7 @@ describe('loopback', function() {
'version', 'version',
]; ];
const actual = Object.getOwnPropertyNames(loopback); var actual = Object.getOwnPropertyNames(loopback);
actual.sort(); actual.sort();
expect(actual).to.include.members(EXPECTED); expect(actual).to.include.members(EXPECTED);
}); });
@ -106,17 +106,17 @@ describe('loopback', function() {
describe('loopback(options)', function() { describe('loopback(options)', function() {
it('supports localRegistry:true', function() { it('supports localRegistry:true', function() {
const app = loopback({localRegistry: true}); var app = loopback({localRegistry: true});
expect(app.registry).to.not.equal(loopback.registry); expect(app.registry).to.not.equal(loopback.registry);
}); });
it('does not load builtin models into the local registry', function() { it('does not load builtin models into the local registry', function() {
const app = loopback({localRegistry: true}); var app = loopback({localRegistry: true});
expect(app.registry.findModel('User')).to.equal(undefined); expect(app.registry.findModel('User')).to.equal(undefined);
}); });
it('supports loadBuiltinModels:true', function() { it('supports loadBuiltinModels:true', function() {
const app = loopback({localRegistry: true, loadBuiltinModels: true}); var app = loopback({localRegistry: true, loadBuiltinModels: true});
expect(app.registry.findModel('User')) expect(app.registry.findModel('User'))
.to.have.property('modelName', 'User'); .to.have.property('modelName', 'User');
}); });
@ -124,7 +124,7 @@ describe('loopback', function() {
describe('loopback.createDataSource(options)', function() { describe('loopback.createDataSource(options)', function() {
it('Create a data source with a connector.', function() { it('Create a data source with a connector.', function() {
const dataSource = loopback.createDataSource({ var dataSource = loopback.createDataSource({
connector: loopback.Memory, connector: loopback.Memory,
}); });
assert(dataSource.connector); assert(dataSource.connector);
@ -133,24 +133,24 @@ describe('loopback', function() {
describe('data source created by loopback', function() { describe('data source created by loopback', function() {
it('should create model extending Model by default', function() { it('should create model extending Model by default', function() {
const dataSource = loopback.createDataSource({ var dataSource = loopback.createDataSource({
connector: loopback.Memory, connector: loopback.Memory,
}); });
const m1 = dataSource.createModel('m1', {}); var m1 = dataSource.createModel('m1', {});
assert(m1.prototype instanceof loopback.Model); assert(m1.prototype instanceof loopback.Model);
}); });
}); });
describe('model created by loopback', function() { describe('model created by loopback', function() {
it('should extend from Model by default', function() { it('should extend from Model by default', function() {
const m1 = loopback.createModel('m1', {}); var m1 = loopback.createModel('m1', {});
assert(m1.prototype instanceof loopback.Model); assert(m1.prototype instanceof loopback.Model);
}); });
}); });
describe('loopback.remoteMethod(Model, fn, [options]);', function() { describe('loopback.remoteMethod(Model, fn, [options]);', function() {
it('Setup a remote method.', function() { it('Setup a remote method.', function() {
const Product = loopback.createModel('product', {price: Number}); var Product = loopback.createModel('product', {price: Number});
Product.stats = function(fn) { Product.stats = function(fn) {
// ... // ...
@ -161,7 +161,7 @@ describe('loopback', function() {
{ {
returns: {arg: 'stats', type: 'array'}, returns: {arg: 'stats', type: 'array'},
http: {path: '/info', verb: 'get'}, http: {path: '/info', verb: 'get'},
}, }
); );
assert.equal(Product.stats.returns.arg, 'stats'); assert.equal(Product.stats.returns.arg, 'stats');
@ -175,12 +175,12 @@ describe('loopback', function() {
describe('loopback.createModel(name, properties, options)', function() { describe('loopback.createModel(name, properties, options)', function() {
describe('options.base', function() { describe('options.base', function() {
it('should extend from options.base', function() { it('should extend from options.base', function() {
const MyModel = loopback.createModel('MyModel', {}, { var MyModel = loopback.createModel('MyModel', {}, {
foo: { foo: {
bar: 'bat', bar: 'bat',
}, },
}); });
const MyCustomModel = loopback.createModel('MyCustomModel', {}, { var MyCustomModel = loopback.createModel('MyCustomModel', {}, {
base: 'MyModel', base: 'MyModel',
foo: { foo: {
bat: 'baz', bat: 'baz',
@ -194,12 +194,12 @@ describe('loopback', function() {
describe('loopback.getModel and getModelByType', function() { describe('loopback.getModel and getModelByType', function() {
it('should be able to get model by name', function() { it('should be able to get model by name', function() {
const MyModel = loopback.createModel('MyModel', {}, { var MyModel = loopback.createModel('MyModel', {}, {
foo: { foo: {
bar: 'bat', bar: 'bat',
}, },
}); });
const MyCustomModel = loopback.createModel('MyCustomModel', {}, { var MyCustomModel = loopback.createModel('MyCustomModel', {}, {
base: 'MyModel', base: 'MyModel',
foo: { foo: {
bat: 'baz', bat: 'baz',
@ -211,12 +211,12 @@ describe('loopback', function() {
assert(loopback.getModel(MyModel) === MyModel); assert(loopback.getModel(MyModel) === MyModel);
}); });
it('should be able to get model by type', function() { it('should be able to get model by type', function() {
const MyModel = loopback.createModel('MyModel', {}, { var MyModel = loopback.createModel('MyModel', {}, {
foo: { foo: {
bar: 'bat', bar: 'bat',
}, },
}); });
const MyCustomModel = loopback.createModel('MyCustomModel', {}, { var MyCustomModel = loopback.createModel('MyCustomModel', {}, {
base: 'MyModel', base: 'MyModel',
foo: { foo: {
bat: 'baz', bat: 'baz',
@ -233,7 +233,7 @@ describe('loopback', function() {
}); });
it('configures remote methods', function() { it('configures remote methods', function() {
const TestModel = loopback.createModel(uniqueModelName, {}, { var TestModel = loopback.createModel(uniqueModelName, {}, {
methods: { methods: {
staticMethod: { staticMethod: {
isStatic: true, isStatic: true,
@ -246,7 +246,7 @@ describe('loopback', function() {
}, },
}); });
const methodNames = TestModel.sharedClass.methods().map(function(m) { var methodNames = TestModel.sharedClass.methods().map(function(m) {
return m.stringName.replace(/^[^.]+\./, ''); // drop the class name return m.stringName.replace(/^[^.]+\./, ''); // drop the class name
}); });
@ -259,7 +259,7 @@ describe('loopback', function() {
describe('loopback.createModel(config)', function() { describe('loopback.createModel(config)', function() {
it('creates the model', function() { it('creates the model', function() {
const model = loopback.createModel({ var model = loopback.createModel({
name: uniqueModelName, name: uniqueModelName,
}); });
@ -267,7 +267,7 @@ describe('loopback', function() {
}); });
it('interprets extra first-level keys as options', function() { it('interprets extra first-level keys as options', function() {
const model = loopback.createModel({ var model = loopback.createModel({
name: uniqueModelName, name: uniqueModelName,
base: 'User', base: 'User',
}); });
@ -276,7 +276,7 @@ describe('loopback', function() {
}); });
it('prefers config.options.key over config.key', function() { it('prefers config.options.key over config.key', function() {
const model = loopback.createModel({ var model = loopback.createModel({
name: uniqueModelName, name: uniqueModelName,
base: 'User', base: 'User',
options: { options: {
@ -290,7 +290,7 @@ describe('loopback', function() {
describe('loopback.configureModel(ModelCtor, config)', function() { describe('loopback.configureModel(ModelCtor, config)', function() {
it('adds new relations', function() { it('adds new relations', function() {
const model = loopback.Model.extend(uniqueModelName); var model = loopback.Model.extend(uniqueModelName);
loopback.configureModel(model, { loopback.configureModel(model, {
dataSource: null, dataSource: null,
@ -306,7 +306,7 @@ describe('loopback', function() {
}); });
it('updates existing relations', function() { it('updates existing relations', function() {
const model = loopback.Model.extend(uniqueModelName, {}, { var model = loopback.Model.extend(uniqueModelName, {}, {
relations: { relations: {
owner: { owner: {
type: 'belongsTo', type: 'belongsTo',
@ -331,8 +331,8 @@ describe('loopback', function() {
}); });
it('updates relations before attaching to a dataSource', function() { it('updates relations before attaching to a dataSource', function() {
const db = loopback.createDataSource({connector: loopback.Memory}); var db = loopback.createDataSource({connector: loopback.Memory});
const model = loopback.Model.extend(uniqueModelName); var model = loopback.Model.extend(uniqueModelName);
// This test used to work because User model was already attached // This test used to work because User model was already attached
// by other tests via `loopback.autoAttach()` // by other tests via `loopback.autoAttach()`
@ -352,13 +352,13 @@ describe('loopback', function() {
}, },
}); });
const owner = model.prototype.owner; var owner = model.prototype.owner;
expect(owner, 'model.prototype.owner').to.be.a('function'); expect(owner, 'model.prototype.owner').to.be.a('function');
expect(owner._targetClass).to.equal('User'); expect(owner._targetClass).to.equal('User');
}); });
it('adds new acls', function() { it('adds new acls', function() {
const model = loopback.Model.extend(uniqueModelName, {}, { var model = loopback.Model.extend(uniqueModelName, {}, {
acls: [ acls: [
{ {
property: 'find', property: 'find',
@ -402,7 +402,7 @@ describe('loopback', function() {
}); });
it('updates existing acls', function() { it('updates existing acls', function() {
const model = loopback.Model.extend(uniqueModelName, {}, { var model = loopback.Model.extend(uniqueModelName, {}, {
acls: [ acls: [
{ {
property: 'find', property: 'find',
@ -439,12 +439,12 @@ describe('loopback', function() {
}); });
it('updates existing settings', function() { it('updates existing settings', function() {
const model = loopback.Model.extend(uniqueModelName, {}, { var model = loopback.Model.extend(uniqueModelName, {}, {
ttl: 10, ttl: 10,
emailVerificationRequired: false, emailVerificationRequired: false,
}); });
const baseName = model.settings.base.name; var baseName = model.settings.base.name;
loopback.configureModel(model, { loopback.configureModel(model, {
dataSource: null, dataSource: null,
@ -465,7 +465,7 @@ describe('loopback', function() {
}); });
it('configures remote methods', function() { it('configures remote methods', function() {
const TestModel = loopback.createModel(uniqueModelName); var TestModel = loopback.createModel(uniqueModelName);
loopback.configureModel(TestModel, { loopback.configureModel(TestModel, {
dataSource: null, dataSource: null,
methods: { methods: {
@ -480,7 +480,7 @@ describe('loopback', function() {
}, },
}); });
const methodNames = TestModel.sharedClass.methods().map(function(m) { var methodNames = TestModel.sharedClass.methods().map(function(m) {
return m.stringName.replace(/^[^.]+\./, ''); // drop the class name return m.stringName.replace(/^[^.]+\./, ''); // drop the class name
}); });
@ -493,14 +493,14 @@ describe('loopback', function() {
describe('loopback object', function() { describe('loopback object', function() {
it('inherits properties from express', function() { it('inherits properties from express', function() {
const express = require('express'); var express = require('express');
for (const i in express) { for (var i in express) {
expect(loopback).to.have.property(i, express[i]); expect(loopback).to.have.property(i, express[i]);
} }
}); });
it('exports all built-in models', function() { it('exports all built-in models', function() {
const expectedModelNames = [ var expectedModelNames = [
'Email', 'Email',
'User', 'User',
'Application', 'Application',
@ -530,7 +530,7 @@ describe('loopback', function() {
} }
it('treats method names that don\'t start with "prototype." as "isStatic:true"', function() { it('treats method names that don\'t start with "prototype." as "isStatic:true"', function() {
const TestModel = loopback.createModel(uniqueModelName); var TestModel = loopback.createModel(uniqueModelName);
loopback.configureModel(TestModel, { loopback.configureModel(TestModel, {
dataSource: null, dataSource: null,
methods: { methods: {
@ -540,13 +540,13 @@ describe('loopback', function() {
}, },
}); });
const methodNames = getAllMethodNamesWithoutClassName(TestModel); var methodNames = getAllMethodNamesWithoutClassName(TestModel);
expect(methodNames).to.include('staticMethod'); expect(methodNames).to.include('staticMethod');
}); });
it('treats method names starting with "prototype." as "isStatic:false"', function() { it('treats method names starting with "prototype." as "isStatic:false"', function() {
const TestModel = loopback.createModel(uniqueModelName); var TestModel = loopback.createModel(uniqueModelName);
loopback.configureModel(TestModel, { loopback.configureModel(TestModel, {
dataSource: null, dataSource: null,
methods: { methods: {
@ -556,15 +556,13 @@ describe('loopback', function() {
}, },
}); });
const methodNames = getAllMethodNamesWithoutClassName(TestModel); var methodNames = getAllMethodNamesWithoutClassName(TestModel);
expect(methodNames).to.include('prototype.instanceMethod'); expect(methodNames).to.include('prototype.instanceMethod');
}); });
// Skip this test in browsers because strong-globalize is not removing it('throws an error when "isStatic:true" and method name starts with "prototype."', function() {
// `{{` and `}}` control characters from the string. var TestModel = loopback.createModel(uniqueModelName);
it.onServer('throws when "isStatic:true" and method name starts with "prototype."', function() {
const TestModel = loopback.createModel(uniqueModelName);
expect(function() { expect(function() {
loopback.configureModel(TestModel, { loopback.configureModel(TestModel, {
dataSource: null, dataSource: null,
@ -575,12 +573,12 @@ describe('loopback', function() {
}, },
}, },
}); });
}).to.throw(Error, 'Remoting metadata for ' + TestModel.modelName + }).to.throw(Error, new Error('Remoting metadata for' + TestModel.modelName +
'.prototype.instanceMethod "isStatic" does not match new method name-based style.'); ' "isStatic" does not match new method name-based style.'));
}); });
it('use "isStatic:true" if method name does not start with "prototype."', function() { it('use "isStatic:true" if method name does not start with "prototype."', function() {
const TestModel = loopback.createModel(uniqueModelName); var TestModel = loopback.createModel(uniqueModelName);
loopback.configureModel(TestModel, { loopback.configureModel(TestModel, {
dataSource: null, dataSource: null,
methods: { methods: {
@ -591,13 +589,13 @@ describe('loopback', function() {
}, },
}); });
const methodNames = getAllMethodNamesWithoutClassName(TestModel); var methodNames = getAllMethodNamesWithoutClassName(TestModel);
expect(methodNames).to.include('staticMethod'); expect(methodNames).to.include('staticMethod');
}); });
it('use "isStatic:false" if method name starts with "prototype."', function() { it('use "isStatic:false" if method name starts with "prototype."', function() {
const TestModel = loopback.createModel(uniqueModelName); var TestModel = loopback.createModel(uniqueModelName);
loopback.configureModel(TestModel, { loopback.configureModel(TestModel, {
dataSource: null, dataSource: null,
methods: { methods: {
@ -608,19 +606,19 @@ describe('loopback', function() {
}, },
}); });
const methodNames = getAllMethodNamesWithoutClassName(TestModel); var methodNames = getAllMethodNamesWithoutClassName(TestModel);
expect(methodNames).to.include('prototype.instanceMethod'); expect(methodNames).to.include('prototype.instanceMethod');
}); });
}); });
describe('Remote method inheritance', function() { describe('Remote method inheritance', function() {
let app; var app;
beforeEach(setupLoopback); beforeEach(setupLoopback);
it('inherits remote methods defined via createModel', function() { it('inherits remote methods defined via createModel', function() {
const Base = app.registry.createModel('Base', {}, { var Base = app.registry.createModel('Base', {}, {
methods: { methods: {
greet: { greet: {
http: {path: '/greet'}, http: {path: '/greet'},
@ -628,7 +626,7 @@ describe('loopback', function() {
}, },
}); });
const MyCustomModel = app.registry.createModel('MyCustomModel', {}, { var MyCustomModel = app.registry.createModel('MyCustomModel', {}, {
base: 'Base', base: 'Base',
methods: { methods: {
hello: { hello: {
@ -636,14 +634,14 @@ describe('loopback', function() {
}, },
}, },
}); });
const methodNames = getAllMethodNamesWithoutClassName(MyCustomModel); var methodNames = getAllMethodNamesWithoutClassName(MyCustomModel);
expect(methodNames).to.include('greet'); expect(methodNames).to.include('greet');
expect(methodNames).to.include('hello'); expect(methodNames).to.include('hello');
}); });
it('same remote method with different metadata should override parent', function() { it('same remote method with different metadata should override parent', function() {
const Base = app.registry.createModel('Base', {}, { var Base = app.registry.createModel('Base', {}, {
methods: { methods: {
greet: { greet: {
http: {path: '/greet'}, http: {path: '/greet'},
@ -651,7 +649,7 @@ describe('loopback', function() {
}, },
}); });
const MyCustomModel = app.registry.createModel('MyCustomModel', {}, { var MyCustomModel = app.registry.createModel('MyCustomModel', {}, {
base: 'Base', base: 'Base',
methods: { methods: {
greet: { greet: {
@ -659,9 +657,9 @@ describe('loopback', function() {
}, },
}, },
}); });
const methodNames = getAllMethodNamesWithoutClassName(MyCustomModel); var methodNames = getAllMethodNamesWithoutClassName(MyCustomModel);
const baseMethod = Base.sharedClass.findMethodByName('greet'); var baseMethod = Base.sharedClass.findMethodByName('greet');
const customMethod = MyCustomModel.sharedClass.findMethodByName('greet'); var customMethod = MyCustomModel.sharedClass.findMethodByName('greet');
// Base Method // Base Method
expect(baseMethod.http).to.eql({path: '/greet'}); expect(baseMethod.http).to.eql({path: '/greet'});
@ -676,7 +674,7 @@ describe('loopback', function() {
}); });
it('does not inherit remote methods defined via configureModel', function() { it('does not inherit remote methods defined via configureModel', function() {
const Base = app.registry.createModel('Base'); var Base = app.registry.createModel('Base');
app.registry.configureModel(Base, { app.registry.configureModel(Base, {
dataSource: null, dataSource: null,
methods: { methods: {
@ -686,7 +684,7 @@ describe('loopback', function() {
}, },
}); });
const MyCustomModel = app.registry.createModel('MyCustomModel', {}, { var MyCustomModel = app.registry.createModel('MyCustomModel', {}, {
base: 'Base', base: 'Base',
methods: { methods: {
hello: { hello: {
@ -694,7 +692,7 @@ describe('loopback', function() {
}, },
}, },
}); });
const methodNames = getAllMethodNamesWithoutClassName(MyCustomModel); var methodNames = getAllMethodNamesWithoutClassName(MyCustomModel);
expect(methodNames).to.not.include('greet'); expect(methodNames).to.not.include('greet');
expect(methodNames).to.include('hello'); expect(methodNames).to.include('hello');
@ -702,8 +700,8 @@ describe('loopback', function() {
it('does not inherit remote methods defined via configureModel after child model ' + it('does not inherit remote methods defined via configureModel after child model ' +
'was created', function() { 'was created', function() {
const Base = app.registry.createModel('Base'); var Base = app.registry.createModel('Base');
const MyCustomModel = app.registry.createModel('MyCustomModel', {}, { var MyCustomModel = app.registry.createModel('MyCustomModel', {}, {
base: 'Base', base: 'Base',
}); });
@ -724,8 +722,8 @@ describe('loopback', function() {
}, },
}, },
}); });
const baseMethodNames = getAllMethodNamesWithoutClassName(Base); var baseMethodNames = getAllMethodNamesWithoutClassName(Base);
const methodNames = getAllMethodNamesWithoutClassName(MyCustomModel); var methodNames = getAllMethodNamesWithoutClassName(MyCustomModel);
expect(baseMethodNames).to.include('greet'); expect(baseMethodNames).to.include('greet');
expect(methodNames).to.not.include('greet'); expect(methodNames).to.not.include('greet');
@ -744,12 +742,12 @@ describe('loopback', function() {
}); });
describe('Hiding shared methods', function() { describe('Hiding shared methods', function() {
let app; var app;
beforeEach(setupLoopback); beforeEach(setupLoopback);
it('hides remote methods using fixed method names', function() { it('hides remote methods using fixed method names', function() {
const TestModel = app.registry.createModel(uniqueModelName); var TestModel = app.registry.createModel(uniqueModelName);
app.model(TestModel, { app.model(TestModel, {
dataSource: null, dataSource: null,
methods: { methods: {
@ -767,7 +765,7 @@ describe('loopback', function() {
}, },
}); });
const publicMethods = getSharedMethods(TestModel); var publicMethods = getSharedMethods(TestModel);
expect(publicMethods).not.to.include.members([ expect(publicMethods).not.to.include.members([
'staticMethod', 'staticMethod',
@ -775,7 +773,7 @@ describe('loopback', function() {
}); });
it('hides remote methods using a glob pattern', function() { it('hides remote methods using a glob pattern', function() {
const TestModel = app.registry.createModel(uniqueModelName); var TestModel = app.registry.createModel(uniqueModelName);
app.model(TestModel, { app.model(TestModel, {
dataSource: null, dataSource: null,
methods: { methods: {
@ -797,7 +795,7 @@ describe('loopback', function() {
}, },
}); });
const publicMethods = getSharedMethods(TestModel); var publicMethods = getSharedMethods(TestModel);
expect(publicMethods).to.include.members([ expect(publicMethods).to.include.members([
'staticMethod', 'staticMethod',
@ -808,7 +806,7 @@ describe('loopback', function() {
}); });
it('hides all remote methods using *', function() { it('hides all remote methods using *', function() {
const TestModel = app.registry.createModel(uniqueModelName); var TestModel = app.registry.createModel(uniqueModelName);
app.model(TestModel, { app.model(TestModel, {
dataSource: null, dataSource: null,
methods: { methods: {
@ -830,7 +828,7 @@ describe('loopback', function() {
}, },
}); });
const publicMethods = getSharedMethods(TestModel); var publicMethods = getSharedMethods(TestModel);
expect(publicMethods).to.be.empty(); expect(publicMethods).to.be.empty();
}); });

View File

@ -1,17 +1,17 @@
// Copyright IBM Corp. 2013,2019. All Rights Reserved. // Copyright IBM Corp. 2013,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const assert = require('assert'); var assert = require('assert');
const loopback = require('../'); var loopback = require('../');
describe('Memory Connector', function() { describe('Memory Connector', function() {
it('Create a model using the memory connector', function(done) { it('Create a model using the memory connector', function(done) {
// use the built in memory function // use the built in memory function
// to create a memory data source // to create a memory data source
let memory = loopback.memory(); var memory = loopback.memory();
// or create it using the standard // or create it using the standard
// data source creation api // data source creation api
@ -21,12 +21,12 @@ describe('Memory Connector', function() {
// create a model using the // create a model using the
// memory data source // memory data source
const properties = { var properties = {
name: String, name: String,
price: Number, price: Number,
}; };
const Product = memory.createModel('product', properties); var Product = memory.createModel('product', properties);
Product.create([ Product.create([
{name: 'apple', price: 0.79}, {name: 'apple', price: 0.79},

View File

@ -1,15 +1,15 @@
// Copyright IBM Corp. 2013,2019. All Rights Reserved. // Copyright IBM Corp. 2013,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const loopback = require(('../')); var loopback = require(('../'));
const assert = require('assert'); var assert = require('assert');
const Application = loopback.Application; var Application = loopback.Application;
describe('Application', function() { describe('Application', function() {
let registeredApp = null; var registeredApp = null;
before(function attachToMemory() { before(function attachToMemory() {
Application.attachTo(loopback.memory()); Application.attachTo(loopback.memory());
@ -18,7 +18,7 @@ describe('Application', function() {
it('honors `application.register` - callback variant', function(done) { it('honors `application.register` - callback variant', function(done) {
Application.register('rfeng', 'MyTestApp', Application.register('rfeng', 'MyTestApp',
{description: 'My test application'}, function(err, result) { {description: 'My test application'}, function(err, result) {
const app = result; var app = result;
assert.equal(app.owner, 'rfeng'); assert.equal(app.owner, 'rfeng');
assert.equal(app.name, 'MyTestApp'); assert.equal(app.name, 'MyTestApp');
assert.equal(app.description, 'My test application'); assert.equal(app.description, 'My test application');
@ -31,7 +31,7 @@ describe('Application', function() {
Application.register('rfeng', 'MyTestApp', Application.register('rfeng', 'MyTestApp',
{description: 'My test application'}) {description: 'My test application'})
.then(function(result) { .then(function(result) {
const app = result; var app = result;
assert.equal(app.owner, 'rfeng'); assert.equal(app.owner, 'rfeng');
assert.equal(app.name, 'MyTestApp'); assert.equal(app.name, 'MyTestApp');
assert.equal(app.description, 'My test application'); assert.equal(app.description, 'My test application');
@ -47,7 +47,7 @@ describe('Application', function() {
Application.create({owner: 'rfeng', Application.create({owner: 'rfeng',
name: 'MyApp1', name: 'MyApp1',
description: 'My first mobile application'}, function(err, result) { description: 'My first mobile application'}, function(err, result) {
const app = result; var app = result;
assert.equal(app.owner, 'rfeng'); assert.equal(app.owner, 'rfeng');
assert.equal(app.name, 'MyApp1'); assert.equal(app.name, 'MyApp1');
assert.equal(app.description, 'My first mobile application'); assert.equal(app.description, 'My first mobile application');
@ -90,7 +90,7 @@ describe('Application', function() {
}, },
}}, }},
function(err, result) { function(err, result) {
const app = result; var app = result;
assert.deepEqual(app.pushSettings.toObject(), { assert.deepEqual(app.pushSettings.toObject(), {
apns: { apns: {
production: false, production: false,
@ -119,7 +119,7 @@ describe('Application', function() {
beforeEach(function(done) { beforeEach(function(done) {
Application.register('rfeng', 'MyApp2', Application.register('rfeng', 'MyApp2',
{description: 'My second mobile application'}, function(err, result) { {description: 'My second mobile application'}, function(err, result) {
const app = result; var app = result;
assert.equal(app.owner, 'rfeng'); assert.equal(app.owner, 'rfeng');
assert.equal(app.name, 'MyApp2'); assert.equal(app.name, 'MyApp2');
assert.equal(app.description, 'My second mobile application'); assert.equal(app.description, 'My second mobile application');
@ -138,7 +138,7 @@ describe('Application', function() {
it('Reset keys', function(done) { it('Reset keys', function(done) {
Application.resetKeys(registeredApp.id, function(err, result) { Application.resetKeys(registeredApp.id, function(err, result) {
const app = result; var app = result;
assert.equal(app.owner, 'rfeng'); assert.equal(app.owner, 'rfeng');
assert.equal(app.name, 'MyApp2'); assert.equal(app.name, 'MyApp2');
assert.equal(app.description, 'My second mobile application'); assert.equal(app.description, 'My second mobile application');
@ -165,7 +165,7 @@ describe('Application', function() {
it('Reset keys - promise variant', function(done) { it('Reset keys - promise variant', function(done) {
Application.resetKeys(registeredApp.id) Application.resetKeys(registeredApp.id)
.then(function(result) { .then(function(result) {
const app = result; var app = result;
assert.equal(app.owner, 'rfeng'); assert.equal(app.owner, 'rfeng');
assert.equal(app.name, 'MyApp2'); assert.equal(app.name, 'MyApp2');
assert.equal(app.description, 'My second mobile application'); assert.equal(app.description, 'My second mobile application');
@ -194,7 +194,7 @@ describe('Application', function() {
it('Reset keys without create a new instance', function(done) { it('Reset keys without create a new instance', function(done) {
Application.resetKeys(registeredApp.id, function(err, result) { Application.resetKeys(registeredApp.id, function(err, result) {
const app = result; var app = result;
assert(app.id); assert(app.id);
assert(app.id === registeredApp.id); assert(app.id === registeredApp.id);
registeredApp = app; registeredApp = app;
@ -206,7 +206,7 @@ describe('Application', function() {
it('Reset keys without create a new instance - promise variant', function(done) { it('Reset keys without create a new instance - promise variant', function(done) {
Application.resetKeys(registeredApp.id) Application.resetKeys(registeredApp.id)
.then(function(result) { .then(function(result) {
const app = result; var app = result;
assert(app.id); assert(app.id);
assert(app.id === registeredApp.id); assert(app.id === registeredApp.id);
registeredApp = app; registeredApp = app;
@ -307,12 +307,12 @@ describe('Application', function() {
describe('Application subclass', function() { describe('Application subclass', function() {
it('should use subclass model name', function(done) { it('should use subclass model name', function(done) {
const MyApp = Application.extend('MyApp'); var MyApp = Application.extend('MyApp');
const ds = loopback.createDataSource({connector: loopback.Memory}); var ds = loopback.createDataSource({connector: loopback.Memory});
MyApp.attachTo(ds); MyApp.attachTo(ds);
MyApp.register('rfeng', 'MyApp123', MyApp.register('rfeng', 'MyApp123',
{description: 'My 123 mobile application'}, function(err, result) { {description: 'My 123 mobile application'}, function(err, result) {
const app = result; var app = result;
assert.equal(app.owner, 'rfeng'); assert.equal(app.owner, 'rfeng');
assert.equal(app.name, 'MyApp123'); assert.equal(app.name, 'MyApp123');
assert.equal(app.description, 'My 123 mobile application'); assert.equal(app.description, 'My 123 mobile application');

View File

@ -1,21 +1,21 @@
// Copyright IBM Corp. 2013,2019. All Rights Reserved. // Copyright IBM Corp. 2013,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const assert = require('assert'); var assert = require('assert');
const async = require('async'); var async = require('async');
const describe = require('./util/describe'); var describe = require('./util/describe');
const loopback = require('../'); var loopback = require('../');
const ACL = loopback.ACL; var ACL = loopback.ACL;
const defineModelTestsWithDataSource = require('./util/model-tests'); var defineModelTestsWithDataSource = require('./util/model-tests');
const PersistedModel = loopback.PersistedModel; var PersistedModel = loopback.PersistedModel;
const Promise = require('bluebird'); var Promise = require('bluebird');
const TaskEmitter = require('strong-task-emitter'); var TaskEmitter = require('strong-task-emitter');
const request = require('supertest'); var request = require('supertest');
const expect = require('./helpers/expect'); var expect = require('./helpers/expect');
describe('Model / PersistedModel', function() { describe('Model / PersistedModel', function() {
defineModelTestsWithDataSource({ defineModelTestsWithDataSource({
@ -26,7 +26,7 @@ describe('Model / PersistedModel', function() {
describe('Model.validatesUniquenessOf(property, options)', function() { describe('Model.validatesUniquenessOf(property, options)', function() {
it('Ensure the value for `property` is unique', function(done) { it('Ensure the value for `property` is unique', function(done) {
const User = PersistedModel.extend('ValidatedUser', { var User = PersistedModel.extend('ValidatedUser', {
'first': String, 'first': String,
'last': String, 'last': String,
'age': Number, 'age': Number,
@ -36,7 +36,7 @@ describe('Model / PersistedModel', function() {
'email': String, 'email': String,
}); });
const dataSource = loopback.createDataSource({ var dataSource = loopback.createDataSource({
connector: loopback.Memory, connector: loopback.Memory,
}); });
@ -44,8 +44,8 @@ describe('Model / PersistedModel', function() {
User.validatesUniquenessOf('email', {message: 'email is not unique'}); User.validatesUniquenessOf('email', {message: 'email is not unique'});
const joe = new User({email: 'joe@joe.com'}); var joe = new User({email: 'joe@joe.com'});
const joe2 = new User({email: 'joe@joe.com'}); var joe2 = new User({email: 'joe@joe.com'});
joe.save(function() { joe.save(function() {
joe2.save(function(err) { joe2.save(function(err) {
@ -60,8 +60,8 @@ describe('Model / PersistedModel', function() {
describe('Model.attachTo(dataSource)', function() { describe('Model.attachTo(dataSource)', function() {
it('Attach a model to a [DataSource](#data-source)', function() { it('Attach a model to a [DataSource](#data-source)', function() {
const MyModel = loopback.createModel('my-model', {name: String}); var MyModel = loopback.createModel('my-model', {name: String});
const dataSource = loopback.createDataSource({ var dataSource = loopback.createDataSource({
connector: loopback.Memory, connector: loopback.Memory,
}); });
@ -76,7 +76,7 @@ describe('Model / PersistedModel', function() {
}); });
describe.onServer('Remote Methods', function() { describe.onServer('Remote Methods', function() {
let User, Post, dataSource, app; var User, Post, dataSource, app;
beforeEach(function() { beforeEach(function() {
app = loopback({localRegistry: true, loadBuiltinModels: true}); app = loopback({localRegistry: true, loadBuiltinModels: true});
@ -132,7 +132,7 @@ describe.onServer('Remote Methods', function() {
describe('Model.create(data, callback)', function() { describe('Model.create(data, callback)', function() {
it('creates model', function(done) { it('creates model', function(done) {
const anObject = {first: 'June'}; var anObject = {first: 'June'};
request(app) request(app)
.post('/users') .post('/users')
// sends an object // sends an object
@ -149,7 +149,7 @@ describe.onServer('Remote Methods', function() {
// batch create must be tested with a remote request because there are // batch create must be tested with a remote request because there are
// coercion being done on strong-remoting side // coercion being done on strong-remoting side
it('creates array of models', function(done) { it('creates array of models', function(done) {
const arrayOfObjects = [ var arrayOfObjects = [
{first: 'John'}, {first: 'Jane'}, {first: 'John'}, {first: 'Jane'},
]; ];
request(app) request(app)
@ -161,8 +161,8 @@ describe.onServer('Remote Methods', function() {
.end(function(err, res) { .end(function(err, res) {
if (err) return done(err); if (err) return done(err);
expect(res.body.length).to.eql(2); expect(res.body.length).to.eql(2);
expect(res.body).to.have.nested.property('[0].first', 'John'); expect(res.body).to.have.deep.property('[0].first', 'John');
expect(res.body).to.have.nested.property('[1].first', 'Jane'); expect(res.body).to.have.deep.property('[1].first', 'Jane');
done(); done();
}); });
}); });
@ -170,7 +170,7 @@ describe.onServer('Remote Methods', function() {
it('creates related models', function(done) { it('creates related models', function(done) {
User.create({first: 'Bob'}, function(err, res) { User.create({first: 'Bob'}, function(err, res) {
expect(res).to.have.property('id'); expect(res).to.have.property('id');
const aPost = {title: 'A story', content: 'Once upon a time'}; var aPost = {title: 'A story', content: 'Once upon a time'};
request(app) request(app)
.post('/users/' + res.id + '/posts') .post('/users/' + res.id + '/posts')
.send(aPost) .send(aPost)
@ -189,7 +189,7 @@ describe.onServer('Remote Methods', function() {
it('creates array of hasMany models', function(done) { it('creates array of hasMany models', function(done) {
User.create({first: 'Bob'}, function(err, res) { User.create({first: 'Bob'}, function(err, res) {
expect(res).to.have.property('id'); expect(res).to.have.property('id');
const twoPosts = [ var twoPosts = [
{title: 'One story', content: 'Content #1'}, {title: 'One story', content: 'Content #1'},
{title: 'Two story', content: 'Content #2'}, {title: 'Two story', content: 'Content #2'},
]; ];
@ -201,21 +201,21 @@ describe.onServer('Remote Methods', function() {
.end(function(err, result) { .end(function(err, result) {
if (err) return done(err); if (err) return done(err);
expect(result.body.length).to.eql(2); expect(result.body.length).to.eql(2);
expect(result.body).to.have.nested.property('[0].title', 'One story'); expect(result.body).to.have.deep.property('[0].title', 'One story');
expect(result.body).to.have.nested.property('[1].title', 'Two story'); expect(result.body).to.have.deep.property('[1].title', 'Two story');
done(); done();
}); });
}); });
}); });
it('rejects array of obj input for hasOne relation', function(done) { it('rejects array of obj input for hasOne relation', function(done) {
const Friend = app.registry.createModel('friend', {name: String}); var Friend = app.registry.createModel('friend', {name: String});
app.model(Friend, {dataSource: 'db'}); app.model(Friend, {dataSource: 'db'});
User.hasOne(Friend); User.hasOne(Friend);
User.create({first: 'Bob'}, function(err, res) { User.create({first: 'Bob'}, function(err, res) {
expect(res).to.have.property('id'); expect(res).to.have.property('id');
const twoFriends = [ var twoFriends = [
{name: 'bob'}, {name: 'bob'},
{name: 'rob'}, {name: 'rob'},
]; ];
@ -226,7 +226,7 @@ describe.onServer('Remote Methods', function() {
.expect(400) .expect(400)
.end(function(err, result) { .end(function(err, result) {
if (err) return done(err); if (err) return done(err);
const resError = result.body.error; var resError = result.body.error;
expect(resError.message).to.match(/value(.*?)not(.*?)object(\.?)/i); expect(resError.message).to.match(/value(.*?)not(.*?)object(\.?)/i);
done(); done();
}); });
@ -258,14 +258,14 @@ describe.onServer('Remote Methods', function() {
describe('Model.upsertWithWhere(where, data, callback)', function() { describe('Model.upsertWithWhere(where, data, callback)', function() {
it('Updates when a Model instance is retreived from data source', function(done) { it('Updates when a Model instance is retreived from data source', function(done) {
const taskEmitter = new TaskEmitter(); var taskEmitter = new TaskEmitter();
taskEmitter taskEmitter
.task(User, 'create', {first: 'jill', second: 'pill'}) .task(User, 'create', {first: 'jill', second: 'pill'})
.task(User, 'create', {first: 'bob', second: 'sob'}) .task(User, 'create', {first: 'bob', second: 'sob'})
.on('done', function() { .on('done', function() {
User.upsertWithWhere({second: 'pill'}, {second: 'jones'}, function(err, user) { User.upsertWithWhere({second: 'pill'}, {second: 'jones'}, function(err, user) {
if (err) return done(err); if (err) return done(err);
const id = user.id; var id = user.id;
User.findById(id, function(err, user) { User.findById(id, function(err, user) {
if (err) return done(err); if (err) return done(err);
assert.equal(user.second, 'jones'); assert.equal(user.second, 'jones');
@ -276,13 +276,13 @@ describe.onServer('Remote Methods', function() {
}); });
it('Creates when no Model instance is retreived from data source', function(done) { it('Creates when no Model instance is retreived from data source', function(done) {
const taskEmitter = new TaskEmitter(); var taskEmitter = new TaskEmitter();
taskEmitter taskEmitter
.task(User, 'create', {first: 'simon', second: 'somers'}) .task(User, 'create', {first: 'simon', second: 'somers'})
.on('done', function() { .on('done', function() {
User.upsertWithWhere({first: 'somers'}, {first: 'Simon'}, function(err, user) { User.upsertWithWhere({first: 'somers'}, {first: 'Simon'}, function(err, user) {
if (err) return done(err); if (err) return done(err);
const id = user.id; var id = user.id;
User.findById(id, function(err, user) { User.findById(id, function(err, user) {
if (err) return done(err); if (err) return done(err);
assert.equal(user.first, 'Simon'); assert.equal(user.first, 'Simon');
@ -315,7 +315,7 @@ describe.onServer('Remote Methods', function() {
.end(function(err, res) { .end(function(err, res) {
if (err) return done(err); if (err) return done(err);
const errorResponse = res.body.error; var errorResponse = res.body.error;
assert(errorResponse); assert(errorResponse);
assert.equal(errorResponse.code, 'MODEL_NOT_FOUND'); assert.equal(errorResponse.code, 'MODEL_NOT_FOUND');
@ -332,7 +332,7 @@ describe.onServer('Remote Methods', function() {
.end(function(err, res) { .end(function(err, res) {
if (err) return done(err); if (err) return done(err);
const userId = res.body.id; var userId = res.body.id;
assert(userId); assert(userId);
request(app) request(app)
.get('/users/' + userId + '?filter[fields]=first') .get('/users/' + userId + '?filter[fields]=first')
@ -358,7 +358,7 @@ describe.onServer('Remote Methods', function() {
.end(function(err, res) { .end(function(err, res) {
if (err) return done(err); if (err) return done(err);
const userId = res.body.id; var userId = res.body.id;
assert(userId); assert(userId);
request(app) request(app)
.post('/users/' + userId + '/posts') .post('/users/' + userId + '/posts')
@ -368,7 +368,7 @@ describe.onServer('Remote Methods', function() {
.end(function(err, res) { .end(function(err, res) {
if (err) return done(err); if (err) return done(err);
const post = res.body; var post = res.body;
request(app) request(app)
.get('/users/' + userId + '?filter[include]=posts') .get('/users/' + userId + '?filter[include]=posts')
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
@ -389,7 +389,7 @@ describe.onServer('Remote Methods', function() {
describe('Model.beforeRemote(name, fn)', function() { describe('Model.beforeRemote(name, fn)', function() {
it('Run a function before a remote method is called by a client', function(done) { it('Run a function before a remote method is called by a client', function(done) {
let hookCalled = false; var hookCalled = false;
User.beforeRemote('create', function(ctx, user, next) { User.beforeRemote('create', function(ctx, user, next) {
hookCalled = true; hookCalled = true;
@ -413,7 +413,7 @@ describe.onServer('Remote Methods', function() {
}); });
it('Does not stop the hook chain after returning a promise', function(done) { it('Does not stop the hook chain after returning a promise', function(done) {
const hooksCalled = []; var hooksCalled = [];
User.beforeRemote('create', function() { User.beforeRemote('create', function() {
hooksCalled.push('first'); hooksCalled.push('first');
@ -441,8 +441,8 @@ describe.onServer('Remote Methods', function() {
describe('Model.afterRemote(name, fn)', function() { describe('Model.afterRemote(name, fn)', function() {
it('Run a function after a remote method is called by a client', function(done) { it('Run a function after a remote method is called by a client', function(done) {
let beforeCalled = false; var beforeCalled = false;
let afterCalled = false; var afterCalled = false;
User.beforeRemote('create', function(ctx, user, next) { User.beforeRemote('create', function(ctx, user, next) {
assert(!afterCalled); assert(!afterCalled);
@ -476,7 +476,7 @@ describe.onServer('Remote Methods', function() {
describe('Model.afterRemoteError(name, fn)', function() { describe('Model.afterRemoteError(name, fn)', function() {
it('runs the function when method fails', function(done) { it('runs the function when method fails', function(done) {
let actualError = 'hook not called'; var actualError = 'hook not called';
User.afterRemoteError('login', function(ctx, next) { User.afterRemoteError('login', function(ctx, next) {
actualError = ctx.error; actualError = ctx.error;
@ -498,7 +498,7 @@ describe.onServer('Remote Methods', function() {
describe('Remote Method invoking context', function() { describe('Remote Method invoking context', function() {
describe('ctx.req', function() { describe('ctx.req', function() {
it('The express ServerRequest object', function(done) { it('The express ServerRequest object', function(done) {
let hookCalled = false; var hookCalled = false;
User.beforeRemote('create', function(ctx, user, next) { User.beforeRemote('create', function(ctx, user, next) {
hookCalled = true; hookCalled = true;
@ -530,7 +530,7 @@ describe.onServer('Remote Methods', function() {
describe('ctx.res', function() { describe('ctx.res', function() {
it('The express ServerResponse object', function(done) { it('The express ServerResponse object', function(done) {
let hookCalled = false; var hookCalled = false;
User.beforeRemote('create', function(ctx, user, next) { User.beforeRemote('create', function(ctx, user, next) {
hookCalled = true; hookCalled = true;
@ -563,15 +563,15 @@ describe.onServer('Remote Methods', function() {
describe('Model.hasMany(Model)', function() { describe('Model.hasMany(Model)', function() {
it('Define a one to many relationship', function(done) { it('Define a one to many relationship', function(done) {
const Book = dataSource.createModel('book', {title: String, author: String}); var Book = dataSource.createModel('book', {title: String, author: String});
const Chapter = dataSource.createModel('chapter', {title: String}); var Chapter = dataSource.createModel('chapter', {title: String});
// by referencing model // by referencing model
Book.hasMany(Chapter); Book.hasMany(Chapter);
Book.create({title: 'Into the Wild', author: 'Jon Krakauer'}, function(err, book) { Book.create({title: 'Into the Wild', author: 'Jon Krakauer'}, function(err, book) {
// using 'chapters' scope for build: // using 'chapters' scope for build:
const c = book.chapters.build({title: 'Chapter 1'}); var c = book.chapters.build({title: 'Chapter 1'});
book.chapters.create({title: 'Chapter 2'}, function() { book.chapters.create({title: 'Chapter 2'}, function() {
c.save(function() { c.save(function() {
Chapter.count({bookId: book.id}, function(err, count) { Chapter.count({bookId: book.id}, function(err, count) {
@ -591,7 +591,7 @@ describe.onServer('Remote Methods', function() {
describe('Model.properties', function() { describe('Model.properties', function() {
it('Normalized properties passed in originally by loopback.createModel()', function() { it('Normalized properties passed in originally by loopback.createModel()', function() {
const props = { var props = {
s: String, s: String,
n: {type: 'Number'}, n: {type: 'Number'},
o: {type: 'String', min: 10, max: 100}, o: {type: 'String', min: 10, max: 100},
@ -599,11 +599,11 @@ describe.onServer('Remote Methods', function() {
g: loopback.GeoPoint, g: loopback.GeoPoint,
}; };
const MyModel = loopback.createModel('foo', props); var MyModel = loopback.createModel('foo', props);
Object.keys(MyModel.definition.properties).forEach(function(key) { Object.keys(MyModel.definition.properties).forEach(function(key) {
const p = MyModel.definition.properties[key]; var p = MyModel.definition.properties[key];
const o = MyModel.definition.properties[key]; var o = MyModel.definition.properties[key];
assert(p); assert(p);
assert(o); assert(o);
assert(typeof p.type === 'function'); assert(typeof p.type === 'function');
@ -613,7 +613,7 @@ describe.onServer('Remote Methods', function() {
// should match the given property // should match the given property
assert( assert(
p.type.name === o.name || p.type.name === o.name ||
p.type.name === o, p.type.name === o
); );
} }
}); });
@ -622,7 +622,7 @@ describe.onServer('Remote Methods', function() {
describe('Model.extend()', function() { describe('Model.extend()', function() {
it('Create a new model by extending an existing model', function() { it('Create a new model by extending an existing model', function() {
const User = loopback.PersistedModel.extend('test-user', { var User = loopback.PersistedModel.extend('test-user', {
email: String, email: String,
}); });
@ -634,7 +634,7 @@ describe.onServer('Remote Methods', function() {
return 'foo'; return 'foo';
}; };
const MyUser = User.extend('my-user', { var MyUser = User.extend('my-user', {
a: String, a: String,
b: String, b: String,
}); });
@ -642,7 +642,7 @@ describe.onServer('Remote Methods', function() {
assert.equal(MyUser.prototype.bar, User.prototype.bar); assert.equal(MyUser.prototype.bar, User.prototype.bar);
assert.equal(MyUser.foo, User.foo); assert.equal(MyUser.foo, User.foo);
const user = new MyUser({ var user = new MyUser({
email: 'foo@bar.com', email: 'foo@bar.com',
a: 'foo', a: 'foo',
b: 'bar', b: 'bar',
@ -656,21 +656,21 @@ describe.onServer('Remote Methods', function() {
describe('Model.extend() events', function() { describe('Model.extend() events', function() {
it('create isolated emitters for subclasses', function() { it('create isolated emitters for subclasses', function() {
const User1 = loopback.createModel('User1', { var User1 = loopback.createModel('User1', {
'first': String, 'first': String,
'last': String, 'last': String,
}); });
const User2 = loopback.createModel('User2', { var User2 = loopback.createModel('User2', {
'name': String, 'name': String,
}); });
let user1Triggered = false; var user1Triggered = false;
User1.once('x', function(event) { User1.once('x', function(event) {
user1Triggered = true; user1Triggered = true;
}); });
let user2Triggered = false; var user2Triggered = false;
User2.once('x', function(event) { User2.once('x', function(event) {
user2Triggered = true; user2Triggered = true;
}); });
@ -703,10 +703,10 @@ describe.onServer('Remote Methods', function() {
function shouldReturn(methodName, expectedAccessType) { function shouldReturn(methodName, expectedAccessType) {
describe(methodName, function() { describe(methodName, function() {
it('should return ' + expectedAccessType, function() { it('should return ' + expectedAccessType, function() {
const remoteMethod = {name: methodName}; var remoteMethod = {name: methodName};
assert.equal( assert.equal(
User._getAccessTypeForMethod(remoteMethod), User._getAccessTypeForMethod(remoteMethod),
expectedAccessType, expectedAccessType
); );
}); });
}); });
@ -715,8 +715,8 @@ describe.onServer('Remote Methods', function() {
describe('Model.getChangeModel()', function() { describe('Model.getChangeModel()', function() {
it('Get the Change Model', function() { it('Get the Change Model', function() {
const UserChange = User.getChangeModel(); var UserChange = User.getChangeModel();
const change = new UserChange(); var change = new UserChange();
assert(change instanceof app.registry.getModel('Change')); assert(change instanceof app.registry.getModel('Change'));
}); });
}); });
@ -733,12 +733,12 @@ describe.onServer('Remote Methods', function() {
describe('Model.checkpoint(callback)', function() { describe('Model.checkpoint(callback)', function() {
it('Create a checkpoint', function(done) { it('Create a checkpoint', function(done) {
const Checkpoint = User.getChangeModel().getCheckpointModel(); var Checkpoint = User.getChangeModel().getCheckpointModel();
const tasks = [ var tasks = [
getCurrentCheckpoint, getCurrentCheckpoint,
checkpoint, checkpoint,
]; ];
let result, current; var result, current;
async.series(tasks, function(err) { async.series(tasks, function(err) {
if (err) return done(err); if (err) return done(err);
@ -766,11 +766,11 @@ describe.onServer('Remote Methods', function() {
describe('Model._getACLModel()', function() { describe('Model._getACLModel()', function() {
it('should return the subclass of ACL', function() { it('should return the subclass of ACL', function() {
const Model = require('../').Model; var Model = require('../').Model;
const originalValue = Model._ACL(); var originalValue = Model._ACL();
const acl = ACL.extend('acl'); var acl = ACL.extend('acl');
Model._ACL(null); // Reset the ACL class for the base model Model._ACL(null); // Reset the ACL class for the base model
const model = Model._ACL(); var model = Model._ACL();
Model._ACL(originalValue); // Reset the value back Model._ACL(originalValue); // Reset the value back
assert.equal(model, acl); assert.equal(model, acl);
}); });
@ -778,21 +778,21 @@ describe.onServer('Remote Methods', function() {
describe('PersistedModel remote methods', function() { describe('PersistedModel remote methods', function() {
it('includes all aliases', function() { it('includes all aliases', function() {
const app = loopback(); var app = loopback();
const model = PersistedModel.extend('PersistedModelForAliases'); var model = PersistedModel.extend('PersistedModelForAliases');
app.dataSource('db', {connector: 'memory'}); app.dataSource('db', {connector: 'memory'});
app.model(model, {dataSource: 'db'}); app.model(model, {dataSource: 'db'});
// this code is used by loopback-sdk-angular codegen // this code is used by loopback-sdk-angular codegen
const metadata = app.handler('rest') var metadata = app.handler('rest')
.adapter .adapter
.getClasses() .getClasses()
.filter(function(c) { return c.name === model.modelName; })[0]; .filter(function(c) { return c.name === model.modelName; })[0];
let methodNames = []; var methodNames = [];
metadata.methods.forEach(function(method) { metadata.methods.forEach(function(method) {
methodNames.push(method.name); methodNames.push(method.name);
let aliases = method.sharedMethod.aliases; var aliases = method.sharedMethod.aliases;
if (method.name.indexOf('prototype.') === 0) { if (method.name.indexOf('prototype.') === 0) {
aliases = aliases.map(function(alias) { aliases = aliases.map(function(alias) {
return 'prototype.' + alias; return 'prototype.' + alias;
@ -826,13 +826,13 @@ describe.onServer('Remote Methods', function() {
}); });
it('emits a `remoteMethodDisabled` event', function() { it('emits a `remoteMethodDisabled` event', function() {
const app = loopback(); var app = loopback();
const model = PersistedModel.extend('TestModelForDisablingRemoteMethod'); var model = PersistedModel.extend('TestModelForDisablingRemoteMethod');
app.dataSource('db', {connector: 'memory'}); app.dataSource('db', {connector: 'memory'});
app.model(model, {dataSource: 'db'}); app.model(model, {dataSource: 'db'});
const callbackSpy = require('sinon').spy(); var callbackSpy = require('sinon').spy();
const TestModel = app.models.TestModelForDisablingRemoteMethod; var TestModel = app.models.TestModelForDisablingRemoteMethod;
TestModel.on('remoteMethodDisabled', callbackSpy); TestModel.on('remoteMethodDisabled', callbackSpy);
TestModel.disableRemoteMethod('findOne', true); TestModel.disableRemoteMethod('findOne', true);
@ -840,13 +840,13 @@ describe.onServer('Remote Methods', function() {
}); });
it('emits a `remoteMethodDisabled` event from disableRemoteMethodByName', function() { it('emits a `remoteMethodDisabled` event from disableRemoteMethodByName', function() {
const app = loopback(); var app = loopback();
const model = PersistedModel.extend('TestModelForDisablingRemoteMethod'); var model = PersistedModel.extend('TestModelForDisablingRemoteMethod');
app.dataSource('db', {connector: 'memory'}); app.dataSource('db', {connector: 'memory'});
app.model(model, {dataSource: 'db'}); app.model(model, {dataSource: 'db'});
const callbackSpy = require('sinon').spy(); var callbackSpy = require('sinon').spy();
const TestModel = app.models.TestModelForDisablingRemoteMethod; var TestModel = app.models.TestModelForDisablingRemoteMethod;
TestModel.on('remoteMethodDisabled', callbackSpy); TestModel.on('remoteMethodDisabled', callbackSpy);
TestModel.disableRemoteMethodByName('findOne'); TestModel.disableRemoteMethodByName('findOne');
@ -854,17 +854,17 @@ describe.onServer('Remote Methods', function() {
}); });
it('emits a `remoteMethodAdded` event', function() { it('emits a `remoteMethodAdded` event', function() {
const app = loopback(); var app = loopback();
app.dataSource('db', {connector: 'memory'}); app.dataSource('db', {connector: 'memory'});
const User = app.registry.getModel('User'); var User = app.registry.getModel('User');
app.model(User, {dataSource: 'db'}); app.model(User, {dataSource: 'db'});
const Token = app.registry.getModel('AccessToken'); var Token = app.registry.getModel('AccessToken');
app.model(Token, {dataSource: 'db'}); app.model(Token, {dataSource: 'db'});
const callbackSpy = require('sinon').spy(); var callbackSpy = require('sinon').spy();
const TestModel = app.models.User; var TestModel = app.models.User;
TestModel.on('remoteMethodAdded', callbackSpy); TestModel.on('remoteMethodAdded', callbackSpy);
TestModel.nestRemoting('accessTokens'); TestModel.nestRemoting('accessTokens');
@ -873,13 +873,13 @@ describe.onServer('Remote Methods', function() {
}); });
it('emits a `remoteMethodAdded` event from remoteMethod', function() { it('emits a `remoteMethodAdded` event from remoteMethod', function() {
const app = loopback(); var app = loopback();
const model = PersistedModel.extend('TestModelForAddingRemoteMethod'); var model = PersistedModel.extend('TestModelForAddingRemoteMethod');
app.dataSource('db', {connector: 'memory'}); app.dataSource('db', {connector: 'memory'});
app.model(model, {dataSource: 'db'}); app.model(model, {dataSource: 'db'});
const callbackSpy = require('sinon').spy(); var callbackSpy = require('sinon').spy();
const TestModel = app.models.TestModelForAddingRemoteMethod; var TestModel = app.models.TestModelForAddingRemoteMethod;
TestModel.on('remoteMethodAdded', callbackSpy); TestModel.on('remoteMethodAdded', callbackSpy);
TestModel.remoteMethod('getTest', { TestModel.remoteMethod('getTest', {
accepts: {arg: 'options', type: 'object', http: 'optionsFromRequest'}, accepts: {arg: 'options', type: 'object', http: 'optionsFromRequest'},
@ -891,7 +891,7 @@ describe.onServer('Remote Methods', function() {
}); });
describe('Model.getApp(cb)', function() { describe('Model.getApp(cb)', function() {
let app, TestModel; var app, TestModel;
beforeEach(function setup() { beforeEach(function setup() {
app = loopback(); app = loopback();
TestModel = loopback.createModel('TestModelForGetApp'); // unique name TestModel = loopback.createModel('TestModelForGetApp'); // unique name
@ -924,15 +924,11 @@ describe.onServer('Remote Methods', function() {
}); });
describe('Model.createOptionsFromRemotingContext', function() { describe('Model.createOptionsFromRemotingContext', function() {
let app, TestModel, accessToken, actualOptions; var app, TestModel, accessToken, userId, actualOptions;
before(setupAppAndRequest); before(setupAppAndRequest);
before(createUserAndAccessToken); before(createUserAndAccessToken);
beforeEach(function() {
TestModel.definition.settings = {};
});
it('sets empty options.accessToken for anonymous requests', function(done) { it('sets empty options.accessToken for anonymous requests', function(done) {
request(app).get('/TestModels/saveOptions') request(app).get('/TestModels/saveOptions')
.expect(204, function(err) { .expect(204, function(err) {
@ -942,36 +938,6 @@ describe.onServer('Remote Methods', function() {
}); });
}); });
it('sets options for juggler', function(done) {
request(app).get('/TestModels/saveOptions')
.expect(204, function(err) {
if (err) return done(err);
expect(actualOptions).to.include({
prohibitHiddenPropertiesInQuery: true,
maxDepthOfQuery: 12,
maxDepthOfData: 32,
});
done();
});
});
it('honors model settings to create options for juggler', function(done) {
TestModel.definition.settings = {
prohibitHiddenPropertiesInQuery: false,
maxDepthOfData: 64,
};
request(app).get('/TestModels/saveOptions')
.expect(204, function(err) {
if (err) return done(err);
expect(actualOptions).to.include({
prohibitHiddenPropertiesInQuery: false,
maxDepthOfQuery: 12,
maxDepthOfData: 64,
});
done();
});
});
it('sets options.accessToken for authorized requests', function(done) { it('sets options.accessToken for authorized requests', function(done) {
request(app).get('/TestModels/saveOptions') request(app).get('/TestModels/saveOptions')
.set('Authorization', accessToken.id) .set('Authorization', accessToken.id)
@ -1055,13 +1021,14 @@ describe.onServer('Remote Methods', function() {
} }
function createUserAndAccessToken() { function createUserAndAccessToken() {
const CREDENTIALS = {email: 'context@example.com', password: 'pass'}; var CREDENTIALS = {email: 'context@example.com', password: 'pass'};
const User = app.registry.getModel('User'); var User = app.registry.getModel('User');
return User.create(CREDENTIALS) return User.create(CREDENTIALS)
.then(function(u) { .then(function(u) {
return User.login(CREDENTIALS); return User.login(CREDENTIALS);
}).then(function(token) { }).then(function(token) {
accessToken = token; accessToken = token;
userId = token.userId;
}); });
} }
}); });

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2018,2019. All Rights Reserved. // Copyright IBM Corp. 2016,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT

View File

@ -1,16 +1,16 @@
// Copyright IBM Corp. 2016,2019. All Rights Reserved. // Copyright IBM Corp. 2016,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const expect = require('./helpers/expect'); var expect = require('./helpers/expect');
const loopback = require('../'); var loopback = require('../');
const ctx = require('../lib/access-context'); var ctx = require('../lib/access-context');
const extend = require('util')._extend; var extend = require('util')._extend;
const AccessContext = ctx.AccessContext; var AccessContext = ctx.AccessContext;
const Principal = ctx.Principal; var Principal = ctx.Principal;
const Promise = require('bluebird'); var Promise = require('bluebird');
const waitForEvent = require('./helpers/wait-for-event'); const waitForEvent = require('./helpers/wait-for-event');
const supertest = require('supertest'); const supertest = require('supertest');
const loggers = require('./helpers/error-loggers'); const loggers = require('./helpers/error-loggers');
@ -19,8 +19,8 @@ const logServerErrorsOtherThan = loggers.logServerErrorsOtherThan;
describe('Multiple users with custom principalType', function() { describe('Multiple users with custom principalType', function() {
this.timeout(10000); this.timeout(10000);
const commonCredentials = {email: 'foo@bar.com', password: 'bar'}; var commonCredentials = {email: 'foo@bar.com', password: 'bar'};
let app, OneUser, AnotherUser, AccessToken, Role, var app, OneUser, AnotherUser, AccessToken, Role,
userFromOneModel, userFromAnotherModel, userRole, userOneBaseContext; userFromOneModel, userFromAnotherModel, userRole, userOneBaseContext;
beforeEach(function setupAppAndModels() { beforeEach(function setupAppAndModels() {
@ -30,7 +30,7 @@ describe('Multiple users with custom principalType', function() {
app.set('remoting', {rest: {handleErrors: false}}); app.set('remoting', {rest: {handleErrors: false}});
app.dataSource('db', {connector: 'memory'}); app.dataSource('db', {connector: 'memory'});
const userModelOptions = { var userModelOptions = {
base: 'User', base: 'User',
// forceId is set to false for the purpose of updating the same affected user within the // forceId is set to false for the purpose of updating the same affected user within the
// `Email Update` test cases. // `Email Update` test cases.
@ -101,7 +101,7 @@ describe('Multiple users with custom principalType', function() {
}, },
function onError(err) { function onError(err) {
expect(err).to.have.property('code', 'LOGIN_FAILED'); expect(err).to.have.property('code', 'LOGIN_FAILED');
}, }
); );
}); });
}); });
@ -118,7 +118,7 @@ describe('Multiple users with custom principalType', function() {
describe('User.logout', function() { describe('User.logout', function() {
it('logs out a user from user model 1 without logging out user from model 2', it('logs out a user from user model 1 without logging out user from model 2',
function() { function() {
let tokenOfOneUser; var tokenOfOneUser;
return Promise.all([ return Promise.all([
OneUser.login(commonCredentials), OneUser.login(commonCredentials),
AnotherUser.login(commonCredentials), AnotherUser.login(commonCredentials),
@ -131,7 +131,7 @@ describe('Multiple users with custom principalType', function() {
return AccessToken.find({}); return AccessToken.find({});
}) })
.then(function(allTokens) { .then(function(allTokens) {
const data = allTokens.map(function(token) { var data = allTokens.map(function(token) {
return {userId: token.userId, principalType: token.principalType}; return {userId: token.userId, principalType: token.principalType};
}); });
expect(data).to.eql([ expect(data).to.eql([
@ -144,7 +144,7 @@ describe('Multiple users with custom principalType', function() {
describe('Password Reset', function() { describe('Password Reset', function() {
describe('User.resetPassword(options)', function() { describe('User.resetPassword(options)', function() {
const options = { var options = {
email: 'foo@bar.com', email: 'foo@bar.com',
redirect: 'http://foobar.com/reset-password', redirect: 'http://foobar.com/reset-password',
}; };
@ -172,7 +172,7 @@ describe('Multiple users with custom principalType', function() {
}); });
describe('AccessToken (session) invalidation when changing email', function() { describe('AccessToken (session) invalidation when changing email', function() {
let anotherUserFromOneModel; var anotherUserFromOneModel;
it('impact only the related user', function() { it('impact only the related user', function() {
return OneUser.create({email: 'original@example.com', password: 'bar'}) return OneUser.create({email: 'original@example.com', password: 'bar'})
@ -192,7 +192,7 @@ describe('Multiple users with custom principalType', function() {
return AccessToken.find({'order': 'principalType ASC'}); return AccessToken.find({'order': 'principalType ASC'});
}) })
.then(function(allTokens) { .then(function(allTokens) {
const data = allTokens.map(function(token) { var data = allTokens.map(function(token) {
return {userId: token.userId, principalType: token.principalType}; return {userId: token.userId, principalType: token.principalType};
}); });
expect(data).to.eql([ expect(data).to.eql([
@ -205,7 +205,7 @@ describe('Multiple users with custom principalType', function() {
}); });
describe('AccessContext', function() { describe('AccessContext', function() {
let ThirdUser, userFromThirdModel, accessContext; var ThirdUser, userFromThirdModel, accessContext;
beforeEach(function() { beforeEach(function() {
accessContext = new AccessContext({registry: OneUser.registry}); accessContext = new AccessContext({registry: OneUser.registry});
@ -221,7 +221,7 @@ describe('Multiple users with custom principalType', function() {
{type: Principal.SCOPE}, {type: Principal.SCOPE},
{type: OneUser.modelName, id: userFromOneModel.id}, {type: OneUser.modelName, id: userFromOneModel.id},
]); ]);
const user = accessContext.getUser(); var user = accessContext.getUser();
expect(user).to.eql({ expect(user).to.eql({
id: userFromOneModel.id, id: userFromOneModel.id,
principalType: OneUser.modelName, principalType: OneUser.modelName,
@ -237,7 +237,7 @@ describe('Multiple users with custom principalType', function() {
{type: 'invalidModelName'}, {type: 'invalidModelName'},
{type: OneUser.modelName, id: userFromOneModel.id}, {type: OneUser.modelName, id: userFromOneModel.id},
]); ]);
const user = accessContext.getUser(); var user = accessContext.getUser();
expect(user).to.eql({ expect(user).to.eql({
id: userFromOneModel.id, id: userFromOneModel.id,
principalType: OneUser.modelName, principalType: OneUser.modelName,
@ -251,7 +251,7 @@ describe('Multiple users with custom principalType', function() {
return ThirdUser.create(commonCredentials) return ThirdUser.create(commonCredentials)
.then(function(userFromThirdModel) { .then(function(userFromThirdModel) {
accessContext.addPrincipal(ThirdUser.modelName, userFromThirdModel.id); accessContext.addPrincipal(ThirdUser.modelName, userFromThirdModel.id);
const user = accessContext.getUser(); var user = accessContext.getUser();
expect(user).to.eql({ expect(user).to.eql({
id: userFromThirdModel.id, id: userFromThirdModel.id,
principalType: ThirdUser.modelName, principalType: ThirdUser.modelName,
@ -272,7 +272,7 @@ describe('Multiple users with custom principalType', function() {
describe('Role model', function() { describe('Role model', function() {
this.timeout(10000); this.timeout(10000);
let RoleMapping, ACL, user; var RoleMapping, ACL, user;
beforeEach(function() { beforeEach(function() {
ACL = app.registry.getModel('ACL'); ACL = app.registry.getModel('ACL');
@ -285,7 +285,7 @@ describe('Multiple users with custom principalType', function() {
describe('role.users()', function() { describe('role.users()', function() {
it('returns users when using custom user principalType', function() { it('returns users when using custom user principalType', function() {
return userRole.principals.create( return userRole.principals.create(
{principalType: OneUser.modelName, principalId: userFromOneModel.id}, {principalType: OneUser.modelName, principalId: userFromOneModel.id}
) )
.then(function() { .then(function() {
return userRole.users({where: {principalType: OneUser.modelName}}); return userRole.users({where: {principalType: OneUser.modelName}});
@ -298,7 +298,7 @@ describe('Multiple users with custom principalType', function() {
it('returns empty array when using invalid principalType', function() { it('returns empty array when using invalid principalType', function() {
return userRole.principals.create( return userRole.principals.create(
{principalType: 'invalidModelName', principalId: userFromOneModel.id}, {principalType: 'invalidModelName', principalId: userFromOneModel.id}
) )
.then(function() { .then(function() {
return userRole.users({where: {principalType: 'invalidModelName'}}); return userRole.users({where: {principalType: 'invalidModelName'}});
@ -312,7 +312,7 @@ describe('Multiple users with custom principalType', function() {
describe('principal.user()', function() { describe('principal.user()', function() {
it('returns the correct user instance', function() { it('returns the correct user instance', function() {
return userRole.principals.create( return userRole.principals.create(
{principalType: OneUser.modelName, principalId: userFromOneModel.id}, {principalType: OneUser.modelName, principalId: userFromOneModel.id}
).then(function(principal) { ).then(function(principal) {
return principal.user(); return principal.user();
}).then(function(user) { }).then(function(user) {
@ -322,7 +322,7 @@ describe('Multiple users with custom principalType', function() {
it('returns null when created with invalid principalType', function() { it('returns null when created with invalid principalType', function() {
return userRole.principals.create( return userRole.principals.create(
{principalType: 'invalidModelName', principalId: userFromOneModel.id}, {principalType: 'invalidModelName', principalId: userFromOneModel.id}
).then(function(principal) { ).then(function(principal) {
return principal.user(); return principal.user();
}).then(function(user) { }).then(function(user) {
@ -346,7 +346,7 @@ describe('Multiple users with custom principalType', function() {
it('supports getRoles()', function() { it('supports getRoles()', function() {
return Role.getRoles( return Role.getRoles(
userOneBaseContext, userOneBaseContext
).then(function(roles) { ).then(function(roles) {
expect(roles).to.eql([ expect(roles).to.eql([
Role.AUTHENTICATED, Role.AUTHENTICATED,
@ -374,7 +374,7 @@ describe('Multiple users with custom principalType', function() {
describe('$owner', function() { describe('$owner', function() {
it('supports legacy behavior with relations', function() { it('supports legacy behavior with relations', function() {
const Album = app.registry.createModel('Album', { var Album = app.registry.createModel('Album', {
name: String, name: String,
userId: Number, userId: Number,
}, { }, {
@ -390,7 +390,7 @@ describe('Multiple users with custom principalType', function() {
return Album.create({name: 'album', userId: userFromOneModel.id}) return Album.create({name: 'album', userId: userFromOneModel.id})
.then(function(album) { .then(function(album) {
const validContext = { var validContext = {
principalType: OneUser.modelName, principalType: OneUser.modelName,
principalId: userFromOneModel.id, principalId: userFromOneModel.id,
model: Album, model: Album,
@ -406,7 +406,7 @@ describe('Multiple users with custom principalType', function() {
// With multiple users config, we cannot resolve a user based just on // With multiple users config, we cannot resolve a user based just on
// his id, as many users from different models could have the same id. // his id, as many users from different models could have the same id.
it('legacy behavior resolves false without belongsTo relation', function() { it('legacy behavior resolves false without belongsTo relation', function() {
const Album = app.registry.createModel('Album', { var Album = app.registry.createModel('Album', {
name: String, name: String,
userId: Number, userId: Number,
owner: Number, owner: Number,
@ -419,7 +419,7 @@ describe('Multiple users with custom principalType', function() {
owner: userFromOneModel.id, owner: userFromOneModel.id,
}) })
.then(function(album) { .then(function(album) {
const authContext = { var authContext = {
principalType: OneUser.modelName, principalType: OneUser.modelName,
principalId: userFromOneModel.id, principalId: userFromOneModel.id,
model: Album, model: Album,
@ -433,7 +433,7 @@ describe('Multiple users with custom principalType', function() {
}); });
it('legacy behavior resolves false if owner has incorrect principalType', function() { it('legacy behavior resolves false if owner has incorrect principalType', function() {
const Album = app.registry.createModel('Album', { var Album = app.registry.createModel('Album', {
name: String, name: String,
userId: Number, userId: Number,
}, { }, {
@ -449,12 +449,12 @@ describe('Multiple users with custom principalType', function() {
return Album.create({name: 'album', userId: userFromOneModel.id}) return Album.create({name: 'album', userId: userFromOneModel.id})
.then(function(album) { .then(function(album) {
const invalidPrincipalTypes = [ var invalidPrincipalTypes = [
'invalidContextName', 'invalidContextName',
'USER', 'USER',
AnotherUser.modelName, AnotherUser.modelName,
]; ];
const invalidContexts = invalidPrincipalTypes.map(principalType => { var invalidContexts = invalidPrincipalTypes.map(principalType => {
return { return {
principalType, principalType,
principalId: userFromOneModel.id, principalId: userFromOneModel.id,
@ -485,12 +485,12 @@ describe('Multiple users with custom principalType', function() {
function() { function() {
// passing {ownerRelations: true} will enable the new $owner role resolver // passing {ownerRelations: true} will enable the new $owner role resolver
// with any belongsTo relation allowing to resolve truthy // with any belongsTo relation allowing to resolve truthy
const Message = createModelWithOptions( var Message = createModelWithOptions(
'ModelWithAllRelations', 'ModelWithAllRelations',
{ownerRelations: true}, {ownerRelations: true}
); );
const messages = [ var messages = [
{content: 'firstMessage', customerId: userFromOneModel.id}, {content: 'firstMessage', customerId: userFromOneModel.id},
{ {
content: 'secondMessage', content: 'secondMessage',
@ -552,7 +552,7 @@ describe('Multiple users with custom principalType', function() {
// helpers // helpers
function isOwnerForMessage(user, msg) { function isOwnerForMessage(user, msg) {
const accessContext = { var accessContext = {
principalType: user.constructor.modelName, principalType: user.constructor.modelName,
principalId: user.id, principalId: user.id,
model: msg.constructor, model: msg.constructor,
@ -569,7 +569,7 @@ describe('Multiple users with custom principalType', function() {
} }
function createModelWithOptions(name, options) { function createModelWithOptions(name, options) {
const baseOptions = { var baseOptions = {
relations: { relations: {
sender: { sender: {
type: 'belongsTo', type: 'belongsTo',
@ -584,10 +584,10 @@ describe('Multiple users with custom principalType', function() {
}, },
}; };
options = extend(baseOptions, options); options = extend(baseOptions, options);
const Model = app.registry.createModel( var Model = app.registry.createModel(
name, name,
{content: String, senderType: String}, {content: String, senderType: String},
options, options
); );
app.model(Model, {dataSource: 'db'}); app.model(Model, {dataSource: 'db'});
return Model; return Model;
@ -616,7 +616,7 @@ describe('Multiple users with custom principalType', function() {
function onError(err) { function onError(err) {
expect(err).to.have.property('statusCode', 400); expect(err).to.have.property('statusCode', 400);
expect(err).to.have.property('code', 'INVALID_PRINCIPAL_TYPE'); expect(err).to.have.property('code', 'INVALID_PRINCIPAL_TYPE');
}, }
); );
}); });
@ -768,7 +768,7 @@ describe('Multiple users with custom principalType', function() {
// helpers // helpers
function createUserModel(app, name, options) { function createUserModel(app, name, options) {
const model = app.registry.createModel(Object.assign({name: name}, options)); var model = app.registry.createModel(Object.assign({name: name}, options));
app.model(model, {dataSource: 'db'}); app.model(model, {dataSource: 'db'});
model.setMaxListeners(0); // allow many User.afterRemote's to be called model.setMaxListeners(0); // allow many User.afterRemote's to be called
return model; return model;
@ -778,9 +778,9 @@ describe('Multiple users with custom principalType', function() {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
emitter.once(name, resolve); emitter.once(name, resolve);
}); });
} };
function getIds(array) { function getIds(array) {
return array.map(function(it) { return it.id; }); return array.map(function(it) { return it.id; });
} };
}); });

View File

@ -1,19 +1,19 @@
// Copyright IBM Corp. 2015,2019. All Rights Reserved. // Copyright IBM Corp. 2015,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const assert = require('assert'); var assert = require('assert');
const expect = require('./helpers/expect'); var expect = require('./helpers/expect');
const loopback = require('../'); var loopback = require('../');
describe('Registry', function() { describe('Registry', function() {
describe('createModel', function() { describe('createModel', function() {
it('should throw error upon extending non-exist base model', function() { it('should throw error upon extending non-exist base model', function() {
const app = loopback(); var app = loopback();
const props = {}; var props = {};
const opts = {base: 'nonexistent'}; var opts = {base: 'nonexistent'};
expect(function() { app.registry.createModel('aModel', props, opts); }) expect(function() { app.registry.createModel('aModel', props, opts); })
.to.throw(/model\s`aModel`(.*)unknown\smodel\s`nonexistent`/); .to.throw(/model\s`aModel`(.*)unknown\smodel\s`nonexistent`/);
}); });
@ -21,26 +21,26 @@ describe('Registry', function() {
describe('one per app', function() { describe('one per app', function() {
it('should allow two apps to reuse the same model name', function(done) { it('should allow two apps to reuse the same model name', function(done) {
const appFoo = loopback(); var appFoo = loopback();
const appBar = loopback(); var appBar = loopback();
const modelName = 'MyModel'; var modelName = 'MyModel';
const subModelName = 'Sub' + modelName; var subModelName = 'Sub' + modelName;
const settings = {base: 'PersistedModel'}; var settings = {base: 'PersistedModel'};
appFoo.set('perAppRegistries', true); appFoo.set('perAppRegistries', true);
appBar.set('perAppRegistries', true); appBar.set('perAppRegistries', true);
const dsFoo = appFoo.dataSource('dsFoo', {connector: 'memory'}); var dsFoo = appFoo.dataSource('dsFoo', {connector: 'memory'});
const dsBar = appFoo.dataSource('dsBar', {connector: 'memory'}); var dsBar = appFoo.dataSource('dsBar', {connector: 'memory'});
const FooModel = appFoo.registry.createModel(modelName, {}, settings); var FooModel = appFoo.registry.createModel(modelName, {}, settings);
appFoo.model(FooModel, {dataSource: dsFoo}); appFoo.model(FooModel, {dataSource: dsFoo});
const FooSubModel = appFoo.registry.createModel(subModelName, {}, settings); var FooSubModel = appFoo.registry.createModel(subModelName, {}, settings);
appFoo.model(FooSubModel, {dataSource: dsFoo}); appFoo.model(FooSubModel, {dataSource: dsFoo});
const BarModel = appBar.registry.createModel(modelName, {}, settings); var BarModel = appBar.registry.createModel(modelName, {}, settings);
appBar.model(BarModel, {dataSource: dsBar}); appBar.model(BarModel, {dataSource: dsBar});
const BarSubModel = appBar.registry.createModel(subModelName, {}, settings); var BarSubModel = appBar.registry.createModel(subModelName, {}, settings);
appBar.model(BarSubModel, {dataSource: dsBar}); appBar.model(BarSubModel, {dataSource: dsBar});
FooModel.hasMany(FooSubModel); FooModel.hasMany(FooSubModel);

File diff suppressed because it is too large Load Diff

View File

@ -1,21 +1,21 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const assert = require('assert'); var assert = require('assert');
const loopback = require('../'); var loopback = require('../');
const defineModelTestsWithDataSource = require('./util/model-tests'); var defineModelTestsWithDataSource = require('./util/model-tests');
describe('RemoteConnector', function() { describe('RemoteConnector', function() {
this.timeout(10000); this.timeout(10000);
let remoteApp, remote; var remoteApp, remote;
defineModelTestsWithDataSource({ defineModelTestsWithDataSource({
beforeEach: function(done) { beforeEach: function(done) {
const test = this; var test = this;
remoteApp = loopback(); remoteApp = loopback();
remoteApp.set('remoting', { remoteApp.set('remoting', {
errorHandler: {debug: true, log: false}, errorHandler: {debug: true, log: false},
@ -40,7 +40,7 @@ describe('RemoteConnector', function() {
enableRemoteReplication: true, enableRemoteReplication: true,
onDefine: function(Model) { onDefine: function(Model) {
const ServerModel = Model.extend('Server' + Model.modelName, {}, { var ServerModel = Model.extend('Server' + Model.modelName, {}, {
plural: Model.pluralModelName, plural: Model.pluralModelName,
// This is the model running on the server & attached to a real // This is the model running on the server & attached to a real
// datasource, that's the place where to keep track of changes // datasource, that's the place where to keep track of changes
@ -54,13 +54,13 @@ describe('RemoteConnector', function() {
}); });
beforeEach(function(done) { beforeEach(function(done) {
const test = this; var test = this;
remoteApp = this.remoteApp = loopback(); remoteApp = this.remoteApp = loopback();
remoteApp.set('remoting', { remoteApp.set('remoting', {
types: {warnWhenOverridingType: false}, types: {warnWhenOverridingType: false},
}); });
remoteApp.use(loopback.rest()); remoteApp.use(loopback.rest());
const ServerModel = this.ServerModel = loopback.PersistedModel.extend('TestModel'); var ServerModel = this.ServerModel = loopback.PersistedModel.extend('TestModel');
remoteApp.model(ServerModel); remoteApp.model(ServerModel);
@ -76,11 +76,11 @@ describe('RemoteConnector', function() {
}); });
it('should support the save method', function(done) { it('should support the save method', function(done) {
let calledServerCreate = false; var calledServerCreate = false;
const RemoteModel = loopback.PersistedModel.extend('TestModel'); var RemoteModel = loopback.PersistedModel.extend('TestModel');
RemoteModel.attachTo(this.remote); RemoteModel.attachTo(this.remote);
const ServerModel = this.ServerModel; var ServerModel = this.ServerModel;
ServerModel.create = function(data, options, cb) { ServerModel.create = function(data, options, cb) {
calledServerCreate = true; calledServerCreate = true;
@ -90,7 +90,7 @@ describe('RemoteConnector', function() {
ServerModel.setupRemoting(); ServerModel.setupRemoting();
const m = new RemoteModel({foo: 'bar'}); var m = new RemoteModel({foo: 'bar'});
m.save(function(err, inst) { m.save(function(err, inst) {
if (err) return done(err); if (err) return done(err);

View File

@ -1,20 +1,20 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const assert = require('assert'); var assert = require('assert');
const loopback = require('../'); var loopback = require('../');
const request = require('supertest'); var request = require('supertest');
describe('remoting coercion', function() { describe('remoting coercion', function() {
it('should coerce arguments based on the type', function(done) { it('should coerce arguments based on the type', function(done) {
let called = false; var called = false;
const app = loopback(); var app = loopback();
app.use(loopback.rest()); app.use(loopback.rest());
const TestModel = app.registry.createModel('TestModel', var TestModel = app.registry.createModel('TestModel',
{}, {},
{base: 'Model'}); {base: 'Model'});
app.model(TestModel, {public: true}); app.model(TestModel, {public: true});

View File

@ -1,16 +1,16 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const loopback = require('../'); var loopback = require('../');
const lt = require('./helpers/loopback-testing-helper'); var lt = require('./helpers/loopback-testing-helper');
const path = require('path'); var path = require('path');
const SIMPLE_APP = path.join(__dirname, 'fixtures', 'simple-integration-app'); var SIMPLE_APP = path.join(__dirname, 'fixtures', 'simple-integration-app');
const app = require(path.join(SIMPLE_APP, 'server/server.js')); var app = require(path.join(SIMPLE_APP, 'server/server.js'));
const assert = require('assert'); var assert = require('assert');
const expect = require('./helpers/expect'); var expect = require('./helpers/expect');
describe('remoting - integration', function() { describe('remoting - integration', function() {
lt.beforeEach.withApp(app); lt.beforeEach.withApp(app);
@ -22,21 +22,21 @@ describe('remoting - integration', function() {
describe('app.remotes.options', function() { describe('app.remotes.options', function() {
it('should load remoting options', function() { it('should load remoting options', function() {
const remotes = app.remotes(); var remotes = app.remotes();
assert.deepEqual(remotes.options, {'json': {'limit': '1kb', 'strict': false}, assert.deepEqual(remotes.options, {'json': {'limit': '1kb', 'strict': false},
'urlencoded': {'limit': '8kb', 'extended': true}, 'urlencoded': {'limit': '8kb', 'extended': true},
'errorHandler': {'debug': true, log: false}}); 'errorHandler': {'debug': true, log: false}});
}); });
it('rest handler', function() { it('rest handler', function() {
const handler = app.handler('rest'); var handler = app.handler('rest');
assert(handler); assert(handler);
}); });
it('should accept request that has entity below 1kb', function(done) { it('should accept request that has entity below 1kb', function(done) {
// Build an object that is smaller than 1kb // Build an object that is smaller than 1kb
let name = ''; var name = '';
for (let i = 0; i < 256; i++) { for (var i = 0; i < 256; i++) {
name += '11'; name += '11';
} }
this.http = this.post('/api/stores'); this.http = this.post('/api/stores');
@ -55,8 +55,8 @@ describe('remoting - integration', function() {
it('should reject request that has entity beyond 1kb', function(done) { it('should reject request that has entity beyond 1kb', function(done) {
// Build an object that is larger than 1kb // Build an object that is larger than 1kb
let name = ''; var name = '';
for (let i = 0; i < 2048; i++) { for (var i = 0; i < 2048; i++) {
name += '11111111111'; name += '11111111111';
} }
this.http = this.post('/api/stores'); this.http = this.post('/api/stores');
@ -79,10 +79,10 @@ describe('remoting - integration', function() {
it('has expected remote methods with default model.settings.replaceOnPUT' + it('has expected remote methods with default model.settings.replaceOnPUT' +
'set to true (3.x)', 'set to true (3.x)',
function() { function() {
const storeClass = findClass('store'); var storeClass = findClass('store');
const methods = getFormattedMethodsExcludingRelations(storeClass.methods); var methods = getFormattedMethodsExcludingRelations(storeClass.methods);
const expectedMethods = [ var expectedMethods = [
'create(data:object:store):store POST /stores', 'create(data:object:store):store POST /stores',
'patchOrCreate(data:object:store):store PATCH /stores', 'patchOrCreate(data:object:store):store PATCH /stores',
'replaceOrCreate(data:object:store):store PUT /stores', 'replaceOrCreate(data:object:store):store PUT /stores',
@ -106,10 +106,10 @@ describe('remoting - integration', function() {
}); });
it('has expected remote methods for scopes', function() { it('has expected remote methods for scopes', function() {
const storeClass = findClass('store'); var storeClass = findClass('store');
const methods = getFormattedScopeMethods(storeClass.methods); var methods = getFormattedScopeMethods(storeClass.methods);
const expectedMethods = [ var expectedMethods = [
'__get__superStores(filter:object):store GET /stores/superStores', '__get__superStores(filter:object):store GET /stores/superStores',
'__create__superStores(data:object:store):store POST /stores/superStores', '__create__superStores(data:object:store):store POST /stores/superStores',
'__delete__superStores() DELETE /stores/superStores', '__delete__superStores() DELETE /stores/superStores',
@ -121,10 +121,10 @@ describe('remoting - integration', function() {
it('should have correct signatures for belongsTo methods', it('should have correct signatures for belongsTo methods',
function() { function() {
const widgetClass = findClass('widget'); var widgetClass = findClass('widget');
const methods = getFormattedPrototypeMethods(widgetClass.methods); var methods = getFormattedPrototypeMethods(widgetClass.methods);
const expectedMethods = [ var expectedMethods = [
'prototype.__get__store(refresh:boolean):store ' + 'prototype.__get__store(refresh:boolean):store ' +
'GET /widgets/:id/store', 'GET /widgets/:id/store',
]; ];
@ -133,10 +133,10 @@ describe('remoting - integration', function() {
it('should have correct signatures for hasMany methods', it('should have correct signatures for hasMany methods',
function() { function() {
const storeClass = findClass('store'); var storeClass = findClass('store');
const methods = getFormattedPrototypeMethods(storeClass.methods); var methods = getFormattedPrototypeMethods(storeClass.methods);
const expectedMethods = [ var expectedMethods = [
'prototype.__findById__widgets(fk:any):widget ' + 'prototype.__findById__widgets(fk:any):widget ' +
'GET /stores/:id/widgets/:fk', 'GET /stores/:id/widgets/:fk',
'prototype.__destroyById__widgets(fk:any) ' + 'prototype.__destroyById__widgets(fk:any) ' +
@ -157,10 +157,10 @@ describe('remoting - integration', function() {
it('should have correct signatures for hasMany-through methods', it('should have correct signatures for hasMany-through methods',
function() { // jscs:disable validateIndentation function() { // jscs:disable validateIndentation
const physicianClass = findClass('physician'); var physicianClass = findClass('physician');
const methods = getFormattedPrototypeMethods(physicianClass.methods); var methods = getFormattedPrototypeMethods(physicianClass.methods);
const expectedMethods = [ var expectedMethods = [
'prototype.__findById__patients(fk:any):patient ' + 'prototype.__findById__patients(fk:any):patient ' +
'GET /physicians/:id/patients/:fk', 'GET /physicians/:id/patients/:fk',
'prototype.__destroyById__patients(fk:any) ' + 'prototype.__destroyById__patients(fk:any) ' +
@ -187,9 +187,9 @@ describe('remoting - integration', function() {
}); });
it('has upsertWithWhere remote method', function() { it('has upsertWithWhere remote method', function() {
const storeClass = findClass('store'); var storeClass = findClass('store');
const methods = getFormattedMethodsExcludingRelations(storeClass.methods); var methods = getFormattedMethodsExcludingRelations(storeClass.methods);
const expectedMethods = [ var expectedMethods = [
'upsertWithWhere(where:object,data:object:store):store POST /stores/upsertWithWhere', 'upsertWithWhere(where:object,data:object:store):store POST /stores/upsertWithWhere',
]; ];
expect(methods).to.include.members(expectedMethods); expect(methods).to.include.members(expectedMethods);
@ -198,21 +198,21 @@ describe('remoting - integration', function() {
describe('createOnlyInstance', function() { describe('createOnlyInstance', function() {
it('sets createOnlyInstance to true if id is generated and forceId is not set to false', it('sets createOnlyInstance to true if id is generated and forceId is not set to false',
function() { function() {
const storeClass = findClass('store'); var storeClass = findClass('store');
const createMethod = getCreateMethod(storeClass.methods); var createMethod = getCreateMethod(storeClass.methods);
assert(createMethod.accepts[0].createOnlyInstance === true); assert(createMethod.accepts[0].createOnlyInstance === true);
}); });
it('sets createOnlyInstance to false if forceId is set to false in the model', function() { it('sets createOnlyInstance to false if forceId is set to false in the model', function() {
const customerClass = findClass('customerforceidfalse'); var customerClass = findClass('customerforceidfalse');
const createMethod = getCreateMethod(customerClass.methods); var createMethod = getCreateMethod(customerClass.methods);
assert(createMethod.accepts[0].createOnlyInstance === false); assert(createMethod.accepts[0].createOnlyInstance === false);
}); });
it('sets createOnlyInstance based on target model for scoped or related methods', it('sets createOnlyInstance based on target model for scoped or related methods',
function() { function() {
const userClass = findClass('user'); var userClass = findClass('user');
const createMethod = userClass.methods.find(function(m) { var createMethod = userClass.methods.find(function(m) {
return (m.name === 'prototype.__create__accessTokens'); return (m.name === 'prototype.__create__accessTokens');
}); });
assert(createMethod.accepts[0].createOnlyInstance === false); assert(createMethod.accepts[0].createOnlyInstance === false);
@ -229,10 +229,10 @@ describe('With model.settings.replaceOnPUT false', function() {
it('should have expected remote methods', it('should have expected remote methods',
function() { function() {
const storeClass = findClass('storeWithReplaceOnPUTfalse'); var storeClass = findClass('storeWithReplaceOnPUTfalse');
const methods = getFormattedMethodsExcludingRelations(storeClass.methods); var methods = getFormattedMethodsExcludingRelations(storeClass.methods);
const expectedMethods = [ var expectedMethods = [
'create(data:object:storeWithReplaceOnPUTfalse):storeWithReplaceOnPUTfalse POST /stores-updating', 'create(data:object:storeWithReplaceOnPUTfalse):storeWithReplaceOnPUTfalse POST /stores-updating',
'patchOrCreate(data:object:storeWithReplaceOnPUTfalse):storeWithReplaceOnPUTfalse PUT /stores-updating', 'patchOrCreate(data:object:storeWithReplaceOnPUTfalse):storeWithReplaceOnPUTfalse PUT /stores-updating',
'patchOrCreate(data:object:storeWithReplaceOnPUTfalse):storeWithReplaceOnPUTfalse PATCH /stores-updating', 'patchOrCreate(data:object:storeWithReplaceOnPUTfalse):storeWithReplaceOnPUTfalse PATCH /stores-updating',
@ -266,10 +266,10 @@ describe('With model.settings.replaceOnPUT true', function() {
it('should have expected remote methods', it('should have expected remote methods',
function() { function() {
const storeClass = findClass('storeWithReplaceOnPUTtrue'); var storeClass = findClass('storeWithReplaceOnPUTtrue');
const methods = getFormattedMethodsExcludingRelations(storeClass.methods); var methods = getFormattedMethodsExcludingRelations(storeClass.methods);
const expectedMethods = [ var expectedMethods = [
'patchOrCreate(data:object:storeWithReplaceOnPUTtrue):storeWithReplaceOnPUTtrue PATCH /stores-replacing', 'patchOrCreate(data:object:storeWithReplaceOnPUTtrue):storeWithReplaceOnPUTtrue PATCH /stores-replacing',
'replaceOrCreate(data:object:storeWithReplaceOnPUTtrue):storeWithReplaceOnPUTtrue POST /stores-replacing/replaceOrCreate', 'replaceOrCreate(data:object:storeWithReplaceOnPUTtrue):storeWithReplaceOnPUTtrue POST /stores-replacing/replaceOrCreate',
'replaceOrCreate(data:object:storeWithReplaceOnPUTtrue):storeWithReplaceOnPUTtrue PUT /stores-replacing', 'replaceOrCreate(data:object:storeWithReplaceOnPUTtrue):storeWithReplaceOnPUTtrue PUT /stores-replacing',
@ -283,11 +283,11 @@ describe('With model.settings.replaceOnPUT true', function() {
}); });
function formatReturns(m) { function formatReturns(m) {
const returns = m.returns; var returns = m.returns;
if (!returns || returns.length === 0) { if (!returns || returns.length === 0) {
return ''; return '';
} }
let type = returns[0].type; var type = returns[0].type;
// handle anonymous type definitions, e.g // handle anonymous type definitions, e.g
// { arg: 'info', type: { count: 'number' } } // { arg: 'info', type: { count: 'number' } }
@ -298,9 +298,9 @@ function formatReturns(m) {
} }
function formatMethod(m) { function formatMethod(m) {
const arr = []; var arr = [];
const endpoints = m.getEndpoints(); var endpoints = m.getEndpoints();
for (let i = 0; i < endpoints.length; i++) { for (var i = 0; i < endpoints.length; i++) {
arr.push([ arr.push([
m.name, m.name,
'(', '(',

Some files were not shown because too many files have changed in this diff Show More