From 751f019580e210ff22fc1ac0eea72cece854534a Mon Sep 17 00:00:00 2001 From: Kaarle Ritvanen Date: Tue, 18 Mar 2014 00:52:30 +0200 Subject: move permission checking from server to model hide all model data and functions inaccessible to the user --- aconf/model/field.lua | 43 +++++++++++++---------- aconf/model/model.lua | 36 ++++++++++++++----- aconf/model/node.lua | 96 ++++++++++++++++++++++++++++++++++++--------------- 3 files changed, 121 insertions(+), 54 deletions(-) (limited to 'aconf/model') diff --git a/aconf/model/field.lua b/aconf/model/field.lua index 26711f2..ec8e2c7 100644 --- a/aconf/model/field.lua +++ b/aconf/model/field.lua @@ -68,8 +68,16 @@ function M.Field:init(params) for _, param in ipairs{'compute', 'store', 'editable'} do local func = self[param] - if type(func) == 'string' then - self[param] = function(obj, ...) return obj[func](obj, ...) end + if func then + if type(func) == 'string' then + local method = func + function func(obj, ...) return obj[method](obj, ...) end + end + if type(func) == 'function' then + self[param] = function(obj, ...) + return func(node.escalate(obj), ...) + end + end end end @@ -128,7 +136,8 @@ function M.Field:meta(context) { type=self.dtype, visible=self.visible, - editable=self:_editable(context), + editable=self:_editable(context) and + node.has_permission(context.parent, 'modify'), condition=self.condition, required=self.required, default=self.default, @@ -193,12 +202,6 @@ function M.Field:_validate(context, value) return value end -function M.Field:check_editable(context) - if not self:_editable(context) then - raise(context.path, 'Is not editable') - end -end - function M.Field:check_required(context) if self.required then raise(context.path, 'Required value not set') end end @@ -208,7 +211,10 @@ function M.Field:normalize(context, value) return value end function M.Field:validate(context, value) end function M.Field:save(context, value) - self:check_editable(context) + if not self:_editable(context) then + raise(context.path, 'Is not editable') + end + if self.store then self.store(context.parent, value, context.txn) else self:_save(context, self:_validate(context, value)) end end @@ -326,9 +332,10 @@ function M.TreeNode:load(context, options) options or {}, 'create', self.create ) or self:_load(context) ) then return end - return self.itype( + local res = self.itype( context, update({editable=self:_editable(context)}, self.iparams) ) + return node.has_permission(res, 'read') and res or nil end function M.TreeNode:save(context, value) @@ -342,13 +349,6 @@ function M.TreeNode:save(context, value) return end - local check = value ~= nil and next(value) ~= nil - if not check then - local old = self:_load(context) - if old and next(old) ~= nil then check = true end - end - if check then self:check_editable(context) end - self:_save(context) if value then @@ -364,6 +364,8 @@ function M.TreeNode:save(context, value) errors:collect(self.save_member, new, k, v) end errors:raise() + + return new end end @@ -384,5 +386,10 @@ function M.Model:init(params) self.dtype = 'model' end +function M.Model:save(context, value) + local new = super(self, M.Model):save(context, value) + if new then node.check_permission(new, 'create') end +end + return M diff --git a/aconf/model/model.lua b/aconf/model/model.lua index 3f90450..5d4145f 100644 --- a/aconf/model/model.lua +++ b/aconf/model/model.lua @@ -153,6 +153,8 @@ function M.Model:init(context) assert(mt.txn) if isinstance(v, M.Action) then + mt.check_permission(k) + local f = v.field and BoundMember(self, k, v.field) if options.create then return f and f:load(options) end @@ -164,7 +166,7 @@ function M.Model:init(context) 'Action does not accept an input argument' ) end - local res = v.func(self, f and f:load()) + local res = v.func(mt.escalate, f and f:load()) if f then f:_save() end return res end @@ -174,9 +176,13 @@ function M.Model:init(context) end if self.is_removable then - function mt.removable() return self:is_removable() end + function mt.removable() + return mt.has_permission('delete') and self:is_removable() + end end + function mt.removing_permitted() return mt.has_permission('modify') end + local value_removable = mt.value_removable function mt.value_removable(v) return type(v) == 'table' and value_removable(v) @@ -190,7 +196,11 @@ function M.Model:init(context) function mt.save(k, v) k = normalize_name(k) local field = mt.member(k, false, Field) - if isinstance(field, fld.TreeNode) then mt.check_removable(k, v) end + + if v == nil and isinstance(field, fld.TreeNode) then + mt.check_removable(k) + else mt.check_permission('modify') end + return field:save(v) end @@ -199,7 +209,15 @@ function M.Model:init(context) end function mt.init_meta(meta) - util.update(meta, {fields=tmeta(Field), actions=tmeta(M.Action)}) + util.update( + meta, + { + fields=tmeta(Field), + actions=util.filter( + function(a) mt.has_permission(a.name) end, tmeta(M.Action) + ) + } + ) end function mt.members() @@ -215,12 +233,13 @@ function M.Model:init(context) end if self.has_permission then - function mt.has_permission(user, permission) - return self:has_permission(user, permission) + function mt.has_permission(permission) + return mt.privileged or self:has_permission(mt.txn.user, permission) end end - for _, f in ipairs(_members(Model)) do mt.load(f.name) end + local tload = getmetatable(mt.escalate).load + for _, f in ipairs(_members(Model)) do tload(f.name) end end function M.Model:fetch(path, create) @@ -228,8 +247,9 @@ function M.Model:fetch(path, create) end function M.Model:match(filter) + local tload = getmetatable(getmetatable(self).escalate).load for k, v in pairs(filter) do - if not util.contains(v, getmetatable(self).load(k)) then return false end + if not util.contains(v, tload(k)) then return false end end return true end diff --git a/aconf/model/node.lua b/aconf/model/node.lua index 92f3a1f..50edac6 100644 --- a/aconf/model/node.lua +++ b/aconf/model/node.lua @@ -40,6 +40,7 @@ function M.BoundMember:init(parent, name, field) field, { txn=pmt.txn, + privileged=pmt.privileged, parent=parent, path=pth.join(pmt.path, name), addr=pth.to_absolute(addr, pmt.addr) @@ -66,13 +67,18 @@ function M.TreeNode:init(context, params) mt.name = pth.name(mt.path) mt.__eq = equal_tns + if not (mt.txn and mt.txn.user) then mt.privileged = true end + mt.escalate = mt.privileged and self or mt.class( + setdefaults({privileged=true}, context), params + ) + function mt.get(k, options) return mt.load(k, options) end function mt.fetch(path, create) if type(path) == 'string' then if pth.is_absolute(path) and mt.path > '/' then assert(not create) - return mt.txn:fetch(path) + return mt.txn:fetch(path, mt.privileged) end path = pth.split(path) end @@ -111,6 +117,26 @@ function M.TreeNode:init(context, params) return getmetatable(next).fetch(path, create) end + function mt.has_permission(permission) + if mt.privileged then return true end + + local p = permission..mt.path + if getmetatable(mt.escalate).fetch('/auth/permissions')[p] then + return mt.txn.user:check_permission(p) + end + + if ({create=true, delete=true})[permission] then + permission = 'modify' + end + return getmetatable(mt.parent).has_permission(permission) + end + + function mt.check_permission(permission) + if not mt.has_permission(permission) then + raise('forbidden', permission..mt.path) + end + end + function mt.removable() end function mt.value_removable(v) @@ -118,13 +144,15 @@ function M.TreeNode:init(context, params) end local function key_removable(k) + if not mt.removing_permitted() then return false end + local res = mt.value_removable(mt.load(k, {dereference=false})) if res == nil then return params.editable end return res end - function mt.check_removable(k, v) - if v == nil and not key_removable(k) then + function mt.check_removable(k) + if not key_removable(k) then raise(pth.join(mt.path, k), 'Cannot be deleted') end end @@ -155,18 +183,6 @@ function M.TreeNode:init(context, params) function mt.__index(t, k) return mt.get(k, {private=true}) end function mt.__newindex(t, k, v) mt.save(k, v) end - function mt.has_permission(user, permission) - local p = permission..mt.path - if mt.fetch('/auth/permissions')[p] then - return user:check_permission(p) - end - - if ({create=true, delete=true})[permission] then - permission = 'modify' - end - return M.has_permission(mt.parent, user, permission) - end - mt.txn.validable[mt.path] = mt.addr end @@ -175,7 +191,8 @@ function M.TreeNode:search_refs(path) if #path == 0 then return {} end - local mt = getmetatable(self) + local mt = getmetatable(getmetatable(self).escalate) + local name = path[1] table.remove(path, 1) @@ -224,13 +241,15 @@ function M.Collection:init(context, params) function mt.load(k, options) return mt.member(k):load(options) end + function mt.removing_permitted() return mt.has_permission('delete') end + if not mt.txn then return end function mt.init_meta(meta) update( meta, { - editable=params.editable, + editable=params.editable and mt.has_permission('create'), members=field:meta(), required=params.required, ['ui-member']=params.ui_member or meta['ui-name']:gsub('s$', ''), @@ -260,13 +279,26 @@ function M.Collection:init(context, params) validate(mt.parent) end end - + function mt.save(k, v) - if not params.editable then - raise(mt.path, 'Collection is not editable') - end + local delete = v == nil + local old = mt.load(k, {dereference=false}) + + if old == nil then + if delete then return end + if not params.editable then + raise(mt.path, 'Collection is not editable') + end + mt.check_permission('create') + + elseif delete then mt.check_removable(k) + + elseif type(old) == 'table' then + mt.check_removable(k) + mt.check_permission('create') + + else mt.check_permission('modify') end - mt.check_removable(k, v) if params.key then local kf = M.BoundMember(self, k, params.key) @@ -287,26 +319,32 @@ function M.List:init(context, params) super(self, M.List):init(context, setdefaults(params, {dtype='list'})) local mt = getmetatable(self) + local tmt = getmetatable(mt.escalate) + + mt._save = mt.save - local save = mt.save function mt.save(k, v) assert(type(k) == 'number') if v == nil then + mt.check_removable(k) local len = #mt.members() while k < len do - save(k, mt.load(k + 1, {dereference=false})) + tmt._save(k, tmt.load(k + 1, {dereference=false})) k = k + 1 end - end - save(k, v) + tmt._save(len) + else mt._save(k, v) end end function mt.insert(v, i) assert(v ~= nil) + mt.check_permission('create') local len = #mt.members() if not i then i = len + 1 end - for j = len,i,-1 do save(j + 1, mt.load(j, {dereference=false})) end - save(i, v) + for j = len,i,-1 do + tmt._save(j + 1, tmt.load(j, {dereference=false})) + end + tmt._save(i, v) end end @@ -336,7 +374,9 @@ end for _, mf in ipairs{ 'addr', + 'check_permission', 'contains', + 'escalate', 'fetch', 'has_permission', 'insert', -- cgit v1.2.3