secure_spec.lua 13 KB

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