From 3d74b1e93ef8f6e3b7a1edaf48dd72e6c02a1c39 Mon Sep 17 00:00:00 2001 From: Ilya Zhuravlev Date: Wed, 28 Mar 2012 23:38:36 +0400 Subject: [PATCH] Started privs mod --- privs/README | 42 ++++++++++ privs/debug.lua | 39 +++++++++ privs/init.lua | 217 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 298 insertions(+) create mode 100644 privs/README create mode 100644 privs/debug.lua create mode 100644 privs/init.lua diff --git a/privs/README b/privs/README new file mode 100644 index 0000000..945ab1b --- /dev/null +++ b/privs/README @@ -0,0 +1,42 @@ +The text written below will be updated... some day + +Privileges mod for Minetest + +Inheritance + Privileges can be divided into privilege groups using ":" symbol. Good practice is to name your privileges like modname:privname[:something]. + For example, mod that teleports players (let's name it "tp") can consists of such privileges: + tp -- global privilege, that includes others + tp:self -- teleport self somewhere + tp:self:coords -- teleport self to arbitrary position + tp:self:player -- teleport self to other player + tp:smb -- teleport somebody somewhere + tp:smb:coords -- teleport somebody to arbitrary position + tp:smb:coords -- teleport somebody to other player + Then you could give "tp" privilege to server administrators/moderators (remember that global admin who is specified in minetest.conf under "name = " parameter has all privileges), they therefore will have full access to all tp: privileges. + Also deeper is more important. So, if you have such privileges: + a - allow + a:b - deny + a:b:c - nil + a:d - nil + and check for a:b:c it will return deny + and a:d will return allow, of course. + +Groups: + Another good practice is to divide players into groups. You (as a mod maker) can do it using such API call: + privs.register(name, [default, group]) + Default value for "group" is just "nil". It means "everybody". You can use any groups in your mods, but preferably ones are: + admin, mod + Admin should have all access (?), by default global admin (specified in minetest.conf) have all privileges. + +Privs mod privileges [default values]: + privs:grant [0] -- grant privileges to players + privs:revoke [0] -- revoke privileges from players + privs:has [0] -- check for privilege + privs:list:self + privs:list:smb + +Chat commands: + /g priv [player] -- grant privilege to player (or yourself), requires privs:grant + /r priv [player] -- revoke, requires privs:revoke + /h priv [player] -- check for [player]'s privilege (or yours), requires privs:has + /l [player] -- list all non-default privileges, requires privs:list diff --git a/privs/debug.lua b/privs/debug.lua new file mode 100644 index 0000000..a2dccd0 --- /dev/null +++ b/privs/debug.lua @@ -0,0 +1,39 @@ +local function print_r (t, indent, done) + done = done or {} + indent = indent or '' + local nextIndent -- Storage for next indentation value + for key, value in pairs (t) do + if type (value) == "table" and not done [value] then + nextIndent = nextIndent or + (indent .. string.rep(' ',string.len(tostring (key))+2)) + -- Shortcut conditional allocation + done [value] = true + print (indent .. "[" .. tostring (key) .. "] => Table {"); + print (nextIndent .. "{"); + print_r (value, nextIndent .. string.rep(' ',2), done) + print (nextIndent .. "}"); + else + print (indent .. "[" .. tostring (key) .. "] => " .. tostring (value).."") + end + end +end + +privs:register("teleport:everybody:coordinates", false) +privs:register("teleport:everybody:coordinates", true, "admin") +privs:register("teleport:everybody:coordinates", true, "admin") +privs:register("drive:car") +privs:register("drive:car:safe:a:b:c") + +privs:allow("drive:car", "@admin") +--privs:deny("drive:car:safe:a", "xyz") +print(privs:check("drive:car:safe:a:b:c", "xyz")) +privs:join("xyz", "admin") +print(privs:check("drive:car:safe:a:b:c", "xyz")) +privs:deny("drive:car:safe", "xyz") +print(privs:check("drive:car:safe:a:b:c", "xyz")) +--privs:allow("drive:car:safe", "xyz") +--privs:save() +--privs:reload() +--print_r(privs:get("")) + +print("--debug finished--") diff --git a/privs/init.lua b/privs/init.lua new file mode 100644 index 0000000..179e8c6 --- /dev/null +++ b/privs/init.lua @@ -0,0 +1,217 @@ +-- Copyright (c) 2012, xyz + +-- Permission is hereby granted, free of charge, to any person obtaining +-- a copy of this software and associated documentation files (the +-- "Software"), to deal in the Software without restriction, including +-- without limitation the rights to use, copy, modify, merge, publish, +-- distribute, sublicense, and/or sell copies of the Software, and to +-- permit persons to whom the Software is furnished to do so, subject to +-- the following conditions: +-- +-- The above copyright notice and this permission notice shall be +-- included in all copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +-- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +-- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +-- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +-- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +-- Save privileges to file every 30 seconds +local SAVE_TIME = 30 + +local DEBUG = false + +local function split(str, sep) + local sep, fields = sep or ":", {} + local pattern = string.format("([^%s]+)", sep) + str:gsub(pattern, function(c) fields[#fields+1] = c end) + return fields +end + +-- splits a:b:c to {a, b, c} +local function parse_privilege(name) + return split(name) +end + +local function build_privilege(privs) + return table.concat(privs, ":") +end + +local function single_or_group(name) + local c = name:sub(1, 1) + if c == "@" or c == "~" then + return name + else + return "~"..name + end +end + +privs = { + privileges = {}, + groups = {}, + -- returns privilege, if not exists -- creates it (if create == true) or returns nil (otherwise) + get = function(self, privilege, create) + if create == nil then + create = false + end + + local particles = parse_privilege(privilege) + local current = self.privileges + for i, particle in ipairs(particles) do + if current[particle] == nil then + if not create then + return nil + else + current[particle] = {} + end + end + current = current[particle] + end + return current + end, + safe_get = function(self, priv) + local privilege = self:get(priv) + if privilege == nil then + if priv == nil then + priv = "~nil~" + end + print("privs:safe_get() => privilege "..priv.." does not exists") + return nil + end + return privilege + end, + -- registers default value for privilege "name" and group "group" + register = function(self, priv, default, group) + local privilege = self:get(priv, true) + if group ~= nil then + privilege["@"..group] = default + else + privilege["~"] = default + end + end, + -- [re]loads privileges from file + reload = function(self) + local fin = io.open("privileges.data", "r") + while true do + local value = fin:read("*number") + if value == nil then + break + end + local priv = fin:read("*line"):sub(2) + local priv_parsed = parse_privilege(priv) + local entity = priv_parsed[#priv_parsed] + table.remove(priv_parsed) + priv = build_privilege(priv_parsed) + privs:get(priv, true) + if value == 0 then + self:deny(priv, entity) + elseif value == 1 then + self:allow(priv, entity) + end + end + fin:close() + end, + -- writes single privilege + write = function(self, privilege, file, current) + for priv, i in pairs(privilege) do + local c = priv:sub(1, 1) + if c == "@" or c == "~" then + file:write((i and 1 or 0).." "..current..priv.."\n") + else + self:write(privilege[priv], file, current..priv..":") + end + end + end, + -- saves privileges to file + save = function(self) + local fout = io.open("privileges.data", "w") + self:write(self.privileges, fout, "") + fout:close() + end, + allow = function(self, priv, name) + local privilege = self:safe_get(priv) + if privilege == nil then + return + end + name = single_or_group(name) + privilege[name] = true + end, + reset = function(self, priv, name) + local privilege = self:safe_get(priv) + if privilege == nil then + return + end + name = single_or_group(name) + privilege[name] = nil + end, + deny = function(self, priv, name) + local privilege = self:safe_get(priv) + if privilege == nil then + return + end + name = single_or_group(name) + privilege[name] = false + end, + check = function(self, priv, name) + if priv == "" then + return false + end + local privilege = self:safe_get(priv) + if privilege == nil then + return + end + + local current = privilege["~"..name] + if current ~= nil then + return current + end + local player_groups = self.groups[name] + if player_groups ~= nil then + for group in pairs(player_groups) do + current = privilege["@"..group] + if current ~= nil then + return current + end + end + end + current = privilege["~"] + if current ~= nil then + return current + end + local priv_new = parse_privilege(priv) + table.remove(priv_new) + return self:check(build_privilege(priv_new), name) + end, + get_player_groups = function(self, player) + if self.groups[player] == nil then + self.groups[player] = {} + end + return self.groups[player] + end, + join = function(self, player, group) + local player_groups = self:get_player_groups(player) + player_groups[group] = true + end, + part = function(self, player, group) + local player_groups = self:get_player_groups(player) + player_groups[group] = nil + end +} + +if arg[1] == "debug" then + DEBUG = true + dofile("debug.lua") +else + -- minetest-specific code should go here + local delta = 0 + minetest:register_globalstep(function(dtime) + delta = delta + dtime + if (delta > SAVE_TIME) then + delta = 0 + privs:save() + end + end) +end