I am trying to give access only to owner of book in but it is not working.I have relationship between user and books.I have seen every question about loopback acl but I don't know where I am making mistake. This is my boot file
var User = app.models.userData;
User.create([
{username: 'John', email: 'john#doe.com', password: 'opensesame',bookId:""},
{username: 'Jane', email: 'jane#doe.com', password: 'opensesame',bookId:""},
{username: 'Bob', email: 'bob#projects.com', password: 'opensesame',bookId:""}
], function(err, users) {
if (err) throw err;
console.log('Created users:', users);
// create project 1 and make john the owner
console.log("This is users[0] " ,users[0].books);
users[0].books.create({
name: 'project1',
price: 100
}, function(err, project) {
if (err) throw err;
User.update({id:users[0].id},{bookId:project.id},function(err,docs){
if(err) throw err;
else{
console.log("Updated",docs);
}
});
console.log('Created project:', project);
})
users[1].books.create({
name: 'project2',
price: 100
}, function(err, project) {
if (err) throw err;
User.update({id:users[1].id},{bookId:project.id},function(err,docs){
if(err) throw err;
else{
console.log("Updated",docs);
}
});
});
});
And This is my usersData.json
"relations": {
"books": {
"type": "hasMany",
"model": "book",
"foreignKey": "ownerId"
}
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property":"findById"
}
This is my books.json
"relations": {
"userData": {
"type": "belongsTo",
"model": "userData",
"foreignKey": "bookId"
}
},
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property":"findById"
}
],
I know there are many questions related to this I have seen every question about this but it is not working.Any help would be appreciated thanks.
Related
Hello I'm new to loopback and I'm stucked on the Role creation and use.So basically what I'm trying to do is to create 2 roles and based on these roles I want to restrict some users to access some resources.The problem is that on every attempt to get some information from the api I'm getting this
{
"error": {
"statusCode": 401,
"name": "Error",
"message": "Authorization Required",
"code": "AUTHORIZATION_REQUIRED",
"stack": "Error: Authorization Required\n at C:\\Users\\HP\\Desktop\\battle-horse\\battle-horse\\node_modules\\loopback\\lib\\application.js:433:21\n at C:\\Users\\HP\\Desktop\\battle-horse\\battle-horse\\node_modules\\loopback\\lib\\model.js:359:7\n at C:\\Users\\HP\\Desktop\\battle-horse\\battle-horse\\node_modules\\loopback\\common\\models\\acl.js:536:16\n at C:\\Users\\HP\\Desktop\\battle-horse\\battle-horse\\node_modules\\async\\dist\\async.js:3888:9\n at C:\\Users\\HP\\Desktop\\battle-horse\\battle-horse\\node_modules\\async\\dist\\async.js:473:16\n at iteratorCallback (C:\\Users\\HP\\Desktop\\battle-horse\\battle-horse\\node_modules\\async\\dist\\async.js:1064:13)\n at C:\\Users\\HP\\Desktop\\battle-horse\\battle-horse\\node_modules\\async\\dist\\async.js:969:16\n at C:\\Users\\HP\\Desktop\\battle-horse\\battle-horse\\node_modules\\async\\dist\\async.js:3885:13\n at C:\\Users\\HP\\Desktop\\battle-horse\\battle-horse\\node_modules\\loopback\\common\\models\\acl.js:518:17\n at C:\\Users\\HP\\Desktop\\battle-horse\\battle-horse\\node_modules\\loopback\\common\\models\\role.js:447:21\n at _combinedTickCallback (internal/process/next_tick.js:131:7)\n at process._tickCallback (internal/process/next_tick.js:180:9)"
}
}
In my application I have 2 models:
1.Client (which extends build in User Model) and has role ```bs_client```
2.Admin(which also extends the build in User Model)
Note that these models were created using loopback cli and has no relationship created yet.
lb model
I'm using Mongodb as database and here is my datasource file
"mongodb": {
"host": "",
"port": 0,
"url": "mongodb+srv://general:234234##/#####?retryWrites=true&w=majority",
"database": "database",
"password": "password",
"name": "mongodb",
"user": "general",
"useNewUrlParser": true,
"includeSubDomains": true,
"useUnifiedTopology": true,
"connector": "mongodb"
}
It seems that the data is being added correctly in my collections (Role, Rolemapping, Client and Access Token).
I'm assigning role to each client dynamically upon creation using this
Client.observe('after save', function setRole(ctx, next) {
if (ctx.instance) {
if (ctx.isNewInstance) {
// look up role based on type
//
app.models.Role.find({where: {name: 'bs_client'}}, function(err, role) {
if (err) { return console.log(err); }
if (role) {
app.models.RoleMapping.create({
principalType: app.models.RoleMapping.User,
principalId: ctx.instance.id,
roleId: role.id,
}, function(err, roleMapping) {
if (err) { return console.log(err); }
console.log('User assigned RoleID ' + role.id + ' (' + ctx.instance.type + ')');
});
};
});
}
} next();
});
and here is my model-config.json
{
"_meta": {
"sources": [
"loopback/common/models",
"loopback/server/models",
"../common/models",
"./models"
],
"mixins": [
"loopback/common/mixins",
"loopback/server/mixins",
"../common/mixins",
"./mixins"
]
},
"User": {
"dataSource": "mongodb",
"public": false
},
"AccessToken": {
"dataSource": "mongodb",
"public": false
},
"ACL": {
"dataSource": "mongodb",
"public": false
},
"RoleMapping": {
"dataSource": "mongodb",
"public": true,
"options": {
"strictObjectIDCoercion": true
}
},
"Role": {
"dataSource": "mongodb",
"public": true
},
"Email": {
"dataSource": "Email"
},
"Client": {
"dataSource": "mongodb",
"public": true
},
}
and in client.json
"acls": [
{
"accessType": "*",
"principalType": "CLIENT",
"principalId": "bs_client",
"permission": "DENY"
},
{
"accessType": "READ",
"principalType": "CLIENT",
"principalId": "bs_client",
"permission": "ALLOW"
},
{
"accessType": "EXECUTE",
"principalType": "CLIENT",
"principalId": "$authenticated",
"permission": "ALLOW",
"property": "create"
},
{
"accessType": "WRITE",
"principalType": "CLIENT",
"principalId": "bs_client",
"permission": "ALLOW"
}
],
Following https://loopback.io/doc/en/lb3/Model-property-reference.html, everything should be working fine, why I'm not able to retrieve "clients" using the configuration above.
Thanks in advance.
This line should look like this everywhere in "acls": "principalType": "ROLE",
example ACL:
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW"
},
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW"
}
],
I'm trying to build a simple blog with loopback. I want to extend get Posts with the amount of comments.
I have two possible ways in my mind.
1) Extend the response of the get-posts by a count of the comments, this would be my favorite way, but I have no idea how to extend the reposne.
2) I have tried to observe the comment saving and to get the posts-model, but I can't change it.
post.json
{
"name": "post",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"title": {
"type": "string",
"required": true
},
"content": {
"type": "string",
"required": true
}
"published": {
"type": "boolean",
"required": true,
"default": false
}
"commentCount": {
"type": "number",
"default": 0
}
},
"validations": [],
"relations": {
"user": {
"type": "belongsTo",
"model": "user",
"foreignKey": ""
},
"comments": {
"type": "hasMany",
"model": "comment",
"foreignKey": ""
}
},
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW",
"property": "find"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW",
"property": "create"
},
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW"
},
{
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW",
"property": [
"__create__comments",
"__get__comments"
]
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__delete__comments"
}
],
"methods": {}
}
comment.json
{
"name": "comment",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"content": {
"type": "string",
"required": true
}
},
"validations": [],
"relations": {
"user": {
"type": "belongsTo",
"model": "user",
"foreignKey": ""
},
"idea": {
"type": "belongsTo",
"model": "post",
"foreignKey": ""
}
},
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW"
},
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW"
},
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW"
}
],
"methods": {}
}
comment.js ##
var loopback = require('loopback');
module.exports = function(Comment) {
Comment.observe('after save', function(ctx, userInstance, next) {
var postId = ctx.instance.postId;
// loopback.getModel('post').definition.rawProperties.commentCount... something... something...
});
};
I'm still very new to loopback and I don't know what is the best way to achieve the solution. Maybe you have a third, better way? Or maybe anyone can help me to complete the comment.js.
Fisrt, in your comment.json, you've written idea instead of post:
"post": { //change here
"type": "belongsTo",
"model": "post",
"foreignKey": ""
}
Secondly, you simply add one commentCount in the post linked to your comment in your after save method and then update the attributes of your post:
'use strict';
var app = require('../../server/server');
var models = app.models;
var Post;
// pattern to get your models on start event
app.on('started', function () {
Post = models.post;
});
module.exports = function(Comment) {
Comment.observe('after save', function(ctx, next) {
// only add a commentCount if it's a new instance
if (ctx.instance && ctx.isNewInstance && ctx.instance.postId) {
Post.findOne({where: {id: ctx.instance.postId}}, function (err, post) {
if (!err) {
post.updateAttributes({commentCount: post.commentCount++});
}
});
}
next();
});
};
Another solution would be to create a customGet endpoint in your post.js file:
'use strict';
module.exports = function(Post) {
Post.customGet = function (postId, cb) {
Post.findOne({where: {id: postId}, include: 'comments'}, function (err, post) {
if(err){
return cb(err, {});
}
post.commentCount = post.comments.length;
return cb(err, post);
});
}
Post.remoteMethod('customGet', {
description: 'New endpoint with the commentCount',
accepts: {arg: 'postId', type: 'string'},
returns: {arg: 'post', type: 'object'},
http: {verb: 'get'}
});
};
You can improve this method a bit but you get the idea.
I would like to get some informations about my user with loopback.
For that I created a "user" model related with "accessToken" model until now a POST on /user, a POST on /user/login and a POST on /user/logout is working.
I added on /common/models/user.json
{
"name": "user",
"base": "User",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {},
"validations": [],
"relations": {
"accessTokens": {
"type": "hasMany",
"model": "accessToken",
"foreignKey": "userId"
}
},
"acls": [
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW",
"property": "logout"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "findById"
}
],
"methods": {}
}
And when I do a GET on /user/{id} I got :
{
"error": {
"statusCode": 401,
"name": "Error",
"message": "Autorisation requise",
"code": "AUTHORIZATION_REQUIRED",
"stack": "Error: Autorisation requise\n at..."
}
}
I guess I didn't understand acl/relation very well
This could be because you are only allowing $owner to findById:
To qualify a $owner, the target model needs to have a belongsTo relation to the User model (or a model that extends User) and property matching the foreign key of the target model instance. The check for $owner is performed only for a remote method that has ‘:id’ on the path, for example, GET /api/users/:id.
Make sure the accessToken you are providing is the owner of the id of the user you are looking for.
If you are not sure, try to replace:
"principalId": "$owner" with "principalId": "$authenticated", then you'll know if that's your problem.
I have a loopback app with mongoDB as below:
when i login as Admin i cannot use post method on dishes. and i get authorization required error.
that becomes possible only when i change the dishes role to ALLOW everyone.
how can i acheive the wanted result with keeping everyone on DENY and only ALLOW certain users to certain operations?
thank you. here is my code..
app/server/model-config.json:
{
"_meta": {
"sources": [
"loopback/common/models",
"loopback/server/models",
"../common/models",
"./models"
],
"mixins": [
"loopback/common/mixins",
"loopback/server/mixins",
"../node_modules/loopback-ds-timestamp-mixin",
"../common/mixins",
"./mixins"
]
},
"User": {
"dataSource": "db"
},
"AccessToken": {
"dataSource": "db",
"public": false
},
"ACL": {
"dataSource": "MongoDB",
"public": false
},
"RoleMapping": {
"dataSource": "MongoDB",
"public": false
},
"Role": {
"dataSource": "MongoDB",
"public": false
},
"dishes": {
"dataSource": "MongoDB",
"public": true
},
"Customer": {
"dataSource": "MongoDB",
"public": true
},
"Comments": {
"dataSource": "MongoDB",
"public": true
}
}
app/common/modles/dishes.json:
{
"name": "dishes",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"name": {
"type": "string",
"required": true
},
"description": {
"type": "string",
"required": true
},
"category": {
"type": "string",
"required": true
},
"image": {
"type": "string",
"required": true
},
"label": {
"type": "string",
"required": true,
"default": "''"
},
"price": {
"type": "string",
"required": true,
"default": "0"
}
},
"mixins": {
"TimeStamp": true
},
"validations": [],
"relations": {
"comments": {
"type": "hasMany",
"model": "Comments",
"foreignKey": ""
},
"customers": {
"type": "hasMany",
"model": "Customer",
"foreignKey": ""
}
},
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW",
"property": "create"
},
{
"accessType": "WRITE",
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW"
}
],
"methods": {}
}
app/common/modles/comments.json:
{
"name": "Comments",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"Rating": {
"type": "number",
"required": true,
"default": 5
},
"comment": {
"type": "string",
"required": true
}
},
"mixins": {
"TimeStamp": true
},
"validations": [],
"relations": {
"dishes": {
"type": "belongsTo",
"model": "dishes",
"foreignKey": ""
},
"customer": {
"type": "belongsTo",
"model": "Customer",
"foreignKey": "customerId"
}
},
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW",
"property": "create"
},
{
"accessType": "WRITE",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW"
}
],
"methods": {}
}
app/common/modles/customer.json:
{
"name": "Customer",
"base": "User",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {},
"validations": [],
"relations": {
"comments": {
"type": "hasMany",
"model": "Comments",
"foreignKey": "customerId"
}
},
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
}
],
"methods": {}
}
and app/server/boot/script.js:
module.exports = function(app) {
var MongoDB = app.dataSources.MongoDB;
MongoDB.automigrate('Customer', function(err) {
if (err) throw (err);
var Customer = app.models.Customer;
Customer.create([
{username: 'Admin', email: 'admin#admin.com', password: 'abcdef'},
{username: 'muppala', email: 'muppala#ust.hk', password: 'abcdef'}
], function(err, users) {
if (err) throw (err);
var Role = app.models.Role;
var RoleMapping = app.models.RoleMapping;
Role.find({ name: 'admin' }, function(err, results) {
if (err) { throw err; }
if (results.length < 1) {
// now we know the DB doesn't have it already, so do the Role creation...
//create the admin role
Role.create({
name: 'admin'
}, function(err, role) {
if (err) throw (err);
//make admin
role.principals.create({
principalType: RoleMapping.USER,
principalId: users[0].id
}, function(err, principal) {
if (err) throw (err);
});
});
}
});
});
});
};
Seeing your last question I imagine what happened.
Somehow the collection Role was created but not mapped to User.
I suggest you to change:
Role.find({ name: 'admin' }, function(err, results) {
if (err) { throw err; }
if (results.length < 1) {
// now we know the DB doesn't have it already, so do the Role creation...
//create the admin role
Role.create({
name: 'admin'
}, function(err, role) {
if (err) throw (err);
//make admin
role.principals.create({
principalType: RoleMapping.USER,
principalId: users[0].id
}, function(err, principal) {
if (err) throw (err);
});
});
}
});
By:
Role.create({
name: 'admin'
}, function(err, role) {
if (err) throw (err);
//make admin
role.principals.create({
principalType: RoleMapping.USER,
principalId: users[0].id
}, function(err, principal) {
if (err) throw (err);
});
});
Drop the Role collection:
db.Role.drop()
and execute Loopback again.
Note: I was doing the same assigment and worked for me.
I am also having the same trouble as I come through the same assignment. Mr Kike's answer works for mine.
First, from cmd, type
>mongo
>use conFusion (Note: conFusion is the name of database of this assignment)
>show collections (Note: to see all collections in database collections)
>db.Role.drop()
And then run the loopback with node . again
I'm fetching a user from a mongo database, but this user that I get, doesn't have any methods, neither all the properties I was expecting. Why would this happen?
This is the code:
app.models.MyUser.findOrCreate({where: {email: req.user.email}}, {
email: req.user.email,
password: sha1sum(JSON.stringify(req.user)),
firstName: req.user.displayName
}, function (err, user) {
if (err) throw err;
console.log(user.login); //undefined
res.json(user);
});
This is the code of my model:
{
"name": "MyUser",
"plural": "myusers",
"base": "User",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"firstName": {
"type": "string"
}
},
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}
],
"methods": []
}
User.login is a static method, not a prototype method. See https://github.com/strongloop/loopback/blob/master/common/models/user.js#L164. You should be able to use user.constructor.login.