diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..837b0bb --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,41 @@ +pipeline{ + agent { node{ label'workstation' }} + options { + // Limit build history with buildDiscarder option: + // daysToKeepStr: history is only kept up to this many days. + // numToKeepStr: only this many build logs are kept. + // artifactDaysToKeepStr: artifacts are only kept up to this many days. + // artifactNumToKeepStr: only this many builds have their artifacts kept. + buildDiscarder(logRotator(numToKeepStr: "1")) + // Enable timestamps in build log console + timestamps() + // Maximum time to run the whole pipeline before canceling it + timeout(time: 1, unit: 'HOURS') + // Use Jenkins ANSI Color Plugin for log console + ansiColor('xterm') + // Limit build concurrency to 1 per branch + disableConcurrentBuilds() + } + stages + { + stage('Build') { + steps { + sh''' + cd $WORKSPACE + [ -d build ] && rm -rf build + mkdir -p build/opt/www/htdocs/os + export BUILDDIR="$WORKSPACE/build/opt/www/htdocs/os" + cp router.lua "$BUILDDIR" + cp -rf controllers "$BUILDDIR" + cp -rf libs "$BUILDDIR" + ''' + script { + // only useful for any master branch + //if (env.BRANCH_NAME =~ /^master/) { + archiveArtifacts artifacts: 'build/', fingerprint: true + //} + } + } + } + } +} \ No newline at end of file diff --git a/README.md b/README.md index 2cdc0c5..0bfa4fe 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,4 @@ -# antos-backend -Backend implementation for AntOS +## AntOS lua-based backend + +This backend is the reference implementation of AntOS server side API. +The backend is implemented for Antd webserver + fcgi plugin + luafcgi FastCGI server \ No newline at end of file diff --git a/controllers/IndexController.lua b/controllers/IndexController.lua new file mode 100644 index 0000000..f1535ea --- /dev/null +++ b/controllers/IndexController.lua @@ -0,0 +1,33 @@ +BaseController:subclass( + "IndexController", + { + registry = {}, + models = {} + } +) + +function IndexController:actionnotfound(...) + return self:index(table.unpack({...})) +end + +function IndexController:index(...) + html() + std.f(WWW_ROOT..DIR_SEP.."index.html") +end + +function IndexController:doc(...) + local api = { + author = "Xuan Sang LE", + email = "xsang.le@gmail.com", + api_name = "AntOS API", + version = "2.0.0-a", + documents = { + vfs = HTTP_ROOT.."/VFS", + vdb = HTTP_ROOT.."/VDB", + user = HTTP_ROOT.."/user", + system = HTTP_ROOT.."/system" + } + } + result(api) + return false +end \ No newline at end of file diff --git a/controllers/SystemController.lua b/controllers/SystemController.lua new file mode 100644 index 0000000..38d3e75 --- /dev/null +++ b/controllers/SystemController.lua @@ -0,0 +1,249 @@ +BaseController:subclass( + "SystemController", + { + registry = {}, + models = {} + } +) + +function SystemController:actionnotfound(...) + return self:index(table.unpack({...})) +end + +function SystemController:index(...) + local api = { + description = "This api handle system operations", + actions = { + ["/packages"] = "Handle all operation relate to package: list, install, cache, uninstall", + ["/settings"] = "Save user setting", + ["/application"] = "Call a specific server side application api", + ["/apigateway"] = "Gateway for executing custom server side code" + } + } + result(api) + return false +end + +function SystemController:packages(...) + auth_or_die("User unauthorized. Please login") + local rq = (JSON.decodeString(REQUEST.json)) + local packages = require("packages") + packages.init(rq.args.paths) + if rq ~= nil then + -- check user command here + if (rq.command == "install") then + packages.install(rq.args) + elseif rq.command == "cache" then + packages.cache(rq.args) + elseif rq.command == "list" then + packages.list(rq.args.paths) + elseif rq.command == "uninstall" then + packages.uninstall(rq.args.path) + else + fail("Uknown packages command") + end + else + fail("Uknown request") + end +end + +function SystemController:settings(...) + auth_or_die("User unauthorized. Please login") + local user = SESSION.user + if user then + local ospath = require("vfs").ospath("home:///", user) + if REQUEST and REQUEST.json then + local file_path = ospath .. "/" .. ".settings.json" + local f = io.open(file_path, "w") + if f then + f:write(REQUEST.json) + f:close() + -- TODO: maybe use ulib + os.execute("chmod o-r "..file_path) + result(true) + else + fail("Cannot save setting") + end + else + fail("No setting founds") + end + else + fail("User not found") + end +end + +function SystemController:application(...) + auth_or_die("User unauthorized. Please login") + local rq = nil + if REQUEST.json ~= nil then + rq = (JSON.decodeString(REQUEST.json)) + else + rq = REQUEST + end + + if rq.path ~= nil then + local pkg = require("vfs").ospath(rq.path) + if pkg == nil then + pkg = WWW_ROOT .. "/packages/" .. rq.path + --die("unkown request path:"..rq.path) + end + pkg = pkg .. "/api.lua" + if ulib.exists(pkg) then + dofile(pkg).exec(rq.method, rq.arguments) + else + fail("Uknown application handler: " .. pkg) + end + else + fail("Uknown request") + end +end + +function SystemController:apigateway(...) + local args={...} + local use_ws = false + if REQUEST and REQUEST.ws == "1" then + -- override the global cout command + cout = std.ws.t + echo = std.ws.t + use_ws = true + else + cout = function(e) + std.json() + echo(e) + end + -- std.json() + end + local exec_with_user_priv = function(data) + local uid = ulib.uid(SESSION.user) + if not ulib.setgid(uid.gid) or not ulib.setuid(uid.id) then + cout("Cannot set permission to execute the code") + return + end + local r, e + e = "{'error': 'Unknow function'}" + -- set env var + local home = ulib.home_dir(uid.id) + ulib.setenv("USER", SESSION.user, 1) + ulib.setenv("LOGNAME", SESSION.user, 1) + if home then + ulib.setenv("HOME", home, 1) + ulib.setenv("PWD", home,1) + local paths = "" + if ulib.exists(home.."/bin") then + paths = home.."/bin:" + end + if ulib.exists(home.."/.local/bin") then + paths = paths..home.."/.local/bin:" + end + local envar = ulib.getenv("PATH") + if envar then + paths = paths..envar + end + ulib.setenv("PATH", paths,1) + end + -- run the code as user + if data.code then + r, e = load(data.code) + if r then + local status, result = pcall(r) + if result then + if (status) then + cout(JSON.encode(result)) + else + cout(result) + end + end + else + cout(e) + end + elseif data.path then + local ospath = require("vfs").ospath(data.path) + r, e = loadfile(ospath) + if r then + local status, result = pcall(r, data.parameters) + if result then + if (status) then + cout(JSON.encode(result)) + else + cout(result) + end + end + else + cout(e) + end + else + cout(e) + end + end + + if (is_auth()) then + local pid = ulib.fork()--std.pfork(HTTP_REQUEST.id) + if (pid == -1) then + cout("{'error':'Cannot create process'}") + elseif pid > 0 then -- parent + -- wait for the child exit or websocket exit + ulib.waitpid(pid, 0) + --ulib.kill(pid) + LOG_INFO("Parent exit") + else -- child + if use_ws then + if std.ws.enable() then + -- read header + local header = std.ws.header() + if header then + if header.mask == 0 then + LOG_WARN("Web socket Data is not masked") + std.ws.close(1012) + elseif header.opcode == std.ws.CLOSE then + LOG_DEBUG("Websocket Connection closed") + -- std.ws.close(1000) + elseif header.opcode == std.ws.TEXT then + -- read the file + local data = std.ws.read(header) + if data then + data = (JSON.decodeString(tostring(data))) + exec_with_user_priv(data) + std.ws.close(1011) + else + print("Error: Invalid request") + std.ws.close(1011) + end + end + else + std.ws.close(1011) + end + else + fail("Web socket is not available.") + end + else + if REQUEST.path then + exec_with_user_priv(REQUEST) + elseif REQUEST.json then + data = JSON.decodeString(REQUEST.json) + exec_with_user_priv(data) + elseif args and #args > 0 then + -- data is encoded in url safe base64 + local encoded = args[1]:gsub('_', '/'):gsub('-', '+') + if #encoded % 4 == 2 then + encoded = encoded.."==" + elseif #encoded %4 == 3 then + encoded = encoded.."=" + end + local decoded = enc.b64decode(encoded) + data = JSON.decodeString(tostring(decoded)) + if data and data.path then + exec_with_user_priv(data) + else + fail("Unknown request") + end + else + fail("Unkown request") + end + end + print("Child exit") + ulib.kill(-1) + end + else + cout('{"error":"User unauthorized. Please login"}') + end +end diff --git a/controllers/UserController.lua b/controllers/UserController.lua new file mode 100644 index 0000000..fbc4a7d --- /dev/null +++ b/controllers/UserController.lua @@ -0,0 +1,102 @@ +BaseController:subclass( + "UserController", + { + registry = {}, + models = {} + } +) + +function UserController:actionnotfound(...) + return self:index(table.unpack({...})) +end + +function UserController:index(...) + local api = { + description = "This api handle the user authentification", + actions = { + ["/auth"] = "Return user information if a user is alreay logged in", + ["/login"] = "Perform a login operation", + ["/logout"] = "Perform a logout operation" + } + } + result(api) + return false +end +--[[ + request query: none + return: + +]] +function UserController:auth(...) + auth_or_die("User unauthorized. Please login") + local user = require("uman").userinfo(SESSION.user) + result(user) + return false +end + +--[[ request: + {"username":"mrsang", "password":"pass"} + return: + {} ]] +function UserController:login(...) + if REQUEST.json ~= nil then + local request = JSON.decodeString(REQUEST.json) + local r = ulib.auth(request.username,request.password) + if r == true then + local salt = utils.generate_salt(20) + local cookie = {sessionid=enc.sha1(request.username..request.password..salt)} -- iotos_user = request.username + local db = sysdb(); + if db == nil then return fail("Cannot setup session") end + local cond = {exp= {["="] = { sessionid = cookie.sessionid }}} + local data = db:find(cond) + --print(data) + if data == nil or data[1] == nil then + --print("insert new data") + data = {sessionid = cookie.sessionid, username=request.username, stamp=os.time(os.date("!*t"))} + else + data = data[1] + --print("Update old data") + data.stamp = os.time(os.date("!*t")) + end + if data.id == nil then + db:insert(data) + else + db:update(data) + end + db:close() + for k,v in pairs(cookie) do + --- TODO: add expire date to cookie + std.setCookie(k.."="..v, "Path=/") + end + std.json() + SESSION.user = request.username + local user = { + result = require("uman").userinfo(request.username), + error = false + } + std.t(JSON.encode(user)) + else + fail("Invalid login") + end + else + fail("Invalid request") + end + return false +end + +function UserController:logout(...) + if SESSION.sessionid ~= nil and SESSION.sessionid ~= '0' then + local cookie = {sessionid='0'} + local db = sysdb() + if db ~= nil then + local cond = {["="] = { sessionid = SESSION.sessionid }} + db:delete(cond) + db:close() + end + for k,v in pairs(cookie) do + std.setCookie(k.."="..v, "Path=/") + end + end + std.json() + std.t(JSON.encode({error=false,result=true})) +end \ No newline at end of file diff --git a/controllers/VDBController.lua b/controllers/VDBController.lua new file mode 100644 index 0000000..e590be6 --- /dev/null +++ b/controllers/VDBController.lua @@ -0,0 +1,130 @@ +BaseController:subclass( + "VDBController", + { + registry = {}, + models = {} + } +) + +function VDBController:actionnotfound(...) + return self:index(table.unpack({...})) +end + +function VDBController:index(...) + local api = { + description = "This api handle database operation", + actions = { + ["/save"] = "Save a record to a table", + ["/get"] = "Get all records or Get a record by id", + ["/select"] = "Select records by a condition", + ["/delete"] = "Delete record(s) by condition or by id" + } + } + result(api) + return false +end + +function VDBController:save(...) + auth_or_die("User unauthorized. Please login") + local rq = (JSON.decodeString(REQUEST.json)) + if (rq ~= nil and rq.table ~= nil) then + local model = require("dbmodel").get(SESSION.user, rq.table, rq.data) + local ret + if model == nil then + fail("Cannot get table metadata:" .. rq.table) + else + if (rq.data.id ~= nil) then + rq.data.id = tonumber(rq.data.id) + ret = model:update(rq.data) + else + ret = model:insert(rq.data) + end + model:close() + if ret == true then + result(ret) + else + fail("Cannot modify/update table " .. rq.table) + end + end + else + fail("Unknown database request") + end + return false +end + +function VDBController:get(...) + auth_or_die("User unauthorized. Please login") + local rq = (JSON.decodeString(REQUEST.json)) + if (rq ~= nil and rq.table ~= nil) then + local model = require("dbmodel").get(SESSION.user, rq.table, nil) + local ret + if model == nil then + fail("Cannot get table metadata:" .. rq.table) + else + if (rq.id == nil) then + ret = model:getAll() + else + ret = model:get(rq.id) + end + model:close() + result(ret) + end + else + fail("Unknown database request") + end +end + +function VDBController:select(...) + auth_or_die("User unauthorized. Please login") + local rq = (JSON.decodeString(REQUEST.json)) + if (rq ~= nil and rq.table ~= nil) then + local model = require("dbmodel").get(SESSION.user, rq.table, nil) + local ret + if model == nil then + fail("Cannot get table metadata:" .. rq.table) + else + if (rq.cond == nil) then + model:close() + return fail("Unknow condition") + else + ret = model:find(rq.cond) + end + model:close() + result(ret) + end + else + fail("Unknown database request") + end +end + +function VDBController:delete(...) + auth_or_die("User unauthorized. Please login") + local rq = (JSON.decodeString(REQUEST.json)) + if (rq ~= nil and rq.table ~= nil) then + local model = require("dbmodel").get(SESSION.user, rq.table, nil) + local ret + if model == nil then + fail("Cannot get table metadata:" .. rq.table) + else + if (rq.id == nil) then + if (rq.cond) then + ret = model:delete(rq.cond) + model:close() + else + model:close() + return fail("Unknow element to delete") + end + else + ret = model:deleteByID(rq.id) + model:close() + end + if ret then + result(ret) + else + fail("Querry error or database is locked") + end + end + else + fail("Unknown database request") + end +end diff --git a/controllers/VFSController.lua b/controllers/VFSController.lua new file mode 100644 index 0000000..2e1028f --- /dev/null +++ b/controllers/VFSController.lua @@ -0,0 +1,222 @@ +BaseController:subclass( + "VFSController", + { + registry = {}, + models = {} + } +) + +function VFSController:actionnotfound(...) + return self:index(table.unpack({...})) +end + +function VFSController:index(...) + local api = { + description = "This api handle file operations", + actions = { + ["/fileinfo"] = "Get file information", + ["/exists"] = "Check if file exists", + ["/delete"] = "Delete a file", + ["/get"] = "Get a file content", + ["/mkdir"] = "Create directory", + ["/move"] = "Move file to a new destination", + ["/publish"] = "Share a file to all users", + ["/scandir"] = "List all files and folders", + ["/write"] = "Write data to file", + ["/shared"] = "Get shared file content" + } + } + result(api) + return false +end + +function VFSController:fileinfo(...) + auth_or_die("User unauthorized. Please login") + local vfspath = (JSON.decodeString(REQUEST.json)).path + local r, m = require("vfs").fileinfo(vfspath) + if r then + result(m) + else + fail(m) + end + return false +end + +function VFSController:exists(...) + auth_or_die("User unauthorized. Please login") + + local vfs = require("vfs") + local rq = (JSON.decodeString(REQUEST.json)) + + if rq ~= nil then + result(vfs.exists(rq.path)) + else + fail("Uknown request") + end + return false +end + +function VFSController:delete(...) + auth_or_die("User unauthorized. Please login") + + local vfs = require("vfs") + local rq = (JSON.decodeString(REQUEST.json)) + + if rq ~= nil then + local r, e = vfs.delete(rq.path) + if r then + result(r) + else + fail(e) + end + else + fail("Uknown request") + end + return false +end + +function VFSController:get(...) + local args = {...} + local uri = implode(args, "/") + auth_or_die("User unauthorized. Please login") + local vfsfile = utils.decodeURI(uri) + local r, m = require("vfs").checkperm(vfsfile, "read") + if r then + std.sendFile(m) + else + fail(m) + end + + return false +end + +function VFSController:mkdir(...) + auth_or_die("User unauthorized. Please login") + + local rq = (JSON.decodeString(REQUEST.json)) + + if rq ~= nil then + local r, m = require("vfs").mkdir(rq.path) + if r then + result(r) + else + fail(m) + end + else + fail("Uknown request") + end + return false +end + +function VFSController:move(...) + auth_or_die("User unauthorized. Please login") + + local rq = (JSON.decodeString(REQUEST.json)) + + if rq ~= nil then + local r, m = require("vfs").move(rq.src, rq.dest) + if r then + result(r) + else + fail(m) + end + else + fail("Uknown request") + end + return false +end + +function VFSController:publish(...) + auth_or_die("User unauthorized. Please login") + + local rq = (JSON.decodeString(REQUEST.json)) + + if rq ~= nil then + local p = nil + if rq.publish then + p = require("vfs").ospath(rq.path) + else + p = require("shared").ospath(rq.path) + end + local user = SESSION.user + local uid = ulib.uid(user) + local st = ulib.file_stat(p) + if uid.id ~= st.uid then + die("Only the owner can share or unshare this file") + end + local entry = {sid = enc.sha1(p), user = SESSION.user, path = p, uid = uid.id} + local db = require("dbmodel").get("sysdb", "shared", entry) + if db == nil then + die("Cannot get system database") + end + local cond = nil + if rq.publish then + cond = {exp = {["="] = {path = p}}} + local data = db:find(cond) + if data == nil or data[0] == nil then + -- insert entry + db:insert(entry) + end + else + cond = {["="] = {sid = rq.path}} + db:delete(cond) + end + db:close() + result(entry.sid) + else + fail("Uknown request") + end + return false +end + +function VFSController:scandir(...) + auth_or_die("User unauthorized. Please login") + local rq = JSON.decodeString(REQUEST.json) + local vfspath = rq.path + local r = require("vfs").readDir(vfspath) + if r == nil then + fail("Resource not found: " .. rq.path) + else + --print(JSON.encode(readDir(ospath, vfspath))) + result(r) + end + return false +end + +function VFSController:upload(...) + auth_or_die("User unauthorized. Please login") + local vfs = require("vfs") + if REQUEST and REQUEST.path then + local r, m = require("vfs").upload(REQUEST.path) + if r then + result(r) + else + self:error(m) + fail(m) + end + else + fail("Invalid query") + end + return false +end + +function VFSController:write(...) + auth_or_die("User unauthorized. Please login") + local rq = (JSON.decodeString(REQUEST.json)) + + if rq ~= nil then + local r, m = require("vfs").write(rq.path, rq.data) + if r then + result(r) + else + fail(m) + end + else + fail("Uknown request") + end + return false +end + +function VFSController:shared(sid) + require("shared").get(sid) +end \ No newline at end of file diff --git a/libs/common.lua b/libs/common.lua new file mode 100644 index 0000000..47ee09e --- /dev/null +++ b/libs/common.lua @@ -0,0 +1,80 @@ +require("silk.core.sqlite") +local TUNNEL_KEYCHAIN = "/opt/www/tmp/channels/antunnel_keychain" +function fail(msg) + std.custom_header("Connection", "close") + std.json() + std.t(JSON.encode({ + error = msg + })) +end + +function result(obj) + std.custom_header("Connection", "close") + std.json() + std.t(JSON.encode({ + result = obj, + error = false + })) +end + +function die(msg) + fail(msg) + debug.traceback = nil + error("Permission denied") + return false +end + +-- check if the sysdb is create, otherwise create the table +function sysdb() + local meta = {} + meta.sessionid = "" + meta.username = "" + meta.stamp = 0 + return require("dbmodel").get("sysdb", "sessions", meta) +end + +function is_auth() + local sessionid = nil + if SESSION.sessionid and SESSION.sessionid ~= '0' then + sessionid = SESSION.sessionid + -- should be used only by API call + elseif REQUEST.sessionid and REQUEST.sessionid ~= '0' then + sessionid = REQUEST.sessionid + elseif REQUEST.access_token and REQUEST.access_token ~= '0' then + sessionid = REQUEST.access_token + end + if sessionid == nil then + return false + end + -- query session id from database + local db = sysdb() + if db == nil then + return false + end + local cond = { + exp = { + ["="] = { + sessionid = sessionid + } + } + } + local data = db:find(cond) + db:close() + if data == nil or data[1] == nil then + return false + end + -- TODO check the stamp + SESSION.user = data[1].username + local f = io.open(TUNNEL_KEYCHAIN, "w") + if f then + f:write(sessionid .. SESSION.user) + f:close() + end + return true +end + +function auth_or_die(msg) + if (is_auth() == false) then + die(msg) + end +end diff --git a/libs/dbmodel.lua b/libs/dbmodel.lua new file mode 100644 index 0000000..ede8ed6 --- /dev/null +++ b/libs/dbmodel.lua @@ -0,0 +1,57 @@ +local model = {} + +model.get = function(name, tbl, data) + local mod = { + name = tbl, + db = DBModel:new{db = name} + } + function mod:close() + self.db:close() + end + function mod:open() + self.db:open() + end + function mod:createTable(m) + return self.db:createTable(self.name,m) + end + function mod:insert(m) + return self.db:insert(self.name,m) + end + function mod:get(id) + return self.db:get(self.name, id) + end + function mod:getAll() + return self.db:getAll(self.name) + end + function mod:find(cond) + return self.db:find(self.name, cond) + end + function mod:update(m) + return self.db:update(self.name, m) + end + function mod:available() + return self.db:available(self.name) + end + function mod:delete(cond) + return self.db:delete(self.name, cond) + end + function mod:deleteByID(id) + return self.db:deleteByID(self.name, id) + end + mod:open() + + if mod:available() then return mod end + if data == nil then return nil end + local meta = {} + --print(JSON.encode(data)) + for k,v in pairs(data) do + if type(v) == "number" or type(v) == "boolean" then + meta[k] = "NUMERIC" + else + meta[k] = "TEXT" + end + end + mod:createTable(meta) + return mod +end +return model \ No newline at end of file diff --git a/libs/packages.lua b/libs/packages.lua new file mode 100644 index 0000000..415593b --- /dev/null +++ b/libs/packages.lua @@ -0,0 +1,119 @@ +local packages={} +local vfs = require("vfs") +local uid = ulib.uid(SESSION.user) + +packages._cache = function(y) + local p = vfs.ospath(y) + local f = io.open(p.."/packages.json", "w") + local has_cache = false + local i = 1 + local meta = {} + if f then + local files = vfs.readDir(y) + for k,v in pairs(files) do + if v.type == "dir" then + local f1 = io.open(vfs.ospath(v.path.."/package.json")) + if f1 then + + local name = utils.basename(v.path) + local mt = JSON.decodeString(f1:read("*all")) + mt.path = v.path + meta[i] ='"'..name..'":'..JSON.encode(mt) + i = i+1 + f1:close() + has_cache = true; + end + end + end + f:write(table.concat(meta, ",")) + f:close() + if has_cache == false then + ulib.delete(p.."/packages.json"); + end + end +end + +-- we will change this later +packages.list = function(paths) + std.json() + std.t("{\"result\" : { ") + local first = true + --std.f(__ROOT__.."/system/packages.json") + for k,v in pairs(paths) do + local osp = vfs.ospath(v.."/packages.json") + if ulib.exists(osp) == false then + packages._cache(v) + end + if ulib.exists(osp) then + if first == false then + std.t(",") + else + first = false + end + std.f(osp) + end + end + std.t("}, \"error\":false}") +end + +-- generate the packages caches +packages.cache = function(args) + -- perform a packages caches + for x,y in pairs(args.paths) do + packages._cache(y) + end + result(true) +end +-- install a function from zip file +packages.install = function(args) + local path = vfs.ospath(args.dest) + local zip = vfs.ospath(args.zip) + if(ulib.exists(path) == false) then + -- create directory if not exist + ulib.mkdir(path) + -- change permission + ulib.chown(path, uid.id, uid.gid) + end + -- extract the zip file to it + if(ulib.unzip(zip, path)) then + -- read metadata + local meta = JSON.decodeFile(path.."/metadata.json") + meta.path = args.dest + meta.scope = "user" + local f=io.open(path.."/package.json","w") + if f then + f:write(JSON.encode(meta)) + f:close() + end + result(true) + else + fail("Problem extracting zip file") + end + +end +-- uninstall the package +packages.uninstall = function(path) + local osf = vfs.ospath(path) + if(osf and ulib.exists(osf) ) then + --remove it + ulib.delete(osf) + result(true) + else + fail("Cannot find package") + end +end +-- set user packages environment +packages.init = function(paths) + if(paths) then + for k,v in pairs(paths) do + local p = vfs.ospath(v) + if p and (ulib.exists(p) == false) then + ulib.mkdir(p) + -- change permission + ulib.chown(p, uid.id, uid.gid) + end + end + end +end + +return packages \ No newline at end of file diff --git a/libs/shared.lua b/libs/shared.lua new file mode 100644 index 0000000..dd5639f --- /dev/null +++ b/libs/shared.lua @@ -0,0 +1,45 @@ +local shared = {} +shared.get = function(sharedid) + if sharedid == "all" then + -- get all shared files + local db = require("dbmodel").get("sysdb", "shared", nil) + if db == nil then die("Cannot get shared database") end + local data = db:getAll() + if data == nil then die("No file found") end + local i = 1 + local ret = {} + for k,v in pairs(data) do + if(ulib.exists(v.path)) then + local r = ulib.file_stat(v.path) + if(r.error == nil) then + r.path = "shared://"..v.sid + r.filename = utils.basename(v.path) + r.mime = std.mimeOf(r.filename) + ret[i] = r + i = i+1 + end + else + local cond = { ["="] = { sid = v.sid } } + db:delete(cond) + end + end + db:close() + --std.json() + result(ret) + else + + local p = shared.ospath(sharedid) + std.sendFile(p) + end +end + +shared.ospath = function(sharedid) + local db = require("dbmodel").get("sysdb", "shared", nil) + if db == nil then die("Cannot get shared database") end + local cond = { exp = { ["="] = { sid = sharedid } } } + local data = db:find(cond) + db:close() + if data == nil or data[1] == nil then die("Cannot get shared file with: "..sharedid) end + return data[1].path +end +return shared; diff --git a/libs/uman.lua b/libs/uman.lua new file mode 100644 index 0000000..b268e9b --- /dev/null +++ b/libs/uman.lua @@ -0,0 +1,27 @@ +local uman={} + +uman.userinfo = function(user) + local info = {} + local uid = ulib.uid(user) + if uid then + -- read the setting + -- use the decodeFile function of JSON instead + local file = require('vfs').ospath("home:///").."/.settings.json" + local st = JSON.decodeFile(file) + if(st) then + info = st + end + info.user = { + username = user, + id = uid.id, + name = user, + groups = uid.groups + } + --print(JSON.encode(info)) + return info + else + return {} + end +end + +return uman \ No newline at end of file diff --git a/libs/vfs.lua b/libs/vfs.lua new file mode 100644 index 0000000..0d9bd9a --- /dev/null +++ b/libs/vfs.lua @@ -0,0 +1,241 @@ +local vfs = {} + +vfs.ospath = function(path) + local user = SESSION.user + local prefix = string.match(path, "%a+:/") + if (prefix ~= nil) then + local suffix = string.gsub(path, prefix, "") + if prefix == "home:/" then + return string.format(VFS_HOME, user) .. '/' .. suffix + elseif prefix == "desktop:/" then + return string.format(VFS_HOME, user) .. "/.desktop/" .. suffix + elseif prefix == "shared:/" then + return require("shared").ospath(ulib.trim(suffix, "/")) + elseif prefix == "os:/" then + return WWW_ROOT .. "/" .. suffix + else + return nil + end + else + return nil; + end +end + +vfs.delete = function(path) + local r, m = vfs.checkperm(path, "write") + if r then + if ulib.delete(m) then + -- change permission + return true, nil + else + return false, "Cant not delete the file" + end + else + return r, m + end +end + +vfs.exists = function(path) + local osfile = vfs.ospath(path) + return ulib.exists(osfile) +end + +vfs.fileinfo = function(vfspath) + local ospath = vfs.ospath(vfspath) + if ospath then + if (ulib.exists(ospath) == false) then + return false, "File not found" + end + local r = ulib.file_stat(ospath) + if (r.error ~= nil) then + return false, r.error + end + r.path = vfspath + r.name = utils.basename(vfspath) + r.mime = std.mimeOf(r.name) + return true, r + else + return false, "Resource not found" + end +end + +vfs.mkdir = function(path) + local file = utils.basename(path) + local folder = string.gsub(path, utils.escape_pattern(file) .. "$", "") + local r, m = vfs.checkperm(folder, "write") + + if r then + local osfile = m .. "/" .. file + local uid = ulib.uid(SESSION.user) + ulib.mkdir(osfile) + -- change permission + ulib.chown(osfile, uid.id, uid.gid) + return true, nil + else + return r, m + end +end + +vfs.move = function(src, dest) + local file = utils.basename(dest) + local folder = string.gsub(dest, utils.escape_pattern(file), "") + + local sp, sm = vfs.checkperm(src, "write") + if sp then + local dp, dm = vfs.checkperm(folder, "write") + if dp then + ulib.move(sm, dm .. "/" .. file) + -- change permission + return true, nil + else + return dp, dm + end + else + return sp, sm + end +end + +vfs.write = function(path, data) + local file = utils.basename(path) + local folder = string.gsub(path, utils.escape_pattern(file), "") + + local r, m = vfs.checkperm(folder, "write") + if r then + local osfile = m .. "/" .. file + + if ulib.exists(osfile) then + local r1, m1 = vfs.checkperm(path, "write") + if not r1 then + return r1, m1 .. ": " .. path + end + end + + local uid = ulib.uid(SESSION.user) + -- + if data ~= "" then + local header = string.match(data, "^data%:[%w%.-%+]+%/[%w%.-%+]+;base64,") + if header ~= nil then + local b64data = string.gsub(data, utils.escape_pattern(header), "") + local barr = enc.b64decode(b64data) + + barr:fileout(osfile) + --[[ if std.isBinary(osfile) then + + else + local f = io.open(osfile, "w") + f:write(bytes.__tostring(barr)) + f:close() + end ]] + else + return false, "Wrong data format" + end + else + slice.new(0):fileout(osfile) + end + -- f:close() + -- change permission + ulib.chown(osfile, uid.id, uid.gid) + return true, nil + else + return r, m .. ": " .. folder + end +end + +vfs.upload = function(path) + if (not path) then + return false, "Unknown upload destination, abort!" + end + local r, m = vfs.checkperm(path, "write") + if (r) then + local uid = ulib.uid(SESSION.user) + local index = 0 + while (REQUEST["upload-" .. index .. ".tmp"] ~= nil) do + local file = m .. "/" .. REQUEST["upload-" .. index .. ".file"] + local ret = ulib.move(REQUEST["upload-" .. index .. ".tmp"], file) + if not ret then + ret = ulib.send_file(REQUEST["upload-" .. index .. ".tmp"], file) + end + if not ret then + return false, "Unable to copy file" + end + ulib.chown(file, uid.id, uid.gid) + index = index + 1 + end + if (index == 0) then + return false, "No file is uploaded" + end + return true, index + else + return r, m + end +end + +vfs.checkperm = function(path, right) + if path:match("^shared:/.*") then + if right == "write" then + return false, "Shared file is readonly" + else + return true + end + end + local osfile = vfs.ospath(path) + local perm = vfs.perm(osfile) + -- print(osfile) + if not ulib.exists(osfile) then + return false, "Resource does not exist" + end + -- check if user own the file + if perm ~= nil then + if perm[right] == true then + -- print("Permission granted") + return true, osfile + else + LOG_INFO("Permission denie") + return false, "You dont have " .. right .. " permission on this file" + end + else + return false, "User is unrecognized" + end +end + +vfs.perm = function(file) + local user = SESSION.user + local uid = ulib.uid(user) + local st = ulib.file_stat(file) + -- check if user own the file + if uid ~= nil and st ~= nil and st.perm ~= nil then + -- print(JSON.encode({uid, st})) + if (uid.id == st.uid) then -- the user owned the file + -- print("file belong to user") + return st.perm.owner + elseif uid.groups and uid.groups[st.gid] then + -- print("User belong to this group") + return st.perm.group + else + -- print("User belong to other") + return st.perm.other + end + else + return nil + end +end + +vfs.readDir = function(vfspath) + if (string.sub(vfspath, -1) == "/") then + prefix = string.sub(vfspath, 1, -2) + else + prefix = vfspath + end + local ospath = vfs.ospath(vfspath) + local r = ulib.read_dir(ospath, prefix) + if (r.error ~= nil) then + return nil + end + -- add extra mime type + for k, v in pairs(r) do + v.mime = std.mimeOf(v.filename) + end + return r +end + +return vfs diff --git a/router.lua b/router.lua new file mode 100644 index 0000000..5870db5 --- /dev/null +++ b/router.lua @@ -0,0 +1,46 @@ + +-- the rewrite rule for the framework +-- should be something like this +-- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>& +-- some global variables +package.path = _SERVER["LIB_DIR"].."/lua/?.lua" +require("silk.api") +-- crypto lib +enc = require("enc") +WWW_ROOT = __ROOT__.."/os" + +-- add aditional paths +package.path = package.path..";"..WWW_ROOT .. '/libs/?.lua' +-- require needed library +require("common") + +DIR_SEP = "/" +VFS_HOME = "/home/%s" +-- class path: path.to.class +CONTROLLER_ROOT = "os.controllers" +MODEL_ROOT = "os.models" +-- file path: path/to/file +VIEW_ROOT = WWW_ROOT..DIR_SEP.."views" + +if HEADER.Host then + HTTP_ROOT= "https://"..HEADER["Host"] +else + HTTP_ROOT = "https://os.lxsang.me" +end + +-- registry object store global variables +local REGISTRY = {} +-- set logging level +REGISTRY.logger = Logger:new{ level = Logger.INFO} +--REGISTRY.db = DBHelper:new{db="sysdb"} +REGISTRY.layout = 'default' +REGISTRY.fileaccess = true + +--REGISTRY.db:open() +local router = Router:new{registry = REGISTRY} +REGISTRY.router = router +router:setPath(CONTROLLER_ROOT) + +router:delegate() +--if REGISTRY.db then REGISTRY.db:close() end +