Action based permissions with basic ideas, API projects?

I am creating a rather large cms-type application with Backbone and Knockout and Knockback (ko + bb bridge library) and I am trying to find a good way to abstract permissions. Also sorry in advance for the novel.

First of all, this is a rather non-standard architecture, and the second question that you can ask is why do not you use something more comprehensive, like Ember or Angular? Case accepted. Right at that moment. :)

So here is my difficulty. I want an elegant api at the controller level and a viewmodel for permissions.

I have an object available to me that looks like this:

{ 'api/pages': { create: true, read: true, update: true, destroy: true }, 'api/links': { create: false, read: true, update: false, destroy: false } ... } 

So, in my router / controllers, I update my collections / models / view modes and then invoke an individual rendering method on an existing view. The look takes care of things such as the release of viewmodels.

 initialize: function() { this.pages = new PagesCollection(); this.links = new LinksCollection(); }, list: function() { var vm = new PageListViewmodel(this.pages, this.links); // adminPage method is available through inheritance this.adminPage('path/to/template', vm); // delegates to kb.renderTemplate under the hood. } 

So the problem with this is that these collections are completely unstructured, i.e. the router knows nothing about them.

But I need to redirect to an unauthorized page if you are not allowed to view a specific resource.

So, with the example above, I was thinking about coding in filters before / after? But where would you indicate what each router method is trying to access?

 list: function() { this.authorize([this.pages, this.links], ['read'], function(pages, links) { // return view. }); } 

The previous code is really awkward.

For viewmodels, which are more understandable, I had the idea to do something like this - ala Ruby CanCan:

 this.currentUser.can('read', collection) // true or false // can() would just look at the endpoint and compare to my perms object. 
+7
source share
2 answers

Nikosh's answer gave me something to run into. I did not think that really redefines route . But here is my solution. I should have mentioned this in a question, but sometimes more than one collection is required for a router to work.

The code on this is really rude and needs testing - but it works! Fiddle here .

Here are the relevant sections. These two methods take care of resolution.

 authorize: function(namedRoute) { if (this.permissions && this.collections) { var perms = this.permissions[namedRoute]; if (!perms) { perms = {}; // if nothing is specified for a particular route, we // assume read access required for all registered controllers. _.each(_.keys(this.collections), function(key) { return perms[key] = []; }); } var authorized = _.chain(perms) .map(function(reqPerms, collKey) { var collection = this.collections[collKey], permKey = _.result(collection, 'url'); // We implicitly check for 'read' if (!_.contains('read')) { reqPerms.push('read'); } return _.every(reqPerms, function(ability) { return userPermissions[permKey][ability]; }); }, this) .every(function(auth){ return auth; }) .value(); return authorized; } return true; }, route: function(route, name, callback) { if (!callback) { callback = this[name]; } var action = function() { // allow anonymous routes through auth check. if (!name || this.authorize(name)) { callback.apply(this, arguments); } else { this.trigger('denied'); } } Backbone.Router.prototype.route.call(this, route, name, action); return this; } 

And each controller / router inherits from a permanent router, where the permissions for each action are displayed like this:

 // Setup routes: { 'list' : 'list', 'list/:id' : 'detail', 'create' : 'create' }, // Collection are registered so we can // keep track of what actions use them collections: { pages: new PagesCollection([{id:1, title: 'stuff'}]), links: new LinksCollection([{id:1, link: 'things'}]) }, // If a router method is not defined, // 'read' access is assumed to be // required for all registered collections. permissions: { detail: { pages: ['update'], links: ['update'] }, create: { pages: ['create'], links: ['create', 'update'] } }, 
+1
source

You can expand your router to wrap your route callbacks to perform validation before allowing the action.

 var Router = Backbone.Router.extend({ routes: { "app/*perm": "go" }, route: function(route, name, callback) { if (!callback) callback = this[name]; var f = function() { var perms = this.authorized(Backbone.history.getFragment()); if (perms === true) { callback.apply(this, arguments); } else { this.trigger('denied', perms); } }; return Backbone.Router.prototype.route.call(this, route, name, f); }, authorized: function(path) { // check if the path is authorized }, go: function(perm) { // perform action } }); 

If the path is allowed, the route is executed as usual, the negation event is fired, if not.

The authorized method can be based on a list of paths mapped to your permission objects, something like this

 var permissions = { 'api/pages': { create: true, read: true, update: true, destroy: true }, 'api/links': { create: false, read: true, update: false, destroy: false } } var Router = Backbone.Router.extend({ routes: { "app/*perm": "go" }, // protected paths, with the corresponding entry in the permissions object permissionsMap: { "app/pages": 'api/pages', "app/links": 'api/links', }, route: function(route, name, callback) { // see above }, // returns true if the path is allowed // returns an object with the path and the permission key used if not authorized: function(path) { var paths, match, permkey, perms; // find an entry for the current path paths = _.keys(this.permissionsMap); match = _.find(paths, function(p) { return path.indexOf(p)===0; }); if (!match) return true; //check if the read permission is allowed permkey = this.permissionsMap[match]; if (!permissions[permkey]) return true; if (permissions[permkey].read) return true; return { path: path, permission: permkey }; }, go: function(perm) {} }); 

And the demo is http://jsfiddle.net/t2vMA/1/

+4
source

All Articles