summaryrefslogtreecommitdiffstats
path: root/server.lua
diff options
context:
space:
mode:
Diffstat (limited to 'server.lua')
-rw-r--r--server.lua508
1 files changed, 252 insertions, 256 deletions
diff --git a/server.lua b/server.lua
index c798852..7e6620f 100644
--- a/server.lua
+++ b/server.lua
@@ -22,269 +22,265 @@ local sessions = {}
return function(env)
- for sid, session in pairs(sessions) do
- if session.expires < os.time() then sessions[sid] = nil end
- end
-
- local method = env.REQUEST_METHOD
- local path = env.PATH_INFO
-
- local function wrap(code, headers, res, encode)
- if not headers then headers = {} end
-
- if res then
- local ctype
- if encode then
- ctype = 'application/json'
- res = json.encode(res)
- else ctype = 'text/plain' end
- headers['Content-Type'] = ctype
- else res = '' end
-
- return code, headers, coroutine.wrap(
- function()
- coroutine.yield(res)
- posix.close(uwsgi.connection_fd())
- aconf.commit()
- end
- )
- end
-
- if path == '/' then
- if method ~= 'GET' then return wrap(405) end
- return wrap(301, {['Location']='/browser/'})
- end
-
- local data
- local length = env.CONTENT_LENGTH and tonumber(
- env.CONTENT_LENGTH
- ) or 0
- if length > 0 then
- local success
- success, data = pcall(
- json.decode,
- env.input:read(length)
- )
- if not success then
- return wrap(400, nil, 'Request not in JSON format')
- end
- end
-
- local session, user, txn_id
- function reset_session_expiry() session.expires = os.time() + 600 end
-
- local sid = tonumber(env.HTTP_X_ACONF_AUTH_TOKEN)
- if sid then
- session = sessions[sid]
- if not session then return wrap(401) end
- reset_session_expiry()
- user = session.user
- txn_id = tonumber(env.HTTP_X_ACONF_TRANSACTION_ID)
- end
-
- local parent_txn
- if txn_id then
- parent_txn = session.txns[txn_id]
- if not parent_txn then
- return wrap(400, nil, 'Invalid transaction ID')
- end
- end
-
- local function new_txn(defer_validation)
- return aconf.start_txn{
- allow_commit_defer=true,
- defer_validation=defer_validation,
- parent=parent_txn
- }
- end
-
- local txn = new_txn(true)
-
- local function fetch_user(name)
- user = name and txn:fetch('/auth/users')[name]
- end
- if user then
- fetch_user(user)
- if not user then return wrap(401) end
- end
-
- if path == '/login' then
- if method == 'POST' then
- if not data.username or not data.password then
- return wrap(401)
- end
- fetch_user(data.username)
- if user and user:check_password(data.password) then
- local sid
- repeat
- sid = math.floor(math.random() * 2^32)
- until not sessions[sid]
-
- session = {user=data.username, last_txn_id=0, txns={}}
- reset_session_expiry()
- sessions[sid] = session
- return wrap(
- 204,
- {
- ['X-AConf-Auth-Token']=sid,
- ['X-AConf-Save-Required']=save_req and 1 or 0
- }
- )
- end
- return wrap(401)
- end
-
- if not user then return wrap(401) end
-
- if method == 'DELETE' then
- sessions[sid] = nil
- return wrap(204)
- end
-
- return wrap(405)
- end
-
- if not user then return wrap(401) end
-
- if path == '/save' then
- if not save_req then return wrap(404) end
- if method ~= 'POST' then return wrap(405) end
- if os.execute('lbu commit') then wrap(204) end
- return wrap(500, nil, 'lbu commit failed')
- end
-
- local success, code, hdr, res = aconf.call(
- function()
- if stringy.startswith(path, '/meta/') then
- if method ~= 'GET' then return 405 end
- return 200, nil, txn:meta(path:sub(6, -1))
- end
-
- if stringy.startswith(path, '/config/') then
- path = path:sub(8, -1)
+ local function wrap(code, headers, res, encode)
+ if not headers then headers = {} end
+
+ if res then
+ local ctype
+ if encode then
+ ctype = 'application/json'
+ res = json.encode(res)
+ else ctype = 'text/plain' end
+ headers['Content-Type'] = ctype
+ else res = '' end
+
+ return code, headers, coroutine.wrap(
+ function()
+ coroutine.yield(res)
+ posix.close(uwsgi.connection_fd())
+ aconf.commit()
+ end
+ )
+ end
+
+ local success, code, headers, res, encode = xpcall(
+ function()
+ for sid, session in pairs(sessions) do
+ if session.expires < os.time() then sessions[sid] = nil end
+ end
+
+ local method = env.REQUEST_METHOD
+ local path = env.PATH_INFO
+
+ if path == '/' then
+ if method ~= 'GET' then return 405 end
+ return 301, {['Location']='/browser/'}
+ end
+
+ local data
+ local length = env.CONTENT_LENGTH and tonumber(env.CONTENT_LENGTH) or 0
+ if length > 0 then
+ local success
+ success, data = pcall(json.decode, env.input:read(length))
+ if not success then
+ return 400, nil, 'Request not in JSON format'
+ end
+ end
+
+ local session, user, txn_id
+ function reset_session_expiry() session.expires = os.time() + 600 end
+
+ local sid = tonumber(env.HTTP_X_ACONF_AUTH_TOKEN)
+ if sid then
+ session = sessions[sid]
+ if not session then return 401 end
+ reset_session_expiry()
+ user = session.user
+ txn_id = tonumber(env.HTTP_X_ACONF_TRANSACTION_ID)
+ end
+
+ local parent_txn
+ if txn_id then
+ parent_txn = session.txns[txn_id]
+ if not parent_txn then
+ return 400, nil, 'Invalid transaction ID'
+ end
+ end
+
+ local function new_txn(defer_validation)
+ return aconf.start_txn{
+ allow_commit_defer=true,
+ defer_validation=defer_validation,
+ parent=parent_txn
+ }
+ end
+
+ local txn = new_txn(true)
+
+ local function fetch_user(name)
+ user = name and txn:fetch('/auth/users')[name]
+ end
+ if user then
+ fetch_user(user)
+ if not user then return 401 end
+ end
+
+ if path == '/login' then
+ if method == 'POST' then
+ if not data.username or not data.password then return 401 end
+ fetch_user(data.username)
+ if user and user:check_password(data.password) then
+ local sid
+ repeat
+ sid = math.floor(math.random() * 2^32)
+ until not sessions[sid]
+
+ session = {user=data.username, last_txn_id=0, txns={}}
+ reset_session_expiry()
+ sessions[sid] = session
+ return 204, {
+ ['X-AConf-Auth-Token']=sid,
+ ['X-AConf-Save-Required']=save_req and 1 or 0
+ }
+ end
+ return 401
+ end
+
+ if not user then return 401 end
+
+ if method == 'DELETE' then
+ sessions[sid] = nil
+ return 204
+ end
+
+ return 405
+ end
+
+ if not user then return 401 end
+
+ if path == '/save' then
+ if not save_req then return 404 end
+ if method ~= 'POST' then return 405 end
+ if os.execute('lbu commit') then return 204 end
+ return 500, nil, 'lbu commit failed'
+ end
+
+ local success, code, hdr, res = aconf.call(
+ function()
+ if stringy.startswith(path, '/meta/') then
+ if method ~= 'GET' then return 405 end
+ return 200, nil, txn:meta(path:sub(6, -1))
+ end
+
+ if stringy.startswith(path, '/config/') then
+ path = path:sub(8, -1)
- local parent, name, res
- if path ~= '/' then
- parent = txn:fetch(aconf.path.parent(path))
- name = aconf.path.name(path)
- end
+ local parent, name, res
+ if path ~= '/' then
+ parent = txn:fetch(aconf.path.parent(path))
+ name = aconf.path.name(path)
+ end
- if method == 'GET' then
- local obj = txn:fetch(path)
+ if method == 'GET' then
+ local obj = txn:fetch(path)
- if type(obj) == 'function' then return 404 end
+ if type(obj) == 'function' then return 404 end
- if isinstance(obj, mnode.TreeNode) then
- if not mnode.has_permission(obj, user, 'read') then
- return 403
- end
+ if isinstance(obj, mnode.TreeNode) then
+ if not mnode.has_permission(obj, user, 'read') then
+ return 403
+ end
- local node = {}
- for k, v in mnode.pairs(obj) do
- local readable = true
+ 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)
-
- elseif isinstance(v, mbin.Data) then v = v.path end
+ if isinstance(v, mnode.TreeNode) then
+ readable = mnode.has_permission(v, user, 'read')
+ v = mnode.path(v)
+
+ elseif isinstance(v, mbin.Data) then v = v.path end
- if readable then node[k] = v end
- end
- res = {data=node, meta=mnode.meta(obj)}
+ if readable then node[k] = v end
+ end
+ res = {data=node, meta=mnode.meta(obj)}
- elseif mnode.has_permission(parent, user, 'read') then
- if isinstance(obj, mbin.Data) then
- obj = obj:encode()
- end
- res = {data=obj, meta=mnode.mmeta(parent, name)}
- else return 403 end
-
- return 200, nil, res
- end
-
- if method == 'POST' then
- local obj = txn:fetch(path)
-
- if isinstance(obj, mnode.List) then
- if not mnode.has_permission(obj, user, 'create') then
- return 403
- end
-
- local index
- if not isinstance(obj, mnode.Set) then
- index = data.index
- data = data.data
- end
- mnode.insert(obj, data, index)
-
- elseif type(obj) == 'function' then
- if not mnode.has_permission(parent, user, name) then
- return 403
- end
- res = obj(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
- if isinstance(parent, mnode.Set) then return 405 end
- 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
+ elseif mnode.has_permission(parent, user, 'read') then
+ if isinstance(obj, mbin.Data) then
+ obj = obj:encode()
+ end
+ res = {data=obj, meta=mnode.mmeta(parent, name)}
+ else return 403 end
+
+ return 200, nil, res
+ end
+
+ if method == 'POST' then
+ local obj = txn:fetch(path)
+
+ if isinstance(obj, mnode.List) then
+ if not mnode.has_permission(obj, user, 'create') then
+ return 403
+ end
+
+ local index
+ if not isinstance(obj, mnode.Set) then
+ index = data.index
+ data = data.data
+ end
+ mnode.insert(obj, data, index)
+
+ elseif type(obj) == 'function' then
+ if not mnode.has_permission(parent, user, name) then
+ return 403
+ end
+ res = obj(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
+ if isinstance(parent, mnode.Set) then return 405 end
+ 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
+ else return 405 end
+ end
- txn:commit()
- return res == nil and 205 or 200, nil, res
- end
-
- if path == '/transaction' then
- if ({DELETE=true, PUT=true})[method] then
- if not txn_id then return 405 end
- if method == 'PUT' then parent_txn:commit() end
- session.txns[txn_id] = nil
- return 204
- end
-
- if method ~= 'POST' then return 405 end
-
- session.last_txn_id = session.last_txn_id + 1
- local txn_id = session.last_txn_id
- session.txns[txn_id] = new_txn()
- return 204, {['X-AConf-Transaction-ID']=txn_id}
- end
-
- return 404
- end
- )
-
- if success then return wrap(code, hdr, res, true) end
-
- if code.conflict then return wrap(409, nil, code.conflict, true) end
-
- return wrap(422, nil, code, true)
- end
+ txn:commit()
+ return res == nil and 205 or 200, nil, res
+ end
+
+ if path == '/transaction' then
+ if ({DELETE=true, PUT=true})[method] then
+ if not txn_id then return 405 end
+ if method == 'PUT' then parent_txn:commit() end
+ session.txns[txn_id] = nil
+ return 204
+ end
+
+ if method ~= 'POST' then return 405 end
+
+ session.last_txn_id = session.last_txn_id + 1
+ local txn_id = session.last_txn_id
+ session.txns[txn_id] = new_txn()
+ return 204, {['X-AConf-Transaction-ID']=txn_id}
+ end
+
+ return 404
+ end
+ )
+
+ if success then return code, hdr, res, true end
+
+ if code.conflict then return 409, nil, code.conflict, true end
+
+ return 422, nil, code, true
+ end,
+ function(err) return err..'\n'..debug.traceback() end
+ )
+
+ if success then return wrap(code, headers, res, encode) end
+
+ print(code)
+ return wrap(500, nil, 'Internal server error')
+end