@synopackageland/cli 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (585) hide show
  1. package/CHANGELOG.md +613 -0
  2. package/README.md +81 -78
  3. package/dist/add.js +39 -16
  4. package/dist/authorization-sdk.d.ts +7 -0
  5. package/dist/authorization-sdk.js +7 -0
  6. package/dist/build-spk/delegate-scope.d.ts +6 -0
  7. package/dist/build-spk/delegate-scope.js +34 -0
  8. package/dist/build-spk/dsm-arch.d.ts +7 -1
  9. package/dist/build-spk/dsm-arch.js +24 -28
  10. package/dist/build-spk/dsm-platforms.json +39 -0
  11. package/dist/build-spk/dsm-version.d.ts +11 -0
  12. package/dist/build-spk/dsm-version.js +17 -0
  13. package/dist/build-spk/gateway.d.ts +1 -1
  14. package/dist/build-spk/gateway.js +2 -2
  15. package/dist/build-spk/generator.js +30 -5
  16. package/dist/build-spk/notices.js +4 -1
  17. package/dist/build-spk/resource-worker.js +3 -0
  18. package/dist/build-spk/wizard-messages.d.ts +66 -0
  19. package/dist/build-spk/wizard-messages.js +75 -9
  20. package/dist/cli.js +30 -18
  21. package/dist/contract/schema-validate.js +8 -0
  22. package/dist/contract/validate.js +30 -0
  23. package/dist/deploy/deploy.d.ts +1 -1
  24. package/dist/deploy/deploy.js +8 -0
  25. package/dist/deploy/plan.js +1 -1
  26. package/dist/deploy/spk-version.js +1 -1
  27. package/dist/error-registry.js +46 -13
  28. package/dist/host/dsm-client.d.ts +8 -4
  29. package/dist/host/dsm-client.js +10 -4
  30. package/dist/host/types.d.ts +2 -1
  31. package/dist/info.js +18 -2
  32. package/dist/init.d.ts +3 -0
  33. package/dist/init.js +49 -18
  34. package/dist/package-metadata.d.ts +21 -0
  35. package/dist/package-metadata.js +101 -0
  36. package/dist/portal/alias.d.ts +25 -0
  37. package/dist/portal/alias.js +48 -0
  38. package/dist/portal/package.d.ts +33 -0
  39. package/dist/portal/package.js +116 -0
  40. package/dist/runtime/delegate-contract.d.ts +79 -0
  41. package/dist/runtime/delegate-contract.js +27 -0
  42. package/dist/runtime/executor-build.d.ts +1 -1
  43. package/dist/runtime/executor-build.js +5 -4
  44. package/dist/runtime/executor-inspect.d.ts +3 -0
  45. package/dist/runtime/executor-inspect.js +11 -5
  46. package/dist/runtime/executor-policy.d.ts +12 -0
  47. package/dist/runtime/executor-policy.js +43 -3
  48. package/dist/runtime/executor-scope-task.d.ts +4 -0
  49. package/dist/runtime/executor-scope-task.js +21 -0
  50. package/dist/runtime/executor-tasks.d.ts +2 -1
  51. package/dist/runtime/executor-tasks.js +4 -4
  52. package/dist/skill.js +1 -0
  53. package/dist/standalone/api-permissions.d.ts +10 -0
  54. package/dist/standalone/api-permissions.js +19 -2
  55. package/dist/standalone/background.d.ts +16 -0
  56. package/dist/standalone/background.js +69 -2
  57. package/dist/standalone/binary.d.ts +1 -1
  58. package/dist/standalone/binary.js +22 -8
  59. package/dist/standalone/build.d.ts +1 -1
  60. package/dist/standalone/build.js +38 -8
  61. package/dist/standalone/daemon.d.ts +1 -1
  62. package/dist/standalone/daemon.js +2 -1
  63. package/dist/standalone/dsm-window.d.ts +3 -1
  64. package/dist/standalone/dsm-window.js +72 -15
  65. package/dist/standalone/inspect-spk.d.ts +9 -1
  66. package/dist/standalone/inspect-spk.js +119 -26
  67. package/dist/standalone/lifecycle.js +86 -8
  68. package/dist/standalone/manifest.d.ts +13 -0
  69. package/dist/standalone/manifest.js +57 -8
  70. package/dist/standalone/native-authorization-window.d.ts +2 -0
  71. package/dist/standalone/native-authorization-window.js +30 -0
  72. package/dist/standalone/native-layout.d.ts +82 -0
  73. package/dist/standalone/native-layout.js +377 -0
  74. package/dist/standalone/native-root-window.d.ts +26 -0
  75. package/dist/standalone/native-root-window.js +282 -0
  76. package/dist/standalone/portal-window.d.ts +21 -0
  77. package/dist/standalone/portal-window.js +155 -0
  78. package/dist/standalone/portal.d.ts +4 -0
  79. package/dist/standalone/portal.js +4 -0
  80. package/dist/standalone/resource.d.ts +5 -3
  81. package/dist/standalone/resource.js +6 -3
  82. package/dist/standalone/root-window.js +62 -15
  83. package/dist/standalone/spk.d.ts +1 -1
  84. package/dist/standalone/spk.js +25 -7
  85. package/dist/templates/_daemon/main_test.go +61 -8
  86. package/dist/templates/_executor/README.md +127 -16
  87. package/dist/templates/_executor/anchor.go +146 -0
  88. package/dist/templates/_executor/anchor_test.go +126 -0
  89. package/dist/templates/_executor/apppriv_review_test.go +1 -1
  90. package/dist/templates/_executor/cancel_contract_test.go +236 -0
  91. package/dist/templates/_executor/copy.go +64 -0
  92. package/dist/templates/_executor/copy_test.go +88 -0
  93. package/dist/templates/_executor/dsm.go +23 -10
  94. package/dist/templates/_executor/dsm_test.go +95 -1
  95. package/dist/templates/_executor/executor.go +260 -49
  96. package/dist/templates/_executor/executor_test.go +36 -6
  97. package/dist/templates/_executor/identity.go +27 -1
  98. package/dist/templates/_executor/identity_kernel_linux_test.go +60 -0
  99. package/dist/templates/_executor/identity_linux.go +82 -0
  100. package/dist/templates/_executor/identity_security_test.go +43 -0
  101. package/dist/templates/_executor/internal/lifecycle/lifecycle.go +5 -0
  102. package/dist/templates/_executor/internal/lifecycle/lifecycle_test.go +61 -3
  103. package/dist/templates/_executor/internal/taskscript/boot.sh +9 -9
  104. package/dist/templates/_executor/internal/taskscript/cmd/main.go +2 -2
  105. package/dist/templates/_executor/internal/taskscript/control.sh +15 -10
  106. package/dist/templates/_executor/internal/taskscript/tasks.go +5 -3
  107. package/dist/templates/_executor/lifecycle_test.go +11 -0
  108. package/dist/templates/_executor/link_linux.go +28 -0
  109. package/dist/templates/_executor/long_running_test.go +530 -0
  110. package/dist/templates/_executor/main.go +72 -20
  111. package/dist/templates/_executor/move_linux.go +490 -0
  112. package/dist/templates/_executor/move_linux_test.go +460 -0
  113. package/dist/templates/_executor/move_validation.go +46 -0
  114. package/dist/templates/_executor/move_validation_test.go +187 -0
  115. package/dist/templates/_executor/operations.go +926 -0
  116. package/dist/templates/_executor/platform_linux.go +24 -173
  117. package/dist/templates/_executor/platform_linux_test.go +9 -65
  118. package/dist/templates/_executor/platform_other.go +30 -4
  119. package/dist/templates/_executor/process.go +3 -0
  120. package/dist/templates/_executor/records.go +242 -0
  121. package/dist/templates/_executor/scope_cancel_contract_test.go +91 -0
  122. package/dist/templates/_executor/scope_client.go +37 -0
  123. package/dist/templates/_executor/scope_contract.go +12 -0
  124. package/dist/templates/_executor/selected_scope.go +146 -0
  125. package/dist/templates/_executor/selected_scope_test.go +186 -0
  126. package/dist/templates/_executor/status_disabled_test.go +33 -0
  127. package/dist/templates/_executor/store.go +4 -3
  128. package/dist/templates/_executor/supervise.go +109 -0
  129. package/dist/templates/_executor/supervise_test.go +130 -0
  130. package/dist/templates/_executor/task_race_review_test.go +1 -1
  131. package/dist/templates/_executor/tasks.go +7 -8
  132. package/dist/templates/_executor/tasks_review_test.go +1 -1
  133. package/dist/templates/_executor/tasks_test.go +4 -4
  134. package/dist/templates/_executor/watch.go +8 -0
  135. package/dist/templates/_executor/watch_test.go +23 -2
  136. package/dist/templates/_executor/write_review_linux_test.go +4 -12
  137. package/dist/templates/_shared/AGENTS.md +129 -0
  138. package/dist/templates/binary/dsm-package.yaml +24 -0
  139. package/dist/templates/binary/i18n/messages/de.json +4 -0
  140. package/dist/templates/binary/i18n/messages/en.json +4 -0
  141. package/dist/templates/binary/i18n/messages/es.json +4 -0
  142. package/dist/templates/binary/i18n/messages/fr.json +4 -0
  143. package/dist/templates/binary/i18n/messages/it.json +4 -0
  144. package/dist/templates/binary/i18n/messages/ja.json +4 -0
  145. package/dist/templates/binary/i18n/messages/ko.json +4 -0
  146. package/dist/templates/binary/i18n/messages/zh-CN.json +4 -0
  147. package/dist/templates/binary/i18n/messages/zh-TW.json +4 -0
  148. package/dist/templates/binary/ui/app.js +19 -4
  149. package/dist/templates/native-authorization/AuthorizationGate.vue +91 -0
  150. package/dist/templates/native-authorization/i18n/de.json +37 -0
  151. package/dist/templates/native-authorization/i18n/en.json +37 -0
  152. package/dist/templates/native-authorization/i18n/es.json +37 -0
  153. package/dist/templates/native-authorization/i18n/fr.json +37 -0
  154. package/dist/templates/native-authorization/i18n/index.ts +15 -0
  155. package/dist/templates/native-authorization/i18n/it.json +37 -0
  156. package/dist/templates/native-authorization/i18n/ja.json +37 -0
  157. package/dist/templates/native-authorization/i18n/ko.json +37 -0
  158. package/dist/templates/native-authorization/i18n/zh-CN.json +37 -0
  159. package/dist/templates/native-authorization/i18n/zh-TW.json +37 -0
  160. package/dist/templates/native-go/README.md +43 -10
  161. package/dist/templates/native-go/backend/cmd/app/main.go +27 -7
  162. package/dist/templates/native-go/backend/internal/api/entry_test.go +47 -0
  163. package/dist/templates/native-go/backend/internal/api/permissions_test.go +3 -3
  164. package/dist/templates/native-go/backend/internal/api/registry.go +11 -0
  165. package/dist/templates/native-go/backend/internal/api/sdk-permissions.json +14 -1
  166. package/dist/templates/native-go/backend/internal/api/server.go +18 -5
  167. package/dist/templates/native-go/backend/internal/app/delegate_config.go +57 -0
  168. package/dist/templates/native-go/backend/internal/app/delegate_config_test.go +28 -0
  169. package/dist/templates/native-go/backend/internal/app/portal.go +30 -0
  170. package/dist/templates/native-go/backend/internal/app/portal_test.go +55 -0
  171. package/dist/templates/native-go/backend/internal/app/routes.go +8 -2
  172. package/dist/templates/native-go/backend/internal/background/runner.go +1 -1
  173. package/dist/templates/native-go/backend/internal/background/store.go +16 -14
  174. package/dist/templates/native-go/backend/internal/dsm/admin.go +8 -2
  175. package/dist/templates/native-go/backend/internal/dsm/admin_linux.go +43 -13
  176. package/dist/templates/native-go/backend/internal/dsm/admin_linux_test.go +56 -0
  177. package/dist/templates/native-go/backend/internal/dsm/admin_other.go +4 -1
  178. package/dist/templates/native-go/backend/internal/dsm/apppriv.go +30 -0
  179. package/dist/templates/native-go/backend/internal/dsm/bridge_file.go +75 -0
  180. package/dist/templates/native-go/backend/internal/dsm/bridge_file_test.go +57 -0
  181. package/dist/templates/native-go/backend/internal/dsm/entry.go +177 -0
  182. package/dist/templates/native-go/backend/internal/dsm/entry_test.go +315 -0
  183. package/dist/templates/native-go/backend/internal/dsm/native/admin-armv7.elf +0 -0
  184. package/dist/templates/native-go/backend/internal/dsm/native/apppriv-aarch64.elf +0 -0
  185. package/dist/templates/native-go/backend/internal/dsm/native/apppriv-armv7.elf +0 -0
  186. package/dist/templates/native-go/backend/internal/dsm/native/apppriv-x86_64.elf +0 -0
  187. package/dist/templates/native-go/backend/internal/dsm/native/apppriv_check.c +33 -0
  188. package/dist/templates/native-go/backend/internal/dsm/native/build.sh +13 -4
  189. package/dist/templates/native-go/backend/internal/dsm/native/provenance.json +9 -3
  190. package/dist/templates/native-go/backend/internal/filestation/check_permission.go +71 -0
  191. package/dist/templates/native-go/backend/internal/filestation/check_permission_test.go +77 -0
  192. package/dist/templates/native-go/backend/internal/filestation/sdk_policy_test.go +39 -0
  193. package/dist/templates/native-go/backend/internal/filestation/service.go +1 -1
  194. package/dist/templates/native-go/backend/internal/portal/portal.go +138 -0
  195. package/dist/templates/native-go/backend/internal/portal/portal_test.go +89 -0
  196. package/dist/templates/native-go/dsm-package.yaml +20 -1
  197. package/dist/templates/native-go/i18n/glossary.json +2 -1
  198. package/dist/templates/native-go/i18n/messages/de.json +1 -0
  199. package/dist/templates/native-go/i18n/messages/en.json +1 -0
  200. package/dist/templates/native-go/i18n/messages/es.json +1 -0
  201. package/dist/templates/native-go/i18n/messages/fr.json +1 -0
  202. package/dist/templates/native-go/i18n/messages/it.json +1 -0
  203. package/dist/templates/native-go/i18n/messages/ja.json +1 -0
  204. package/dist/templates/native-go/i18n/messages/ko.json +1 -0
  205. package/dist/templates/native-go/i18n/messages/zh-CN.json +1 -0
  206. package/dist/templates/native-go/i18n/messages/zh-TW.json +1 -0
  207. package/dist/templates/native-go/ui/app.css +10 -0
  208. package/dist/templates/native-go/ui/app.js +11 -1
  209. package/dist/templates/native-go-delegate/DELEGATE.md +126 -5
  210. package/dist/templates/native-go-delegate/backend/cmd/background-worker/main.go +7 -0
  211. package/dist/templates/native-go-delegate/backend/internal/app/delegate.go +11 -1
  212. package/dist/templates/native-go-delegate/backend/internal/app/delegate_routes_test.go +14 -0
  213. package/dist/templates/native-go-delegate/backend/internal/background/delegated_moves.go +270 -0
  214. package/dist/templates/native-go-delegate/backend/internal/delegate/admission_recovery_test.go +410 -0
  215. package/dist/templates/native-go-delegate/backend/internal/delegate/cancel_contract_test.go +95 -0
  216. package/dist/templates/native-go-delegate/backend/internal/delegate/client.go +8 -0
  217. package/dist/templates/native-go-delegate/backend/internal/delegate/destination.go +110 -0
  218. package/dist/templates/native-go-delegate/backend/internal/delegate/destination_state.go +117 -0
  219. package/dist/templates/native-go-delegate/backend/internal/delegate/destination_state_test.go +60 -0
  220. package/dist/templates/native-go-delegate/backend/internal/delegate/destination_test.go +116 -0
  221. package/dist/templates/native-go-delegate/backend/internal/delegate/example.go +81 -0
  222. package/dist/templates/native-go-delegate/backend/internal/delegate/example_test.go +91 -0
  223. package/dist/templates/native-go-delegate/backend/internal/delegate/executor_contract_test.go +511 -0
  224. package/dist/templates/native-go-delegate/backend/internal/delegate/files.go +1 -1
  225. package/dist/templates/native-go-delegate/backend/internal/delegate/long_running_routes.go +92 -0
  226. package/dist/templates/native-go-delegate/backend/internal/delegate/long_running_routes_test.go +99 -0
  227. package/dist/templates/native-go-delegate/backend/internal/delegate/move.go +348 -0
  228. package/dist/templates/native-go-delegate/backend/internal/delegate/move_test.go +351 -0
  229. package/dist/templates/native-go-delegate/backend/internal/delegate/operations_list.go +65 -0
  230. package/dist/templates/native-go-delegate/backend/internal/delegate/operations_list_test.go +114 -0
  231. package/dist/templates/native-go-delegate/backend/internal/delegate/retry_test.go +1 -1
  232. package/dist/templates/native-go-delegate/backend/internal/delegate/service.go +58 -17
  233. package/dist/templates/native-go-delegate/backend/internal/delegate/staging.go +121 -0
  234. package/dist/templates/native-go-delegate/backend/internal/delegate/status_disabled_test.go +51 -0
  235. package/dist/templates/native-go-delegate/backend/internal/delegate/testdata/executor_contract.txt +112 -0
  236. package/dist/templates/native-go-delegate/backend/internal/delegate/transient_contract_test.go +137 -0
  237. package/dist/templates/native-go-delegate/backend/internal/filestation/files.go +1 -31
  238. package/dist/templates/native-go-delegate/frontend/src/CapabilityPanels.vue +30 -3
  239. package/dist/templates/native-go-delegate/frontend/src/delegate/BackgroundSavePanel.vue +113 -0
  240. package/dist/templates/native-go-delegate/frontend/src/delegate/copy.ts +5 -6
  241. package/dist/templates/native-go-delegate/frontend/test/background-save.test.ts +121 -0
  242. package/dist/templates/native-go-delegate/frontend/test/scope-settings.test.ts +49 -0
  243. package/dist/templates/native-go-delegate/test.sh +1 -0
  244. package/dist/templates/native-go-jobs/frontend/src/background/i18n/de.json +2 -2
  245. package/dist/templates/native-go-jobs/frontend/src/background/i18n/en.json +2 -2
  246. package/dist/templates/native-go-jobs/frontend/src/background/i18n/es.json +12 -12
  247. package/dist/templates/native-go-jobs/frontend/src/background/i18n/fr.json +4 -4
  248. package/dist/templates/native-go-jobs/frontend/src/background/i18n/it.json +17 -17
  249. package/dist/templates/native-go-jobs/frontend/src/background/i18n/ja.json +7 -7
  250. package/dist/templates/native-go-jobs/frontend/src/background/i18n/ko.json +15 -15
  251. package/dist/templates/native-go-jobs/frontend/src/background/i18n/zh-CN.json +3 -3
  252. package/dist/templates/native-go-jobs/frontend/src/background/i18n/zh-TW.json +2 -2
  253. package/dist/templates/native-go-jobs/frontend/test/background.test.ts +25 -28
  254. package/dist/templates/native-go-vue/README.md +4 -0
  255. package/dist/templates/native-go-vue/frontend/package-lock.json +4 -4
  256. package/dist/templates/native-go-vue/frontend/package.json +1 -1
  257. package/dist/templates/native-go-vue/frontend/src/App.vue +18 -13
  258. package/dist/templates/native-go-vue/frontend/src/context.ts +31 -0
  259. package/dist/templates/native-go-vue/frontend/src/delegate/ExecutorSettings.vue +52 -8
  260. package/dist/templates/native-go-vue/frontend/src/delegate/catalogs.ts +11 -0
  261. package/dist/templates/native-go-vue/frontend/src/delegate/copy.test.ts +12 -0
  262. package/dist/templates/native-go-vue/frontend/src/delegate/copy.ts +4 -4
  263. package/dist/templates/native-go-vue/frontend/src/delegate/executor-setup.ts +4 -2
  264. package/dist/templates/native-go-vue/frontend/src/delegate/facade.test.ts +100 -0
  265. package/dist/templates/native-go-vue/frontend/src/delegate/facade.ts +85 -0
  266. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/de.json +46 -0
  267. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/en.json +12 -2
  268. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/es.json +46 -0
  269. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/fr.json +46 -0
  270. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/it.json +46 -0
  271. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/ja.json +46 -0
  272. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/ko.json +46 -0
  273. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/de.json +42 -0
  274. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/en.json +42 -0
  275. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/es.json +42 -0
  276. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/fr.json +42 -0
  277. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/it.json +42 -0
  278. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/ja.json +42 -0
  279. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/ko.json +42 -0
  280. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-CN.json +42 -0
  281. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-TW.json +42 -0
  282. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/zh-CN.json +46 -0
  283. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/zh-TW.json +15 -5
  284. package/dist/templates/native-go-vue/frontend/src/delegate/long-running.ts +57 -0
  285. package/dist/templates/native-go-vue/frontend/src/delegate/save-copy.test.ts +12 -0
  286. package/dist/templates/native-go-vue/frontend/src/delegate/save-copy.ts +24 -0
  287. package/dist/templates/native-go-vue/frontend/src/delegate/scoped-picker.test.ts +12 -0
  288. package/dist/templates/native-go-vue/frontend/src/delegate/scoped-picker.ts +21 -0
  289. package/dist/templates/native-go-vue/frontend/src/files/files.test.ts +7 -0
  290. package/dist/templates/native-go-vue/frontend/src/i18n/de.json +20 -0
  291. package/dist/templates/native-go-vue/frontend/src/i18n/en.json +20 -0
  292. package/dist/templates/native-go-vue/frontend/src/i18n/es.json +20 -0
  293. package/dist/templates/native-go-vue/frontend/src/i18n/fr.json +20 -0
  294. package/dist/templates/native-go-vue/frontend/src/i18n/it.json +20 -0
  295. package/dist/templates/native-go-vue/frontend/src/i18n/ja.json +20 -0
  296. package/dist/templates/native-go-vue/frontend/src/i18n/ko.json +20 -0
  297. package/dist/templates/native-go-vue/frontend/src/i18n/zh-CN.json +20 -0
  298. package/dist/templates/native-go-vue/frontend/src/i18n/zh-TW.json +20 -0
  299. package/dist/templates/native-go-vue/frontend/src/i18n.ts +13 -0
  300. package/dist/templates/native-go-vue/frontend/src/identity/LoginPage.vue +16 -14
  301. package/dist/templates/native-go-vue/frontend/src/identity/i18n/de.json +27 -0
  302. package/dist/templates/native-go-vue/frontend/src/identity/i18n/en.json +27 -0
  303. package/dist/templates/native-go-vue/frontend/src/identity/i18n/es.json +27 -0
  304. package/dist/templates/native-go-vue/frontend/src/identity/i18n/fr.json +27 -0
  305. package/dist/templates/native-go-vue/frontend/src/identity/i18n/it.json +27 -0
  306. package/dist/templates/native-go-vue/frontend/src/identity/i18n/ja.json +27 -0
  307. package/dist/templates/native-go-vue/frontend/src/identity/i18n/ko.json +27 -0
  308. package/dist/templates/native-go-vue/frontend/src/identity/i18n/zh-CN.json +27 -0
  309. package/dist/templates/native-go-vue/frontend/src/identity/i18n/zh-TW.json +27 -0
  310. package/dist/templates/native-go-vue/frontend/src/identity/i18n.ts +14 -0
  311. package/dist/templates/native-go-vue/frontend/src/identity/session.test.ts +4 -0
  312. package/dist/templates/native-go-vue/frontend/src/identity/session.ts +9 -8
  313. package/dist/templates/native-go-vue/frontend/src/nas/NasPicker.vue +31 -12
  314. package/dist/templates/native-go-vue/frontend/src/nas/destination.test.ts +48 -0
  315. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/de.json +1 -1
  316. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/en.json +1 -1
  317. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/es.json +10 -10
  318. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/fr.json +1 -1
  319. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/it.json +12 -12
  320. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/ja.json +15 -15
  321. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/ko.json +12 -12
  322. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/zh-CN.json +1 -1
  323. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/zh-TW.json +1 -1
  324. package/dist/templates/native-go-vue/frontend/src/portal/PortalSettings.vue +39 -0
  325. package/dist/templates/native-go-vue/frontend/src/shell/i18n/fr.json +1 -1
  326. package/dist/templates/native-go-vue/frontend/src/shell/i18n/ja.json +1 -1
  327. package/dist/templates/native-go-vue/frontend/test/apps.test.ts +14 -0
  328. package/dist/templates/native-go-vue/frontend/test/portal-settings.test.ts +71 -0
  329. package/dist/templates/native-go-vue/frontend/test/shell-i18n.test.ts +50 -0
  330. package/dist/validate.js +2 -1
  331. package/package.json +12 -2
  332. package/schema/package-metadata.schema.json +101 -0
  333. package/schema/synology-app.schema.json +112 -0
  334. package/skills/SKILL.md +109 -3
  335. package/skills/reference/create.md +101 -2
  336. package/skills/reference/deploy.md +2 -2
  337. package/skills/reference/files.md +113 -2
  338. package/skills/reference/identity.md +70 -1
  339. package/skills/reference/portal.md +17 -0
  340. package/skills/reference/ui-vue.md +12 -0
  341. package/templates/_daemon/main_test.go +61 -8
  342. package/templates/_executor/README.md +127 -16
  343. package/templates/_executor/anchor.go +146 -0
  344. package/templates/_executor/anchor_test.go +126 -0
  345. package/templates/_executor/apppriv_review_test.go +1 -1
  346. package/templates/_executor/cancel_contract_test.go +236 -0
  347. package/templates/_executor/copy.go +64 -0
  348. package/templates/_executor/copy_test.go +88 -0
  349. package/templates/_executor/dsm.go +23 -10
  350. package/templates/_executor/dsm_test.go +95 -1
  351. package/templates/_executor/executor.go +260 -49
  352. package/templates/_executor/executor_test.go +36 -6
  353. package/templates/_executor/identity.go +27 -1
  354. package/templates/_executor/identity_kernel_linux_test.go +60 -0
  355. package/templates/_executor/identity_linux.go +82 -0
  356. package/templates/_executor/identity_security_test.go +43 -0
  357. package/templates/_executor/internal/lifecycle/lifecycle.go +5 -0
  358. package/templates/_executor/internal/lifecycle/lifecycle_test.go +61 -3
  359. package/templates/_executor/internal/taskscript/boot.sh +9 -9
  360. package/templates/_executor/internal/taskscript/cmd/main.go +2 -2
  361. package/templates/_executor/internal/taskscript/control.sh +15 -10
  362. package/templates/_executor/internal/taskscript/tasks.go +5 -3
  363. package/templates/_executor/lifecycle_test.go +11 -0
  364. package/templates/_executor/link_linux.go +28 -0
  365. package/templates/_executor/long_running_test.go +530 -0
  366. package/templates/_executor/main.go +72 -20
  367. package/templates/_executor/move_linux.go +490 -0
  368. package/templates/_executor/move_linux_test.go +460 -0
  369. package/templates/_executor/move_validation.go +46 -0
  370. package/templates/_executor/move_validation_test.go +187 -0
  371. package/templates/_executor/operations.go +926 -0
  372. package/templates/_executor/platform_linux.go +24 -173
  373. package/templates/_executor/platform_linux_test.go +9 -65
  374. package/templates/_executor/platform_other.go +30 -4
  375. package/templates/_executor/process.go +3 -0
  376. package/templates/_executor/records.go +242 -0
  377. package/templates/_executor/scope_cancel_contract_test.go +91 -0
  378. package/templates/_executor/scope_client.go +37 -0
  379. package/templates/_executor/scope_contract.go +12 -0
  380. package/templates/_executor/selected_scope.go +146 -0
  381. package/templates/_executor/selected_scope_test.go +186 -0
  382. package/templates/_executor/status_disabled_test.go +33 -0
  383. package/templates/_executor/store.go +4 -3
  384. package/templates/_executor/supervise.go +109 -0
  385. package/templates/_executor/supervise_test.go +130 -0
  386. package/templates/_executor/task_race_review_test.go +1 -1
  387. package/templates/_executor/tasks.go +7 -8
  388. package/templates/_executor/tasks_review_test.go +1 -1
  389. package/templates/_executor/tasks_test.go +4 -4
  390. package/templates/_executor/watch.go +8 -0
  391. package/templates/_executor/watch_test.go +23 -2
  392. package/templates/_executor/write_review_linux_test.go +4 -12
  393. package/templates/_shared/AGENTS.md +129 -0
  394. package/templates/binary/dsm-package.yaml +24 -0
  395. package/templates/binary/i18n/messages/de.json +4 -0
  396. package/templates/binary/i18n/messages/en.json +4 -0
  397. package/templates/binary/i18n/messages/es.json +4 -0
  398. package/templates/binary/i18n/messages/fr.json +4 -0
  399. package/templates/binary/i18n/messages/it.json +4 -0
  400. package/templates/binary/i18n/messages/ja.json +4 -0
  401. package/templates/binary/i18n/messages/ko.json +4 -0
  402. package/templates/binary/i18n/messages/zh-CN.json +4 -0
  403. package/templates/binary/i18n/messages/zh-TW.json +4 -0
  404. package/templates/binary/ui/app.js +19 -4
  405. package/templates/native-authorization/AuthorizationGate.vue +91 -0
  406. package/templates/native-authorization/i18n/de.json +37 -0
  407. package/templates/native-authorization/i18n/en.json +37 -0
  408. package/templates/native-authorization/i18n/es.json +37 -0
  409. package/templates/native-authorization/i18n/fr.json +37 -0
  410. package/templates/native-authorization/i18n/index.ts +15 -0
  411. package/templates/native-authorization/i18n/it.json +37 -0
  412. package/templates/native-authorization/i18n/ja.json +37 -0
  413. package/templates/native-authorization/i18n/ko.json +37 -0
  414. package/templates/native-authorization/i18n/zh-CN.json +37 -0
  415. package/templates/native-authorization/i18n/zh-TW.json +37 -0
  416. package/templates/native-go/README.md +43 -10
  417. package/templates/native-go/backend/cmd/app/main.go +27 -7
  418. package/templates/native-go/backend/internal/api/entry_test.go +47 -0
  419. package/templates/native-go/backend/internal/api/permissions_test.go +3 -3
  420. package/templates/native-go/backend/internal/api/registry.go +11 -0
  421. package/templates/native-go/backend/internal/api/sdk-permissions.json +14 -1
  422. package/templates/native-go/backend/internal/api/server.go +18 -5
  423. package/templates/native-go/backend/internal/app/delegate_config.go +57 -0
  424. package/templates/native-go/backend/internal/app/delegate_config_test.go +28 -0
  425. package/templates/native-go/backend/internal/app/portal.go +30 -0
  426. package/templates/native-go/backend/internal/app/portal_test.go +55 -0
  427. package/templates/native-go/backend/internal/app/routes.go +8 -2
  428. package/templates/native-go/backend/internal/background/runner.go +1 -1
  429. package/templates/native-go/backend/internal/background/store.go +16 -14
  430. package/templates/native-go/backend/internal/dsm/admin.go +8 -2
  431. package/templates/native-go/backend/internal/dsm/admin_linux.go +43 -13
  432. package/templates/native-go/backend/internal/dsm/admin_linux_test.go +56 -0
  433. package/templates/native-go/backend/internal/dsm/admin_other.go +4 -1
  434. package/templates/native-go/backend/internal/dsm/apppriv.go +30 -0
  435. package/templates/native-go/backend/internal/dsm/bridge_file.go +75 -0
  436. package/templates/native-go/backend/internal/dsm/bridge_file_test.go +57 -0
  437. package/templates/native-go/backend/internal/dsm/entry.go +177 -0
  438. package/templates/native-go/backend/internal/dsm/entry_test.go +315 -0
  439. package/templates/native-go/backend/internal/dsm/native/admin-armv7.elf +0 -0
  440. package/templates/native-go/backend/internal/dsm/native/apppriv-aarch64.elf +0 -0
  441. package/templates/native-go/backend/internal/dsm/native/apppriv-armv7.elf +0 -0
  442. package/templates/native-go/backend/internal/dsm/native/apppriv-x86_64.elf +0 -0
  443. package/templates/native-go/backend/internal/dsm/native/apppriv_check.c +33 -0
  444. package/templates/native-go/backend/internal/dsm/native/build.sh +13 -4
  445. package/templates/native-go/backend/internal/dsm/native/provenance.json +9 -3
  446. package/templates/native-go/backend/internal/filestation/check_permission.go +71 -0
  447. package/templates/native-go/backend/internal/filestation/check_permission_test.go +77 -0
  448. package/templates/native-go/backend/internal/filestation/sdk_policy_test.go +39 -0
  449. package/templates/native-go/backend/internal/filestation/service.go +1 -1
  450. package/templates/native-go/backend/internal/portal/portal.go +138 -0
  451. package/templates/native-go/backend/internal/portal/portal_test.go +89 -0
  452. package/templates/native-go/dsm-package.yaml +20 -1
  453. package/templates/native-go/i18n/glossary.json +2 -1
  454. package/templates/native-go/i18n/messages/de.json +1 -0
  455. package/templates/native-go/i18n/messages/en.json +1 -0
  456. package/templates/native-go/i18n/messages/es.json +1 -0
  457. package/templates/native-go/i18n/messages/fr.json +1 -0
  458. package/templates/native-go/i18n/messages/it.json +1 -0
  459. package/templates/native-go/i18n/messages/ja.json +1 -0
  460. package/templates/native-go/i18n/messages/ko.json +1 -0
  461. package/templates/native-go/i18n/messages/zh-CN.json +1 -0
  462. package/templates/native-go/i18n/messages/zh-TW.json +1 -0
  463. package/templates/native-go/ui/app.css +10 -0
  464. package/templates/native-go/ui/app.js +11 -1
  465. package/templates/native-go-delegate/DELEGATE.md +126 -5
  466. package/templates/native-go-delegate/backend/cmd/background-worker/main.go +7 -0
  467. package/templates/native-go-delegate/backend/internal/app/delegate.go +11 -1
  468. package/templates/native-go-delegate/backend/internal/app/delegate_routes_test.go +14 -0
  469. package/templates/native-go-delegate/backend/internal/background/delegated_moves.go +270 -0
  470. package/templates/native-go-delegate/backend/internal/delegate/admission_recovery_test.go +410 -0
  471. package/templates/native-go-delegate/backend/internal/delegate/cancel_contract_test.go +95 -0
  472. package/templates/native-go-delegate/backend/internal/delegate/client.go +8 -0
  473. package/templates/native-go-delegate/backend/internal/delegate/destination.go +110 -0
  474. package/templates/native-go-delegate/backend/internal/delegate/destination_state.go +117 -0
  475. package/templates/native-go-delegate/backend/internal/delegate/destination_state_test.go +60 -0
  476. package/templates/native-go-delegate/backend/internal/delegate/destination_test.go +116 -0
  477. package/templates/native-go-delegate/backend/internal/delegate/example.go +81 -0
  478. package/templates/native-go-delegate/backend/internal/delegate/example_test.go +91 -0
  479. package/templates/native-go-delegate/backend/internal/delegate/executor_contract_test.go +511 -0
  480. package/templates/native-go-delegate/backend/internal/delegate/files.go +1 -1
  481. package/templates/native-go-delegate/backend/internal/delegate/long_running_routes.go +92 -0
  482. package/templates/native-go-delegate/backend/internal/delegate/long_running_routes_test.go +99 -0
  483. package/templates/native-go-delegate/backend/internal/delegate/move.go +348 -0
  484. package/templates/native-go-delegate/backend/internal/delegate/move_test.go +351 -0
  485. package/templates/native-go-delegate/backend/internal/delegate/operations_list.go +65 -0
  486. package/templates/native-go-delegate/backend/internal/delegate/operations_list_test.go +114 -0
  487. package/templates/native-go-delegate/backend/internal/delegate/retry_test.go +1 -1
  488. package/templates/native-go-delegate/backend/internal/delegate/service.go +58 -17
  489. package/templates/native-go-delegate/backend/internal/delegate/staging.go +121 -0
  490. package/templates/native-go-delegate/backend/internal/delegate/status_disabled_test.go +51 -0
  491. package/templates/native-go-delegate/backend/internal/delegate/testdata/executor_contract.txt +112 -0
  492. package/templates/native-go-delegate/backend/internal/delegate/transient_contract_test.go +137 -0
  493. package/templates/native-go-delegate/backend/internal/filestation/files.go +1 -31
  494. package/templates/native-go-delegate/frontend/src/CapabilityPanels.vue +30 -3
  495. package/templates/native-go-delegate/frontend/src/delegate/BackgroundSavePanel.vue +113 -0
  496. package/templates/native-go-delegate/frontend/src/delegate/copy.ts +5 -6
  497. package/templates/native-go-delegate/frontend/test/background-save.test.ts +121 -0
  498. package/templates/native-go-delegate/frontend/test/scope-settings.test.ts +49 -0
  499. package/templates/native-go-delegate/test.sh +1 -0
  500. package/templates/native-go-jobs/frontend/src/background/i18n/de.json +2 -2
  501. package/templates/native-go-jobs/frontend/src/background/i18n/en.json +2 -2
  502. package/templates/native-go-jobs/frontend/src/background/i18n/es.json +12 -12
  503. package/templates/native-go-jobs/frontend/src/background/i18n/fr.json +4 -4
  504. package/templates/native-go-jobs/frontend/src/background/i18n/it.json +17 -17
  505. package/templates/native-go-jobs/frontend/src/background/i18n/ja.json +7 -7
  506. package/templates/native-go-jobs/frontend/src/background/i18n/ko.json +15 -15
  507. package/templates/native-go-jobs/frontend/src/background/i18n/zh-CN.json +3 -3
  508. package/templates/native-go-jobs/frontend/src/background/i18n/zh-TW.json +2 -2
  509. package/templates/native-go-jobs/frontend/test/background.test.ts +25 -28
  510. package/templates/native-go-vue/README.md +4 -0
  511. package/templates/native-go-vue/frontend/package-lock.json +4 -4
  512. package/templates/native-go-vue/frontend/package.json +1 -1
  513. package/templates/native-go-vue/frontend/src/App.vue +18 -13
  514. package/templates/native-go-vue/frontend/src/context.ts +31 -0
  515. package/templates/native-go-vue/frontend/src/delegate/ExecutorSettings.vue +52 -8
  516. package/templates/native-go-vue/frontend/src/delegate/catalogs.ts +11 -0
  517. package/templates/native-go-vue/frontend/src/delegate/copy.test.ts +12 -0
  518. package/templates/native-go-vue/frontend/src/delegate/copy.ts +4 -4
  519. package/templates/native-go-vue/frontend/src/delegate/executor-setup.ts +4 -2
  520. package/templates/native-go-vue/frontend/src/delegate/facade.test.ts +100 -0
  521. package/templates/native-go-vue/frontend/src/delegate/facade.ts +85 -0
  522. package/templates/native-go-vue/frontend/src/delegate/i18n/de.json +46 -0
  523. package/templates/native-go-vue/frontend/src/delegate/i18n/en.json +12 -2
  524. package/templates/native-go-vue/frontend/src/delegate/i18n/es.json +46 -0
  525. package/templates/native-go-vue/frontend/src/delegate/i18n/fr.json +46 -0
  526. package/templates/native-go-vue/frontend/src/delegate/i18n/it.json +46 -0
  527. package/templates/native-go-vue/frontend/src/delegate/i18n/ja.json +46 -0
  528. package/templates/native-go-vue/frontend/src/delegate/i18n/ko.json +46 -0
  529. package/templates/native-go-vue/frontend/src/delegate/i18n/save/de.json +42 -0
  530. package/templates/native-go-vue/frontend/src/delegate/i18n/save/en.json +42 -0
  531. package/templates/native-go-vue/frontend/src/delegate/i18n/save/es.json +42 -0
  532. package/templates/native-go-vue/frontend/src/delegate/i18n/save/fr.json +42 -0
  533. package/templates/native-go-vue/frontend/src/delegate/i18n/save/it.json +42 -0
  534. package/templates/native-go-vue/frontend/src/delegate/i18n/save/ja.json +42 -0
  535. package/templates/native-go-vue/frontend/src/delegate/i18n/save/ko.json +42 -0
  536. package/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-CN.json +42 -0
  537. package/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-TW.json +42 -0
  538. package/templates/native-go-vue/frontend/src/delegate/i18n/zh-CN.json +46 -0
  539. package/templates/native-go-vue/frontend/src/delegate/i18n/zh-TW.json +15 -5
  540. package/templates/native-go-vue/frontend/src/delegate/long-running.ts +57 -0
  541. package/templates/native-go-vue/frontend/src/delegate/save-copy.test.ts +12 -0
  542. package/templates/native-go-vue/frontend/src/delegate/save-copy.ts +24 -0
  543. package/templates/native-go-vue/frontend/src/delegate/scoped-picker.test.ts +12 -0
  544. package/templates/native-go-vue/frontend/src/delegate/scoped-picker.ts +21 -0
  545. package/templates/native-go-vue/frontend/src/files/files.test.ts +7 -0
  546. package/templates/native-go-vue/frontend/src/i18n/de.json +20 -0
  547. package/templates/native-go-vue/frontend/src/i18n/en.json +20 -0
  548. package/templates/native-go-vue/frontend/src/i18n/es.json +20 -0
  549. package/templates/native-go-vue/frontend/src/i18n/fr.json +20 -0
  550. package/templates/native-go-vue/frontend/src/i18n/it.json +20 -0
  551. package/templates/native-go-vue/frontend/src/i18n/ja.json +20 -0
  552. package/templates/native-go-vue/frontend/src/i18n/ko.json +20 -0
  553. package/templates/native-go-vue/frontend/src/i18n/zh-CN.json +20 -0
  554. package/templates/native-go-vue/frontend/src/i18n/zh-TW.json +20 -0
  555. package/templates/native-go-vue/frontend/src/i18n.ts +13 -0
  556. package/templates/native-go-vue/frontend/src/identity/LoginPage.vue +16 -14
  557. package/templates/native-go-vue/frontend/src/identity/i18n/de.json +27 -0
  558. package/templates/native-go-vue/frontend/src/identity/i18n/en.json +27 -0
  559. package/templates/native-go-vue/frontend/src/identity/i18n/es.json +27 -0
  560. package/templates/native-go-vue/frontend/src/identity/i18n/fr.json +27 -0
  561. package/templates/native-go-vue/frontend/src/identity/i18n/it.json +27 -0
  562. package/templates/native-go-vue/frontend/src/identity/i18n/ja.json +27 -0
  563. package/templates/native-go-vue/frontend/src/identity/i18n/ko.json +27 -0
  564. package/templates/native-go-vue/frontend/src/identity/i18n/zh-CN.json +27 -0
  565. package/templates/native-go-vue/frontend/src/identity/i18n/zh-TW.json +27 -0
  566. package/templates/native-go-vue/frontend/src/identity/i18n.ts +14 -0
  567. package/templates/native-go-vue/frontend/src/identity/session.test.ts +4 -0
  568. package/templates/native-go-vue/frontend/src/identity/session.ts +9 -8
  569. package/templates/native-go-vue/frontend/src/nas/NasPicker.vue +31 -12
  570. package/templates/native-go-vue/frontend/src/nas/destination.test.ts +48 -0
  571. package/templates/native-go-vue/frontend/src/nas/i18n/files/de.json +1 -1
  572. package/templates/native-go-vue/frontend/src/nas/i18n/files/en.json +1 -1
  573. package/templates/native-go-vue/frontend/src/nas/i18n/files/es.json +10 -10
  574. package/templates/native-go-vue/frontend/src/nas/i18n/files/fr.json +1 -1
  575. package/templates/native-go-vue/frontend/src/nas/i18n/files/it.json +12 -12
  576. package/templates/native-go-vue/frontend/src/nas/i18n/files/ja.json +15 -15
  577. package/templates/native-go-vue/frontend/src/nas/i18n/files/ko.json +12 -12
  578. package/templates/native-go-vue/frontend/src/nas/i18n/files/zh-CN.json +1 -1
  579. package/templates/native-go-vue/frontend/src/nas/i18n/files/zh-TW.json +1 -1
  580. package/templates/native-go-vue/frontend/src/portal/PortalSettings.vue +39 -0
  581. package/templates/native-go-vue/frontend/src/shell/i18n/fr.json +1 -1
  582. package/templates/native-go-vue/frontend/src/shell/i18n/ja.json +1 -1
  583. package/templates/native-go-vue/frontend/test/apps.test.ts +14 -0
  584. package/templates/native-go-vue/frontend/test/portal-settings.test.ts +71 -0
  585. package/templates/native-go-vue/frontend/test/shell-i18n.test.ts +50 -0
@@ -0,0 +1,315 @@
1
+ package dsm
2
+
3
+ import (
4
+ "context"
5
+ "encoding/json"
6
+ "errors"
7
+ "net/http"
8
+ "net/http/httptest"
9
+ "os"
10
+ "path/filepath"
11
+ "strconv"
12
+ "strings"
13
+ "testing"
14
+ "time"
15
+
16
+ "dsmapp/internal/api"
17
+ )
18
+
19
+ type fixedAuth struct {
20
+ id api.Identity
21
+ err error
22
+ }
23
+
24
+ func (f fixedAuth) Authenticate(*http.Request) (api.Identity, error) { return f.id, f.err }
25
+
26
+ type lookup struct {
27
+ calls int
28
+ user, app, ip string
29
+ allowed bool
30
+ err error
31
+ }
32
+
33
+ func (l *lookup) check(_ context.Context, user, app, ip string) (bool, error) {
34
+ l.calls++
35
+ l.user, l.app, l.ip = user, app, ip
36
+ return l.allowed && l.err == nil, l.err
37
+ }
38
+
39
+ func newTestGate(t *testing.T, mode string, next api.Authenticator, l *lookup) (*EntryGate, *time.Time) {
40
+ t.Helper()
41
+ auth, err := NewEntryGate(next, mode, "DemoApp", t.TempDir())
42
+ if err != nil {
43
+ t.Fatal(err)
44
+ }
45
+ gate := auth.(*EntryGate)
46
+ now := time.Unix(1_700_000_000, 0)
47
+ gate.now = func() time.Time { return now }
48
+ gate.allowed = l.check
49
+ return gate, &now
50
+ }
51
+
52
+ func ordinary(user string) fixedAuth { return fixedAuth{id: api.Identity{User: user}} }
53
+
54
+ func TestUndeclaredEntryKeepsTheAuthenticatorUnchanged(t *testing.T) {
55
+ next := ordinary("bob")
56
+ got, err := NewEntryGate(next, "", "DemoApp", t.TempDir())
57
+ if err != nil || got != api.Authenticator(next) {
58
+ t.Fatalf("legacy entry wrapped the authenticator: %T %v", got, err)
59
+ }
60
+ }
61
+
62
+ func TestUnknownEntryModeFailsAtStartup(t *testing.T) {
63
+ for _, mode := range []string{"all-users", "users", "App-Privilege", "admin"} {
64
+ if _, err := NewEntryGate(ordinary("bob"), mode, "DemoApp", t.TempDir()); err == nil {
65
+ t.Fatalf("accepted entry mode %q", mode)
66
+ }
67
+ }
68
+ if _, err := NewEntryGate(nil, EntryAppPrivilege, "DemoApp", t.TempDir()); err == nil {
69
+ t.Fatal("accepted a missing authenticator")
70
+ }
71
+ if _, err := NewEntryGate(ordinary("bob"), EntryAppPrivilege, "Demo.App", t.TempDir()); err == nil {
72
+ t.Fatal("accepted an invalid package ID")
73
+ }
74
+ }
75
+
76
+ func TestAdministratorsEnterWithoutAppPrivilegeLookup(t *testing.T) {
77
+ for _, mode := range []string{EntryAdminOnly, EntryAppPrivilege} {
78
+ l := &lookup{}
79
+ gate, _ := newTestGate(t, mode, fixedAuth{id: api.Identity{User: "alice", Admin: true}}, l)
80
+ id, err := gate.Authenticate(sessionRequest())
81
+ if err != nil || !id.Admin || id.User != "alice" || l.calls != 0 {
82
+ t.Fatalf("%s: %+v %v calls=%d", mode, id, err, l.calls)
83
+ }
84
+ }
85
+ }
86
+
87
+ func TestAdminOnlyEntryRefusesOrdinaryUsers(t *testing.T) {
88
+ l := &lookup{allowed: true}
89
+ gate, _ := newTestGate(t, EntryAdminOnly, ordinary("bob"), l)
90
+ if _, err := gate.Authenticate(sessionRequest()); !errors.Is(err, api.ErrAdminRequired) || l.calls != 0 {
91
+ t.Fatalf("err=%v calls=%d", err, l.calls)
92
+ }
93
+ }
94
+
95
+ func TestAppPrivilegeEntryAsksDSMForTheRegisteredAppAndClientAddress(t *testing.T) {
96
+ l := &lookup{allowed: true}
97
+ gate, _ := newTestGate(t, EntryAppPrivilege, ordinary(`CORP\bob`), l)
98
+ id, err := gate.Authenticate(sessionRequest())
99
+ if err != nil || id.User != `CORP\bob` || id.Admin {
100
+ t.Fatalf("%+v %v", id, err)
101
+ }
102
+ if l.user != `CORP\bob` || l.app != "SYNO.SDS.DemoApp.Instance" || l.ip != "192.0.2.7" {
103
+ t.Fatalf("lookup got user=%q app=%q ip=%q", l.user, l.app, l.ip)
104
+ }
105
+ }
106
+
107
+ func TestAppPrivilegeDenialAndLookupFailureBothRefuse(t *testing.T) {
108
+ gate, _ := newTestGate(t, EntryAppPrivilege, ordinary("bob"), &lookup{allowed: false})
109
+ if _, err := gate.Authenticate(sessionRequest()); !errors.Is(err, api.ErrAppPrivilegeDenied) {
110
+ t.Fatalf("denied: %v", err)
111
+ }
112
+ gate, _ = newTestGate(t, EntryAppPrivilege, ordinary("bob"), &lookup{err: errors.New("bridge exited 2")})
113
+ _, err := gate.Authenticate(sessionRequest())
114
+ if err == nil || errors.Is(err, api.ErrAppPrivilegeDenied) || errors.Is(err, api.ErrNoSession) {
115
+ t.Fatalf("lookup failure must surface as unavailable: %v", err)
116
+ }
117
+ }
118
+
119
+ func TestUnparseableClientAddressRefusesWithoutLookup(t *testing.T) {
120
+ for _, addr := range []string{"", "not-an-ip", "192.0.2.300:1", "[::1"} {
121
+ l := &lookup{allowed: true}
122
+ gate, _ := newTestGate(t, EntryAppPrivilege, ordinary("bob"), l)
123
+ r := sessionRequest()
124
+ r.RemoteAddr = addr
125
+ if _, err := gate.Authenticate(r); err == nil || l.calls != 0 {
126
+ t.Fatalf("%q: err=%v calls=%d", addr, err, l.calls)
127
+ }
128
+ }
129
+ l := &lookup{allowed: true}
130
+ gate, _ := newTestGate(t, EntryAppPrivilege, ordinary("bob"), l)
131
+ r := sessionRequest()
132
+ r.RemoteAddr = "[2001:db8::7]:443"
133
+ if _, err := gate.Authenticate(r); err != nil || l.ip != "2001:db8::7" {
134
+ t.Fatalf("IPv6: %v %q", err, l.ip)
135
+ }
136
+ }
137
+
138
+ func TestSessionAndAuthenticationFailuresPassThroughUnchanged(t *testing.T) {
139
+ for _, want := range []error{api.ErrNoSession, errors.New("authenticate.cgi unavailable")} {
140
+ l := &lookup{allowed: true}
141
+ gate, _ := newTestGate(t, EntryAppPrivilege, fixedAuth{err: want}, l)
142
+ if _, err := gate.Authenticate(sessionRequest()); !errors.Is(err, want) || l.calls != 0 {
143
+ t.Fatalf("err=%v calls=%d", err, l.calls)
144
+ }
145
+ }
146
+ }
147
+
148
+ func TestDecisionsAreCachedBrieflyPerUserAppAndAddress(t *testing.T) {
149
+ l := &lookup{allowed: true}
150
+ gate, now := newTestGate(t, EntryAppPrivilege, ordinary("bob"), l)
151
+ for range 3 {
152
+ if _, err := gate.Authenticate(sessionRequest()); err != nil {
153
+ t.Fatal(err)
154
+ }
155
+ }
156
+ if l.calls != 1 {
157
+ t.Fatalf("cached decision looked up %d times", l.calls)
158
+ }
159
+ l.allowed = false
160
+ *now = now.Add(EntryCacheTTL - time.Second)
161
+ if _, err := gate.Authenticate(sessionRequest()); err != nil || l.calls != 1 {
162
+ t.Fatalf("within TTL: %v calls=%d", err, l.calls)
163
+ }
164
+ *now = now.Add(2 * time.Second)
165
+ if _, err := gate.Authenticate(sessionRequest()); !errors.Is(err, api.ErrAppPrivilegeDenied) || l.calls != 2 {
166
+ t.Fatalf("after TTL: %v calls=%d", err, l.calls)
167
+ }
168
+ r := sessionRequest()
169
+ r.RemoteAddr = "198.51.100.4:1"
170
+ gate.Authenticate(r)
171
+ if l.calls != 3 {
172
+ t.Fatalf("another address reused a cached decision: calls=%d", l.calls)
173
+ }
174
+ }
175
+
176
+ func TestCacheSurvivesAcrossCGIProcessesAndIgnoresFutureEntries(t *testing.T) {
177
+ dir := t.TempDir()
178
+ l := &lookup{allowed: true}
179
+ first, err := NewEntryGate(ordinary("bob"), EntryAppPrivilege, "DemoApp", dir)
180
+ if err != nil {
181
+ t.Fatal(err)
182
+ }
183
+ now := time.Unix(1_700_000_000, 0)
184
+ g1 := first.(*EntryGate)
185
+ g1.now = func() time.Time { return now }
186
+ g1.allowed = l.check
187
+ g1.Authenticate(sessionRequest())
188
+ second, _ := NewEntryGate(ordinary("bob"), EntryAppPrivilege, "DemoApp", dir)
189
+ g2 := second.(*EntryGate)
190
+ g2.now = func() time.Time { return now.Add(time.Second) }
191
+ g2.allowed = g1.allowed
192
+ g2.Authenticate(sessionRequest())
193
+ if l.calls != 1 {
194
+ t.Fatalf("a new process ignored the shared cache: calls=%d", l.calls)
195
+ }
196
+ g2.now = func() time.Time { return now.Add(-time.Minute) }
197
+ g2.Authenticate(sessionRequest())
198
+ if l.calls != 2 {
199
+ t.Fatalf("an entry from the future was trusted: calls=%d", l.calls)
200
+ }
201
+ info, err := os.Stat(filepath.Join(dir, "entry", entryCacheFile))
202
+ if err != nil || info.Mode().Perm() != 0o600 {
203
+ t.Fatalf("cache file: %v %v", info, err)
204
+ }
205
+ }
206
+
207
+ func TestFailuresAreNeverCached(t *testing.T) {
208
+ l := &lookup{err: errors.New("bridge exited 2")}
209
+ gate, _ := newTestGate(t, EntryAppPrivilege, ordinary("bob"), l)
210
+ gate.Authenticate(sessionRequest())
211
+ l.err, l.allowed = nil, true
212
+ if _, err := gate.Authenticate(sessionRequest()); err != nil || l.calls != 2 {
213
+ t.Fatalf("err=%v calls=%d", err, l.calls)
214
+ }
215
+ }
216
+
217
+ func TestCacheIsBoundedAndToleratesCorruption(t *testing.T) {
218
+ l := &lookup{allowed: true}
219
+ gate, _ := newTestGate(t, EntryAppPrivilege, ordinary("bob"), l)
220
+ for i := range EntryCacheMaxEntries + 20 {
221
+ r := sessionRequest()
222
+ r.RemoteAddr = "10.0." + strconv.Itoa(i/250) + "." + strconv.Itoa(i%250) + ":1"
223
+ if _, err := gate.Authenticate(r); err != nil {
224
+ t.Fatal(err)
225
+ }
226
+ }
227
+ entries, err := gate.readCache()
228
+ if err != nil || len(entries) > EntryCacheMaxEntries {
229
+ t.Fatalf("cache holds %d entries: %v", len(entries), err)
230
+ }
231
+ if err := os.WriteFile(filepath.Join(gate.dir, entryCacheFile), []byte("{not json"), 0o600); err != nil {
232
+ t.Fatal(err)
233
+ }
234
+ before := l.calls
235
+ if _, err := gate.Authenticate(sessionRequest()); err != nil || l.calls != before+1 {
236
+ t.Fatalf("corrupt cache: %v calls=%d", err, l.calls-before)
237
+ }
238
+ }
239
+
240
+ func TestUnwritableCacheStillDecides(t *testing.T) {
241
+ l := &lookup{allowed: false}
242
+ gate, _ := newTestGate(t, EntryAppPrivilege, ordinary("bob"), l)
243
+ gate.dir = filepath.Join(t.TempDir(), "missing", "nested")
244
+ if _, err := gate.Authenticate(sessionRequest()); !errors.Is(err, api.ErrAppPrivilegeDenied) {
245
+ t.Fatal(err)
246
+ }
247
+ }
248
+
249
+ func TestAppPrivilegeRefusesMalformedInputsBeforeTheBridge(t *testing.T) {
250
+ for _, app := range []string{"", "SYNO", "SYNO.SDS.Demo App.Instance", "SYNO..Instance", "SYNO.SDS.Demo.Instance\n"} {
251
+ if appID.MatchString(app) {
252
+ t.Fatalf("accepted app ID %q", app)
253
+ }
254
+ }
255
+ if !appID.MatchString("SYNO.SDS.DemoApp.Instance") {
256
+ t.Fatal("refused the generated app ID")
257
+ }
258
+ for _, args := range [][3]string{{"", "SYNO.SDS.Demo.Instance", "10.0.0.5"}, {"bob", "SYNO.SDS.Demo.Instance", "10.0.0.05"}, {"bob", "SYNO.SDS.Demo.Instance", "fe80::1%eth0"}, {"bob", "bad", "10.0.0.5"}} {
259
+ if ok, err := AppPrivilege(context.Background(), args[0], args[1], args[2]); ok || err == nil {
260
+ t.Fatalf("%q: %v %v", args, ok, err)
261
+ }
262
+ }
263
+ }
264
+
265
+ // admin-only is the floor of every route: SDK routes the policy marks
266
+ // authenticated (nas.files.list, bg.jobs.submit, delegate.status) refuse an
267
+ // ordinary user exactly like app routes, while administrators reach them.
268
+ func TestAdminOnlyEntryFloorsSDKRoutesAtAdministrators(t *testing.T) {
269
+ sdkRoutes := []string{"nas.files.list", "bg.jobs.submit", "delegate.status", "portal.status", "executor.status"}
270
+ for _, tc := range []struct {
271
+ id api.Identity
272
+ code string
273
+ }{
274
+ {api.Identity{User: "bob"}, "admin_required"},
275
+ {api.Identity{User: "alice", Admin: true}, ""},
276
+ } {
277
+ executed := 0
278
+ var routes []api.Route
279
+ for _, method := range sdkRoutes {
280
+ routes = append(routes, api.Handle(method, func(context.Context, api.Caller, json.RawMessage) (any, error) {
281
+ executed++
282
+ return map[string]bool{"ok": true}, nil
283
+ }))
284
+ }
285
+ gate, _ := newTestGate(t, EntryAdminOnly, fixedAuth{id: tc.id}, &lookup{allowed: true})
286
+ srv, err := api.NewServerWithPermissions(gate, []byte(`{"schema":1,"default":"administrators","methods":{}}`), routes...)
287
+ if err != nil {
288
+ t.Fatal(err)
289
+ }
290
+ for _, method := range sdkRoutes {
291
+ req := httptest.NewRequest(http.MethodPost, "http://nas.local/webman/3rdparty/DemoApp/cgi/api.cgi", strings.NewReader(`{"method":"`+method+`"}`))
292
+ req.Header.Set("Content-Type", "application/json")
293
+ rec := httptest.NewRecorder()
294
+ srv.ServeHTTP(rec, req)
295
+ var env struct {
296
+ Error *struct {
297
+ Code string `json:"code"`
298
+ } `json:"error"`
299
+ }
300
+ if err := json.Unmarshal(rec.Body.Bytes(), &env); err != nil {
301
+ t.Fatalf("%s: %q", method, rec.Body.String())
302
+ }
303
+ got := ""
304
+ if env.Error != nil {
305
+ got = env.Error.Code
306
+ }
307
+ if got != tc.code {
308
+ t.Fatalf("%s as %+v: code %q, want %q", method, tc.id, got, tc.code)
309
+ }
310
+ }
311
+ if want := map[bool]int{true: len(sdkRoutes), false: 0}[tc.id.Admin]; executed != want {
312
+ t.Fatalf("%+v executed %d handlers, want %d", tc.id, executed, want)
313
+ }
314
+ }
315
+ }
@@ -0,0 +1,33 @@
1
+ // Minimal DSM SDK bridge for Application Privileges. DSM evaluates its own
2
+ // user, group and everyone rules, allow/deny precedence and IP ranges; this
3
+ // bridge only passes the verified principal, the registered app ID and the
4
+ // client address, and prints DSM's exact decision.
5
+ #include <dlfcn.h>
6
+ #include <stdio.h>
7
+ #include <string.h>
8
+
9
+ #ifndef DSM_SDK_LIBRARY
10
+ #define DSM_SDK_LIBRARY "/usr/lib/libsynosdk.so"
11
+ #endif
12
+
13
+ // ABI measured on DSM 7.4 (vt-nas, kernel 4.4): int (const char *user,
14
+ // const char *app, const char *ip) returning 1 (allowed) or 0 (refused).
15
+ // A NULL address refuses every account, so the address is mandatory.
16
+ typedef int (*apppriv_check_fn)(const char *, const char *, const char *);
17
+ _Static_assert(sizeof(int) == 4, "DSM BOOL ABI requires a 32-bit int");
18
+
19
+ static int bounded(const char *value, size_t max) {
20
+ return value[0] != '\0' && strlen(value) <= max;
21
+ }
22
+
23
+ int main(int argc, char **argv) {
24
+ if (argc != 4 || !bounded(argv[1], 1024) || !bounded(argv[2], 128) || !bounded(argv[3], 64)) return 2;
25
+ void *library = dlopen(DSM_SDK_LIBRARY, RTLD_NOW | RTLD_LOCAL);
26
+ if (!library) return 2;
27
+ apppriv_check_fn check = (apppriv_check_fn)dlsym(library, "SLIBAppPrivUserHas");
28
+ if (!check) { dlclose(library); return 2; }
29
+ int result = check(argv[1], argv[2], argv[3]);
30
+ dlclose(library);
31
+ if (result != 0 && result != 1) return 2;
32
+ return printf("%d\n", result) == 2 ? 0 : 2;
33
+ }
@@ -1,9 +1,18 @@
1
1
  #!/bin/sh
2
2
  set -eu
3
3
  # Zig 0.16.0 cross-compiles this small libc/dlopen bridge. No proprietary SDK
4
- # headers or libraries are distributed. GNU ABI 2.17 covers both DSM targets.
4
+ # headers or libraries are distributed. GNU ABI 2.17 covers every DSM target.
5
5
  cd "$(dirname "$0")"
6
6
  [ "$(zig version)" = "0.16.0" ] || { echo 'requires Zig 0.16.0' >&2; exit 2; }
7
- for arch in x86_64 aarch64; do
8
- zig cc admin_check.c -target "$arch-linux-gnu.2.17" -std=c11 -O2 -Wall -Wextra -Werror -fPIE -pie -fstack-protector-strong -D_FORTIFY_SOURCE=2 -Wl,-z,relro,-z,now -Wl,--build-id=none -s -ldl -o "admin-$arch.elf"
9
- done
7
+ build() { # <arch> <zig target> [compiler flags]
8
+ arch=$1 target=$2
9
+ shift 2
10
+ for bridge in admin apppriv; do
11
+ zig cc "${bridge}_check.c" -target "$target" "$@" -std=c11 -O2 -Wall -Wextra -Werror -fPIE -pie -fstack-protector-strong -D_FORTIFY_SOURCE=2 -Wl,-z,relro,-z,now -Wl,--build-id=none -s -ldl -o "$bridge-$arch.elf"
12
+ done
13
+ }
14
+ build x86_64 x86_64-linux-gnu.2.17
15
+ build aarch64 aarch64-linux-gnu.2.17
16
+ # ARMv7-A, VFPv3 and NEON, no integer divide: the Cortex-A9 (armada38x,
17
+ # monaco) and Cortex-A15 (alpine, alpine4k) models both run it.
18
+ build armv7 arm-linux-gnueabihf.2.17 -mcpu=baseline
@@ -3,10 +3,16 @@
3
3
  "abi": "GNU libc 2.17",
4
4
  "files": {
5
5
  "admin_check.c": "87dbc9be7742f52bdde6ed3bf73ea79facc6f0708b317696eff03624b4c3276b",
6
- "build.sh": "deae3f75b53882efbf6c0d31197892a8571f1b20ae42c92f99ee6782e3286d1e",
6
+ "apppriv_check.c": "08249e0a7d3e03fbb02da43f8c7323af11b914448d4a5928f99d39bb430a553c",
7
+ "build.sh": "89f05d98b60b4d28113f8faa97897ac18a7a5387afef3e85cfa47fa98b13629a",
7
8
  "admin-x86_64.elf": "cb14c60b74a59847f14d27e9361f874f82d99f6416407df861655b742c3a976c",
8
- "admin-aarch64.elf": "2a8f33a8c31837bfc498a486baf78ac9b0549f743a9adf89de6d094db61e8baa"
9
+ "admin-aarch64.elf": "2a8f33a8c31837bfc498a486baf78ac9b0549f743a9adf89de6d094db61e8baa",
10
+ "apppriv-x86_64.elf": "62b62a00d2b7bb725d4e10a3aecfd59005f943f98355a129cb418213a4595363",
11
+ "apppriv-aarch64.elf": "8ef803709f0bb775cac607833f1d45f3025371da268297ec6c0253f7431a9167",
12
+ "admin-armv7.elf": "da4a0f986adcb66ab7d09d1e8478ee8b22ae9d6ccb1461ea4552789a7663f1f8",
13
+ "apppriv-armv7.elf": "19d86118414f0d04f0806b5f3e199f26092b5521f24e70847093532a2b84b2a7"
9
14
  },
10
15
  "sdkSymbol": "SLIBGroupIsAdminGroupMem",
11
- "sdkRealtime": true
16
+ "sdkRealtime": true,
17
+ "appPrivSymbol": "SLIBAppPrivUserHas"
12
18
  }
@@ -0,0 +1,71 @@
1
+ package filestation
2
+
3
+ import (
4
+ "context"
5
+ "dsmapp/internal/api"
6
+ "encoding/json"
7
+ "net/http"
8
+ "time"
9
+ )
10
+
11
+ // CheckPermission is an online write check with the current user's session.
12
+ // It creates no probe file and does not grant any offline authority.
13
+ func (c *Client) CheckPermission(ctx context.Context, r *http.Request, path string) error {
14
+ auth, err := api.DSMSession(r)
15
+ if err != nil {
16
+ return err
17
+ }
18
+ r = r.Clone(ctx)
19
+ r.Header.Set("X-SYNO-TOKEN", auth["dsm_synotoken"])
20
+ if !validPath(path) {
21
+ return fail("invalid-request")
22
+ }
23
+ v := fields("SYNO.FileStation.CheckPermission", "write", "3")
24
+ v.Set("path", path)
25
+ v.Set("filename", ".synopkgland-permission-probe")
26
+ v.Set("overwrite", "false")
27
+ v.Set("size", "0")
28
+ var data map[string]json.RawMessage
29
+ if err := c.call(ctx, r, v, &data); err != nil {
30
+ return err
31
+ }
32
+ var skip *bool
33
+ if json.Unmarshal(data["blSkip"], &skip) != nil || skip == nil || *skip {
34
+ return fail("denied")
35
+ }
36
+ // File Station can return per-file refusal inside a success envelope.
37
+ for _, key := range []string{"error", "errors"} {
38
+ if raw := data[key]; len(raw) != 0 && string(raw) != "null" && string(raw) != "[]" {
39
+ return fail("denied")
40
+ }
41
+ }
42
+ return nil
43
+ }
44
+
45
+ // Keep the original DSMClient contract usable by existing read-only adapters.
46
+ // An adapter lacking the new method refuses the write check.
47
+ type permissionClient interface {
48
+ CheckPermission(context.Context, *http.Request, string) error
49
+ }
50
+
51
+ func (s *Service) CheckPermission(ctx context.Context, caller api.Caller, r *http.Request, path string) error {
52
+ if !validPath(path) {
53
+ return fail("invalid-request")
54
+ }
55
+ auth, err := api.DSMSession(r)
56
+ if err != nil {
57
+ return err
58
+ }
59
+ r = r.Clone(ctx)
60
+ r.Header.Set("X-SYNO-TOKEN", auth["dsm_synotoken"])
61
+ ctx, cancel := context.WithTimeout(ctx, 20*time.Second)
62
+ defer cancel()
63
+ if err := s.verify(ctx, caller, r); err != nil {
64
+ return err
65
+ }
66
+ client, ok := s.client.(permissionClient)
67
+ if !ok {
68
+ return fail("unsupported")
69
+ }
70
+ return client.CheckPermission(ctx, r, path)
71
+ }
@@ -0,0 +1,77 @@
1
+ package filestation
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "net/http"
7
+ "net/http/httptest"
8
+ "testing"
9
+ )
10
+
11
+ func TestCheckPermissionRequiresVerifiedSessionAndExplicitWriteResult(t *testing.T) {
12
+ for _, body := range []string{`{"blSkip":false}`, `{}`, `{"blSkip":true}`, `{"blSkip":null}`, `{"blSkip":false,"errors":[{"code":407}]}`} {
13
+ t.Run(body, func(t *testing.T) {
14
+ checks := 0
15
+ client := fakeClient(t, func(w http.ResponseWriter, r *http.Request) {
16
+ r.ParseForm()
17
+ switch r.Form.Get("api") {
18
+ case "SYNO.Core.NormalUser":
19
+ fmt.Fprint(w, `{"success":true,"data":{"username":"alice"}}`)
20
+ case "SYNO.FileStation.CheckPermission":
21
+ checks++
22
+ if r.Form.Get("version") != "3" || r.Form.Get("method") != "write" || r.Form.Get("path") != "/home" || r.Form.Get("filename") != ".synopkgland-permission-probe" || r.Form.Get("overwrite") != "false" || r.Form.Get("size") != "0" || r.Header.Get("Cookie") != "id=test-session" || r.Header.Get("X-SYNO-TOKEN") != "test-token" {
23
+ t.Error("incorrect permission check transport")
24
+ }
25
+ fmt.Fprintf(w, `{"success":true,"data":%s}`, body)
26
+ default:
27
+ t.Error("unexpected API")
28
+ }
29
+ })
30
+ service := NewService(client)
31
+ err := service.CheckPermission(context.Background(), callerFor(t, "alice"), sessionRequest(), "/home")
32
+ if (err == nil) != (body == `{"blSkip":false}`) || checks != 1 {
33
+ t.Fatalf("checks=%d err=%v", checks, err)
34
+ }
35
+ checks = 0
36
+ if err := service.CheckPermission(context.Background(), callerFor(t, "bob"), sessionRequest(), "/home"); err == nil || checks != 0 {
37
+ t.Fatal("mismatched subject checked write permission")
38
+ }
39
+ if err := service.CheckPermission(context.Background(), callerFor(t, "alice"), sessionRequest(), "/home/../etc"); err == nil || checks != 0 {
40
+ t.Fatal("invalid path checked")
41
+ }
42
+ })
43
+ }
44
+ }
45
+
46
+ func TestCheckPermissionValidatesCredentialPairBeforeDSM(t *testing.T) {
47
+ calls := 0
48
+ client := fakeClient(t, func(w http.ResponseWriter, r *http.Request) {
49
+ calls++
50
+ r.ParseForm()
51
+ if r.Form.Get("api") == "SYNO.Core.NormalUser" {
52
+ fmt.Fprint(w, `{"success":true,"data":{"username":"alice"}}`)
53
+ } else {
54
+ fmt.Fprint(w, `{"success":true,"data":{"blSkip":false}}`)
55
+ }
56
+ })
57
+ service := NewService(client)
58
+ for index, r := range []*http.Request{nil, httptest.NewRequest("POST", "/", nil), sessionRequest(), sessionRequest()} {
59
+ if r != nil && r.Header.Get("X-SYNO-TOKEN") != "" {
60
+ if index == 2 {
61
+ r.Header.Del("X-SYNO-TOKEN")
62
+ } else {
63
+ r.Header.Set("SynoToken", "different")
64
+ }
65
+ }
66
+ before := calls
67
+ if err := service.CheckPermission(context.Background(), callerFor(t, "alice"), r, "/home"); err == nil || calls != before {
68
+ t.Fatal("invalid session reached DSM", err, calls)
69
+ }
70
+ }
71
+ r := sessionRequest()
72
+ r.Header.Del("X-SYNO-TOKEN")
73
+ r.Header.Set("SynoToken", "test-token")
74
+ if err := service.CheckPermission(context.Background(), callerFor(t, "alice"), r, "/home"); err != nil {
75
+ t.Fatal("canonical alias refused", err)
76
+ }
77
+ }
@@ -0,0 +1,39 @@
1
+ package filestation
2
+
3
+ import (
4
+ "encoding/json"
5
+ "os"
6
+ "sort"
7
+ "testing"
8
+ )
9
+
10
+ // Every files entry in the SDK policy must name a route this package serves.
11
+ func TestSDKPolicyListsExactlyTheFileRoutes(t *testing.T) {
12
+ raw, err := os.ReadFile("../api/sdk-permissions.json")
13
+ if err != nil {
14
+ t.Fatal(err)
15
+ }
16
+ var policy struct {
17
+ Files map[string]string `json:"files"`
18
+ }
19
+ if err := json.Unmarshal(raw, &policy); err != nil {
20
+ t.Fatal(err)
21
+ }
22
+ var declared, routed []string
23
+ for method := range policy.Files {
24
+ declared = append(declared, method)
25
+ }
26
+ for _, route := range (&Service{}).Routes() {
27
+ routed = append(routed, route.Method())
28
+ }
29
+ sort.Strings(declared)
30
+ sort.Strings(routed)
31
+ if len(declared) != len(routed) {
32
+ t.Fatalf("policy %v, routes %v", declared, routed)
33
+ }
34
+ for i := range declared {
35
+ if declared[i] != routed[i] {
36
+ t.Fatalf("policy %v, routes %v", declared, routed)
37
+ }
38
+ }
39
+ }
@@ -204,7 +204,7 @@ func (s *Service) discover(ctx context.Context, r *http.Request, folder string,
204
204
  // Browse lists share roots (empty folder) or one folder through the caller's
205
205
  // own DSM session, so File Station's current permissions decide what is
206
206
  // visible. It is read-only metadata and stores no grant; background jobs use it
207
- // to let an administrator pick the file that ReadFile will then import.
207
+ // to let an authenticated caller pick the file that ReadFile will then import.
208
208
  func (s *Service) Browse(ctx context.Context, c api.Caller, r *http.Request, folder string, offset int) (Page, error) {
209
209
  if !c.Authenticated() {
210
210
  return Page{}, fail("session_required")