Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "createOnly" properties option #5

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,16 @@ your model config.
}
```

Attempting to update a ReadOnly model will reult in a 403 error.
Attempting to update a ReadOnly model will reult in a 403 error for this entire model.

OPTIONS
=============

The specific fields that are to be marked as readonly can be set by passing an
object to the mixin options.

In this example we mark the `status` and `role` fields as readonly.
In this example we mark the `status` field as readonly for all actions, but the
`role` field can be set on create, but not for updates (it is marked `"createOnly"`).

```json
{
Expand All @@ -117,14 +118,16 @@ In this example we mark the `status` and `role` fields as readonly.
"mixins": {
"ReadOnly" : {
"status" : true,
"role" : true
"role" : "createOnly"
}
}
}
```

Any data set by a REST client in ReadOnly properties will be stripped out
on the way to the server and will not be saved on the updated model instance.
However, any method calls from within the server application can still
update any of this data.

TESTING
=============
Expand Down
7 changes: 5 additions & 2 deletions lib/read-only.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ module.exports = function(Model, options) {
if (properties) {
debug('Creating %s : Read only properties are %j', Model.modelName, properties);
Object.keys(properties).forEach(function(key) {
debug('The \'%s\' property is read only, removing incoming data', key);
delete body[key];
if (properties[key] === true ||
(properties[key].toString().toLowerCase() === 'createonly' && ctx.instance)) {
debug('The \'%s\' property is read only, removing incoming data', key);
delete body[key];
}
});
next();
} else {
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/simple-app/common/models/person.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"mixins": {
"ReadOnly": {
"status": true,
"role": true
"role": "createOnly"
}
}
}
30 changes: 24 additions & 6 deletions test/test-mixinsources.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ describe('loopback datasource readonly property (mixin sources.js)', function()

describe('when called remotely', function() {
lt.beforeEach.givenModel('Product', {name: 'some book', type: 'book', status: 'pending'}, 'product');
lt.beforeEach.givenModel('Person', {name: 'Tom', status: 'disabled', role: 'user'}, 'Person');
lt.beforeEach.givenModel('AuditTrail', {event: 'edit', user: 'tom'}, 'audittrail');
lt.beforeEach.givenModel('Product', {name: 'book 1', type: 'book', status: 'disabled'}, 'book1', 'product');
lt.beforeEach.givenModel('Product', {name: 'book 12', type: 'book', status: 'pending'}, 'book2', 'product');

it('should not save readonly properties on create.', function(done) {
var product = this.product;
this.post('/api/products')
Expand All @@ -52,7 +57,24 @@ describe('loopback datasource readonly property (mixin sources.js)', function()
});
});

lt.beforeEach.givenModel('Product', {name: 'some book', type: 'book', status: 'pending'}, 'product');
it('should save createOnly properties on create.', function(done) {
var Person = this.Person;
this.post('/api/people')
.send({
name: 'John',
status: 'active',
role: 'other user'
})
.expect(200)
.end(function(err, res) {
expect(err).to.not.exist;
expect(res.body.name).to.equal('John');
expect(res.body.status).to.not.exist;
expect(res.body.role).to.equal('other user');
done();
});
});

it('should not change readonly properties on update (single readonly property)', function(done) {
var product = this.product;
this.put('/api/products/' + product.id)
Expand All @@ -69,14 +91,13 @@ describe('loopback datasource readonly property (mixin sources.js)', function()
});
});

lt.beforeEach.givenModel('Person', {name: 'Tom', status: 'disabled', role: 'user'}, 'Person');
it('should not change readonly properties on update (multiple readonly properties)', function(done) {
var Person = this.Person;
this.put('/api/people/' + Person.id)
.send({
name: 'Tom (edited)',
status: 'active',
role: 'user'
role: 'user (edited)'
})
.expect(200)
.end(function(err, res) {
Expand All @@ -88,7 +109,6 @@ describe('loopback datasource readonly property (mixin sources.js)', function()
});
});

lt.beforeEach.givenModel('AuditTrail', {event: 'edit', user: 'tom'}, 'audittrail');
it('should not change readonly properties on update (full read only model)', function(done) {
var audittrail = this.audittrail;
this.put('/api/audittrails/' + audittrail.id)
Expand All @@ -100,8 +120,6 @@ describe('loopback datasource readonly property (mixin sources.js)', function()
.end(done);
});

lt.beforeEach.givenModel('Product', {name: 'book 1', type: 'book', status: 'disabled'}, 'book1', 'product');
lt.beforeEach.givenModel('Product', {name: 'book 12', type: 'book', status: 'pending'}, 'book2', 'product');
it('should not change readonly properties with bulk updates', function(done) {
var self = this;
var data = { status: 'disabled' };
Expand Down
14 changes: 7 additions & 7 deletions test/test-serverjs.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describe('loopback datasource readonly property (server.js)', function() {
// A model with 2 readonly properties.
var Person = this.Person = loopback.PersistedModel.extend('person',
{ name: String, status: String, role: String },
{ mixins: { ReadOnly: { status: true, role: true } } }
{ mixins: { ReadOnly: { status: true, role: "createOnly" } } }
);
Person.attachTo(loopback.memory());
app.model(Person);
Expand Down Expand Up @@ -68,7 +68,12 @@ describe('loopback datasource readonly property (server.js)', function() {
});

describe('when called remotely', function() {

lt.beforeEach.givenModel('product', {name: 'some book', type: 'book', status: 'pending'});
lt.beforeEach.givenModel('person', {name: 'Tom', status: 'disabled', role: 'user'});
lt.beforeEach.givenModel('audittrail', {event: 'edit', user: 'tom'});
lt.beforeEach.givenModel('product', {name: 'book 1', type: 'book', status: 'disabled'}, 'book1');
lt.beforeEach.givenModel('product', {name: 'book 12', type: 'book', status: 'pending'}, 'book2');

it('should not save readonly properties on create.', function(done) {
var product = this.product;
this.post('/products')
Expand All @@ -85,7 +90,6 @@ describe('loopback datasource readonly property (server.js)', function() {
});
});

lt.beforeEach.givenModel('product', {name: 'some book', type: 'book', status: 'pending'});
it('should not change readonly properties on update (single readonly property)', function(done) {
var product = this.product;
this.put('/products/' + product.id)
Expand All @@ -102,7 +106,6 @@ describe('loopback datasource readonly property (server.js)', function() {
});
});

lt.beforeEach.givenModel('person', {name: 'Tom', status: 'disabled', role: 'user'});
it('should not change readonly properties on update (multiple readonly properties)', function(done) {
var person = this.person;
this.put('/people/' + person.id)
Expand All @@ -121,7 +124,6 @@ describe('loopback datasource readonly property (server.js)', function() {
});
});

lt.beforeEach.givenModel('audittrail', {event: 'edit', user: 'tom'});
it('should not change readonly properties on update (full read only model)', function(done) {
var audittrail = this.audittrail;
this.put('/audittrails/' + audittrail.id)
Expand All @@ -133,8 +135,6 @@ describe('loopback datasource readonly property (server.js)', function() {
.end(done);
});

lt.beforeEach.givenModel('product', {name: 'book 1', type: 'book', status: 'disabled'}, 'book1');
lt.beforeEach.givenModel('product', {name: 'book 12', type: 'book', status: 'pending'}, 'book2');
it('should not change readonly properties with bulk updates', function(done) {
var self = this;
var data = { 'status': 'disabled' };
Expand Down