summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKaarle Ritvanen <kaarle.ritvanen@datakunkku.fi>2013-03-25 23:11:53 +0200
committerKaarle Ritvanen <kaarle.ritvanen@datakunkku.fi>2013-03-25 23:13:12 +0200
commitc832941ab3abff5d1ea826ac8a5ad36c6cb4006d (patch)
tree1ef9c55fbbaf5a779d70da0be4f0686763c40bbe
parentf4c5db6fb7d128ba5ed9c2078524f65ae7cce3d3 (diff)
downloadaconf-c832941ab3abff5d1ea826ac8a5ad36c6cb4006d.tar.bz2
aconf-c832941ab3abff5d1ea826ac8a5ad36c6cb4006d.tar.xz
basic access control
-rw-r--r--acf/init.lua4
-rw-r--r--acf/model/aaa.lua53
-rw-r--r--acf/model/init.lua16
-rw-r--r--acf/model/model.lua6
-rw-r--r--acf/model/node.lua13
-rw-r--r--acf/model/permission.lua18
-rw-r--r--acf/model/root.lua24
-rw-r--r--acf/modules/aaa.lua26
-rw-r--r--acf/modules/awall.lua2
-rw-r--r--acf/transaction/init.lua2
-rw-r--r--config/aaa.json2
-rw-r--r--protocol.txt2
-rw-r--r--server.lua71
13 files changed, 179 insertions, 60 deletions
diff --git a/acf/init.lua b/acf/init.lua
index b20d1a1..341c0f0 100644
--- a/acf/init.lua
+++ b/acf/init.lua
@@ -5,9 +5,11 @@ See LICENSE file for license details
module(..., package.seeall)
+require 'acf.model'
+require 'acf.model.aaa'
+
require('acf.loader').loadmods('modules')
-require 'acf.model'
call = require('acf.error').call
require 'acf.object'
require 'acf.path'
diff --git a/acf/model/aaa.lua b/acf/model/aaa.lua
new file mode 100644
index 0000000..93f07dc
--- /dev/null
+++ b/acf/model/aaa.lua
@@ -0,0 +1,53 @@
+--[[
+Copyright (c) 2012-2013 Kaarle Ritvanen
+See LICENSE file for license details
+--]]
+
+module(..., package.seeall)
+
+local M = require('acf.model')
+
+Role = M.new()
+Role.permissions = M.Set{type=M.Reference{scope='../../../permissions'}}
+
+
+User = M.new()
+User.password = M.String
+User.real_name = M.String
+User.superuser = M.Boolean{default=false}
+User.roles = M.Set{type=M.Reference{scope='../../../roles'}}
+
+function User:check_password(password) return password == self.password end
+
+function User:check_permission(permission)
+ -- TODO audit trail
+ print('check permission', permission)
+
+ if self.superuser then return true end
+
+ assert(getmetatable(self).txn:search('/auth/permissions')[permission])
+
+ for _, role in M.node.pairs(self.roles) do
+ for _, p in M.node.pairs(role.permissions) do
+ if p == permission then return true end
+ end
+ end
+ return false
+end
+
+
+Authentication = M.new()
+Authentication.users = M.Collection{type=User}
+Authentication.roles = M.Collection{type=Role}
+Authentication.permissions = M.Set{
+ type=M.String,
+ addr='/volatile/aaa/permissions'
+}
+
+M.register(
+ 'auth',
+ '/json'..require('lfs').currentdir()..'/config/aaa.json',
+ Authentication
+)
+
+M.permission.defaults('/auth')
diff --git a/acf/model/init.lua b/acf/model/init.lua
index 71d0744..bf67f98 100644
--- a/acf/model/init.lua
+++ b/acf/model/init.lua
@@ -17,6 +17,8 @@ new = model.new
local to_field = model.to_field
node = require('acf.model.node')
+permission = require('acf.model.permission')
+register = require('acf.model.root').register
set = require('acf.model.set')
local object = require('acf.object')
@@ -32,8 +34,6 @@ require 'stringy'
-- TODO object-specific actions
--- TODO access control
-
local Primitive = class(Field)
@@ -220,15 +220,3 @@ function Mixed:save(context, value)
if type(value) == 'table' then super(self, Mixed):save(context, value)
else Primitive.save(self, context, value) end
end
-
-
-
-RootModel = new()
-
-function RootModel:init(txn)
- super(self, RootModel):init{txn=txn, path='/', addr='/volatile'}
-end
-
-function register(name, addr, field)
- RootModel[name] = to_field(field, addr)
-end
diff --git a/acf/model/model.lua b/acf/model/model.lua
index 0a312a3..365fe20 100644
--- a/acf/model/model.lua
+++ b/acf/model/model.lua
@@ -103,4 +103,10 @@ function Model:init(context)
if not field.compute then field:validate_saved() end
end
end
+
+ if self.has_permission then
+ function mt.has_permission(user, permission)
+ return self:has_permission(user, permission)
+ end
+ end
end
diff --git a/acf/model/node.lua b/acf/model/node.lua
index 3960b90..09e3d69 100644
--- a/acf/model/node.lua
+++ b/acf/model/node.lua
@@ -57,6 +57,18 @@ function TreeNode:init(context)
function mt.__index(t, k) return mt.get(k) end
function mt.__newindex(t, k, v) mt.set(k, v) end
+ function mt.has_permission(user, permission)
+ local p = permission..mt.path
+ if mt.txn:search('/auth/permissions')[p] then
+ return user:check_permission(p)
+ end
+
+ if ({create=true, delete=true})[permission] then
+ permission = 'modify'
+ end
+ return has_permission(mt.parent, user, permission)
+ end
+
mt.txn.validable[mt.path] = mt.addr
end
@@ -117,6 +129,7 @@ local function meta_func(attr)
end
addr = meta_func('addr')
+has_permission = meta_func('has_permission')
members = meta_func('members')
meta = meta_func('meta')
mmeta = meta_func('mmeta')
diff --git a/acf/model/permission.lua b/acf/model/permission.lua
new file mode 100644
index 0000000..9a6d1d4
--- /dev/null
+++ b/acf/model/permission.lua
@@ -0,0 +1,18 @@
+--[[
+Copyright (c) 2012-2013 Kaarle Ritvanen
+See LICENSE file for license details
+--]]
+
+module(..., package.seeall)
+
+local add = require('acf.model.set').add
+local start = require('acf.transaction').start
+
+function define(path, ...)
+ local txn = start()
+ local db = txn:search('/auth/permissions')
+ for _, permission in ipairs(arg) do add(db, permission..path) end
+ txn:commit()
+end
+
+function defaults(path) define(path, 'read', 'create', 'modify', 'update') end
diff --git a/acf/model/root.lua b/acf/model/root.lua
new file mode 100644
index 0000000..a786064
--- /dev/null
+++ b/acf/model/root.lua
@@ -0,0 +1,24 @@
+--[[
+Copyright (c) 2012-2013 Kaarle Ritvanen
+See LICENSE file for license details
+--]]
+
+module(..., package.seeall)
+
+local model = require('acf.model.model')
+local super = require('acf.object').super
+
+
+RootModel = model.new()
+
+function RootModel:init(txn)
+ super(self, RootModel):init{txn=txn, path='/', addr='/volatile'}
+end
+
+function RootModel:has_permission(user, permission)
+ return permission == 'read'
+end
+
+function register(name, addr, field)
+ RootModel[name] = model.to_field(field, addr)
+end
diff --git a/acf/modules/aaa.lua b/acf/modules/aaa.lua
deleted file mode 100644
index e98e686..0000000
--- a/acf/modules/aaa.lua
+++ /dev/null
@@ -1,26 +0,0 @@
---[[
-Copyright (c) 2012-2013 Kaarle Ritvanen
-See LICENSE file for license details
---]]
-
-module(..., package.seeall)
-
-local M = require('acf.model')
-
-Role = M.new()
-Role.permissions = M.Set{type=M.Reference{scope='../../../permissions'}}
-
-User = M.new()
-User.password = M.String
-User.real_name = M.String
-User.roles = M.Set{type=M.Reference{scope='../../../roles'}}
-function User:check_password(password) return password == self.password end
-
-Authentication = M.new()
-Authentication.users = M.Collection{type=User}
-Authentication.roles = M.Collection{type=Role}
-Authentication.permissions = M.Set{type=M.String}
-
-M.register('auth',
- '/json'..require('lfs').currentdir()..'/config/aaa.json',
- Authentication)
diff --git a/acf/modules/awall.lua b/acf/modules/awall.lua
index 2920bdb..fa120c6 100644
--- a/acf/modules/awall.lua
+++ b/acf/modules/awall.lua
@@ -125,3 +125,5 @@ AWall.ipset = M.Collection{type=IPSet}
M.register('awall',
'/json'..require('lfs').currentdir()..'/config/awall.json',
AWall)
+
+M.permission.defaults('/awall')
diff --git a/acf/transaction/init.lua b/acf/transaction/init.lua
index 586e6bb..48b1832 100644
--- a/acf/transaction/init.lua
+++ b/acf/transaction/init.lua
@@ -6,7 +6,7 @@ See LICENSE file for license details
module(..., package.seeall)
local ErrorDict = require('acf.error').ErrorDict
-local RootModel = require('acf.model').RootModel
+local RootModel = require('acf.model.root').RootModel
local object = require('acf.object')
local super = object.super
local pth = require('acf.path')
diff --git a/config/aaa.json b/config/aaa.json
index 248bd32..480c25d 100644
--- a/config/aaa.json
+++ b/config/aaa.json
@@ -1 +1 @@
-{"users":{"admin":{"password":"admin"}}} \ No newline at end of file
+{"users":{"admin":{"password":"admin","superuser":true}}} \ No newline at end of file
diff --git a/protocol.txt b/protocol.txt
index a0ba4d4..bc0998b 100644
--- a/protocol.txt
+++ b/protocol.txt
@@ -95,6 +95,8 @@ Use of HTTP status codes:
401 Authentication error
+403 Forbidden
+
404 Not found
405 Method not allowed
diff --git a/server.lua b/server.lua
index 43a6a09..c4f3fa4 100644
--- a/server.lua
+++ b/server.lua
@@ -4,6 +4,7 @@ See LICENSE file for license details
--]]
require 'acf'
+local mnode = acf.model.node
local isinstance = acf.object.isinstance
require 'json'
@@ -122,22 +123,31 @@ return function(env)
local obj = txn:search(path)
local res
- if isinstance(obj, acf.model.node.TreeNode) then
- local node = {}
- for k, v in acf.model.node.pairs(obj) do
- node[k] = isinstance(
- v,
- acf.model.node.TreeNode
- ) and acf.model.node.path(v) or v
+ if isinstance(obj, mnode.TreeNode) then
+ if not mnode.has_permission(obj, user, 'read') then
+ return 403
end
- res = {data=node, meta=acf.model.node.meta(obj)}
- else
- res = {
- data=obj,
- meta=acf.model.node.mmeta(parent, name)
- }
- end
+ local node = {}
+ for k, v in mnode.pairs(obj) do
+ local readable = true
+
+ if isinstance(v, mnode.TreeNode) then
+ readable = mnode.has_permission(
+ v,
+ user,
+ 'read'
+ )
+ v = mnode.path(v)
+ end
+
+ if readable then node[k] = v end
+ end
+ res = {data=node, meta=mnode.meta(obj)}
+
+ elseif mnode.has_permission(parent, user, 'read') then
+ res = {data=obj, meta=mnode.mmeta(parent, name)}
+ else return 403 end
return 200, nil, res
end
@@ -149,11 +159,38 @@ return function(env)
return 405
end
+ if not mnode.has_permission(obj, user, 'create') then
+ return 403
+ end
+
acf.model.set.add(obj, data)
- elseif method == 'DELETE' then parent[name] = nil
- elseif method == 'PUT' then parent[name] = data
- else return 405 end
+ else
+ local obj = parent[name]
+ if obj ~= nil and not isinstance(obj, mnode.TreeNode) then
+ obj = parent
+ end
+
+ if method == 'DELETE' then
+ if obj == nil then return 404 end
+ if not mnode.has_permission(obj, user, 'delete') then
+ return 403
+ end
+ parent[name] = nil
+
+ elseif method == 'PUT' then
+ local permission = 'modify'
+ if obj == nil then
+ obj = parent
+ permission = 'create'
+ end
+ if not mnode.has_permission(obj, user, permission) then
+ return 403
+ end
+ parent[name] = data
+
+ else return 405 end
+ end
txn:commit()
return 205