Merge remote-tracking branch 'origin/production' into production
This commit is contained in:
commit
05dd7bc84b
|
@ -3,6 +3,14 @@
|
||||||
"content": [
|
"content": [
|
||||||
"docs/api.md",
|
"docs/api.md",
|
||||||
"docs/api-app.md",
|
"docs/api-app.md",
|
||||||
|
"lib/models/access-token.js",
|
||||||
|
"lib/models/access-context.js",
|
||||||
|
"lib/models/acl.js",
|
||||||
|
"lib/models/application.js",
|
||||||
|
"lib/models/email.js",
|
||||||
|
"lib/models/model.js",
|
||||||
|
"lib/models/role.js",
|
||||||
|
"lib/models/user.js",
|
||||||
"docs/api-datasource.md",
|
"docs/api-datasource.md",
|
||||||
"docs/api-geopoint.md",
|
"docs/api-geopoint.md",
|
||||||
"docs/api-model.md",
|
"docs/api-model.md",
|
||||||
|
|
|
@ -58,6 +58,7 @@ Initialize an application from an options object or a set of JSON and JavaScript
|
||||||
1. **DataSources** are created from an `options.dataSources` object or `datasources.json` in the current directory
|
1. **DataSources** are created from an `options.dataSources` object or `datasources.json` in the current directory
|
||||||
2. **Models** are created from an `options.models` object or `models.json` in the current directory
|
2. **Models** are created from an `options.models` object or `models.json` in the current directory
|
||||||
3. Any JavaScript files in the `./models` directory are loaded with `require()`.
|
3. Any JavaScript files in the `./models` directory are loaded with `require()`.
|
||||||
|
4. Any JavaScript files in the `./boot` directory are loaded with `require()`.
|
||||||
|
|
||||||
**Options**
|
**Options**
|
||||||
|
|
||||||
|
|
|
@ -310,10 +310,18 @@ User.find({
|
||||||
|
|
||||||
**Note:** See the specific connector's [docs](#connectors) for more info.
|
**Note:** See the specific connector's [docs](#connectors) for more info.
|
||||||
|
|
||||||
### Model.destroyAll(callback)
|
### Model.destroyAll([where], callback)
|
||||||
|
|
||||||
Delete all Model instances from data source. **Note:** destroyAll method does not perform destroy hooks.
|
Delete all Model instances from data source. **Note:** destroyAll method does not perform destroy hooks.
|
||||||
|
|
||||||
|
```js
|
||||||
|
Product.destroyAll({price: {gt: 99}}, function(err) {
|
||||||
|
// removed matching products
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
> **NOTE:* `where` is optional and a where object... do NOT pass a filter object
|
||||||
|
|
||||||
### Model.findById(id, callback)
|
### Model.findById(id, callback)
|
||||||
|
|
||||||
Find instance by id.
|
Find instance by id.
|
||||||
|
|
|
@ -290,6 +290,7 @@ app.boot = function(options) {
|
||||||
|
|
||||||
// require directories
|
// require directories
|
||||||
var requiredModels = requireDir(path.join(appRootDir, 'models'));
|
var requiredModels = requireDir(path.join(appRootDir, 'models'));
|
||||||
|
var requiredBootScripts = requireDir(path.join(appRootDir, 'boot'));
|
||||||
}
|
}
|
||||||
|
|
||||||
function assertIsValidConfig(name, config) {
|
function assertIsValidConfig(name, config) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# Application
|
# Application
|
||||||
|
|
||||||
Application model captures the metadata for a loopback application.
|
Application model represents the metadata for a client application that has its
|
||||||
|
own identity and associated configuration with the LoopBack server.
|
||||||
|
|
||||||
## Each application has the following basic properties:
|
## Each application has the following basic properties:
|
||||||
|
|
||||||
|
@ -25,7 +26,8 @@ Application model captures the metadata for a loopback application.
|
||||||
|
|
||||||
## Security keys
|
## Security keys
|
||||||
|
|
||||||
The following keys are automatically generated by the application creation process. They can be reset upon request.
|
The following keys are automatically generated by the application creation
|
||||||
|
process. They can be reset upon request.
|
||||||
|
|
||||||
* clientKey: Secret for mobile clients
|
* clientKey: Secret for mobile clients
|
||||||
* javaScriptKey: Secret for JavaScript clients
|
* javaScriptKey: Secret for JavaScript clients
|
||||||
|
@ -40,48 +42,50 @@ The application can be configured to support multiple methods of push notificati
|
||||||
* pushSettings
|
* pushSettings
|
||||||
|
|
||||||
|
|
||||||
{
|
pushSettings: {
|
||||||
pushSettings: [
|
apns: {
|
||||||
{ "platform": "apns",
|
certData: config.apnsCertData,
|
||||||
"apns": {
|
keyData: config.apnsKeyData,
|
||||||
"pushOptions": {
|
production: false, // Development mode
|
||||||
"gateway": "gateway.sandbox.push.apple.com",
|
pushOptions: {
|
||||||
"cert": "credentials/apns_cert_dev.pem",
|
// Extra options can go here for APN
|
||||||
"key": "credentials/apns_key_dev.pem"
|
|
||||||
},
|
},
|
||||||
|
feedbackOptions: {
|
||||||
"feedbackOptions": {
|
batchFeedback: true,
|
||||||
"gateway": "feedback.sandbox.push.apple.com",
|
interval: 300
|
||||||
"cert": "credentials/apns_cert_dev.pem",
|
}
|
||||||
"key": "credentials/apns_key_dev.pem",
|
},
|
||||||
"batchFeedback": true,
|
gcm: {
|
||||||
"interval": 300
|
serverApiKey: config.gcmServerApiKey
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}}
|
|
||||||
]}
|
|
||||||
|
|
||||||
|
|
||||||
## Authentication schemes
|
## Authentication schemes
|
||||||
|
|
||||||
|
|
||||||
* authenticationEnabled
|
* authenticationEnabled
|
||||||
* anonymousAllowed
|
* anonymousAllowed
|
||||||
* authenticationSchemes
|
* authenticationSchemes
|
||||||
|
|
||||||
### Authentication scheme settings
|
### Authentication scheme settings
|
||||||
|
|
||||||
* scheme: Name of the authentication scheme, such as local, facebook, google, twitter, linkedin, github
|
* scheme: Name of the authentication scheme, such as local, facebook, google,
|
||||||
|
twitter, linkedin, github
|
||||||
* credential: Scheme-specific credentials
|
* credential: Scheme-specific credentials
|
||||||
|
|
||||||
## APIs for Application model
|
## APIs for Application model
|
||||||
|
|
||||||
In addition to the CRUD methods, the Application model also has the following apis:
|
In addition to the CRUD methods, the Application model also has the following
|
||||||
|
apis:
|
||||||
|
|
||||||
### Register a new application
|
### Register a new application
|
||||||
|
|
||||||
You can register a new application by providing the owner user id, applicaiton name, and other properties in the options object.
|
You can register a new application by providing the owner user id, application
|
||||||
|
name, and other properties in the options object.
|
||||||
|
|
||||||
Application.register('rfeng', 'MyApp1', {description: 'My first loopback application'}, function (err, result) {
|
Application.register('rfeng', 'MyApp1',
|
||||||
|
{description: 'My first loopback application'},
|
||||||
|
function (err, result) {
|
||||||
var app = result;
|
var app = result;
|
||||||
...
|
...
|
||||||
});
|
});
|
||||||
|
@ -97,7 +101,9 @@ You can reset keys for a given application by id.
|
||||||
|
|
||||||
### Authenticate by appId and key
|
### Authenticate by appId and key
|
||||||
|
|
||||||
You can authenticate an application by id and one of the keys. If successful, it calls back with the key name in the result argument. Otherwise, the keyName is null.
|
You can authenticate an application by id and one of the keys. If successful,
|
||||||
|
it calls back with the key name in the result argument. Otherwise, the
|
||||||
|
keyName is null.
|
||||||
|
|
||||||
Application.authenticate(appId, clientKey, function (err, keyName) {
|
Application.authenticate(appId, clientKey, function (err, keyName) {
|
||||||
assert.equal(keyName, 'clientKey');
|
assert.equal(keyName, 'clientKey');
|
||||||
|
@ -105,21 +111,3 @@ You can authenticate an application by id and one of the keys. If successful, it
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
# Installation
|
|
||||||
|
|
||||||
Installation captures the installation of the application on devices.
|
|
||||||
|
|
||||||
Each record of installation has the following properties:
|
|
||||||
|
|
||||||
* id: Generated id that uniquely identifies the installation
|
|
||||||
* appId: Application id
|
|
||||||
* appVersion: Application version
|
|
||||||
* userId: The current user id that logs into the application
|
|
||||||
* deviceToken: Device token
|
|
||||||
* deviceType: Device type, such as apns
|
|
||||||
* subscriptions: An Array of tags that represents subscriptions of notifications
|
|
||||||
* status: Status of the application, production/sandbox/disabled
|
|
||||||
* created: Timestamp of the recored being created
|
|
||||||
* modified: Timestamp of the recored being modified
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
var loopback = require('../loopback');
|
var loopback = require('../loopback');
|
||||||
var AccessToken = require('./access-token');
|
var AccessToken = require('./access-token');
|
||||||
|
var debug = require('debug')('loopback:security:access-context');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Access context represents the context for a request to access protected
|
* Access context represents the context for a request to access protected
|
||||||
* resources
|
* resources
|
||||||
*
|
*
|
||||||
* The AccessContext instance contains the following properties:
|
* @class
|
||||||
* @property {Principal[]} principals An array of principals
|
* @property {Principal[]} principals An array of principals
|
||||||
* @property {Function} model The model class
|
* @property {Function} model The model class
|
||||||
* @property {String} modelName The model name
|
* @property {String} modelName The model name
|
||||||
|
@ -95,6 +96,8 @@ AccessContext.prototype.addPrincipal = function (principalType, principalId, pri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.principals.push(principal);
|
this.principals.push(principal);
|
||||||
|
|
||||||
|
debug('adding principal %j', principal);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -135,6 +138,36 @@ AccessContext.prototype.isAuthenticated = function() {
|
||||||
return !!(this.getUserId() || this.getAppId());
|
return !!(this.getUserId() || this.getAppId());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print debug info for access context.
|
||||||
|
*/
|
||||||
|
|
||||||
|
AccessContext.prototype.debug = function() {
|
||||||
|
if(debug.enabled) {
|
||||||
|
debug('---AccessContext---');
|
||||||
|
if(this.principals && this.principals.length) {
|
||||||
|
debug('principals:')
|
||||||
|
this.principals.forEach(function(principal) {
|
||||||
|
debug('principal: %j', principal)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
debug('principals: %j', this.principals);
|
||||||
|
}
|
||||||
|
debug('modelName %s', this.modelName);
|
||||||
|
debug('modelId %s', this.modelId);
|
||||||
|
debug('property %s', this.property);
|
||||||
|
debug('method %s', this.method);
|
||||||
|
debug('accessType %s', this.accessType);
|
||||||
|
if(this.accessToken) {
|
||||||
|
debug('accessToken:')
|
||||||
|
debug(' id %j', this.accessToken.id);
|
||||||
|
debug(' ttl %j', this.accessToken.ttl);
|
||||||
|
}
|
||||||
|
debug('getUserId() %s', this.getUserId());
|
||||||
|
debug('isAuthenticated() %s', this.isAuthenticated());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represents the abstract notion of a principal, which can be used
|
* This class represents the abstract notion of a principal, which can be used
|
||||||
* to represent any entity, such as an individual, a corporation, and a login id
|
* to represent any entity, such as an individual, a corporation, and a login id
|
||||||
|
@ -142,7 +175,7 @@ AccessContext.prototype.isAuthenticated = function() {
|
||||||
* @param {*} id The princiapl id
|
* @param {*} id The princiapl id
|
||||||
* @param {String} [name] The principal name
|
* @param {String} [name] The principal name
|
||||||
* @returns {Principal}
|
* @returns {Principal}
|
||||||
* @constructor
|
* @class
|
||||||
*/
|
*/
|
||||||
function Principal(type, id, name) {
|
function Principal(type, id, name) {
|
||||||
if (!(this instanceof Principal)) {
|
if (!(this instanceof Principal)) {
|
||||||
|
@ -178,7 +211,7 @@ Principal.prototype.equals = function (p) {
|
||||||
* @param {String} accessType The access type
|
* @param {String} accessType The access type
|
||||||
* @param {String} permission The permission
|
* @param {String} permission The permission
|
||||||
* @returns {AccessRequest}
|
* @returns {AccessRequest}
|
||||||
* @constructor
|
* @class
|
||||||
*/
|
*/
|
||||||
function AccessRequest(model, property, accessType, permission) {
|
function AccessRequest(model, property, accessType, permission) {
|
||||||
if (!(this instanceof AccessRequest)) {
|
if (!(this instanceof AccessRequest)) {
|
||||||
|
@ -188,6 +221,15 @@ function AccessRequest(model, property, accessType, permission) {
|
||||||
this.property = property || AccessContext.ALL;
|
this.property = property || AccessContext.ALL;
|
||||||
this.accessType = accessType || AccessContext.ALL;
|
this.accessType = accessType || AccessContext.ALL;
|
||||||
this.permission = permission || AccessContext.DEFAULT;
|
this.permission = permission || AccessContext.DEFAULT;
|
||||||
|
|
||||||
|
if(debug.enabled) {
|
||||||
|
debug('---AccessRequest---');
|
||||||
|
debug(' model %s', this.model);
|
||||||
|
debug(' property %s', this.property);
|
||||||
|
debug(' accessType %s', this.accessType);
|
||||||
|
debug(' permission %s', this.permission);
|
||||||
|
debug(' isWildcard() %s', this.isWildcard());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/**
|
/*!
|
||||||
* Module Dependencies.
|
* Module Dependencies.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -14,6 +14,10 @@ var Model = require('../loopback').Model
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default AccessToken properties.
|
* Default AccessToken properties.
|
||||||
|
*
|
||||||
|
* @property id {String} - Generated token ID
|
||||||
|
* @property ttl {Number} - Time to live
|
||||||
|
* @property created {Date} - When the token was created
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var properties = {
|
var properties = {
|
||||||
|
@ -25,7 +29,15 @@ var properties = {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extends from the built in `loopback.Model` type.
|
* Token based authentication and access control.
|
||||||
|
*
|
||||||
|
* **Default ACLs**
|
||||||
|
*
|
||||||
|
* - DENY EVERYONE `*`
|
||||||
|
* - ALLOW EVERYONE create
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @inherits {Model}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var AccessToken = module.exports = Model.extend('AccessToken', properties, {
|
var AccessToken = module.exports = Model.extend('AccessToken', properties, {
|
||||||
|
@ -44,12 +56,22 @@ var AccessToken = module.exports = Model.extend('AccessToken', properties, {
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Anonymous Token
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* assert(AccessToken.ANONYMOUS.id === '$anonymous');
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
|
||||||
AccessToken.ANONYMOUS = new AccessToken({id: '$anonymous'});
|
AccessToken.ANONYMOUS = new AccessToken({id: '$anonymous'});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a cryptographically random access token id.
|
* Create a cryptographically random access token id.
|
||||||
*
|
*
|
||||||
* @param {Function} callback
|
* @callback {Function} callback
|
||||||
|
* @param {Error} err
|
||||||
|
* @param {String} token
|
||||||
*/
|
*/
|
||||||
|
|
||||||
AccessToken.createAccessTokenId = function (fn) {
|
AccessToken.createAccessTokenId = function (fn) {
|
||||||
|
@ -85,7 +107,9 @@ AccessToken.beforeCreate = function (next, data) {
|
||||||
*
|
*
|
||||||
* @param {ServerRequest} req
|
* @param {ServerRequest} req
|
||||||
* @param {Object} [options] Options for finding the token
|
* @param {Object} [options] Options for finding the token
|
||||||
* @param {Function} callback Calls back with a token if one exists otherwise null or an error.
|
* @callback {Function} callback
|
||||||
|
* @param {Error} err
|
||||||
|
* @param {AccessToken} token
|
||||||
*/
|
*/
|
||||||
|
|
||||||
AccessToken.findForRequest = function(req, options, cb) {
|
AccessToken.findForRequest = function(req, options, cb) {
|
||||||
|
@ -116,6 +140,14 @@ AccessToken.findForRequest = function(req, options, cb) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the token.
|
||||||
|
*
|
||||||
|
* @callback {Function} callback
|
||||||
|
* @param {Error} err
|
||||||
|
* @param {Boolean} isValid
|
||||||
|
*/
|
||||||
|
|
||||||
AccessToken.prototype.validate = function(cb) {
|
AccessToken.prototype.validate = function(cb) {
|
||||||
try {
|
try {
|
||||||
assert(
|
assert(
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/**
|
/*!
|
||||||
Schema ACL options
|
Schema ACL options
|
||||||
|
|
||||||
Object level permissions, for example, an album owned by a user
|
Object level permissions, for example, an album owned by a user
|
||||||
|
@ -44,7 +44,7 @@ var AccessRequest = ctx.AccessRequest;
|
||||||
var role = require('./role');
|
var role = require('./role');
|
||||||
var Role = role.Role;
|
var Role = role.Role;
|
||||||
|
|
||||||
/**
|
/*!
|
||||||
* Schema for Scope which represents the permissions that are granted to client
|
* Schema for Scope which represents the permissions that are granted to client
|
||||||
* applications by the resource owner
|
* applications by the resource owner
|
||||||
*/
|
*/
|
||||||
|
@ -53,7 +53,6 @@ var ScopeSchema = {
|
||||||
description: String
|
description: String
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resource owner grants/delegates permissions to client applications
|
* Resource owner grants/delegates permissions to client applications
|
||||||
*
|
*
|
||||||
|
@ -61,10 +60,31 @@ var ScopeSchema = {
|
||||||
* from the resource owner (user or system)?
|
* from the resource owner (user or system)?
|
||||||
*
|
*
|
||||||
* Scope has many resource access entries
|
* Scope has many resource access entries
|
||||||
* @type {createModel|*}
|
* @class
|
||||||
*/
|
*/
|
||||||
var Scope = loopback.createModel('Scope', ScopeSchema);
|
var Scope = loopback.createModel('Scope', ScopeSchema);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the given scope is allowed to access the model/property
|
||||||
|
* @param {String} scope The scope name
|
||||||
|
* @param {String} model The model name
|
||||||
|
* @param {String} property The property/method/relation name
|
||||||
|
* @param {String} accessType The access type
|
||||||
|
* @callback {Function} callback
|
||||||
|
* @param {String|Error} err The error object
|
||||||
|
* @param {AccessRequest} result The access permission
|
||||||
|
*/
|
||||||
|
Scope.checkPermission = function (scope, model, property, accessType, callback) {
|
||||||
|
Scope.findOne({where: {name: scope}}, function (err, scope) {
|
||||||
|
if (err) {
|
||||||
|
callback && callback(err);
|
||||||
|
} else {
|
||||||
|
ACL.checkPermission(ACL.SCOPE, scope.id, model, property, accessType, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* System grants permissions to principals (users/applications, can be grouped
|
* System grants permissions to principals (users/applications, can be grouped
|
||||||
* into roles).
|
* into roles).
|
||||||
|
@ -75,7 +95,6 @@ var Scope = loopback.createModel('Scope', ScopeSchema);
|
||||||
* For a given principal, such as client application and/or user, is it allowed
|
* For a given principal, such as client application and/or user, is it allowed
|
||||||
* to access (read/write/execute)
|
* to access (read/write/execute)
|
||||||
* the protected resource?
|
* the protected resource?
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
var ACLSchema = {
|
var ACLSchema = {
|
||||||
model: String, // The name of the model
|
model: String, // The name of the model
|
||||||
|
@ -105,6 +124,14 @@ var ACLSchema = {
|
||||||
principalId: String
|
principalId: String
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Model for access control meta data.
|
||||||
|
*
|
||||||
|
* @header ACL
|
||||||
|
* @class
|
||||||
|
* @inherits Model
|
||||||
|
*/
|
||||||
|
|
||||||
var ACL = loopback.createModel('ACL', ACLSchema);
|
var ACL = loopback.createModel('ACL', ACLSchema);
|
||||||
|
|
||||||
ACL.ALL = AccessContext.ALL;
|
ACL.ALL = AccessContext.ALL;
|
||||||
|
@ -160,10 +187,9 @@ ACL.getMatchingScore = function getMatchingScore(rule, req) {
|
||||||
* Resolve permission from the ACLs
|
* Resolve permission from the ACLs
|
||||||
* @param {Object[]) acls The list of ACLs
|
* @param {Object[]) acls The list of ACLs
|
||||||
* @param {Object} req The request
|
* @param {Object} req The request
|
||||||
* @returns {Object} The effective ACL
|
* @returns {AccessRequest} result The effective ACL
|
||||||
*/
|
*/
|
||||||
ACL.resolvePermission = function resolvePermission(acls, req) {
|
ACL.resolvePermission = function resolvePermission(acls, req) {
|
||||||
debug('resolvePermission(): %j %j', acls, req);
|
|
||||||
// Sort by the matching score in descending order
|
// Sort by the matching score in descending order
|
||||||
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);
|
||||||
|
@ -198,7 +224,6 @@ ACL.resolvePermission = function resolvePermission(acls, req) {
|
||||||
|
|
||||||
var res = new AccessRequest(req.model, req.property, req.accessType,
|
var res = new AccessRequest(req.model, req.property, req.accessType,
|
||||||
permission || ACL.DEFAULT);
|
permission || ACL.DEFAULT);
|
||||||
debug('resolvePermission() returns: %j', res);
|
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -210,7 +235,6 @@ ACL.resolvePermission = function resolvePermission(acls, req) {
|
||||||
* @return {Object[]} An array of ACLs
|
* @return {Object[]} An array of ACLs
|
||||||
*/
|
*/
|
||||||
ACL.getStaticACLs = function getStaticACLs(model, property) {
|
ACL.getStaticACLs = function getStaticACLs(model, property) {
|
||||||
debug('getStaticACLs(): %s %s', model, property);
|
|
||||||
var modelClass = loopback.getModel(model);
|
var modelClass = loopback.getModel(model);
|
||||||
var staticACLs = [];
|
var staticACLs = [];
|
||||||
if (modelClass && modelClass.settings.acls) {
|
if (modelClass && modelClass.settings.acls) {
|
||||||
|
@ -223,6 +247,8 @@ ACL.getStaticACLs = function getStaticACLs(model, property) {
|
||||||
accessType: acl.accessType,
|
accessType: acl.accessType,
|
||||||
permission: acl.permission
|
permission: acl.permission
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
staticACLs[staticACLs.length - 1].debug('Adding ACL');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
var prop = modelClass &&
|
var prop = modelClass &&
|
||||||
|
@ -242,7 +268,6 @@ ACL.getStaticACLs = function getStaticACLs(model, property) {
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
debug('getStaticACLs() returns: %j', staticACLs);
|
|
||||||
return staticACLs;
|
return staticACLs;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -257,13 +282,11 @@ ACL.getStaticACLs = function getStaticACLs(model, property) {
|
||||||
*
|
*
|
||||||
* @callback callback
|
* @callback callback
|
||||||
* @param {String|Error} err The error object
|
* @param {String|Error} err The error object
|
||||||
* @param {Object} the access permission
|
* @param {AccessRequest} result The access permission
|
||||||
*/
|
*/
|
||||||
ACL.checkPermission = function checkPermission(principalType, principalId,
|
ACL.checkPermission = function checkPermission(principalType, principalId,
|
||||||
model, property, accessType,
|
model, property, accessType,
|
||||||
callback) {
|
callback) {
|
||||||
debug('checkPermission(): %s %s %s %s %s', principalType, principalId, model,
|
|
||||||
property, accessType);
|
|
||||||
property = property || ACL.ALL;
|
property = property || ACL.ALL;
|
||||||
var 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;
|
||||||
|
@ -276,8 +299,8 @@ ACL.checkPermission = function checkPermission(principalType, principalId,
|
||||||
var resolved = ACL.resolvePermission(acls, req);
|
var resolved = ACL.resolvePermission(acls, req);
|
||||||
|
|
||||||
if(resolved && resolved.permission === ACL.DENY) {
|
if(resolved && resolved.permission === ACL.DENY) {
|
||||||
// Fail fast
|
debug('Permission denied by statically resolved permission');
|
||||||
debug('checkPermission(): %j', resolved);
|
debug(' Resolved Permission: %j', resolved);
|
||||||
process.nextTick(function() {
|
process.nextTick(function() {
|
||||||
callback && callback(null, resolved);
|
callback && callback(null, resolved);
|
||||||
});
|
});
|
||||||
|
@ -297,32 +320,21 @@ ACL.checkPermission = function checkPermission(principalType, principalId,
|
||||||
var modelClass = loopback.getModel(model);
|
var modelClass = loopback.getModel(model);
|
||||||
resolved.permission = (modelClass && modelClass.settings.defaultPermission) || ACL.ALLOW;
|
resolved.permission = (modelClass && modelClass.settings.defaultPermission) || ACL.ALLOW;
|
||||||
}
|
}
|
||||||
debug('checkPermission(): %j', resolved);
|
|
||||||
callback && callback(null, resolved);
|
callback && callback(null, resolved);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
ACL.prototype.debug = function() {
|
||||||
* Check if the given scope is allowed to access the model/property
|
if(debug.enabled) {
|
||||||
* @param {String} scope The scope name
|
debug('---ACL---')
|
||||||
* @param {String} model The model name
|
debug('model %s', this.model);
|
||||||
* @param {String} property The property/method/relation name
|
debug('property %s', this.property);
|
||||||
* @param {String} accessType The access type
|
debug('principalType %s', this.principalType);
|
||||||
* @param {Function} callback The callback function
|
debug('principalId %s', this.principalId);
|
||||||
*
|
debug('accessType %s', this.accessType);
|
||||||
* @callback callback
|
debug('permission %s', this.permission);
|
||||||
* @param {String|Error} err The error object
|
}
|
||||||
* @param {Object} the access permission
|
|
||||||
*/
|
|
||||||
Scope.checkPermission = function (scope, model, property, accessType, callback) {
|
|
||||||
Scope.findOne({where: {name: scope}}, function (err, scope) {
|
|
||||||
if (err) {
|
|
||||||
callback && callback(err);
|
|
||||||
} else {
|
|
||||||
ACL.checkPermission(ACL.SCOPE, scope.id, model, property, accessType, callback);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the request has the permission to access
|
* Check if the request has the permission to access
|
||||||
|
@ -335,8 +347,6 @@ Scope.checkPermission = function (scope, model, property, accessType, callback)
|
||||||
* @param {Function} callback
|
* @param {Function} callback
|
||||||
*/
|
*/
|
||||||
ACL.checkAccess = function (context, callback) {
|
ACL.checkAccess = function (context, callback) {
|
||||||
debug('checkAccess(): %j', context);
|
|
||||||
|
|
||||||
if(!(context instanceof AccessContext)) {
|
if(!(context instanceof AccessContext)) {
|
||||||
context = new AccessContext(context);
|
context = new AccessContext(context);
|
||||||
}
|
}
|
||||||
|
@ -407,14 +417,12 @@ ACL.checkAccess = function (context, callback) {
|
||||||
* @param {String} model The model name
|
* @param {String} model The model name
|
||||||
* @param {*} modelId The model id
|
* @param {*} modelId The model id
|
||||||
* @param {String} method The method name
|
* @param {String} method The method name
|
||||||
* @param callback The callback function
|
* @end
|
||||||
*
|
* @callback {Function} callback
|
||||||
* @callback callback
|
|
||||||
* @param {String|Error} err The error object
|
* @param {String|Error} err The error object
|
||||||
* @param {Boolean} allowed is the request allowed
|
* @param {Boolean} allowed is the request allowed
|
||||||
*/
|
*/
|
||||||
ACL.checkAccessForToken = function (token, model, modelId, method, callback) {
|
ACL.checkAccessForToken = function (token, model, modelId, method, callback) {
|
||||||
debug('checkAccessForToken(): %j %s %s %s', token, model, modelId, method);
|
|
||||||
assert(token, 'Access token is required');
|
assert(token, 'Access token is required');
|
||||||
|
|
||||||
var context = new AccessContext({
|
var context = new AccessContext({
|
||||||
|
@ -427,12 +435,13 @@ ACL.checkAccessForToken = function (token, model, modelId, method, callback) {
|
||||||
|
|
||||||
context.accessType = context.model._getAccessTypeForMethod(method);
|
context.accessType = context.model._getAccessTypeForMethod(method);
|
||||||
|
|
||||||
|
context.debug();
|
||||||
|
|
||||||
ACL.checkAccess(context, function (err, access) {
|
ACL.checkAccess(context, function (err, access) {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback && callback(err);
|
callback && callback(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
debug('checkAccessForToken(): %j', access);
|
|
||||||
callback && callback(null, access.permission !== ACL.DENY);
|
callback && callback(null, access.permission !== ACL.DENY);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,27 +7,43 @@ var AuthenticationSchemeSchema = {
|
||||||
credential: Object // Scheme-specific credentials
|
credential: Object // Scheme-specific credentials
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// See https://github.com/argon/node-apn/blob/master/doc/apn.markdown
|
||||||
var APNSSettingSchema = {
|
var APNSSettingSchema = {
|
||||||
|
/**
|
||||||
|
* production or development mode. It denotes what default APNS servers to be
|
||||||
|
* used to send notifications
|
||||||
|
* - true (production mode)
|
||||||
|
* - push: gateway.push.apple.com:2195
|
||||||
|
* - feedback: feedback.push.apple.com:2196
|
||||||
|
* - false (development mode, the default)
|
||||||
|
* - push: gateway.sandbox.push.apple.com:2195
|
||||||
|
* - feedback: feedback.sandbox.push.apple.com:2196
|
||||||
|
*/
|
||||||
|
production: Boolean,
|
||||||
|
certData: String, // The certificate data loaded from the cert.pem file
|
||||||
|
keyData: String, // The key data loaded from the key.pem file
|
||||||
|
|
||||||
pushOptions: {type: {
|
pushOptions: {type: {
|
||||||
gateway: String,
|
gateway: String,
|
||||||
cert: String,
|
port: Number
|
||||||
key: String
|
|
||||||
}},
|
}},
|
||||||
|
|
||||||
feedbackOptions: {type: {
|
feedbackOptions: {type: {
|
||||||
gateway: String,
|
gateway: String,
|
||||||
cert: String,
|
port: Number,
|
||||||
key: String,
|
|
||||||
batchFeedback: Boolean,
|
batchFeedback: Boolean,
|
||||||
interval: Number
|
interval: Number
|
||||||
}}
|
}}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var GcmSettingsSchema = {
|
||||||
|
serverApiKey: String
|
||||||
|
};
|
||||||
|
|
||||||
// Push notification settings
|
// Push notification settings
|
||||||
var PushNotificationSettingSchema = {
|
var PushNotificationSettingSchema = {
|
||||||
platform: {type: String, required: true}, // apns, gcm, mpns
|
apns: APNSSettingSchema,
|
||||||
// configuration: {type: Object} // platform-specific configurations
|
gcm: GcmSettingsSchema
|
||||||
apns: APNSSettingSchema
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -74,7 +90,6 @@ var ApplicationSchema = {
|
||||||
modified: {type: Date, default: Date}
|
modified: {type: Date, default: Date}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Application management functions
|
* Application management functions
|
||||||
*/
|
*/
|
||||||
|
@ -91,6 +106,12 @@ function generateKey(hmacKey, algorithm, encoding) {
|
||||||
return hmac.digest('base64');
|
return hmac.digest('base64');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manage client applications and organize their users.
|
||||||
|
* @class
|
||||||
|
* @inherits {Model}
|
||||||
|
*/
|
||||||
|
|
||||||
var Application = loopback.createModel('Application', ApplicationSchema);
|
var Application = loopback.createModel('Application', ApplicationSchema);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -135,7 +156,8 @@ Application.register = function (owner, name, options, cb) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset keys for the application instance
|
* Reset keys for the application instance
|
||||||
* @param cb
|
* @callback {Function} callback
|
||||||
|
* @param {Error} err
|
||||||
*/
|
*/
|
||||||
Application.prototype.resetKeys = function (cb) {
|
Application.prototype.resetKeys = function (cb) {
|
||||||
this.clientKey = generateKey('client');
|
this.clientKey = generateKey('client');
|
||||||
|
@ -149,8 +171,9 @@ Application.prototype.resetKeys = function(cb) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset keys for a given application by the appId
|
* Reset keys for a given application by the appId
|
||||||
* @param appId
|
* @param {Any} appId
|
||||||
* @param cb
|
* @callback {Function} callback
|
||||||
|
* @param {Error} err
|
||||||
*/
|
*/
|
||||||
Application.resetKeys = function (appId, cb) {
|
Application.resetKeys = function (appId, cb) {
|
||||||
Application.findById(appId, function (err, app) {
|
Application.findById(appId, function (err, app) {
|
||||||
|
@ -163,10 +186,21 @@ Application.resetKeys = function(appId, cb) {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Authenticate the application id and key.
|
||||||
*
|
*
|
||||||
* @param appId
|
* `matched` will be one of
|
||||||
* @param key
|
*
|
||||||
* @param cb
|
* - clientKey
|
||||||
|
* - javaScriptKey
|
||||||
|
* - restApiKey
|
||||||
|
* - windowsKey
|
||||||
|
* - masterKey
|
||||||
|
*
|
||||||
|
* @param {Any} appId
|
||||||
|
* @param {String} key
|
||||||
|
* @callback {Function} callback
|
||||||
|
* @param {Error} err
|
||||||
|
* @param {String} matched - The matching key
|
||||||
*/
|
*/
|
||||||
Application.authenticate = function (appId, key, cb) {
|
Application.authenticate = function (appId, key, cb) {
|
||||||
Application.findById(appId, function (err, app) {
|
Application.findById(appId, function (err, app) {
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
/**
|
/*!
|
||||||
* Module Dependencies.
|
* Module Dependencies.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var Model = require('../loopback').Model
|
var Model = require('../loopback').Model
|
||||||
, loopback = require('../loopback');
|
, loopback = require('../loopback');
|
||||||
|
|
||||||
/**
|
|
||||||
* Default Email properties.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var properties = {
|
var properties = {
|
||||||
to: {type: String, required: true},
|
to: {type: String, required: true},
|
||||||
from: {type: String, required: true},
|
from: {type: String, required: true},
|
||||||
|
@ -18,7 +14,43 @@ var properties = {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extends from the built in `loopback.Model` type.
|
* The Email Model.
|
||||||
|
*
|
||||||
|
* **Properties**
|
||||||
|
*
|
||||||
|
* - `to` - **{ String }** **required**
|
||||||
|
* - `from` - **{ String }** **required**
|
||||||
|
* - `subject` - **{ String }** **required**
|
||||||
|
* - `text` - **{ String }**
|
||||||
|
* - `html` - **{ String }**
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @inherits {Model}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var Email = module.exports = Model.extend('Email', properties);
|
var Email = module.exports = Model.extend('Email', properties);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an email with the given `options`.
|
||||||
|
*
|
||||||
|
* Example Options:
|
||||||
|
*
|
||||||
|
* ```json
|
||||||
|
* {
|
||||||
|
* from: "Fred Foo ✔ <foo@blurdybloop.com>", // sender address
|
||||||
|
* to: "bar@blurdybloop.com, baz@blurdybloop.com", // list of receivers
|
||||||
|
* subject: "Hello ✔", // Subject line
|
||||||
|
* text: "Hello world ✔", // plaintext body
|
||||||
|
* html: "<b>Hello world ✔</b>" // html body
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* See https://github.com/andris9/Nodemailer for other supported options.
|
||||||
|
*
|
||||||
|
* @param {Object} options
|
||||||
|
* @param {Function} callback Called after the e-mail is sent or the sending failed
|
||||||
|
*/
|
||||||
|
|
||||||
|
Email.prototype.send = function() {
|
||||||
|
throw new Error('You must connect the Email Model to a Mail connector');
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
/**
|
/*!
|
||||||
* Module Dependencies.
|
* Module Dependencies.
|
||||||
*/
|
*/
|
||||||
var loopback = require('../loopback');
|
var loopback = require('../loopback');
|
||||||
|
@ -7,7 +7,10 @@ var modeler = new ModelBuilder();
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define the built in loopback.Model.
|
* The built in loopback.Model.
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @param {Object} data
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var Model = module.exports = modeler.define('Model');
|
var Model = module.exports = modeler.define('Model');
|
||||||
|
@ -124,7 +127,7 @@ function getACL() {
|
||||||
* @param {String} method The method name
|
* @param {String} method The method name
|
||||||
* @param callback The callback function
|
* @param callback The callback function
|
||||||
*
|
*
|
||||||
* @callback callback
|
* @callback {Function} callback
|
||||||
* @param {String|Error} err The error object
|
* @param {String|Error} err The error object
|
||||||
* @param {Boolean} allowed is the request allowed
|
* @param {Boolean} allowed is the request allowed
|
||||||
*/
|
*/
|
||||||
|
@ -136,7 +139,7 @@ Model.checkAccess = function(token, modelId, method, callback) {
|
||||||
ACL.checkAccessForToken(token, this.modelName, modelId, methodName, callback);
|
ACL.checkAccessForToken(token, this.modelName, modelId, methodName, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/*!
|
||||||
* Determine the access type for the given `RemoteMethod`.
|
* Determine the access type for the given `RemoteMethod`.
|
||||||
*
|
*
|
||||||
* @api private
|
* @api private
|
||||||
|
|
|
@ -26,6 +26,10 @@ var RoleMappingSchema = {
|
||||||
principalId: String // The principal id
|
principalId: String // The principal id
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map Roles to
|
||||||
|
*/
|
||||||
|
|
||||||
var RoleMapping = loopback.createModel('RoleMapping', RoleMappingSchema, {
|
var RoleMapping = loopback.createModel('RoleMapping', RoleMappingSchema, {
|
||||||
relations: {
|
relations: {
|
||||||
role: {
|
role: {
|
||||||
|
@ -43,7 +47,9 @@ RoleMapping.ROLE = 'ROLE';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the application principal
|
* Get the application principal
|
||||||
* @param callback
|
* @callback {Function} callback
|
||||||
|
* @param {Error} err
|
||||||
|
* @param {Application} application
|
||||||
*/
|
*/
|
||||||
RoleMapping.prototype.application = function (callback) {
|
RoleMapping.prototype.application = function (callback) {
|
||||||
if (this.principalType === RoleMapping.APPLICATION) {
|
if (this.principalType === RoleMapping.APPLICATION) {
|
||||||
|
@ -57,7 +63,9 @@ RoleMapping.prototype.application = function (callback) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the user principal
|
* Get the user principal
|
||||||
* @param callback
|
* @callback {Function} callback
|
||||||
|
* @param {Error} err
|
||||||
|
* @param {User} user
|
||||||
*/
|
*/
|
||||||
RoleMapping.prototype.user = function (callback) {
|
RoleMapping.prototype.user = function (callback) {
|
||||||
if (this.principalType === RoleMapping.USER) {
|
if (this.principalType === RoleMapping.USER) {
|
||||||
|
@ -71,7 +79,9 @@ RoleMapping.prototype.user = function (callback) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the child role principal
|
* Get the child role principal
|
||||||
* @param callback
|
* @callback {Function} callback
|
||||||
|
* @param {Error} err
|
||||||
|
* @param {User} childUser
|
||||||
*/
|
*/
|
||||||
RoleMapping.prototype.childRole = function (callback) {
|
RoleMapping.prototype.childRole = function (callback) {
|
||||||
if (this.principalType === RoleMapping.ROLE) {
|
if (this.principalType === RoleMapping.ROLE) {
|
||||||
|
@ -84,7 +94,8 @@ RoleMapping.prototype.childRole = function (callback) {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define the Role model with `hasMany` relation to RoleMapping
|
* The Role Model
|
||||||
|
* @class
|
||||||
*/
|
*/
|
||||||
var Role = loopback.createModel('Role', RoleSchema, {
|
var Role = loopback.createModel('Role', RoleSchema, {
|
||||||
relations: {
|
relations: {
|
||||||
|
@ -249,7 +260,9 @@ Role.registerResolver(Role.AUTHENTICATED, function(role, context, callback) {
|
||||||
/**
|
/**
|
||||||
* Check if the user id is authenticated
|
* Check if the user id is authenticated
|
||||||
* @param {Object} context The security context
|
* @param {Object} context The security context
|
||||||
* @param {Function} callback The callback function
|
* @callback {Function} callback
|
||||||
|
* @param {Error} err
|
||||||
|
* @param {Boolean} isAuthenticated
|
||||||
*/
|
*/
|
||||||
Role.isAuthenticated = function isAuthenticated(context, callback) {
|
Role.isAuthenticated = function isAuthenticated(context, callback) {
|
||||||
process.nextTick(function() {
|
process.nextTick(function() {
|
||||||
|
@ -274,7 +287,9 @@ Role.registerResolver(Role.EVERYONE, function (role, context, callback) {
|
||||||
*
|
*
|
||||||
* @param {String} role The role name
|
* @param {String} role The role name
|
||||||
* @param {Object} context The context object
|
* @param {Object} context The context object
|
||||||
* @param {Function} callback
|
* @callback {Function} callback
|
||||||
|
* @param {Error} err
|
||||||
|
* @param {Boolean} isInRole
|
||||||
*/
|
*/
|
||||||
Role.isInRole = function (role, context, callback) {
|
Role.isInRole = function (role, context, callback) {
|
||||||
debug('isInRole(): %s %j', role, context);
|
debug('isInRole(): %s %j', role, context);
|
||||||
|
@ -355,7 +370,7 @@ Role.isInRole = function (role, context, callback) {
|
||||||
* @param {Object} context The security context
|
* @param {Object} context The security context
|
||||||
* @param {Function} callback
|
* @param {Function} callback
|
||||||
*
|
*
|
||||||
* @callback callback
|
* @callback {Function} callback
|
||||||
* @param err
|
* @param err
|
||||||
* @param {String[]} An array of role ids
|
* @param {String[]} An array of role ids
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -44,14 +44,15 @@ var properties = {
|
||||||
status: String,
|
status: String,
|
||||||
created: Date,
|
created: Date,
|
||||||
lastUpdated: Date
|
lastUpdated: Date
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Default User options.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var options = {
|
var options = {
|
||||||
acls: [
|
acls: [
|
||||||
|
{
|
||||||
|
principalType: ACL.ROLE,
|
||||||
|
principalId: Role.EVERYONE,
|
||||||
|
permission: ACL.DENY,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
principalType: ACL.ROLE,
|
principalType: ACL.ROLE,
|
||||||
principalId: Role.EVERYONE,
|
principalId: Role.EVERYONE,
|
||||||
|
@ -63,12 +64,49 @@ var options = {
|
||||||
principalId: Role.OWNER,
|
principalId: Role.OWNER,
|
||||||
permission: ACL.ALLOW,
|
permission: ACL.ALLOW,
|
||||||
property: 'removeById'
|
property: 'removeById'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
principalType: ACL.ROLE,
|
||||||
|
principalId: Role.EVERYONE,
|
||||||
|
permission: ACL.ALLOW,
|
||||||
|
property: "login"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
principalType: ACL.ROLE,
|
||||||
|
principalId: Role.EVERYONE,
|
||||||
|
permission: ACL.ALLOW,
|
||||||
|
property: "logout"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
principalType: ACL.ROLE,
|
||||||
|
principalId: Role.OWNER,
|
||||||
|
permission: ACL.ALLOW,
|
||||||
|
property: "findById"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
principalType: ACL.ROLE,
|
||||||
|
principalId: Role.OWNER,
|
||||||
|
permission: ACL.ALLOW,
|
||||||
|
property: "updateAttributes"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extends from the built in `loopback.Model` type.
|
* Extends from the built in `loopback.Model` type.
|
||||||
|
*
|
||||||
|
* Default `User` ACLs.
|
||||||
|
*
|
||||||
|
* - DENY EVERYONE `*`
|
||||||
|
* - ALLOW EVERYONE `create`
|
||||||
|
* - ALLOW OWNER `removeById`
|
||||||
|
* - ALLOW EVERYONE `login`
|
||||||
|
* - ALLOW EVERYONE `logout`
|
||||||
|
* - ALLOW EVERYONE `findById`
|
||||||
|
* - ALLOW OWNER `updateAttributes`
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @inherits {Model}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var User = module.exports = Model.extend('User', properties, options);
|
var User = module.exports = Model.extend('User', properties, options);
|
||||||
|
@ -76,11 +114,16 @@ var User = module.exports = Model.extend('User', properties, options);
|
||||||
/**
|
/**
|
||||||
* Login a user by with the given `credentials`.
|
* Login a user by with the given `credentials`.
|
||||||
*
|
*
|
||||||
|
* ```js
|
||||||
* User.login({username: 'foo', password: 'bar'}, function (err, token) {
|
* User.login({username: 'foo', password: 'bar'}, function (err, token) {
|
||||||
* console.log(token.id);
|
* console.log(token.id);
|
||||||
* });
|
* });
|
||||||
|
* ```
|
||||||
*
|
*
|
||||||
* @param {Object} credentials
|
* @param {Object} credentials
|
||||||
|
* @callback {Function} callback
|
||||||
|
* @param {Error} err
|
||||||
|
* @param {AccessToken} token
|
||||||
*/
|
*/
|
||||||
|
|
||||||
User.login = function (credentials, fn) {
|
User.login = function (credentials, fn) {
|
||||||
|
@ -121,11 +164,15 @@ User.login = function (credentials, fn) {
|
||||||
/**
|
/**
|
||||||
* Logout a user with the given accessToken id.
|
* Logout a user with the given accessToken id.
|
||||||
*
|
*
|
||||||
|
* ```js
|
||||||
* User.logout('asd0a9f8dsj9s0s3223mk', function (err) {
|
* User.logout('asd0a9f8dsj9s0s3223mk', function (err) {
|
||||||
* console.log(err || 'Logged out');
|
* console.log(err || 'Logged out');
|
||||||
* });
|
* });
|
||||||
|
* ```
|
||||||
*
|
*
|
||||||
* @param {String} accessTokenID
|
* @param {String} accessTokenID
|
||||||
|
* @callback {Function} callback
|
||||||
|
* @param {Error} err
|
||||||
*/
|
*/
|
||||||
|
|
||||||
User.logout = function (tokenId, fn) {
|
User.logout = function (tokenId, fn) {
|
||||||
|
@ -159,8 +206,9 @@ User.prototype.hasPassword = function (plain, fn) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify a user's identity.
|
* Verify a user's identity by sending them a confirmation email.
|
||||||
*
|
*
|
||||||
|
* ```js
|
||||||
* var options = {
|
* var options = {
|
||||||
* type: 'email',
|
* type: 'email',
|
||||||
* to: user.email,
|
* to: user.email,
|
||||||
|
@ -169,6 +217,7 @@ User.prototype.hasPassword = function (plain, fn) {
|
||||||
* };
|
* };
|
||||||
*
|
*
|
||||||
* user.verify(options, next);
|
* user.verify(options, next);
|
||||||
|
* ```
|
||||||
*
|
*
|
||||||
* @param {Object} options
|
* @param {Object} options
|
||||||
*/
|
*/
|
||||||
|
@ -237,6 +286,16 @@ User.prototype.verify = function (options, fn) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirm the user's identity.
|
||||||
|
*
|
||||||
|
* @param {Any} userId
|
||||||
|
* @param {String} token The validation token
|
||||||
|
* @param {String} redirect URL to redirect the user to once confirmed
|
||||||
|
* @callback {Function} callback
|
||||||
|
* @param {Error} err
|
||||||
|
*/
|
||||||
User.confirm = function (uid, token, redirect, fn) {
|
User.confirm = function (uid, token, redirect, fn) {
|
||||||
this.findById(uid, function (err, user) {
|
this.findById(uid, function (err, user) {
|
||||||
if(err) {
|
if(err) {
|
||||||
|
@ -259,6 +318,16 @@ User.confirm = function (uid, token, redirect, fn) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a short lived acess token for temporary login. Allows users
|
||||||
|
* to change passwords if forgotten.
|
||||||
|
*
|
||||||
|
* @options {Object} options
|
||||||
|
* @prop {String} email The user's email address
|
||||||
|
* @callback {Function} callback
|
||||||
|
* @param {Error} err
|
||||||
|
*/
|
||||||
|
|
||||||
User.resetPassword = function(options, cb) {
|
User.resetPassword = function(options, cb) {
|
||||||
var UserModel = this;
|
var UserModel = this;
|
||||||
var ttl = UserModel.settings.resetPasswordTokenTTL || DEFAULT_RESET_PW_TTL;
|
var ttl = UserModel.settings.resetPasswordTokenTTL || DEFAULT_RESET_PW_TTL;
|
||||||
|
@ -294,7 +363,7 @@ User.resetPassword = function(options, cb) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*!
|
||||||
* Setup an extended user model.
|
* Setup an extended user model.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -327,7 +396,13 @@ User.setup = function () {
|
||||||
UserModel.logout,
|
UserModel.logout,
|
||||||
{
|
{
|
||||||
accepts: [
|
accepts: [
|
||||||
{arg: 'sid', type: 'string', required: true}
|
{arg: 'access_token', type: 'string', required: true, http: function(ctx) {
|
||||||
|
var req = ctx && ctx.req;
|
||||||
|
var accessToken = req && req.accessToken;
|
||||||
|
var tokenID = accessToken && accessToken.id;
|
||||||
|
|
||||||
|
return tokenID;
|
||||||
|
}}
|
||||||
],
|
],
|
||||||
http: {verb: 'all'}
|
http: {verb: 'all'}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
"Platform",
|
"Platform",
|
||||||
"mBaaS"
|
"mBaaS"
|
||||||
],
|
],
|
||||||
"version": "1.3.4",
|
"version": "1.4.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "mocha -R spec"
|
"test": "mocha -R spec"
|
||||||
},
|
},
|
||||||
|
@ -29,14 +29,15 @@
|
||||||
"async": "~0.2.9"
|
"async": "~0.2.9"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"loopback-datasource-juggler": "~1.2.0"
|
"loopback-datasource-juggler": "~1.2.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"loopback-datasource-juggler": "~1.2.0",
|
"loopback-datasource-juggler": "~1.2.11",
|
||||||
"mocha": "~1.14.0",
|
"mocha": "~1.14.0",
|
||||||
"strong-task-emitter": "0.0.x",
|
"strong-task-emitter": "0.0.x",
|
||||||
"supertest": "~0.8.1",
|
"supertest": "~0.8.1",
|
||||||
"chai": "~1.8.1"
|
"chai": "~1.8.1",
|
||||||
|
"loopback-testing": "~0.1.0"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
|
@ -0,0 +1,179 @@
|
||||||
|
var loopback = require('../');
|
||||||
|
var lt = require('loopback-testing');
|
||||||
|
var path = require('path');
|
||||||
|
var ACCESS_CONTROL_APP = path.join(__dirname, 'fixtures', 'access-control');
|
||||||
|
var app = require(path.join(ACCESS_CONTROL_APP, 'app.js'));
|
||||||
|
var assert = require('assert');
|
||||||
|
var USER = {email: 'test@test.test', password: 'test'};
|
||||||
|
var CURRENT_USER = {email: 'current@test.test', password: 'test'};
|
||||||
|
|
||||||
|
describe('access control - integration', function () {
|
||||||
|
|
||||||
|
lt.beforeEach.withApp(app);
|
||||||
|
|
||||||
|
describe('accessToken', function() {
|
||||||
|
// it('should be a sublcass of AccessToken', function () {
|
||||||
|
// assert(app.models.accessToken.prototype instanceof loopback.AccessToken);
|
||||||
|
// });
|
||||||
|
|
||||||
|
it('should have a validate method', function () {
|
||||||
|
var token = new app.models.accessToken;
|
||||||
|
assert.equal(typeof token.validate, 'function');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('/accessToken', function() {
|
||||||
|
|
||||||
|
lt.beforeEach.givenModel('accessToken', {}, 'randomToken');
|
||||||
|
|
||||||
|
lt.it.shouldBeAllowedWhenCalledAnonymously('POST', '/api/accessTokens');
|
||||||
|
lt.it.shouldBeAllowedWhenCalledUnauthenticated('POST', '/api/accessTokens');
|
||||||
|
lt.it.shouldBeAllowedWhenCalledByUser(USER, 'POST', '/api/accessTokens');
|
||||||
|
|
||||||
|
lt.it.shouldBeDeniedWhenCalledAnonymously('GET', '/api/accessTokens');
|
||||||
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('GET', '/api/accessTokens');
|
||||||
|
lt.it.shouldBeDeniedWhenCalledByUser(USER, 'GET', '/api/accessTokens');
|
||||||
|
|
||||||
|
lt.it.shouldBeDeniedWhenCalledAnonymously('PUT', '/api/accessTokens');
|
||||||
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('PUT', '/api/accessTokens');
|
||||||
|
lt.it.shouldBeDeniedWhenCalledByUser(USER, 'PUT', '/api/accessTokens');
|
||||||
|
|
||||||
|
lt.it.shouldBeDeniedWhenCalledAnonymously('GET', urlForToken);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('GET', urlForToken);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledByUser(USER, 'GET', urlForToken);
|
||||||
|
|
||||||
|
lt.it.shouldBeDeniedWhenCalledAnonymously('PUT', urlForToken);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('PUT', urlForToken);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledByUser(USER, 'PUT', urlForToken);
|
||||||
|
|
||||||
|
lt.it.shouldBeDeniedWhenCalledAnonymously('DELETE', urlForToken);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('DELETE', urlForToken);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledByUser(USER, 'DELETE', urlForToken);
|
||||||
|
|
||||||
|
function urlForToken() {
|
||||||
|
return '/api/accessTokens/' + this.randomToken.id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('/users', function () {
|
||||||
|
|
||||||
|
lt.beforeEach.givenModel('user', USER, 'randomUser');
|
||||||
|
|
||||||
|
lt.it.shouldBeDeniedWhenCalledAnonymously('GET', '/api/users');
|
||||||
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('GET', '/api/users');
|
||||||
|
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'GET', '/api/users');
|
||||||
|
|
||||||
|
lt.it.shouldBeDeniedWhenCalledAnonymously('GET', urlForUser);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('GET', urlForUser);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER,'GET', urlForUser);
|
||||||
|
|
||||||
|
lt.it.shouldBeAllowedWhenCalledAnonymously('POST', '/api/users');
|
||||||
|
lt.it.shouldBeAllowedWhenCalledByUser(CURRENT_USER, 'POST', '/api/users');
|
||||||
|
|
||||||
|
lt.it.shouldBeAllowedWhenCalledByUser(CURRENT_USER, 'POST', '/api/users/logout');
|
||||||
|
|
||||||
|
lt.describe.whenCalledRemotely('DELETE', '/api/users', function() {
|
||||||
|
lt.it.shouldNotBeFound();
|
||||||
|
});
|
||||||
|
|
||||||
|
lt.it.shouldBeDeniedWhenCalledAnonymously('PUT', urlForUser);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('PUT', urlForUser);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PUT', urlForUser);
|
||||||
|
|
||||||
|
lt.it.shouldBeDeniedWhenCalledAnonymously('PUT', urlForUser);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('PUT', urlForUser);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PUT', urlForUser);
|
||||||
|
|
||||||
|
lt.describe.whenLoggedInAsUser(CURRENT_USER, function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
this.url = '/api/users/' + this.user.id + '?ok';
|
||||||
|
});
|
||||||
|
lt.describe.whenCalledRemotely('DELETE', '/api/users/:id', function() {
|
||||||
|
lt.it.shouldBeAllowed();
|
||||||
|
});
|
||||||
|
lt.describe.whenCalledRemotely('GET', '/api/users/:id', function() {
|
||||||
|
lt.it.shouldBeAllowed();
|
||||||
|
});
|
||||||
|
lt.describe.whenCalledRemotely('PUT', '/api/users/:id', function() {
|
||||||
|
lt.it.shouldBeAllowed();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
lt.it.shouldBeDeniedWhenCalledAnonymously('DELETE', urlForUser);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('DELETE', urlForUser);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'DELETE', urlForUser);
|
||||||
|
|
||||||
|
function urlForUser() {
|
||||||
|
return '/api/users/' + this.randomUser.id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('/banks', function () {
|
||||||
|
lt.beforeEach.givenModel('bank');
|
||||||
|
|
||||||
|
lt.it.shouldBeAllowedWhenCalledAnonymously('GET', '/api/banks');
|
||||||
|
lt.it.shouldBeAllowedWhenCalledUnauthenticated('GET', '/api/banks');
|
||||||
|
lt.it.shouldBeAllowedWhenCalledByUser(CURRENT_USER, 'GET', '/api/banks');
|
||||||
|
|
||||||
|
lt.it.shouldBeAllowedWhenCalledAnonymously('GET', urlForBank);
|
||||||
|
lt.it.shouldBeAllowedWhenCalledUnauthenticated('GET', urlForBank);
|
||||||
|
lt.it.shouldBeAllowedWhenCalledByUser(CURRENT_USER, 'GET', urlForBank);
|
||||||
|
|
||||||
|
lt.it.shouldBeDeniedWhenCalledAnonymously('POST', '/api/banks');
|
||||||
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('POST', '/api/banks');
|
||||||
|
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'POST', '/api/banks');
|
||||||
|
|
||||||
|
lt.it.shouldBeDeniedWhenCalledAnonymously('PUT', urlForBank);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('PUT', urlForBank);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PUT', urlForBank);
|
||||||
|
|
||||||
|
lt.it.shouldBeDeniedWhenCalledAnonymously('DELETE', urlForBank);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('DELETE', urlForBank);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'DELETE', urlForBank);
|
||||||
|
|
||||||
|
function urlForBank() {
|
||||||
|
return '/api/banks/' + this.bank.id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('/accounts', function () {
|
||||||
|
lt.beforeEach.givenModel('account');
|
||||||
|
|
||||||
|
lt.it.shouldBeDeniedWhenCalledAnonymously('GET', '/api/accounts');
|
||||||
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('GET', '/api/accounts');
|
||||||
|
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'GET', '/api/accounts');
|
||||||
|
|
||||||
|
lt.it.shouldBeDeniedWhenCalledAnonymously('GET', urlForAccount);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('GET', urlForAccount);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'GET', urlForAccount);
|
||||||
|
|
||||||
|
lt.it.shouldBeDeniedWhenCalledAnonymously('POST', '/api/accounts');
|
||||||
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('POST', '/api/accounts');
|
||||||
|
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'POST', '/api/accounts');
|
||||||
|
|
||||||
|
lt.it.shouldBeDeniedWhenCalledAnonymously('PUT', urlForAccount);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('PUT', urlForAccount);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PUT', urlForAccount);
|
||||||
|
|
||||||
|
lt.describe.whenLoggedInAsUser(CURRENT_USER, function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
this.url = '/api/accounts/' + this.user.accountId;
|
||||||
|
});
|
||||||
|
lt.describe.whenCalledRemotely('PUT', '/api/accounts/:id', function() {
|
||||||
|
lt.it.shouldBeAllowed();
|
||||||
|
});
|
||||||
|
lt.describe.whenCalledRemotely('DELETE', '/api/accounts/:id', function() {
|
||||||
|
lt.it.shouldBeDenied();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
lt.it.shouldBeDeniedWhenCalledAnonymously('DELETE', urlForAccount);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('DELETE', urlForAccount);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'DELETE', urlForAccount);
|
||||||
|
|
||||||
|
function urlForAccount() {
|
||||||
|
return '/api/accounts/' + this.account.id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -7,6 +7,7 @@ var role = require('../lib/models/role');
|
||||||
var Role = role.Role;
|
var Role = role.Role;
|
||||||
var RoleMapping = role.RoleMapping;
|
var RoleMapping = role.RoleMapping;
|
||||||
var User = loopback.User;
|
var User = loopback.User;
|
||||||
|
var testModel;
|
||||||
|
|
||||||
function checkResult(err, result) {
|
function checkResult(err, result) {
|
||||||
// console.log(err, result);
|
// console.log(err, result);
|
||||||
|
@ -15,6 +16,17 @@ function checkResult(err, result) {
|
||||||
|
|
||||||
describe('security scopes', function () {
|
describe('security scopes', function () {
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
var ds = this.ds = loopback.createDataSource({connector: loopback.Memory});
|
||||||
|
testModel = loopback.Model.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 () {
|
it("should allow access to models for the given scope by wildcard", function () {
|
||||||
Scope.create({name: 'userScope', description: 'access user information'}, function (err, scope) {
|
Scope.create({name: 'userScope', description: 'access user information'}, function (err, scope) {
|
||||||
ACL.create({principalType: ACL.SCOPE, principalId: scope.id, model: 'User', property: ACL.ALL,
|
ACL.create({principalType: ACL.SCOPE, principalId: scope.id, model: 'User', property: ACL.ALL,
|
||||||
|
@ -29,27 +41,24 @@ describe('security scopes', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should allow access to models for the given scope", function () {
|
it("should allow access to models for the given scope", function () {
|
||||||
var ds = loopback.createDataSource({connector: loopback.Memory});
|
Scope.create({name: 'testModelScope', description: 'access testModel information'}, function (err, scope) {
|
||||||
Scope.attachTo(ds);
|
|
||||||
ACL.attachTo(ds);
|
|
||||||
Scope.create({name: 'userScope', description: 'access user information'}, function (err, scope) {
|
|
||||||
ACL.create({principalType: ACL.SCOPE, principalId: scope.id,
|
ACL.create({principalType: ACL.SCOPE, principalId: scope.id,
|
||||||
model: 'User', property: 'name', accessType: ACL.READ, permission: ACL.ALLOW},
|
model: 'testModel', property: 'name', accessType: ACL.READ, permission: ACL.ALLOW},
|
||||||
function (err, resource) {
|
function (err, resource) {
|
||||||
ACL.create({principalType: ACL.SCOPE, principalId: scope.id,
|
ACL.create({principalType: ACL.SCOPE, principalId: scope.id,
|
||||||
model: 'User', property: 'name', accessType: ACL.WRITE, permission: ACL.DENY},
|
model: 'testModel', property: 'name', accessType: ACL.WRITE, permission: ACL.DENY},
|
||||||
function (err, resource) {
|
function (err, resource) {
|
||||||
// console.log(resource);
|
// console.log(resource);
|
||||||
Scope.checkPermission('userScope', 'User', ACL.ALL, ACL.ALL, function (err, perm) {
|
Scope.checkPermission('testModelScope', 'testModel', ACL.ALL, ACL.ALL, function (err, perm) {
|
||||||
assert(perm.permission === ACL.DENY); // because name.WRITE == DENY
|
assert(perm.permission === ACL.DENY); // because name.WRITE == DENY
|
||||||
});
|
});
|
||||||
Scope.checkPermission('userScope', 'User', 'name', ACL.ALL, function (err, perm) {
|
Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.ALL, function (err, perm) {
|
||||||
assert(perm.permission === ACL.DENY); // because name.WRITE == DENY
|
assert(perm.permission === ACL.DENY); // because name.WRITE == DENY
|
||||||
});
|
});
|
||||||
Scope.checkPermission('userScope', 'User', 'name', ACL.READ, function (err, perm) {
|
Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.READ, function (err, perm) {
|
||||||
assert(perm.permission === ACL.ALLOW);
|
assert(perm.permission === ACL.ALLOW);
|
||||||
});
|
});
|
||||||
Scope.checkPermission('userScope', 'User', 'name', ACL.WRITE, function (err, perm) {
|
Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.WRITE, function (err, perm) {
|
||||||
assert(perm.permission === ACL.DENY);
|
assert(perm.permission === ACL.DENY);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -63,9 +72,6 @@ describe('security scopes', function () {
|
||||||
describe('security ACLs', function () {
|
describe('security ACLs', function () {
|
||||||
|
|
||||||
it("should allow access to models for the given principal by wildcard", function () {
|
it("should allow access to models for the given principal by wildcard", function () {
|
||||||
var ds = loopback.createDataSource({connector: loopback.Memory});
|
|
||||||
ACL.attachTo(ds);
|
|
||||||
|
|
||||||
ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL,
|
ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL,
|
||||||
accessType: ACL.ALL, permission: ACL.ALLOW}, function (err, acl) {
|
accessType: ACL.ALL, permission: ACL.ALLOW}, function (err, acl) {
|
||||||
|
|
||||||
|
@ -87,28 +93,25 @@ describe('security ACLs', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should allow access to models by exception", function () {
|
it("should allow access to models by exception", function () {
|
||||||
var ds = loopback.createDataSource({connector: loopback.Memory});
|
ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'testModel', property: ACL.ALL,
|
||||||
ACL.attachTo(ds);
|
|
||||||
|
|
||||||
ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL,
|
|
||||||
accessType: ACL.ALL, permission: ACL.DENY}, function (err, acl) {
|
accessType: ACL.ALL, permission: ACL.DENY}, function (err, acl) {
|
||||||
|
|
||||||
ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL,
|
ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'testModel', property: ACL.ALL,
|
||||||
accessType: ACL.READ, permission: ACL.ALLOW}, function (err, acl) {
|
accessType: ACL.READ, permission: ACL.ALLOW}, function (err, acl) {
|
||||||
|
|
||||||
ACL.checkPermission(ACL.USER, 'u001', 'User', 'name', ACL.READ, function (err, perm) {
|
ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.READ, function (err, perm) {
|
||||||
assert(perm.permission === ACL.ALLOW);
|
assert(perm.permission === ACL.ALLOW);
|
||||||
});
|
});
|
||||||
|
|
||||||
ACL.checkPermission(ACL.USER, 'u001', 'User', ACL.ALL, ACL.READ, function (err, perm) {
|
ACL.checkPermission(ACL.USER, 'u001', 'testModel', ACL.ALL, ACL.READ, function (err, perm) {
|
||||||
assert(perm.permission === ACL.ALLOW);
|
assert(perm.permission === ACL.ALLOW);
|
||||||
});
|
});
|
||||||
|
|
||||||
ACL.checkPermission(ACL.USER, 'u001', 'User', 'name', ACL.WRITE, function (err, perm) {
|
ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.WRITE, function (err, perm) {
|
||||||
assert(perm.permission === ACL.DENY);
|
assert(perm.permission === ACL.DENY);
|
||||||
});
|
});
|
||||||
|
|
||||||
ACL.checkPermission(ACL.USER, 'u001', 'User', 'name', ACL.ALL, function (err, perm) {
|
ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.ALL, function (err, perm) {
|
||||||
assert(perm.permission === ACL.DENY);
|
assert(perm.permission === ACL.DENY);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -119,8 +122,7 @@ describe('security ACLs', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should honor defaultPermission from the model", function () {
|
it("should honor defaultPermission from the model", function () {
|
||||||
var ds = loopback.createDataSource({connector: loopback.Memory});
|
var ds = this.ds;
|
||||||
ACL.attachTo(ds);
|
|
||||||
var Customer = ds.createModel('Customer', {
|
var Customer = ds.createModel('Customer', {
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -152,7 +154,7 @@ describe('security ACLs', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should honor static ACLs from the model", function () {
|
it("should honor static ACLs from the model", function () {
|
||||||
var ds = loopback.createDataSource({connector: loopback.Memory});
|
var ds = this.ds;
|
||||||
var Customer = ds.createModel('Customer', {
|
var Customer = ds.createModel('Customer', {
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -188,14 +190,9 @@ describe('security ACLs', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should check access against LDL, ACL, and Role", function () {
|
it("should check access against LDL, ACL, and Role", function () {
|
||||||
var ds = loopback.createDataSource({connector: loopback.Memory});
|
|
||||||
ACL.attachTo(ds);
|
|
||||||
Role.attachTo(ds);
|
|
||||||
RoleMapping.attachTo(ds);
|
|
||||||
User.attachTo(ds);
|
|
||||||
|
|
||||||
// var log = console.log;
|
// var log = console.log;
|
||||||
var log = function() {};
|
var log = function() {};
|
||||||
|
var ds = this.ds;
|
||||||
|
|
||||||
// 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) {
|
||||||
|
@ -246,21 +243,7 @@ describe('security ACLs', function () {
|
||||||
}, function(err, access) {
|
}, function(err, access) {
|
||||||
assert(!err && access.permission === ACL.ALLOW);
|
assert(!err && access.permission === ACL.ALLOW);
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
|
||||||
ACL.checkAccess({
|
|
||||||
principals: [
|
|
||||||
{principalType: ACL.USER, principalId: userId}
|
|
||||||
],
|
|
||||||
model: 'Customer',
|
|
||||||
accessType: ACL.READ
|
|
||||||
}, function(err, access) {
|
|
||||||
assert(!err && access.permission === ACL.DENY);
|
|
||||||
});
|
});
|
||||||
*/
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
var path = require('path');
|
||||||
|
var SIMPLE_APP = path.join(__dirname, 'fixtures', 'simple-app');
|
||||||
|
|
||||||
describe('app', function() {
|
describe('app', function() {
|
||||||
|
|
||||||
describe('app.model(Model)', function() {
|
describe('app.model(Model)', function() {
|
||||||
|
@ -75,6 +78,23 @@ describe('app', function() {
|
||||||
expect(this.app.get('baz')).to.eql(true);
|
expect(this.app.get('baz')).to.eql(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('boot and models directories', function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
var app = this.app = loopback();
|
||||||
|
app.boot(SIMPLE_APP);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should run all modules in the boot directory', function () {
|
||||||
|
assert(process.loadedFooJS);
|
||||||
|
delete process.loadedFooJS;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should run all modules in the models directory', function () {
|
||||||
|
assert(process.loadedBarJS);
|
||||||
|
delete process.loadedBarJS;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('PaaS and npm env variables', function() {
|
describe('PaaS and npm env variables', function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
this.boot = function () {
|
this.boot = function () {
|
||||||
|
@ -162,7 +182,7 @@ describe('app', function() {
|
||||||
it('Load config files', function () {
|
it('Load config files', function () {
|
||||||
var app = loopback();
|
var app = loopback();
|
||||||
|
|
||||||
app.boot(require('path').join(__dirname, 'fixtures', 'simple-app'));
|
app.boot(SIMPLE_APP);
|
||||||
|
|
||||||
assert(app.models.foo);
|
assert(app.models.foo);
|
||||||
assert(app.models.Foo);
|
assert(app.models.Foo);
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
var loopback = require('../../../');
|
||||||
|
var path = require('path');
|
||||||
|
var app = module.exports = loopback();
|
||||||
|
|
||||||
|
app.boot(__dirname);
|
||||||
|
|
||||||
|
var apiPath = '/api';
|
||||||
|
app.use(loopback.cookieParser('secret'));
|
||||||
|
app.use(loopback.token({model: app.models.accessToken}));
|
||||||
|
app.use(apiPath, loopback.rest());
|
||||||
|
app.use(app.router);
|
||||||
|
app.use(loopback.urlNotFound());
|
||||||
|
app.use(loopback.errorHandler());
|
||||||
|
app.enableAuth();
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"port": 3000,
|
||||||
|
"host": "0.0.0.0"
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"db": {
|
||||||
|
"defaultForType": "db",
|
||||||
|
"connector": "memory"
|
||||||
|
},
|
||||||
|
"mail": {
|
||||||
|
"defaultForType": "mail",
|
||||||
|
"connector": "mail"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,163 @@
|
||||||
|
{
|
||||||
|
"email": {
|
||||||
|
"options": {
|
||||||
|
"base": "Email",
|
||||||
|
"acls": [
|
||||||
|
{
|
||||||
|
"accessType": "*",
|
||||||
|
"permission": "DENY",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$everyone"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dataSource": "mail",
|
||||||
|
"public": false
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"options": {
|
||||||
|
"base": "User",
|
||||||
|
"relations": {
|
||||||
|
"accessTokens": {
|
||||||
|
"model": "accessToken",
|
||||||
|
"type": "hasMany",
|
||||||
|
"foreignKey": "userId"
|
||||||
|
},
|
||||||
|
"account": {
|
||||||
|
"model": "account",
|
||||||
|
"type": "belongsTo"
|
||||||
|
},
|
||||||
|
"transactions": {
|
||||||
|
"model": "transaction",
|
||||||
|
"type": "hasMany"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"acls": [
|
||||||
|
{
|
||||||
|
"accessType": "*",
|
||||||
|
"permission": "DENY",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$everyone"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dataSource": "db",
|
||||||
|
"public": true
|
||||||
|
},
|
||||||
|
"accessToken": {
|
||||||
|
"options": {
|
||||||
|
"base": "AccessToken",
|
||||||
|
"baseUrl": "access-tokens",
|
||||||
|
"acls": [
|
||||||
|
{
|
||||||
|
"accessType": "*",
|
||||||
|
"permission": "DENY",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$everyone"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"permission": "ALLOW",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$everyone",
|
||||||
|
"property": "create"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dataSource": "db",
|
||||||
|
"public": true
|
||||||
|
},
|
||||||
|
"bank": {
|
||||||
|
"options": {
|
||||||
|
"relations": {
|
||||||
|
"users": {
|
||||||
|
"model": "user",
|
||||||
|
"type": "hasMany"
|
||||||
|
},
|
||||||
|
"accounts": {
|
||||||
|
"model": "account",
|
||||||
|
"type": "hasMany"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"acls": [
|
||||||
|
{
|
||||||
|
"accessType": "*",
|
||||||
|
"permission": "DENY",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$everyone"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"accessType": "READ",
|
||||||
|
"permission": "ALLOW",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$everyone"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"properties": {},
|
||||||
|
"public": true,
|
||||||
|
"dataSource": "db"
|
||||||
|
},
|
||||||
|
"account": {
|
||||||
|
"options": {
|
||||||
|
"relations": {
|
||||||
|
"transactions": {
|
||||||
|
"model": "transaction",
|
||||||
|
"type": "hasMany"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"acls": [
|
||||||
|
{
|
||||||
|
"accessType": "*",
|
||||||
|
"permission": "DENY",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$everyone"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"accessType": "*",
|
||||||
|
"permission": "ALLOW",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$owner"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"permission": "DENY",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$owner",
|
||||||
|
"property": "removeById"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"properties": {},
|
||||||
|
"public": true,
|
||||||
|
"dataSource": "db"
|
||||||
|
},
|
||||||
|
"transaction": {
|
||||||
|
"options": {
|
||||||
|
"acls": [
|
||||||
|
{
|
||||||
|
"accessType": "*",
|
||||||
|
"permission": "DENY",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$everyone"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"properties": {},
|
||||||
|
"public": true,
|
||||||
|
"dataSource": "db"
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"options": {
|
||||||
|
"acls": [
|
||||||
|
{
|
||||||
|
"accessType": "WRITE",
|
||||||
|
"permission": "DENY",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$everyone"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"properties": {},
|
||||||
|
"public": true,
|
||||||
|
"dataSource": "db"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
this is not a js file!
|
|
@ -0,0 +1 @@
|
||||||
|
process.loadedFooJS = true;
|
|
@ -0,0 +1 @@
|
||||||
|
process.loadedBarJS = true;
|
|
@ -0,0 +1,13 @@
|
||||||
|
var loopback = require('../../../');
|
||||||
|
var path = require('path');
|
||||||
|
var app = module.exports = loopback();
|
||||||
|
|
||||||
|
app.boot(__dirname);
|
||||||
|
app.use(loopback.favicon());
|
||||||
|
app.use(loopback.cookieParser({secret: app.get('cookieSecret')}));
|
||||||
|
var apiPath = '/api';
|
||||||
|
app.use(apiPath, loopback.rest());
|
||||||
|
app.use(app.router);
|
||||||
|
app.use(loopback.static(path.join(__dirname, 'public')));
|
||||||
|
app.use(loopback.urlNotFound());
|
||||||
|
app.use(loopback.errorHandler());
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"port": 3000,
|
||||||
|
"host": "0.0.0.0",
|
||||||
|
"cookieSecret": "2d13a01d-44fb-455c-80cb-db9cb3cd3cd0"
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"db": {
|
||||||
|
"defaultForType": "db",
|
||||||
|
"connector": "memory"
|
||||||
|
},
|
||||||
|
"mail": {
|
||||||
|
"defaultForType": "mail",
|
||||||
|
"connector": "mail"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
{
|
||||||
|
"email": {
|
||||||
|
"dataSource": "mail",
|
||||||
|
"public": false,
|
||||||
|
"options": {
|
||||||
|
"base": "Email"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"dataSource": "db",
|
||||||
|
"public": true,
|
||||||
|
"options": {
|
||||||
|
"base": "User",
|
||||||
|
"relations": {
|
||||||
|
"accessTokens": {
|
||||||
|
"model": "accessToken",
|
||||||
|
"type": "hasMany",
|
||||||
|
"foreignKey": "userId"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"accessToken": {
|
||||||
|
"dataSource": "db",
|
||||||
|
"public": true,
|
||||||
|
"options": {
|
||||||
|
"base": "AccessToken"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"widget": {
|
||||||
|
"properties": {},
|
||||||
|
"public": true,
|
||||||
|
"dataSource": "db"
|
||||||
|
},
|
||||||
|
"store": {
|
||||||
|
"properties": {},
|
||||||
|
"public": true,
|
||||||
|
"dataSource": "db",
|
||||||
|
"options": {
|
||||||
|
"relations": {
|
||||||
|
"widgets": {
|
||||||
|
"model": "widget",
|
||||||
|
"type": "hasMany"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,9 @@ describe('Application', function () {
|
||||||
var registeredApp = null;
|
var registeredApp = null;
|
||||||
|
|
||||||
it('Create a new application', function (done) {
|
it('Create a new application', function (done) {
|
||||||
Application.create({owner: 'rfeng', name: 'MyApp1', description: 'My first mobile application'}, function (err, result) {
|
Application.create({owner: 'rfeng',
|
||||||
|
name: 'MyApp1',
|
||||||
|
description: 'My first mobile application'}, function (err, result) {
|
||||||
var 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');
|
||||||
|
@ -22,8 +24,59 @@ describe('Application', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Create a new application with push settings', function (done) {
|
||||||
|
Application.create({owner: 'rfeng',
|
||||||
|
name: 'MyAppWithPush',
|
||||||
|
description: 'My push mobile application',
|
||||||
|
pushSettings: {
|
||||||
|
apns: {
|
||||||
|
production: false,
|
||||||
|
certData: 'cert',
|
||||||
|
keyData: 'key',
|
||||||
|
pushOptions: {
|
||||||
|
gateway: 'gateway.sandbox.push.apple.com',
|
||||||
|
port: 2195
|
||||||
|
},
|
||||||
|
feedbackOptions: {
|
||||||
|
gateway: 'feedback.sandbox.push.apple.com',
|
||||||
|
port: 2196,
|
||||||
|
interval: 300,
|
||||||
|
batchFeedback: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
gcm: {
|
||||||
|
serverApiKey: 'serverKey'
|
||||||
|
}
|
||||||
|
}},
|
||||||
|
function (err, result) {
|
||||||
|
var app = result;
|
||||||
|
assert.deepEqual(app.pushSettings.toObject(), {
|
||||||
|
apns: {
|
||||||
|
production: false,
|
||||||
|
certData: 'cert',
|
||||||
|
keyData: 'key',
|
||||||
|
pushOptions: {
|
||||||
|
gateway: 'gateway.sandbox.push.apple.com',
|
||||||
|
port: 2195
|
||||||
|
},
|
||||||
|
feedbackOptions: {
|
||||||
|
gateway: 'feedback.sandbox.push.apple.com',
|
||||||
|
port: 2196,
|
||||||
|
interval: 300,
|
||||||
|
batchFeedback: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
gcm: {
|
||||||
|
serverApiKey: 'serverKey'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
done(err, result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
beforeEach(function (done) {
|
beforeEach(function (done) {
|
||||||
Application.register('rfeng', 'MyApp2', {description: 'My second mobile application'}, function (err, result) {
|
Application.register('rfeng', 'MyApp2',
|
||||||
|
{description: 'My second mobile application'}, function (err, result) {
|
||||||
var 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');
|
||||||
|
@ -66,43 +119,48 @@ describe('Application', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Authenticate with application id & clientKey', function (done) {
|
it('Authenticate with application id & clientKey', function (done) {
|
||||||
Application.authenticate(registeredApp.id, registeredApp.clientKey, function (err, result) {
|
Application.authenticate(registeredApp.id, registeredApp.clientKey,
|
||||||
|
function (err, result) {
|
||||||
assert.equal(result, 'clientKey');
|
assert.equal(result, 'clientKey');
|
||||||
done(err, result);
|
done(err, result);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Authenticate with application id & javaScriptKey', function (done) {
|
it('Authenticate with application id & javaScriptKey', function (done) {
|
||||||
Application.authenticate(registeredApp.id, registeredApp.javaScriptKey, function (err, result) {
|
Application.authenticate(registeredApp.id, registeredApp.javaScriptKey,
|
||||||
|
function (err, result) {
|
||||||
assert.equal(result, 'javaScriptKey');
|
assert.equal(result, 'javaScriptKey');
|
||||||
done(err, result);
|
done(err, result);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Authenticate with application id & restApiKey', function (done) {
|
it('Authenticate with application id & restApiKey', function (done) {
|
||||||
Application.authenticate(registeredApp.id, registeredApp.restApiKey, function (err, result) {
|
Application.authenticate(registeredApp.id, registeredApp.restApiKey,
|
||||||
|
function (err, result) {
|
||||||
assert.equal(result, 'restApiKey');
|
assert.equal(result, 'restApiKey');
|
||||||
done(err, result);
|
done(err, result);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Authenticate with application id & masterKey', function (done) {
|
it('Authenticate with application id & masterKey', function (done) {
|
||||||
Application.authenticate(registeredApp.id, registeredApp.masterKey, function (err, result) {
|
Application.authenticate(registeredApp.id, registeredApp.masterKey,
|
||||||
|
function (err, result) {
|
||||||
assert.equal(result, 'masterKey');
|
assert.equal(result, 'masterKey');
|
||||||
done(err, result);
|
done(err, result);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('Authenticate with application id & windowsKey', function (done) {
|
it('Authenticate with application id & windowsKey', function (done) {
|
||||||
Application.authenticate(registeredApp.id, registeredApp.windowsKey, function (err, result) {
|
Application.authenticate(registeredApp.id, registeredApp.windowsKey,
|
||||||
|
function (err, result) {
|
||||||
assert.equal(result, 'windowsKey');
|
assert.equal(result, 'windowsKey');
|
||||||
done(err, result);
|
done(err, result);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Fail to authenticate with application id & invalid key', function (done) {
|
it('Fail to authenticate with application id & invalid key', function (done) {
|
||||||
Application.authenticate(registeredApp.id, 'invalid-key', function (err, result) {
|
Application.authenticate(registeredApp.id, 'invalid-key',
|
||||||
|
function (err, result) {
|
||||||
assert(!result);
|
assert(!result);
|
||||||
done(err, result);
|
done(err, result);
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
var loopback = require('../');
|
||||||
|
var lt = require('loopback-testing');
|
||||||
|
var path = require('path');
|
||||||
|
var SIMPLE_APP = path.join(__dirname, 'fixtures', 'simple-integration-app');
|
||||||
|
var app = require(path.join(SIMPLE_APP, 'app.js'));
|
||||||
|
var assert = require('assert');
|
||||||
|
|
||||||
|
describe('relations - integration', function () {
|
||||||
|
|
||||||
|
lt.beforeEach.withApp(app);
|
||||||
|
|
||||||
|
lt.beforeEach.givenModel('store');
|
||||||
|
beforeEach(function(done) {
|
||||||
|
this.widgetName = 'foo';
|
||||||
|
this.store.widgets.create({
|
||||||
|
name: this.widgetName
|
||||||
|
}, function() {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
afterEach(function(done) {
|
||||||
|
this.app.models.widget.destroyAll(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('/store/:id/widgets', function () {
|
||||||
|
beforeEach(function() {
|
||||||
|
this.url = '/api/stores/' + this.store.id + '/widgets';
|
||||||
|
});
|
||||||
|
lt.describe.whenCalledRemotely('GET', '/api/stores/:id/widgets', function() {
|
||||||
|
it('should succeed with statusCode 200', function() {
|
||||||
|
assert.equal(this.res.statusCode, 200);
|
||||||
|
});
|
||||||
|
describe('widgets (response.body)', function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
this.widgets = this.res.body;
|
||||||
|
this.widget = this.res.body[0];
|
||||||
|
});
|
||||||
|
it('should be an array', function() {
|
||||||
|
assert(Array.isArray(this.widgets));
|
||||||
|
});
|
||||||
|
it('should include a single widget', function() {
|
||||||
|
assert(this.widgets.length === 1);
|
||||||
|
assert(this.widget);
|
||||||
|
});
|
||||||
|
it('should be a valid widget', function() {
|
||||||
|
assert(this.widget.id);
|
||||||
|
assert.equal(this.widget.storeId, this.store.id);
|
||||||
|
assert.equal(this.widget.name, this.widgetName);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('POST /api/store/:id/widgets', function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
this.newWidgetName = 'baz';
|
||||||
|
this.newWidget = {
|
||||||
|
name: this.newWidgetName
|
||||||
|
};
|
||||||
|
});
|
||||||
|
beforeEach(function(done) {
|
||||||
|
this.http = this.post(this.url, this.newWidget);
|
||||||
|
this.http.send(this.newWidget);
|
||||||
|
this.http.end(function(err) {
|
||||||
|
if(err) return done(err);
|
||||||
|
this.req = this.http.req;
|
||||||
|
this.res = this.http.res;
|
||||||
|
done();
|
||||||
|
}.bind(this));
|
||||||
|
});
|
||||||
|
it('should succeed with statusCode 200', function() {
|
||||||
|
assert.equal(this.res.statusCode, 200);
|
||||||
|
});
|
||||||
|
describe('widget (response.body)', function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
this.widget = this.res.body;
|
||||||
|
});
|
||||||
|
it('should be an object', function() {
|
||||||
|
assert(typeof this.widget === 'object');
|
||||||
|
assert(!Array.isArray(this.widget));
|
||||||
|
});
|
||||||
|
it('should be a valid widget', function() {
|
||||||
|
assert(this.widget.id);
|
||||||
|
assert.equal(this.widget.storeId, this.store.id);
|
||||||
|
assert.equal(this.widget.name, this.newWidgetName);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should have a single widget with storeId', function (done) {
|
||||||
|
this.app.models.widget.count({
|
||||||
|
storeId: this.store.id
|
||||||
|
}, function(err, count) {
|
||||||
|
if(err) return done(err);
|
||||||
|
assert.equal(count, 2);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -17,6 +17,7 @@ describe('User', function(){
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(function (done) {
|
beforeEach(function (done) {
|
||||||
|
app.use(loopback.token());
|
||||||
app.use(loopback.rest());
|
app.use(loopback.rest());
|
||||||
app.model(User);
|
app.model(User);
|
||||||
|
|
||||||
|
@ -153,7 +154,6 @@ describe('User', function(){
|
||||||
|
|
||||||
it('Logout a user by providing the current accessToken id (over rest)', function(done) {
|
it('Logout a user by providing the current accessToken id (over rest)', function(done) {
|
||||||
login(logout);
|
login(logout);
|
||||||
|
|
||||||
function login(fn) {
|
function login(fn) {
|
||||||
request(app)
|
request(app)
|
||||||
.post('/users/login')
|
.post('/users/login')
|
||||||
|
@ -171,22 +171,22 @@ describe('User', function(){
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function logout(err, sid) {
|
function logout(err, token) {
|
||||||
request(app)
|
request(app)
|
||||||
.post('/users/logout')
|
.post('/users/logout')
|
||||||
|
.set('Authorization', token)
|
||||||
.expect(204)
|
.expect(204)
|
||||||
.send({sid: sid})
|
.end(verify(token, done));
|
||||||
.end(verify(sid, done));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function verify(sid, done) {
|
function verify(token, done) {
|
||||||
assert(sid);
|
assert(token);
|
||||||
|
|
||||||
return function (err) {
|
return function (err) {
|
||||||
if(err) return done(err);
|
if(err) return done(err);
|
||||||
|
|
||||||
AccessToken.findById(sid, function (err, accessToken) {
|
AccessToken.findById(token, function (err, accessToken) {
|
||||||
assert(!accessToken, 'accessToken should not exist after logging out');
|
assert(!accessToken, 'accessToken should not exist after logging out');
|
||||||
done(err);
|
done(err);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue