-
Notifications
You must be signed in to change notification settings - Fork 374
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
Design patterns and examples #38
Comments
👍 I'd be curious how to use this for a basic blog type site. How would you allow users to see their own drafts, but not other user's? |
curious if anyone found some good examples of using this module? |
First of it is important to note that is is just a library that you build your application around. When designing an application where resource access will be controlled using ACL, you will first want to organize your resources in a logical manner. My application has clear separations of view logic, domain logic, and model logic where all routes into the domain logic are RESTful For example, Consider a model Books
Next, I begin to thing about the actions that will be executed on these routes. My app is relatively simple, so these actions will be the standard HTTP CRUD actions
The next part is your role definitions. I like to keep my role definitions simple at first and then build on them later.
The final part is actual user definitions. This part is agnostic to how you are managing user accounts, but the idea is that a user definition is mapped to a role definition. When it comes time to actually check permissions, you will be checking if a user has permission on a particular resource. Now we map our resources to our permissions and roles. To bootstrap my access control table, I organize this information into two data structures. The first data structure will map roles -> resources -> permissions Here is what my first structure looks like: var publicRole = {
name: 'public',
resources: [
],
permissions: []
};
var adminRole = {
name: 'admin',
resources: [
'/books',
'/books/:param1',
'/books/:param1/pages',
'/books/:param1/pages/:pageId'
],
permissions: '*'
};
var userRole = {
name: 'user',
resources: [
'/books',
],
permissions: ['get', 'post']
};
var allRoles = [
publicRole,
adminRole,
userRole
]; And the second data structure for the user definitions var users = [
{
username: 'public',
roles: ['public'],
password: 'public'
},
{
username: 'admin',
roles: ['admin'],
password: 'admin_password'
},
{
username: 'foobar',
roles: ['user'],
password: 'barfoo'
}
]; I have defined the roles I mentioned earlier, associated them with the relevant resources and permissions, and defined users and associated them with their roles. I admit, I am skipping a few steps, I apologize if this is still a little confusing. If you look at the resource definitions, you will see :paramx, these are placeholder that I have put in. They are not defined by node_acl. Node acl only does string matching, so in order to check paths with parameters, I needed to find a way to generalize paramaterized paths. I will come back to this. After you have defined your ACL and User lists, you will need to add them to the ACL table. I will not go into the code, but the basic algorithm is.
This is sufficient for this basic example. A more advanced example would allow you to have resource specific permissions. Now that your ACL data has been persisted, we will define the access control logic. The idea is regardless of your webserver, you want to intercept every request on a controlled route and check if the current user is permitted to perform the request. For our purposes a request can be defined as a route and an action to be performed on that route. Because that is too abstract, I will assume we are using Express. Our basic express route configuration would be app.get('/books', booksCtrl.listBooks)
app.post('/books', booksCtrl.createBook)
app.get('/books/:bookId', booksCtrl.getBook)
app.put('/books/:bookId', booksCtrl.editBook)
app.delete('/books/:bookId', booksCtrl.deleteBook)
app.get('/books/:bookId/pages', booksCtrl.listPages)
app.post('/books/:bookId/pages', booksCtrl.addPage)
app.get('/books/:bookId/pages/:pageId', booksCtrl.getPage)
app.put('/books/:bookId/pages/:pageId', booksCtrl.editPage)
app.delete('/books/:bookId/pages/:pageId', booksCtrl.deletePage) To integrate node_acl into this, we could go very simple and use the middleware method. app.get('/books/:bookId', node_acl.middleware(), booksCtrl.getBook)
app.put('/books/:bookId', node_acl.middleware(), booksCtrl.editBook)
app.delete('/books/:bookId', node_acl.middleware(), booksCtrl.deleteBook)
app.get('/books/:bookId/pages', node_acl.middleware(), booksCtrl.listPages)
app.post('/books/:bookId/pages', node_acl.middleware(), booksCtrl.addPage)
app.get('/books/:bookId/pages/:pageId', node_acl.middleware(), booksCtrl.getPage)
app.put('/books/:bookId/pages/:pageId', node_acl.middleware(), booksCtrl.editPage)
app.delete('/books/:bookId/pages/:pageId', node_acl.middleware(), booksCtrl.deletePage) By default, this will perform the following on every request func (reqest, response, next) ->
node_acl.isAllowed(request.userId, url, httpMethod, func(error, isAllowed) ->
if (isAllowed) ->
next()
else ->
response.notAllowed()
) I had two problems with using the middleware function at all
So I wrote my own middleware function, it looks a little like this function myMiddleware(req, res, next) ->
if (req.user is undefined) ->
req.user = { id: 'public' }
id = req.user.id
// here i need to normalize the route in order to ignore routes with parameters
routeParts = req.path.split('/')
for each part in routeParths
if (part matches an id format) ->
replace it with (':param' + counter)
routeParts.join('/')
// this will convert a route /books/abc123 to /books/:param1
// now actually do the acl check
node_acl.isAllowed(id, routeParts, request.method, func(err, isAllowed) ->
if(isAllowed) ->
next();
else
response.notAllowed I hope this is helpful. I wanted to submit my advice fairly quickly and I couldn't share my exact working code examples, sorry if I wasn't very clear. |
That was a great introductory tutorial. Do you mind if I put it on the wiki? |
Sure, go for it. The example needs some cleaning up though, it was written pretty hastily. |
cheers @icompuiz that was really helpful. I appreciate the in depth response and the time taken for it. I've used the ZF2 acl module before so I am fairly familiar with the approach taken here but your response has clarified a few implementation points for me, so thanks. I think the one thing I will still need to play around with (hopefully today when I get a moment) is implementing this with something like mongoose and persisting the users roles in their own schema similar to the ZF2 implementation. |
Wow @icompuiz this is really detailed and informative! Thanks so much! I must say It's been some time since I worked on the project where this was implemented - so I forget what we wound up doing. Anyhow I'll keep this approach in mind for future projects. Thanks again! |
So I was trying to implement this today and tried several approaches but I seem to just be missing it. If I had a very simple example project with routes for books and users with a few mongoose user accounts with a roles attribute how might I go about implementing this? |
Check out my additions. To summarize the additions
Note, I don't think it will run, but the added sections speak to what you will need. My additions were made based on the assumptions made in the example above. Also, my example has two dependencies: node-async and lodash/underscore.js. As @manast mentioned earlier, you may be able to handle the asynchronousness (not a word) a little better by using the promises some of the functions return. |
@icompuiz WOW! ok, seriously I thought it might just be one or two additional lines of implementation I was missing. This is a little more full on than I first appreciated. Really appreciate the time you took to expand and flesh this out. I will go through this thoroughly and get my head around it all now. Again, a huge thanks for the time you've taken to help explain this all. |
@icompuiz the link you provided shows "nothing to compare" :( |
Whoops, sorry. Here is a link to the commit icompuiz/express-mongoose-acl@e27ef6c |
I created some example for using node_acl with mongo and expressjs: https://gist.github.com/danwit/11307969 |
@danwit this is by far the easiest gist for getting up to speed with this modules implementation. thanks! |
@chasevida thanks! Good to hear somebody found use in it. I dug into passportjs (authentication) the last couple of days and tried to combine it with node_acl to get a full auth process working. Maybe this adds to this conversation too: https://gist.github.com/danwit/e0a7c5ad57c9ce5659d2 |
I am building my first node-acl project, and I am needing to control access on the resource-level. Let's say I have a single blog, 5 authors, 1 admin. The admin should be able to do anything... no problem there. Each author should only be able to see, update, and delete their own blog posts. Here is my methodology (not yet implemented - wanted some advice first and then I will post back here with more details). The final process will be a little more refined than this, but here goes:
function getUserPosts() {
var promise = new Promise();
ACL.whatResources('user_' + req.user_id, function(err, resources) {
if(err) return promise.reject(err);
// filter the post IDs
var postIds = [];
for(var resourceName in resources) {
if( resourceName.indexOf('blog_post_') === 0 ) {
postIds.push( resourceName.split('blog_post_')[1] );
}
}
promise.resolve(null, postIds);
});
return promise;
}
getUserPosts().then(function(ids) {
db.blog_posts.find({_id: {$in: ids}}, function(err, posts) {
// Show the user his posts
});
}, function(err) {
// handle error
}); The |
Just an idea that may improve your example. Instead of creating a user_id role for the owner of the blog post create a blog_post_id role. Then use |
@manast - Thanks for the response. I went down that route originally and here's why I switched directions:
Its funny that you, me, and our other developer all had the same initial idea. An argument was made against my methodology about redundancy in the sense that multiple users are going to have the same permissions to the same resource. My counter to that is "such is the nature of entity-level permissions - a lot of users are going to have the same access to many of the same resources. But eventually UserA is only going to have READ permissions where everybody else has full CRUD". Both methods can be used to achieve the same result, but my way actually feels a little more natural (as you put it) once I started writing code. Thoughts? |
i used this github.com/chasevida/express-mongoose-acl.git but some error is occued why? |
/*
var nodeAcl = new acl(new acl.mongodbBackend(mongoose.connection.db)); app.use( nodeAcl.middleware ); //nodeAcl.allow('guest', ['books'], ['get', 'post']); // throws error /*
in this commended portion included but some error is occuerd |
@DesignByOnyx I'm looking to set up a very similar acl, do you have a fuller example of the code you could share? Thanks. |
Hi all, I needed to build in authentication and authorization to an Express app recently and came across this issue. This discussion was very helpful, especially the examples by @icompuiz. As a result of my research and seeing that there is a need for a solid example of usage of the ACL, I created one. It uses Passport for authentication, the ACL for authorization, MongoDB and Mongoose for data, and SendGrid for email verification and password reset. Feedback and assistance in bug and security fixes would be appreciated. |
@icompuiz I tried using the reference but the URL normalise middleware doesn't work. Here is a open issue (#205) pls help. Also for some off reason i cannot access Update
|
@chasevida So how we implement to view (ejs or jade). We have a lot of pages and buttons, tags,...?? |
@TungXuan sorry, it's been a long time since I've been on this thread (over 2 years) and I have since moved in other directions. I think you may have to check in with others or open a new specific issue to discuss having this working within views. |
There are several good examples of node_acl implemented in one file. But I have yet to find one that separates concerns across several files. I don't think it's a good practice to have on big bloated server.js file. Does anyone know where there are some working examples? |
in koa, we can use
|
Can you suggest any good examples of sites, apps, etc. using this module? I'm still having a bit of trouble wrapping my head around some of the concepts of ACL, which is preventing me from fully integrating this module.
Specifically, I'm stuck on the following:
matt
hasview, list
permission froarticles #1 - 100
but notarticles 101-200
. When matt queriesGET api/articles
how do I limit his access to articles 1-100? Would I access ACL to get his available resources, and set something likefind().where('_id').in(mattsArticles)
. Would I even use ACL for this?pre.('save')
middleware?Thanks in advance for helping me understand this better!
The text was updated successfully, but these errors were encountered: