我正在用Backbone、Knockout和Knockback(ko+bb桥库)构建一个相当大的cms类型的应用程序,我正在努力找出一个抽象权限的好方法。也为小说提前道歉。
首先,这是一个非常非标准的体系结构,你可能会问的第二个问题是,为什么不使用更全面的东西,比如Ember或Angular?要点。这就是目前的情况。:)
这就是我的困惑。我希望在控制器和视图模型级别都有一个优雅的权限api。
我有一个对象,看起来像这样:
{
'api/pages': {
create: true, read: true, update: true, destroy: true
},
'api/links': {
create: false, read: true, update: false, destroy: false
}
...
}
因此,在我的路由器/控制器中,我正在更新我的集合/模型/视图模型,然后在现有视图上调用自定义渲染方法。视图负责诸如发布视图模型之类的事务。
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.
}
所以问题是,这些集合完全是非结构化的,即路由器对它们一无所知。
但我需要的是,如果你不被允许查看特定的资源,它可以重定向到未经授权的页面。
所以在上面的例子中,我考虑过在前置/后置过滤器中进行编码?但是,您将在哪里指定每个路由器方法试图访问的内容?
list: function() {
this.authorize([this.pages, this.links], ['read'], function(pages, links) {
// return view.
});
}
前面的代码真的很丰富。。
对于更简单的视图模型,我有这样的想法——ala Ruby的CanCan:
this.currentUser.can('read', collection) // true or false
// can() would just look at the endpoint and compare to my perms object.
您可以扩展路由器以包装路由回调,以便在允许操作之前执行有效性检查。
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
}
});
如果路径被授权,路由将照常执行,否则将触发拒绝事件。
authorized
方法可以基于映射到您的权限对象的路径列表,类似于以下
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) {}
});
还有一个演示http://jsfiddle.net/t2vMA/1/
Nikoshr的回答给了我一些可以借鉴的东西。我没有想过要真正覆盖route
本身。但这是我的解决方案。我本应该在问题中提到它,但有时路由器操作需要多个集合。
这方面的代码真的很粗糙,需要测试——但它很有效!在这里打闹。
以下是相关部分-这两种方法负责授权。
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;
}
每个控制器/路由器都继承自perm路由器,其中每个操作的权限映射如下:
// 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']
}
},