Sails.js - protect blueprint associations using policies - sails.js

In order to create a many-to-many association between models, I use the blueprints to access something like:
/api/item/1/tags/2
How can I protect this action using policies?
This action doesn't seem to fit any of the find/create/update/destroy policies.

There's no need for custom routing here; the blueprint you're referring to is called populate, so it can be protected in your config/policies.js with:
ItemController: {
populate: 'somePolicy'
}

check this:
module.exports.routes = {
//Set blueprints
'GET /findAllUsers': {model: 'user', blueprint: 'find'},
'GET /user/findAll': {blueprint: 'find'}
'GET /user/findAll': {blueprint: 'find', model: 'pet'}
// Set policies in routes
'/foo': {policy: 'myPolicy'}
// Mix of blueprints and policies
'GET /mix-of-both': [
{policy: 'isLoggued'},
{blueprint: 'find', model: 'tag'}
]
}
Check the official docs: http://sailsjs.org/#/documentation/concepts/Routes/RouteTargetSyntax.html
I hope it helps!

Related

API reference: Where to find all the properties available for aggregation binding info object?

Where can I find aggregation binding properties like path, factory, etc.. in the API reference?
Just like in following code
<List items="{
path: '/Products',
factory: '.productListFactory',
...
}">
Where to find more such properties and their descriptions?
You can see more such properties in ManagedObject#bindAggregation.
Additionally, parameters and events await key-value pairs of which the possible keys are documented in the corresponding model type.
For example, the keys for parameters, when binding with a v2.ODataModel, can be found in ODataModel#bindList or in ODataListBinding. The latter module also documents which events can be registered.
{ // Standard options. See ManagedObject bindAggregation
path: '...',
filters: [/*...*/],
...
parameters: { // Model dependent. See e.g. ODataListBinding constructor
expand: 'ToA, ToB/ToB1/ToB2, ToC',
select: 'ProductID, ProductName',
custom: 'myCustomQuery',
batchGroupId: 'myGroup',
},
events: { // Model dependent. See e.g. ODataListBinding events
dataRequested: '.onDataRequested',
dataRequested: '.onDataReceived',
change: '.onDataChange'
}
}
That's a binding (they're enclosed in {}).
You can use the getBindingInfo method.
So, for example, if your List control has the id "mylist":
this.getView().byId("mylist").getBindingInfo("items");
It will return you an object containing all the binding properties, such as path and factory.

An attempt on making REST API for nested attribute using Restivus

So, I'm trying to show how Restivus API works and Meteor is nice to my colleagues. :)
I've made a simple blog app at http://askar-blog.meteor.com/ (thanks to DiscoverMeteor book).
My repo https://github.com/tenzan/blog
(I'm reading https://github.com/kahmali/meteor-restivus#restivus)
I have three collections:
users
posts
comments
So, post has many comments. Usually, we used to have comments as a nested documents inside of a post, but from the Meteor's nature these two attributes are split up into different collections.
I want to implement a REST API, so that I can access (including CRUD operations) posts and collections in the way:
http://example.com/api/posts - all posts
http://example.com/api/posts/post_id - a specific post
http://example.com/api/posts/post_id/comments - all comments that belongs to a given post
http://example.com/api/posts/post_id/comments/comment_id - a specific comment that belongs to a given post
If you have a look at my repo, you will see there're posts.js and comments.js under lib/collections.
As I understood, to enable REST API, I will need the following snippet in the posts.js:
if (Meteor.isServer) {
// Global API configuration
var Api = new Restivus({
useDefaultAuth: true,
prettyJson: true
});
// Generates: GET, POST on /api/post and GET, PUT, DELETE on
// /api/items/:id for the Posts collection
Api.addCollection(Posts);
// Generates: POST on /api/users and GET, DELETE /api/users/:id for
// Meteor.users collection
Api.addCollection(Meteor.users, {
excludedEndpoints: ['getAll', 'put'],
routeOptions: {
authRequired: true
},
endpoints: {
post: {
authRequired: false
},
delete: {
roleRequired: 'admin'
}
}
});
As you see, I've added Api.addCollection(Posts); and I've confirmed I can access all posts or a specific one.
My questions:
1- How can I setup API to access comments for their parent post?
2 - Will I have to have to following code to access posts ? I'm asking because, I'm already able to access them as I have Api.addCollection(Posts); :
Maps to: /api/posts/:id
Api.addRoute('posts/:id', {authRequired: true}, {
get: function () {
return Posts.findOne(this.urlParams.id);
},
delete: {
roleRequired: ['author', 'admin'],
action: function () {
if (Articles.remove(this.urlParams.id)) {
return {status: 'success', data: {message: 'Post removed'}};
}
return {
statusCode: 404,
body: {status: 'fail', message: 'Post not found'}
};
}
}
});
I apologise, I got confused myself trying to figure out the correct way of making a REST API.
Please feel free to add anything important on this regard I have missed here.
I've discussed with the author of the package
https://github.com/kahmali/meteor-restivus/issues/128
The feature is being developed
https://github.com/kahmali/meteor-restivus/issues/70

Sails.js policy not working for a single controller

I have 4 Policies in sails.js, SuperUser, Admin, User and SuOrAdmin and 3 models with the blueprint controllers, this is the policies config:
'*': 'SuperUser',
User:{
'*': 'SuOrAdmin',
findOne: 'User'
},
Empresa:{
'*': 'SuperUser',
findOne: 'Admin',
findOne: 'User'
},
Noticia:{
'*': 'SuOrAdmin',
find: 'User',
findOne: 'User'
}
when i log in with the SuperUser i can CRUD all the models except the find method of Noticia, but when i log in with an Admin i can CRUD the Noticia model, this is the SuOrAdmin policy:
module.exports = function(req, res, next) {
// User is allowed, proceed to the next policy,
// or if this is the last policy, the controller
if ( (req.session.user && req.session.user.admin) || (req.session.SuperUser) ) {
return next();
}
// User is not allowed
// (default res.forbidden() behavior can be overridden in `config/403.js`)
return res.json(403, {error: 'You are not permitted to perform this action.'});
};
Can somebody help me please, i have been stucked 2 days in this problem.
#mikermcneil
You can apply multiple policies to a single function as below
Noticia:{
'*': 'superAdmin',
find: ['isUser','isSuperAdmin','isAdmin'],
...
}
In some cases this may still not be the easiest approach.
An alternative simpler way in your case could be that you include each level in the previous one, so a higher level user can always use a lower level user permissions, for example, these are your levels
isUser
isAdmin
isSuperAdmin
In the isUser method , first check if its an admin, return next, then check your user logic
module.exports = function isUser(req, res, next) {
if(is_admin)return next(); //you need to provide logic for is_admin here
//remaining user check logic below
....
}
Similarly in the Admin method, first check if its SuperAdmin and return next, then check your admin logic
module.exports = function isAdmin(req, res, next) {
if(is_su)return next(); //you need to provide logic for is_su here
//remaining admin check logic below
....
}
definitely check out #arkoak's answer as far as an approach. I'm not sure if this will solve your complete problem or not, but here's something else that might help.
Currently, you're mapping models on the LHS of your policies config. The reason it's working is because we have some decent guessing logic in-place-- but actually you want to be using controllers. Policies are just configurable middleware that sit between incoming requests from users and your controller actions.
So for instance, instead of User as the key, try:
UserController:{
'*': 'SuOrAdmin',
findOne: 'User'
}
The other thing I'd mention is to clarify that policies do not cascade-- that is, if you have:
'*': 'foo',
NoticiaController: { '*': 'bar' }
...then the actions of NoticiaController will only be protected under bar (not foo AND bar- catch my drift?)
As for your exact problem of not being able to find from Noticia as a superadmin, I believe it's because your policies are mutually exclusive, like what #arkoak suggested.
Hope that helps!

Adding custom baucis routes to its generated swagger api

When we create our apps, we usually add our own routes.
So using the baucis.rest i added some custom routes like example:
var controller = baucis.rest( {
singular: 'User'
} );
controller.put('/myroute/:id', function(req,res,done){
//doing something
})
My app runs and using the swagger ui i can see the operations about users.
GET /Users/{id} description
PUT /Users/{id} description
......
I would like to add my "/myroute/:id' to the generated swagger api.
PUT /Users/myroute/{id} description
Does anyone know how to do about this?
As of v0.9.0 , you can modify the swagger definitions directly. It's purely cosmetic, only altering the swagger documentation, not any other functionality.
For example:
var controller = baucis.rest('User');
controller.swagger.apis.push({
'path': '/Users/myroute/{id}',
'description': 'Myroute custom description.',
'operations': [
{
'httpMethod': 'PUT',
'nickname': 'putSomethingCustom',
'responseClass': 'User',
'summary': 'Something custom.'
}
]
})
controller.swagger.models is also exposed.

how to query attributes inside a role in chef?

I am using chef version 10.16.2
I have a role (in ruby format). I need to access an attrubute set in one of the cookbooks
eg.
name "basebox"
description "A basic box with some packages, ruby and rbenv installed"
deployers = node['users']['names'].find {|k,v| v['role'] == "deploy" }
override_attributes {
{"rbenv" => {
"group_users" => deployers
}
}
}
run_list [
"recipe[users]",
"recipe[packages]",
"recipe[nginx]",
"recipe[ruby]"
]
I am using chef-solo so i cannot use search as given on http://wiki.opscode.com/display/chef/Search#Search-FindNodeswithaRoleintheExpandedRunList
How do i access node attributes in a role definition ?
Roles are JSON data.
That is, when you upload the role Ruby file to the server with knife, they are converted to JSON. Consider this role:
name "gaming-system"
description "Systems used for gaming"
run_list(
"recipe[steam::installer]",
"recipe[teamspeak3::client]"
)
When I upload it with knife role from file gaming-system.rb, I have this on the server:
{
"name": "gaming-system",
"description": "Systems used for gaming",
"json_class": "Chef::Role",
"default_attributes": {
},
"override_attributes": {
},
"chef_type": "role",
"run_list": [
"recipe[steam::installer]",
"recipe[teamspeak3::client]"
],
"env_run_lists": {
}
}
The reason for the Ruby DSL is that it is "nicer" or "easier" to write than the JSON. Compare the lines and syntax, and it's easy to see which is preferable to new users (who may not be familiar with JSON).
That data is consumed through the API. If you need to do any logic with attributes on your node, do it in a recipe.
Not sure if I 100% follow, but if you want to access an attribute which is set by a role from a recipe, then you just call it like any other node attribute. For example, in the case you presented, assuming the node has the basebox role in its run_list, you would just call:
node['rbenv']['group_users']
The role attributes are merged into the node.
HTH