neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

secure_spec.lua (15585B)


      1 local t = require('test.testutil')
      2 local n = require('test.functional.testnvim')()
      3 local Screen = require('test.functional.ui.screen')
      4 
      5 local eq = t.eq
      6 local clear = n.clear
      7 local command = n.command
      8 local pathsep = n.get_pathsep()
      9 local is_os = t.is_os
     10 local api = n.api
     11 local exec_lua = n.exec_lua
     12 local feed_command = n.feed_command
     13 local feed = n.feed
     14 local fn = n.fn
     15 local stdpath = fn.stdpath
     16 local pcall_err = t.pcall_err
     17 local matches = t.matches
     18 local read_file = t.read_file
     19 
     20 describe('vim.secure', function()
     21  describe('read()', function()
     22    local xstate = 'Xstate_lua_secure'
     23    local screen ---@type test.functional.ui.screen
     24 
     25    before_each(function()
     26      clear { env = { XDG_STATE_HOME = xstate } }
     27      n.mkdir_p(xstate .. pathsep .. (is_os('win') and 'nvim-data' or 'nvim'))
     28 
     29      t.mkdir('Xdir')
     30      t.mkdir('Xdir/Xsubdir')
     31      t.write_file('Xdir/Xfile.txt', [[hello, world]])
     32 
     33      t.write_file(
     34        'Xfile',
     35        [[
     36        let g:foobar = 42
     37      ]]
     38      )
     39      screen = Screen.new(500, 8)
     40    end)
     41 
     42    after_each(function()
     43      screen:detach()
     44      os.remove('Xfile')
     45      n.rmdir('Xdir')
     46      n.rmdir(xstate)
     47    end)
     48 
     49    it('regular file', function()
     50      screen:set_default_attr_ids({
     51        [1] = { bold = true, foreground = Screen.colors.Blue1 },
     52        [2] = { bold = true, reverse = true },
     53        [3] = { bold = true, foreground = Screen.colors.SeaGreen },
     54        [4] = { reverse = true },
     55      })
     56 
     57      local cwd = fn.getcwd()
     58      local msg = 'exrc: Found untrusted code. To enable it, choose (v)iew then run `:trust`:'
     59      local path = ('%s%sXfile'):format(cwd, pathsep)
     60 
     61      -- Need to use feed_command instead of exec_lua because of the confirmation prompt
     62      feed_command([[lua vim.secure.read('Xfile')]])
     63      screen:expect([[
     64        {MATCH: +}|
     65        {1:~{MATCH: +}}|*2
     66        {2:{MATCH: +}}|
     67        :lua vim.secure.read('Xfile'){MATCH: +}|
     68        {3:]] .. msg .. [[}{MATCH: *}|
     69        {3:]] .. path .. [[}{MATCH: *}|
     70        {3:[i]gnore, (v)iew, (d)eny: }^{MATCH: +}|
     71      ]])
     72      feed('d')
     73      screen:expect([[
     74        ^{MATCH: +}|
     75        {1:~{MATCH: +}}|*6
     76        {MATCH: +}|
     77      ]])
     78 
     79      local trust = assert(read_file(stdpath('state') .. pathsep .. 'trust'))
     80      eq(string.format('! %s', cwd .. pathsep .. 'Xfile'), vim.trim(trust))
     81      eq(vim.NIL, exec_lua([[return vim.secure.read('Xfile')]]))
     82 
     83      os.remove(stdpath('state') .. pathsep .. 'trust')
     84 
     85      feed_command([[lua vim.secure.read('Xfile')]])
     86      screen:expect([[
     87        {MATCH: +}|
     88        {1:~{MATCH: +}}|*2
     89        {2:{MATCH: +}}|
     90        :lua vim.secure.read('Xfile'){MATCH: +}|
     91        {3:]] .. msg .. [[}{MATCH: *}|
     92        {3:]] .. path .. [[}{MATCH: *}|
     93        {3:[i]gnore, (v)iew, (d)eny: }^{MATCH: +}|
     94      ]])
     95      feed('v')
     96      feed(':trust<CR>')
     97      screen:expect([[
     98        ^let g:foobar = 42{MATCH: +}|
     99        {1:~{MATCH: +}}|*2
    100        {2:]] .. fn.fnamemodify(cwd, ':~') .. pathsep .. [[Xfile [RO]{MATCH: +}}|
    101        {MATCH: +}|
    102        {1:~{MATCH: +}}|
    103        {4:[No Name]{MATCH: +}}|
    104        Allowed in trust database: "]] .. cwd .. pathsep .. [[Xfile"{MATCH: +}|
    105      ]])
    106      -- close the split for the next test below.
    107      feed(':q<CR>')
    108 
    109      local hash = fn.sha256(assert(read_file('Xfile')))
    110      trust = assert(read_file(stdpath('state') .. pathsep .. 'trust'))
    111      eq(string.format('%s %s', hash, cwd .. pathsep .. 'Xfile'), vim.trim(trust))
    112      eq('let g:foobar = 42\n', exec_lua([[return vim.secure.read('Xfile')]]))
    113 
    114      os.remove(stdpath('state') .. pathsep .. 'trust')
    115 
    116      feed_command([[lua vim.secure.read('Xfile')]])
    117      screen:expect([[
    118        {MATCH: +}|
    119        {1:~{MATCH: +}}|*2
    120        {2:{MATCH: +}}|
    121        :lua vim.secure.read('Xfile'){MATCH: +}|
    122        {3:]] .. msg .. [[}{MATCH: *}|
    123        {3:]] .. path .. [[}{MATCH: *}|
    124        {3:[i]gnore, (v)iew, (d)eny: }^{MATCH: +}|
    125      ]])
    126      feed('i')
    127      screen:expect([[
    128        ^{MATCH: +}|
    129        {1:~{MATCH: +}}|*6
    130        {MATCH: +}|
    131      ]])
    132 
    133      -- Trust database is not updated
    134      eq(nil, read_file(stdpath('state') .. pathsep .. 'trust'))
    135 
    136      feed_command([[lua vim.secure.read('Xfile')]])
    137      screen:expect([[
    138        {MATCH: +}|
    139        {1:~{MATCH: +}}|*2
    140        {2:{MATCH: +}}|
    141        :lua vim.secure.read('Xfile'){MATCH: +}|
    142        {3:]] .. msg .. [[}{MATCH: +}|
    143        {3:]] .. path .. [[}{MATCH: *}|
    144        {3:[i]gnore, (v)iew, (d)eny: }^{MATCH: +}|
    145      ]])
    146      feed('v')
    147      screen:expect([[
    148        ^let g:foobar = 42{MATCH: +}|
    149        {1:~{MATCH: +}}|*2
    150        {2:]] .. fn.fnamemodify(cwd, ':~') .. pathsep .. [[Xfile [RO]{MATCH: +}}|
    151        {MATCH: +}|
    152        {1:~{MATCH: +}}|
    153        {4:[No Name]{MATCH: +}}|
    154        {MATCH: +}|
    155      ]])
    156 
    157      -- Trust database is not updated
    158      eq(nil, read_file(stdpath('state') .. pathsep .. 'trust'))
    159 
    160      -- Cannot write file
    161      pcall_err(command, 'write')
    162      eq(true, api.nvim_get_option_value('readonly', {}))
    163    end)
    164 
    165    it('directory', function()
    166      screen:set_default_attr_ids({
    167        [1] = { bold = true, foreground = Screen.colors.Blue1 },
    168        [2] = { bold = true, reverse = true },
    169        [3] = { bold = true, foreground = Screen.colors.SeaGreen },
    170        [4] = { reverse = true },
    171      })
    172 
    173      local cwd = fn.getcwd()
    174      local msg =
    175        'exrc: Found untrusted code. DIRECTORY trust is decided only by name, not contents:'
    176      local path = ('%s%sXdir'):format(cwd, pathsep)
    177 
    178      -- Need to use feed_command instead of exec_lua because of the confirmation prompt
    179      feed_command([[lua vim.secure.read('Xdir')]])
    180      screen:expect([[
    181        {MATCH: +}|
    182        {1:~{MATCH: +}}|*2
    183        {2:{MATCH: +}}|
    184        :lua vim.secure.read('Xdir'){MATCH: +}|
    185        {3:]] .. msg .. [[}{MATCH: +}|
    186        {3:]] .. path .. [[}{MATCH: +}|
    187        {3:[i]gnore, (v)iew, (d)eny, (a)llow: }^{MATCH: +}|
    188      ]])
    189      feed('d')
    190      screen:expect([[
    191        ^{MATCH: +}|
    192        {1:~{MATCH: +}}|*6
    193        {MATCH: +}|
    194      ]])
    195 
    196      local trust = assert(read_file(stdpath('state') .. pathsep .. 'trust'))
    197      eq(string.format('! %s', cwd .. pathsep .. 'Xdir'), vim.trim(trust))
    198      eq(vim.NIL, exec_lua([[return vim.secure.read('Xdir')]]))
    199 
    200      os.remove(stdpath('state') .. pathsep .. 'trust')
    201 
    202      feed_command([[lua vim.secure.read('Xdir')]])
    203      screen:expect([[
    204        {MATCH: +}|
    205        {1:~{MATCH: +}}|*2
    206        {2:{MATCH: +}}|
    207        :lua vim.secure.read('Xdir'){MATCH: +}|
    208        {3:]] .. msg .. [[}{MATCH: +}|
    209        {3:]] .. path .. [[}{MATCH: +}|
    210        {3:[i]gnore, (v)iew, (d)eny, (a)llow: }^{MATCH: +}|
    211      ]])
    212      feed('a')
    213      screen:expect([[
    214        ^{MATCH: +}|
    215        {1:~{MATCH: +}}|*6
    216        {MATCH: +}|
    217      ]])
    218 
    219      -- Directories aren't hashed in the trust database, instead a slug ("directory") is stored
    220      -- instead.
    221      local expected_hash = 'directory'
    222      trust = assert(read_file(stdpath('state') .. pathsep .. 'trust'))
    223      eq(string.format('%s %s', expected_hash, cwd .. pathsep .. 'Xdir'), vim.trim(trust))
    224      eq(true, exec_lua([[return vim.secure.read('Xdir')]]))
    225 
    226      os.remove(stdpath('state') .. pathsep .. 'trust')
    227 
    228      feed_command([[lua vim.secure.read('Xdir')]])
    229      screen:expect([[
    230        {MATCH: +}|
    231        {1:~{MATCH: +}}|*2
    232        {2:{MATCH: +}}|
    233        :lua vim.secure.read('Xdir'){MATCH: +}|
    234        {3:]] .. msg .. [[}{MATCH: +}|
    235        {3:]] .. path .. [[}{MATCH: +}|
    236        {3:[i]gnore, (v)iew, (d)eny, (a)llow: }^{MATCH: +}|
    237      ]])
    238      feed('i')
    239      screen:expect([[
    240        ^{MATCH: +}|
    241        {1:~{MATCH: +}}|*6
    242        {MATCH: +}|
    243      ]])
    244 
    245      -- Trust database is not updated
    246      eq(nil, read_file(stdpath('state') .. pathsep .. 'trust'))
    247 
    248      feed_command([[lua vim.secure.read('Xdir')]])
    249      screen:expect([[
    250        {MATCH: +}|
    251        {1:~{MATCH: +}}|*2
    252        {2:{MATCH: +}}|
    253        :lua vim.secure.read('Xdir'){MATCH: +}|
    254        {3:]] .. msg .. [[}{MATCH: +}|
    255        {3:]] .. path .. [[}{MATCH: +}|
    256        {3:[i]gnore, (v)iew, (d)eny, (a)llow: }^{MATCH: +}|
    257      ]])
    258      feed('v')
    259      screen:expect([[
    260        ^{MATCH: +}|
    261        {1:~{MATCH: +}}|*2
    262        {2:]] .. fn.fnamemodify(cwd, ':~') .. pathsep .. [[Xdir [RO]{MATCH: +}}|
    263        {MATCH: +}|
    264        {1:~{MATCH: +}}|
    265        {4:[No Name]{MATCH: +}}|
    266        {MATCH: +}|
    267      ]])
    268 
    269      -- Trust database is not updated
    270      eq(nil, read_file(stdpath('state') .. pathsep .. 'trust'))
    271    end)
    272  end)
    273 
    274  describe('trust()', function()
    275    local xstate = 'Xstate_lua_secure'
    276    local test_file = 'Xtest_functional_lua_secure'
    277    local test_dir = 'Xtest_functional_lua_secure_dir'
    278 
    279    setup(function()
    280      clear { env = { XDG_STATE_HOME = xstate } }
    281    end)
    282 
    283    before_each(function()
    284      n.mkdir_p(xstate .. pathsep .. (is_os('win') and 'nvim-data' or 'nvim'))
    285      t.write_file(test_file, 'test')
    286      t.mkdir(test_dir)
    287    end)
    288 
    289    after_each(function()
    290      os.remove(test_file)
    291      n.rmdir(test_dir)
    292      n.rmdir(xstate)
    293    end)
    294 
    295    it('returns error when passing both path and bufnr', function()
    296      matches(
    297        '"path" and "bufnr" are mutually exclusive',
    298        pcall_err(exec_lua, [[vim.secure.trust({action='deny', bufnr=0, path=...})]], test_file)
    299      )
    300    end)
    301 
    302    it('returns error when passing neither path or bufnr', function()
    303      matches(
    304        'one of "path" or "bufnr" is required',
    305        pcall_err(exec_lua, [[vim.secure.trust({action='deny'})]])
    306      )
    307    end)
    308 
    309    it('trust then deny then remove a file using bufnr', function()
    310      local cwd = fn.getcwd()
    311      local hash = fn.sha256(assert(read_file(test_file)))
    312      local full_path = cwd .. pathsep .. test_file
    313 
    314      command('edit ' .. test_file)
    315      eq({ true, full_path }, exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]]))
    316      local trust = assert(read_file(stdpath('state') .. pathsep .. 'trust'))
    317      eq(string.format('%s %s', hash, full_path), vim.trim(trust))
    318 
    319      eq({ true, full_path }, exec_lua([[return {vim.secure.trust({action='deny', bufnr=0})}]]))
    320      trust = assert(read_file(stdpath('state') .. pathsep .. 'trust'))
    321      eq(string.format('! %s', full_path), vim.trim(trust))
    322 
    323      eq({ true, full_path }, exec_lua([[return {vim.secure.trust({action='remove', bufnr=0})}]]))
    324      trust = assert(read_file(stdpath('state') .. pathsep .. 'trust'))
    325      eq('', vim.trim(trust))
    326    end)
    327 
    328    it('deny then trust then remove a file using bufnr', function()
    329      local cwd = fn.getcwd()
    330      local hash = fn.sha256(assert(read_file(test_file)))
    331      local full_path = cwd .. pathsep .. test_file
    332 
    333      command('edit ' .. test_file)
    334      eq({ true, full_path }, exec_lua([[return {vim.secure.trust({action='deny', bufnr=0})}]]))
    335      local trust = assert(read_file(stdpath('state') .. pathsep .. 'trust'))
    336      eq(string.format('! %s', full_path), vim.trim(trust))
    337 
    338      eq({ true, full_path }, exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]]))
    339      trust = assert(read_file(stdpath('state') .. pathsep .. 'trust'))
    340      eq(string.format('%s %s', hash, full_path), vim.trim(trust))
    341 
    342      eq({ true, full_path }, exec_lua([[return {vim.secure.trust({action='remove', bufnr=0})}]]))
    343      trust = assert(read_file(stdpath('state') .. pathsep .. 'trust'))
    344      eq('', vim.trim(trust))
    345    end)
    346 
    347    it('trust using bufnr then deny then remove a file using path', function()
    348      local cwd = fn.getcwd()
    349      local hash = fn.sha256(assert(read_file(test_file)))
    350      local full_path = cwd .. pathsep .. test_file
    351 
    352      command('edit ' .. test_file)
    353      eq({ true, full_path }, exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]]))
    354      local trust = assert(read_file(stdpath('state') .. pathsep .. 'trust'))
    355      eq(string.format('%s %s', hash, full_path), vim.trim(trust))
    356 
    357      eq(
    358        { true, full_path },
    359        exec_lua([[return {vim.secure.trust({action='deny', path=...})}]], test_file)
    360      )
    361      trust = assert(read_file(stdpath('state') .. pathsep .. 'trust'))
    362      eq(string.format('! %s', full_path), vim.trim(trust))
    363 
    364      eq(
    365        { true, full_path },
    366        exec_lua([[return {vim.secure.trust({action='remove', path=...})}]], test_file)
    367      )
    368      trust = assert(read_file(stdpath('state') .. pathsep .. 'trust'))
    369      eq('', vim.trim(trust))
    370    end)
    371 
    372    it('trust then deny then remove a file using path', function()
    373      local cwd = fn.getcwd()
    374      local hash = fn.sha256(assert(read_file(test_file)))
    375      local full_path = cwd .. pathsep .. test_file
    376 
    377      eq(
    378        { true, full_path },
    379        exec_lua([[return {vim.secure.trust({action='allow', path=...})}]], test_file)
    380      )
    381      local trust = assert(read_file(stdpath('state') .. pathsep .. 'trust'))
    382      eq(string.format('%s %s', hash, full_path), vim.trim(trust))
    383 
    384      eq(
    385        { true, full_path },
    386        exec_lua([[return {vim.secure.trust({action='deny', path=...})}]], test_file)
    387      )
    388      trust = assert(read_file(stdpath('state') .. pathsep .. 'trust'))
    389      eq(string.format('! %s', full_path), vim.trim(trust))
    390 
    391      eq(
    392        { true, full_path },
    393        exec_lua([[return {vim.secure.trust({action='remove', path=...})}]], test_file)
    394      )
    395      trust = assert(read_file(stdpath('state') .. pathsep .. 'trust'))
    396      eq('', vim.trim(trust))
    397    end)
    398 
    399    it('deny then trust then remove a file using bufnr', function()
    400      local cwd = fn.getcwd()
    401      local hash = fn.sha256(assert(read_file(test_file)))
    402      local full_path = cwd .. pathsep .. test_file
    403 
    404      command('edit ' .. test_file)
    405      eq(
    406        { true, full_path },
    407        exec_lua([[return {vim.secure.trust({action='deny', path=...})}]], test_file)
    408      )
    409      local trust = assert(read_file(stdpath('state') .. pathsep .. 'trust'))
    410      eq(string.format('! %s', full_path), vim.trim(trust))
    411 
    412      eq({ true, full_path }, exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]]))
    413      trust = assert(read_file(stdpath('state') .. pathsep .. 'trust'))
    414      eq(string.format('%s %s', hash, full_path), vim.trim(trust))
    415 
    416      eq(
    417        { true, full_path },
    418        exec_lua([[return {vim.secure.trust({action='remove', path=...})}]], test_file)
    419      )
    420      trust = assert(read_file(stdpath('state') .. pathsep .. 'trust'))
    421      eq('', vim.trim(trust))
    422    end)
    423 
    424    it('trust returns error when buffer not associated to file', function()
    425      command('new')
    426      eq(
    427        { false, 'buffer is not associated with a file' },
    428        exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]])
    429      )
    430    end)
    431 
    432    it('trust then deny then remove a directory using bufnr', function()
    433      local cwd = fn.getcwd()
    434      local full_path = cwd .. pathsep .. test_dir
    435      command('edit ' .. test_dir)
    436 
    437      eq({ true, full_path }, exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]]))
    438      local trust = assert(read_file(stdpath('state') .. pathsep .. 'trust'))
    439      eq(string.format('directory %s', full_path), vim.trim(trust))
    440 
    441      eq({ true, full_path }, exec_lua([[return {vim.secure.trust({action='deny', bufnr=0})}]]))
    442      trust = assert(read_file(stdpath('state') .. pathsep .. 'trust'))
    443      eq(string.format('! %s', full_path), vim.trim(trust))
    444 
    445      eq({ true, full_path }, exec_lua([[return {vim.secure.trust({action='remove', bufnr=0})}]]))
    446      trust = assert(read_file(stdpath('state') .. pathsep .. 'trust'))
    447      eq('', vim.trim(trust))
    448    end)
    449  end)
    450 end)