diff options
author | Jlll1 <arghantentua@tutanota.com> | 2022-11-28 20:23:04 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-28 12:23:04 -0700 |
commit | f004812b338340e5f5157aa68d09d3f0e5605c6c (patch) | |
tree | 415f51509f9b19037d87bb3e8d2286b8a68da2a1 | |
parent | 77a0f4a542ad9354c647b6bafc1bbd5579212a9e (diff) | |
download | rneovim-f004812b338340e5f5157aa68d09d3f0e5605c6c.tar.gz rneovim-f004812b338340e5f5157aa68d09d3f0e5605c6c.tar.bz2 rneovim-f004812b338340e5f5157aa68d09d3f0e5605c6c.zip |
feat(secure): add `:trust` command and vim.secure.trust() (#21107)
Introduce vim.secure.trust() to programmatically manage the trust
database. Use this function in a new :trust ex command which can
be used as a simple frontend.
Resolves: https://github.com/neovim/neovim/issues/21092
Co-authored-by: Gregory Anders <greg@gpanders.com>
Co-authored-by: ii14 <ii14@users.noreply.github.com>
-rw-r--r-- | runtime/doc/editing.txt | 28 | ||||
-rw-r--r-- | runtime/doc/index.txt | 1 | ||||
-rw-r--r-- | runtime/doc/lua.txt | 24 | ||||
-rw-r--r-- | runtime/doc/news.txt | 3 | ||||
-rw-r--r-- | runtime/doc/options.txt | 2 | ||||
-rw-r--r-- | runtime/lua/vim/secure.lua | 139 | ||||
-rw-r--r-- | src/nvim/ex_cmds.c | 27 | ||||
-rw-r--r-- | src/nvim/ex_cmds.lua | 6 | ||||
-rw-r--r-- | src/nvim/ex_docmd.c | 1 | ||||
-rw-r--r-- | src/nvim/globals.h | 2 | ||||
-rw-r--r-- | src/nvim/lua/executor.c | 48 | ||||
-rw-r--r-- | test/functional/ex_cmds/trust_spec.lua | 176 | ||||
-rw-r--r-- | test/functional/lua/secure_spec.lua | 107 |
13 files changed, 538 insertions, 26 deletions
diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt index 76c528ef3c..e379d9eeb1 100644 --- a/runtime/doc/editing.txt +++ b/runtime/doc/editing.txt @@ -1650,4 +1650,32 @@ There are three different types of searching: currently work with 'path' items that contain a URL or use the double star with depth limiter (/usr/**2) or upward search (;) notations. +============================================================================== +11. Trusted Files *trust* + +Nvim has the ability to execute arbitrary code through the 'exrc' option. In +order to prevent executing code from untrusted sources, Nvim has the concept of +"trusted files". An untrusted file will not be executed without the user's +consent, and a user can permanently mark a file as trusted or untrusted using +the |:trust| command or the |vim.secure.read()| function. + + *:trust* *E5570* +:trust [++deny] [++remove] [{file}] + + Manage files in the trust database. Without any options + or arguments, :trust adds the file associated with the + current buffer to the trust database, along with the + SHA256 hash of its contents. + + [++deny] marks the file associated with the current + buffer (or {file}, if given) as denied; no prompts will + be displayed to the user and the file will never be + executed. + + [++remove] removes the file associated with the current + buffer (or {file}, if given) from the trust database. + Future attempts to read the file in a secure setting + (i.e. with 'exrc' or |vim.secure.read()|) will prompt + the user if the file is trusted. + vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt index 376487ad1d..66353e05f3 100644 --- a/runtime/doc/index.txt +++ b/runtime/doc/index.txt @@ -1633,6 +1633,7 @@ tag command action ~ |:topleft| :to[pleft] make split window appear at top or far left |:tprevious| :tp[revious] jump to previous matching tag |:trewind| :tr[ewind] jump to first matching tag +|:trust| :trust add or remove file from trust database |:try| :try execute commands, abort on error or exception |:tselect| :ts[elect] list matching tags and select one |:tunmap| :tunma[p] like ":unmap" but for |Terminal-mode| diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 005b6409d6..bcbbd69f11 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -2371,4 +2371,28 @@ read({path}) *vim.secure.read()* (string|nil) The contents of the given file if it exists and is trusted, or nil otherwise. + See also: ~ + |:trust| + +trust({opts}) *vim.secure.trust()* + Manage the trust database. + + The trust database is located at |$XDG_STATE_HOME|/nvim/trust. + + Parameters: ~ + • {opts} (table) + • action (string): "allow" to add a file to the trust database + and trust it, "deny" to add a file to the trust database and + deny it, "remove" to remove file from the trust database + • path (string|nil): Path to a file to update. Mutually + exclusive with {bufnr}. Cannot be used when {action} is + "allow". + • bufnr (number|nil): Buffer number to update. Mutually + exclusive with {path}. + + Return: ~ + (boolean, string) success, msg: + • true and full path of target file if operation was successful + • false and error message on failure + vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl: diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 70643cf00c..b155bae5a4 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -39,6 +39,9 @@ NEW FEATURES *news-features* The following new APIs or features were added. +• |vim.secure.trust()|, |:trust| allows the user to manage files in trust + database. + • |vim.diagnostic.open_float()| (and therefore |vim.diagnostic.config()|) now accepts a `suffix` option which, by default, renders LSP error codes. Similarly, the `virtual_text` configuration in |vim.diagnostic.config()| now diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 36eabfdcbf..33bade3545 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -2275,6 +2275,8 @@ A jump table for the options with a short description can be found at |Q_op|. file are persisted to a trust database. The user is only prompted again if the file contents change. See |vim.secure.read()|. + Use |:trust| to manage the trusted file database. + This option cannot be set from a |modeline| or in the |sandbox|, for security reasons. diff --git a/runtime/lua/vim/secure.lua b/runtime/lua/vim/secure.lua index 341ff8df05..0b4d7d53a2 100644 --- a/runtime/lua/vim/secure.lua +++ b/runtime/lua/vim/secure.lua @@ -1,9 +1,50 @@ local M = {} +---@private +--- Reads trust database from $XDG_STATE_HOME/nvim/trust. +--- +---@return (table) Contents of trust database, if it exists. Empty table otherwise. +local function read_trust() + local trust = {} + local f = io.open(vim.fn.stdpath('state') .. '/trust', 'r') + if f then + local contents = f:read('*a') + if contents then + for line in vim.gsplit(contents, '\n') do + local hash, file = string.match(line, '^(%S+) (.+)$') + if hash and file then + trust[file] = hash + end + end + end + f:close() + end + return trust +end + +---@private +--- Writes provided {trust} table to trust database at +--- $XDG_STATE_HOME/nvim/trust. +--- +---@param trust (table) Trust table to write +local function write_trust(trust) + vim.validate({ trust = { trust, 't' } }) + local f = assert(io.open(vim.fn.stdpath('state') .. '/trust', 'w')) + + local t = {} + for p, h in pairs(trust) do + t[#t + 1] = string.format('%s %s\n', h, p) + end + f:write(table.concat(t)) + f:close() +end + --- Attempt to read the file at {path} prompting the user if the file should be --- trusted. The user's choice is persisted in a trust database at --- $XDG_STATE_HOME/nvim/trust. --- +---@see |:trust| +--- ---@param path (string) Path to a file to read. --- ---@return (string|nil) The contents of the given file if it exists and is @@ -15,22 +56,7 @@ function M.read(path) return nil end - local trust = {} - do - local f = io.open(vim.fn.stdpath('state') .. '/trust', 'r') - if f then - local contents = f:read('*a') - if contents then - for line in vim.gsplit(contents, '\n') do - local hash, file = string.match(line, '^(%S+) (.+)$') - if hash and file then - trust[file] = hash - end - end - end - f:close() - end - end + local trust = read_trust() if trust[fullpath] == '!' then -- File is denied @@ -86,21 +112,82 @@ function M.read(path) trust[fullpath] = hash end - do - local f, err = io.open(vim.fn.stdpath('state') .. '/trust', 'w') - if not f then - error(err) + write_trust(trust) + + return contents +end + +--- Manage the trust database. +--- +--- The trust database is located at |$XDG_STATE_HOME|/nvim/trust. +--- +---@param opts (table): +--- - action (string): "allow" to add a file to the trust database and trust it, +--- "deny" to add a file to the trust database and deny it, +--- "remove" to remove file from the trust database +--- - path (string|nil): Path to a file to update. Mutually exclusive with {bufnr}. +--- Cannot be used when {action} is "allow". +--- - bufnr (number|nil): Buffer number to update. Mutually exclusive with {path}. +---@return (boolean, string) success, msg: +--- - true and full path of target file if operation was successful +--- - false and error message on failure +function M.trust(opts) + vim.validate({ + path = { opts.path, 's', true }, + bufnr = { opts.bufnr, 'n', true }, + action = { + opts.action, + function(m) + return m == 'allow' or m == 'deny' or m == 'remove' + end, + [["allow" or "deny" or "remove"]], + }, + }) + + local path = opts.path + local bufnr = opts.bufnr + local action = opts.action + + if path and bufnr then + error('path and bufnr are mutually exclusive', 2) + end + + local fullpath + if path then + fullpath = vim.loop.fs_realpath(vim.fs.normalize(path)) + else + local bufname = vim.api.nvim_buf_get_name(bufnr) + if bufname == '' then + return false, 'buffer is not associated with a file' end + fullpath = vim.loop.fs_realpath(vim.fs.normalize(bufname)) + end + + if not fullpath then + return false, string.format('invalid path: %s', path) + end + + local trust = read_trust() + + if action == 'allow' then + assert(bufnr, 'bufnr is required when action is "allow"') - local t = {} - for p, h in pairs(trust) do - t[#t + 1] = string.format('%s %s\n', h, p) + local newline = vim.bo[bufnr].fileformat == 'unix' and '\n' or '\r\n' + local contents = table.concat(vim.api.nvim_buf_get_lines(bufnr, 0, -1, false), newline) + if vim.bo[bufnr].endofline then + contents = contents .. newline end - f:write(table.concat(t)) - f:close() + local hash = vim.fn.sha256(contents) + + trust[fullpath] = hash + elseif action == 'deny' then + trust[fullpath] = '!' + elseif action == 'remove' then + trust[fullpath] = nil end - return contents + write_trust(trust) + return true, fullpath end return M diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 34144fbdfc..96e61c13fb 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -53,6 +53,7 @@ #include "nvim/highlight_group.h" #include "nvim/indent.h" #include "nvim/input.h" +#include "nvim/lua/executor.h" #include "nvim/macros.h" #include "nvim/main.h" #include "nvim/mark.h" @@ -4960,3 +4961,29 @@ void ex_oldfiles(exarg_T *eap) } } } + +void ex_trust(exarg_T *eap) +{ + const char *const p = skiptowhite(eap->arg); + char *arg1 = xmemdupz(eap->arg, (size_t)(p - eap->arg)); + const char *action = "allow"; + const char *path = skipwhite(p); + + if (strcmp(arg1, "++deny") == 0) { + action = "deny"; + } else if (strcmp(arg1, "++remove") == 0) { + action = "remove"; + } else if (*arg1 != '\0') { + semsg(e_invarg2, arg1); + goto theend; + } + + if (path[0] == '\0') { + path = NULL; + } + + nlua_trust(action, path); + +theend: + xfree(arg1); +} diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index e3eea884c4..6911d318cc 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -2934,6 +2934,12 @@ module.cmds = { func='ex_tag', }, { + command='trust', + flags=bit.bor(EXTRA, FILE1, TRLBAR, LOCK_OK), + addr_type='ADDR_NONE', + func='ex_trust', + }, + { command='try', flags=bit.bor(TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 37396a22ad..c2d73360e3 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -1827,6 +1827,7 @@ static bool skip_cmd(const exarg_T *eap) case CMD_throw: case CMD_tilde: case CMD_topleft: + case CMD_trust: case CMD_unlet: case CMD_unlockvar: case CMD_verbose: diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 130f3f6c48..737c92bc8c 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -1016,6 +1016,8 @@ EXTERN char e_highlight_group_name_too_long[] INIT(= N_("E1249: Highlight group EXTERN char e_undobang_cannot_redo_or_move_branch[] INIT(= N_("E5767: Cannot use :undo! to redo or move to a different undo branch")); +EXTERN char e_trustfile[] INIT(= N_("E5570: Cannot update trust file: %s")); + EXTERN char top_bot_msg[] INIT(= N_("search hit TOP, continuing at BOTTOM")); EXTERN char bot_top_msg[] INIT(= N_("search hit BOTTOM, continuing at TOP")); diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 43a3b12a98..5380559baf 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -2217,3 +2217,51 @@ char *nlua_read_secure(const char *path) return buf; } + +bool nlua_trust(const char *action, const char *path) +{ + lua_State *const lstate = global_lstate; + lua_getglobal(lstate, "vim"); + lua_getfield(lstate, -1, "secure"); + lua_getfield(lstate, -1, "trust"); + + lua_newtable(lstate); + lua_pushstring(lstate, "action"); + lua_pushstring(lstate, action); + lua_settable(lstate, -3); + if (path == NULL) { + lua_pushstring(lstate, "bufnr"); + lua_pushnumber(lstate, 0); + lua_settable(lstate, -3); + } else { + lua_pushstring(lstate, "path"); + lua_pushstring(lstate, path); + lua_settable(lstate, -3); + } + + if (nlua_pcall(lstate, 1, 2)) { + nlua_error(lstate, _("Error executing vim.secure.trust: %.*s")); + return false; + } + + bool success = lua_toboolean(lstate, -2); + const char *msg = lua_tostring(lstate, -1); + if (msg != NULL) { + if (success) { + if (strcmp(action, "allow") == 0) { + smsg("Allowed \"%s\" in trust database.", msg); + } else if (strcmp(action, "deny") == 0) { + smsg("Denied \"%s\" in trust database.", msg); + } else if (strcmp(action, "remove") == 0) { + smsg("Removed \"%s\" from trust database.", msg); + } + } else { + semsg(e_trustfile, msg); + } + } + + // Pop return values, "vim" and "secure" + lua_pop(lstate, 4); + + return success; +} diff --git a/test/functional/ex_cmds/trust_spec.lua b/test/functional/ex_cmds/trust_spec.lua new file mode 100644 index 0000000000..10ee02a790 --- /dev/null +++ b/test/functional/ex_cmds/trust_spec.lua @@ -0,0 +1,176 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') + +local eq = helpers.eq +local clear = helpers.clear +local command = helpers.command +local pathsep = helpers.get_pathsep() +local is_os = helpers.is_os +local funcs = helpers.funcs + +describe(':trust', function() + local xstate = 'Xstate' + + setup(function() + helpers.mkdir_p(xstate .. pathsep .. (is_os('win') and 'nvim-data' or 'nvim')) + end) + + teardown(function() + helpers.rmdir(xstate) + end) + + before_each(function() + helpers.write_file('test_file', 'test') + clear{env={XDG_STATE_HOME=xstate}} + end) + + after_each(function() + os.remove('test_file') + end) + + it('trust then deny then remove a file using current buffer', function() + local screen = Screen.new(80, 8) + screen:attach() + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + }) + + local cwd = funcs.getcwd() + local hash = funcs.sha256(helpers.read_file('test_file')) + + command('edit test_file') + command('trust') + screen:expect([[ + ^test | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + "]] .. cwd .. pathsep .. [[test_file" trusted.{MATCH:%s+}| + ]]) + local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('%s %s', hash, cwd .. pathsep .. 'test_file'), vim.trim(trust)) + + command('trust ++deny') + screen:expect([[ + ^test | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + "]] .. cwd .. pathsep .. [[test_file" denied.{MATCH:%s+}| + ]]) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('! %s', cwd .. pathsep .. 'test_file'), vim.trim(trust)) + + command('trust ++remove') + screen:expect([[ + ^test | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + "]] .. cwd .. pathsep .. [[test_file" removed.{MATCH:%s+}| + ]]) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format(''), vim.trim(trust)) + end) + + it('deny then trust then remove a file using current buffer', function() + local screen = Screen.new(80, 8) + screen:attach() + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + }) + + local cwd = funcs.getcwd() + local hash = funcs.sha256(helpers.read_file('test_file')) + + command('edit test_file') + command('trust ++deny') + screen:expect([[ + ^test | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + "]] .. cwd .. pathsep .. [[test_file" denied.{MATCH:%s+}| + ]]) + local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('! %s', cwd .. pathsep .. 'test_file'), vim.trim(trust)) + + command('trust') + screen:expect([[ + ^test | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + "]] .. cwd .. pathsep .. [[test_file" trusted.{MATCH:%s+}| + ]]) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('%s %s', hash, cwd .. pathsep .. 'test_file'), vim.trim(trust)) + + command('trust ++remove') + screen:expect([[ + ^test | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + "]] .. cwd .. pathsep .. [[test_file" removed.{MATCH:%s+}| + ]]) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format(''), vim.trim(trust)) + end) + + it('deny then remove a file using file path', function() + local screen = Screen.new(80, 8) + screen:attach() + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + }) + + local cwd = funcs.getcwd() + + command('trust ++deny test_file') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + "]] .. cwd .. pathsep .. [[test_file" denied.{MATCH:%s+}| + ]]) + local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('! %s', cwd .. pathsep .. 'test_file'), vim.trim(trust)) + + command('trust ++remove test_file') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + "]] .. cwd .. pathsep .. [[test_file" removed.{MATCH:%s+}| + ]]) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format(''), vim.trim(trust)) + end) +end) diff --git a/test/functional/lua/secure_spec.lua b/test/functional/lua/secure_spec.lua index a5eeee8494..46ca2bba8f 100644 --- a/test/functional/lua/secure_spec.lua +++ b/test/functional/lua/secure_spec.lua @@ -168,4 +168,111 @@ describe('vim.secure', function() eq(false, curbufmeths.get_option('modifiable')) end) end) + + describe('trust()', function() + local xstate = 'Xstate' + + setup(function() + helpers.mkdir_p(xstate .. pathsep .. (is_os('win') and 'nvim-data' or 'nvim')) + end) + + teardown(function() + helpers.rmdir(xstate) + end) + + before_each(function() + helpers.write_file('test_file', 'test') + end) + + after_each(function() + os.remove('test_file') + end) + + it('returns error when passing both path and bufnr', function() + eq('path and bufnr are mutually exclusive', + pcall_err(exec_lua, [[vim.secure.trust({action='deny', bufnr=0, path='test_file'})]])) + end) + + it('trust then deny then remove a file using bufnr', function() + local cwd = funcs.getcwd() + local hash = funcs.sha256(helpers.read_file('test_file')) + local full_path = cwd .. pathsep .. 'test_file' + + command('edit test_file') + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]])) + local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('%s %s', hash, full_path), vim.trim(trust)) + + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='deny', bufnr=0})}]])) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('! %s', full_path), vim.trim(trust)) + + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='remove', bufnr=0})}]])) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq('', vim.trim(trust)) + end) + + it('deny then trust then remove a file using bufnr', function() + local cwd = funcs.getcwd() + local hash = funcs.sha256(helpers.read_file('test_file')) + local full_path = cwd .. pathsep .. 'test_file' + + command('edit test_file') + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='deny', bufnr=0})}]])) + local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('! %s', full_path), vim.trim(trust)) + + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]])) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('%s %s', hash, full_path), vim.trim(trust)) + + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='remove', bufnr=0})}]])) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq('', vim.trim(trust)) + end) + + it('trust using bufnr then deny then remove a file using path', function() + local cwd = funcs.getcwd() + local hash = funcs.sha256(helpers.read_file('test_file')) + local full_path = cwd .. pathsep .. 'test_file' + + command('edit test_file') + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]])) + local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('%s %s', hash, full_path), vim.trim(trust)) + + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='deny', path='test_file'})}]])) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('! %s', full_path), vim.trim(trust)) + + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='remove', path='test_file'})}]])) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq('', vim.trim(trust)) + end) + + it('deny then trust then remove a file using bufnr', function() + local cwd = funcs.getcwd() + local hash = funcs.sha256(helpers.read_file('test_file')) + local full_path = cwd .. pathsep .. 'test_file' + + command('edit test_file') + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='deny', path='test_file'})}]])) + local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('! %s', full_path), vim.trim(trust)) + + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]])) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('%s %s', hash, full_path), vim.trim(trust)) + + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='remove', path='test_file'})}]])) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq('', vim.trim(trust)) + end) + + it('trust returns error when buffer not associated to file', function() + command('new') + eq({false, 'buffer is not associated with a file'}, + exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]])) + end) + end) end) |