@synopackageland/cli 0.6.1 → 0.7.1
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.
- package/CHANGELOG.md +846 -0
- package/README.md +81 -89
- package/dist/add.d.ts +4 -1
- package/dist/add.js +45 -17
- package/dist/build-spk/delegate-scope.d.ts +6 -0
- package/dist/build-spk/delegate-scope.js +34 -0
- package/dist/build-spk/dsm-arch.d.ts +7 -1
- package/dist/build-spk/dsm-arch.js +24 -28
- package/dist/build-spk/dsm-platforms.json +42 -0
- package/dist/build-spk/dsm-version.d.ts +11 -0
- package/dist/build-spk/dsm-version.js +17 -0
- package/dist/build-spk/gateway.js +8 -4
- package/dist/build-spk/generator.js +30 -5
- package/dist/build-spk/go-toolchain.d.ts +20 -0
- package/dist/build-spk/go-toolchain.js +44 -0
- package/dist/build-spk/notices.js +4 -1
- package/dist/build-spk/resource-worker.js +3 -0
- package/dist/build-spk/wizard-messages.d.ts +66 -0
- package/dist/build-spk/wizard-messages.js +75 -9
- package/dist/cli.js +28 -14
- package/dist/contract/schema-validate.js +8 -0
- package/dist/contract/validate.js +30 -0
- package/dist/deploy/deploy.d.ts +1 -1
- package/dist/deploy/deploy.js +8 -0
- package/dist/deploy/native-install-values.d.ts +17 -0
- package/dist/deploy/native-install-values.js +32 -0
- package/dist/deploy/plan.d.ts +6 -2
- package/dist/deploy/plan.js +58 -7
- package/dist/deploy/spk-version.js +1 -1
- package/dist/error-registry.js +54 -20
- package/dist/host/dsm-client.d.ts +8 -4
- package/dist/host/dsm-client.js +63 -21
- package/dist/host/dsm-http.js +24 -2
- package/dist/host/types.d.ts +3 -1
- package/dist/info.js +45 -10
- package/dist/init.d.ts +3 -0
- package/dist/init.js +50 -19
- package/dist/package-metadata.d.ts +21 -0
- package/dist/package-metadata.js +101 -0
- package/dist/portal/alias.d.ts +25 -0
- package/dist/portal/alias.js +48 -0
- package/dist/portal/package.d.ts +33 -0
- package/dist/portal/package.js +116 -0
- package/dist/runtime/delegate-contract.d.ts +79 -0
- package/dist/runtime/delegate-contract.js +27 -0
- package/dist/runtime/dsm-version.d.ts +6 -0
- package/dist/runtime/dsm-version.js +8 -0
- package/dist/runtime/executor-build.js +10 -6
- package/dist/runtime/executor-inspect.d.ts +3 -0
- package/dist/runtime/executor-inspect.js +11 -5
- package/dist/runtime/executor-policy.d.ts +12 -0
- package/dist/runtime/executor-policy.js +43 -3
- package/dist/runtime/executor-scope-task.d.ts +4 -0
- package/dist/runtime/executor-scope-task.js +21 -0
- package/dist/runtime/executor-tasks.d.ts +2 -1
- package/dist/runtime/executor-tasks.js +4 -4
- package/dist/skill.js +1 -0
- package/dist/standalone/api-permissions.d.ts +10 -0
- package/dist/standalone/api-permissions.js +19 -2
- package/dist/standalone/background.d.ts +16 -0
- package/dist/standalone/background.js +69 -2
- package/dist/standalone/binary.d.ts +1 -1
- package/dist/standalone/binary.js +22 -8
- package/dist/standalone/build.d.ts +1 -1
- package/dist/standalone/build.js +38 -8
- package/dist/standalone/daemon.d.ts +1 -0
- package/dist/standalone/daemon.js +10 -5
- package/dist/standalone/dsm-window.d.ts +3 -1
- package/dist/standalone/dsm-window.js +78 -49
- package/dist/standalone/inspect-spk.d.ts +8 -0
- package/dist/standalone/inspect-spk.js +110 -27
- package/dist/standalone/lifecycle.js +86 -8
- package/dist/standalone/manifest.d.ts +13 -0
- package/dist/standalone/manifest.js +57 -8
- package/dist/standalone/native-authorization-window.d.ts +1 -1
- package/dist/standalone/native-authorization-window.js +9 -1
- package/dist/standalone/native-layout.d.ts +3 -0
- package/dist/standalone/native-layout.js +37 -5
- package/dist/standalone/native-root-window.d.ts +4 -2
- package/dist/standalone/native-root-window.js +33 -18
- package/dist/standalone/portal-window.d.ts +21 -0
- package/dist/standalone/portal-window.js +155 -0
- package/dist/standalone/portal.d.ts +4 -0
- package/dist/standalone/portal.js +4 -0
- package/dist/standalone/resource.d.ts +5 -3
- package/dist/standalone/resource.js +6 -3
- package/dist/standalone/root-window.d.ts +4 -1
- package/dist/standalone/root-window.js +193 -36
- package/dist/standalone/spk.d.ts +1 -1
- package/dist/standalone/spk.js +25 -7
- package/dist/templates/_daemon/go.mod +1 -1
- package/dist/templates/_daemon/main.go +6 -3
- package/dist/templates/_daemon/main_test.go +82 -9
- package/dist/templates/_daemon/testdata/child.go +3 -0
- package/dist/templates/_executor/README.md +130 -16
- package/dist/templates/_executor/anchor.go +156 -0
- package/dist/templates/_executor/anchor_test.go +161 -0
- package/dist/templates/_executor/apppriv_review_test.go +1 -1
- package/dist/templates/_executor/cancel_contract_test.go +236 -0
- package/dist/templates/_executor/copy.go +64 -0
- package/dist/templates/_executor/copy_test.go +88 -0
- package/dist/templates/_executor/digest_review_test.go +2 -2
- package/dist/templates/_executor/dsm.go +63 -13
- package/dist/templates/_executor/dsm_test.go +95 -1
- package/dist/templates/_executor/executor.go +260 -49
- package/dist/templates/_executor/executor_test.go +36 -6
- package/dist/templates/_executor/go.mod +2 -0
- package/dist/templates/_executor/identity.go +27 -1
- package/dist/templates/_executor/identity_kernel_linux_test.go +60 -0
- package/dist/templates/_executor/identity_linux.go +82 -0
- package/dist/templates/_executor/identity_security_test.go +43 -0
- package/dist/templates/_executor/internal/lifecycle/lifecycle.go +5 -0
- package/dist/templates/_executor/internal/lifecycle/lifecycle_test.go +61 -3
- package/dist/templates/_executor/internal/taskscript/boot.sh +9 -9
- package/dist/templates/_executor/internal/taskscript/cmd/main.go +2 -2
- package/dist/templates/_executor/internal/taskscript/control.sh +15 -10
- package/dist/templates/_executor/internal/taskscript/tasks.go +5 -3
- package/dist/templates/_executor/lifecycle_test.go +11 -0
- package/dist/templates/_executor/link_linux.go +28 -0
- package/dist/templates/_executor/long_running_test.go +530 -0
- package/dist/templates/_executor/main.go +83 -25
- package/dist/templates/_executor/managed.go +3 -3
- package/dist/templates/_executor/managed_test.go +3 -3
- package/dist/templates/_executor/move_linux.go +490 -0
- package/dist/templates/_executor/move_linux_test.go +460 -0
- package/dist/templates/_executor/move_validation.go +46 -0
- package/dist/templates/_executor/move_validation_test.go +187 -0
- package/dist/templates/_executor/operations.go +926 -0
- package/dist/templates/_executor/path_test.go +18 -0
- package/dist/templates/_executor/pid.go +37 -0
- package/dist/templates/_executor/pid_test.go +113 -0
- package/dist/templates/_executor/platform_linux.go +24 -173
- package/dist/templates/_executor/platform_linux_test.go +9 -65
- package/dist/templates/_executor/platform_other.go +38 -4
- package/dist/templates/_executor/process.go +3 -0
- package/dist/templates/_executor/purge_log.go +18 -0
- package/dist/templates/_executor/records.go +242 -0
- package/dist/templates/_executor/scope_cancel_contract_test.go +91 -0
- package/dist/templates/_executor/scope_client.go +37 -0
- package/dist/templates/_executor/scope_contract.go +12 -0
- package/dist/templates/_executor/selected_scope.go +146 -0
- package/dist/templates/_executor/selected_scope_test.go +186 -0
- package/dist/templates/_executor/status_disabled_test.go +33 -0
- package/dist/templates/_executor/store.go +4 -3
- package/dist/templates/_executor/supervise.go +109 -0
- package/dist/templates/_executor/supervise_test.go +130 -0
- package/dist/templates/_executor/task_race_review_test.go +1 -1
- package/dist/templates/_executor/tasks.go +52 -15
- package/dist/templates/_executor/tasks_dsm71_test.go +114 -0
- package/dist/templates/_executor/tasks_review_test.go +1 -1
- package/dist/templates/_executor/tasks_test.go +62 -5
- package/dist/templates/_executor/upgrade_watch_test.go +13 -5
- package/dist/templates/_executor/watch.go +8 -0
- package/dist/templates/_executor/watch_test.go +23 -2
- package/dist/templates/_executor/write_review_linux_test.go +4 -12
- package/dist/templates/_gateway/go.mod +2 -0
- package/dist/templates/_shared/AGENTS.md +129 -0
- package/dist/templates/binary/dsm-package.yaml +24 -0
- package/dist/templates/binary/i18n/messages/de.json +4 -0
- package/dist/templates/binary/i18n/messages/en.json +4 -0
- package/dist/templates/binary/i18n/messages/es.json +4 -0
- package/dist/templates/binary/i18n/messages/fr.json +4 -0
- package/dist/templates/binary/i18n/messages/it.json +4 -0
- package/dist/templates/binary/i18n/messages/ja.json +4 -0
- package/dist/templates/binary/i18n/messages/ko.json +4 -0
- package/dist/templates/binary/i18n/messages/zh-CN.json +4 -0
- package/dist/templates/binary/i18n/messages/zh-TW.json +4 -0
- package/dist/templates/binary/ui/app.js +19 -4
- package/dist/templates/native-authorization/AuthorizationGate.vue +5 -3
- package/dist/templates/native-authorization/i18n/de.json +1 -0
- package/dist/templates/native-authorization/i18n/en.json +1 -0
- package/dist/templates/native-authorization/i18n/es.json +2 -1
- package/dist/templates/native-authorization/i18n/fr.json +1 -0
- package/dist/templates/native-authorization/i18n/it.json +11 -10
- package/dist/templates/native-authorization/i18n/ja.json +1 -0
- package/dist/templates/native-authorization/i18n/ko.json +10 -9
- package/dist/templates/native-authorization/i18n/zh-CN.json +6 -5
- package/dist/templates/native-authorization/i18n/zh-TW.json +6 -5
- package/dist/templates/native-go/README.md +43 -10
- package/dist/templates/native-go/backend/cmd/app/main.go +27 -7
- package/dist/templates/native-go/backend/go.mod +1 -1
- package/dist/templates/native-go/backend/internal/api/entry_test.go +47 -0
- package/dist/templates/native-go/backend/internal/api/permissions_test.go +3 -3
- package/dist/templates/native-go/backend/internal/api/registry.go +11 -0
- package/dist/templates/native-go/backend/internal/api/sdk-permissions.json +14 -1
- package/dist/templates/native-go/backend/internal/api/server.go +18 -5
- package/dist/templates/native-go/backend/internal/app/delegate_config.go +57 -0
- package/dist/templates/native-go/backend/internal/app/delegate_config_test.go +28 -0
- package/dist/templates/native-go/backend/internal/app/portal.go +30 -0
- package/dist/templates/native-go/backend/internal/app/portal_test.go +55 -0
- package/dist/templates/native-go/backend/internal/app/routes.go +8 -2
- package/dist/templates/native-go/backend/internal/background/runner.go +1 -1
- package/dist/templates/native-go/backend/internal/background/store.go +16 -14
- package/dist/templates/native-go/backend/internal/dsm/admin.go +8 -2
- package/dist/templates/native-go/backend/internal/dsm/admin_linux.go +43 -13
- package/dist/templates/native-go/backend/internal/dsm/admin_linux_test.go +56 -0
- package/dist/templates/native-go/backend/internal/dsm/admin_other.go +4 -1
- package/dist/templates/native-go/backend/internal/dsm/apppriv.go +30 -0
- package/dist/templates/native-go/backend/internal/dsm/bridge_file.go +75 -0
- package/dist/templates/native-go/backend/internal/dsm/bridge_file_test.go +57 -0
- package/dist/templates/native-go/backend/internal/dsm/entry.go +177 -0
- package/dist/templates/native-go/backend/internal/dsm/entry_test.go +315 -0
- package/dist/templates/native-go/backend/internal/dsm/native/admin-armv7.elf +0 -0
- package/dist/templates/native-go/backend/internal/dsm/native/apppriv-aarch64.elf +0 -0
- package/dist/templates/native-go/backend/internal/dsm/native/apppriv-armv7.elf +0 -0
- package/dist/templates/native-go/backend/internal/dsm/native/apppriv-x86_64.elf +0 -0
- package/dist/templates/native-go/backend/internal/dsm/native/apppriv_check.c +33 -0
- package/dist/templates/native-go/backend/internal/dsm/native/build.sh +13 -4
- package/dist/templates/native-go/backend/internal/dsm/native/provenance.json +9 -3
- package/dist/templates/native-go/backend/internal/filestation/check_permission.go +71 -0
- package/dist/templates/native-go/backend/internal/filestation/check_permission_test.go +77 -0
- package/dist/templates/native-go/backend/internal/filestation/client.go +39 -3
- package/dist/templates/native-go/backend/internal/filestation/download_dsm71_test.go +82 -0
- package/dist/templates/native-go/backend/internal/filestation/sdk_policy_test.go +39 -0
- package/dist/templates/native-go/backend/internal/filestation/service.go +1 -1
- package/dist/templates/native-go/backend/internal/identity/README.md +5 -1
- package/dist/templates/native-go/backend/internal/identity/dsm.go +24 -5
- package/dist/templates/native-go/backend/internal/identity/http.go +1 -1
- package/dist/templates/native-go/backend/internal/identity/identity_test.go +48 -1
- package/dist/templates/native-go/backend/internal/identity/service.go +1 -1
- package/dist/templates/native-go/backend/internal/portal/portal.go +138 -0
- package/dist/templates/native-go/backend/internal/portal/portal_test.go +89 -0
- package/dist/templates/native-go/dsm-package.yaml +20 -1
- package/dist/templates/native-go/i18n/glossary.json +2 -1
- package/dist/templates/native-go/i18n/messages/de.json +1 -0
- package/dist/templates/native-go/i18n/messages/en.json +1 -0
- package/dist/templates/native-go/i18n/messages/es.json +1 -0
- package/dist/templates/native-go/i18n/messages/fr.json +1 -0
- package/dist/templates/native-go/i18n/messages/it.json +1 -0
- package/dist/templates/native-go/i18n/messages/ja.json +1 -0
- package/dist/templates/native-go/i18n/messages/ko.json +1 -0
- package/dist/templates/native-go/i18n/messages/zh-CN.json +1 -0
- package/dist/templates/native-go/i18n/messages/zh-TW.json +1 -0
- package/dist/templates/native-go/ui/app.css +10 -0
- package/dist/templates/native-go/ui/app.js +11 -1
- package/dist/templates/native-go-delegate/DELEGATE.md +126 -5
- package/dist/templates/native-go-delegate/backend/cmd/background-worker/main.go +7 -0
- package/dist/templates/native-go-delegate/backend/internal/app/delegate.go +11 -1
- package/dist/templates/native-go-delegate/backend/internal/app/delegate_routes_test.go +14 -0
- package/dist/templates/native-go-delegate/backend/internal/background/delegated_moves.go +270 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/admission_recovery_test.go +410 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/cancel_contract_test.go +95 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/client.go +8 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/destination.go +110 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/destination_state.go +117 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/destination_state_test.go +60 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/destination_test.go +116 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/example.go +81 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/example_test.go +91 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/executor_contract_test.go +511 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/files.go +1 -1
- package/dist/templates/native-go-delegate/backend/internal/delegate/long_running_routes.go +92 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/long_running_routes_test.go +99 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/move.go +348 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/move_test.go +351 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/operations_list.go +65 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/operations_list_test.go +114 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/retry_test.go +1 -1
- package/dist/templates/native-go-delegate/backend/internal/delegate/service.go +58 -17
- package/dist/templates/native-go-delegate/backend/internal/delegate/staging.go +121 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/status_disabled_test.go +51 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/testdata/executor_contract.txt +112 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/transient_contract_test.go +137 -0
- package/dist/templates/native-go-delegate/backend/internal/filestation/files.go +1 -31
- package/dist/templates/native-go-delegate/frontend/src/CapabilityPanels.vue +30 -3
- package/dist/templates/native-go-delegate/frontend/src/delegate/BackgroundSavePanel.vue +113 -0
- package/dist/templates/native-go-delegate/frontend/src/delegate/copy.ts +5 -6
- package/dist/templates/native-go-delegate/frontend/test/background-save.test.ts +121 -0
- package/dist/templates/native-go-delegate/frontend/test/delegate.test.ts +15 -0
- package/dist/templates/native-go-delegate/frontend/test/scope-settings.test.ts +49 -0
- package/dist/templates/native-go-delegate/test.sh +1 -0
- package/dist/templates/native-go-jobs/frontend/src/background/i18n/de.json +2 -2
- package/dist/templates/native-go-jobs/frontend/src/background/i18n/en.json +2 -2
- package/dist/templates/native-go-jobs/frontend/src/background/i18n/es.json +12 -12
- package/dist/templates/native-go-jobs/frontend/src/background/i18n/fr.json +4 -4
- package/dist/templates/native-go-jobs/frontend/src/background/i18n/it.json +17 -17
- package/dist/templates/native-go-jobs/frontend/src/background/i18n/ja.json +7 -7
- package/dist/templates/native-go-jobs/frontend/src/background/i18n/ko.json +15 -15
- package/dist/templates/native-go-jobs/frontend/src/background/i18n/zh-CN.json +3 -3
- package/dist/templates/native-go-jobs/frontend/src/background/i18n/zh-TW.json +2 -2
- package/dist/templates/native-go-jobs/frontend/test/background.test.ts +25 -28
- package/dist/templates/native-go-root-ops/backend/internal/app/executor.go +30 -2
- package/dist/templates/native-go-root-ops/backend/internal/app/executor_test.go +64 -0
- package/dist/templates/native-go-vue/README.md +4 -0
- package/dist/templates/native-go-vue/frontend/package-lock.json +4 -4
- package/dist/templates/native-go-vue/frontend/package.json +1 -1
- package/dist/templates/native-go-vue/frontend/src/App.vue +18 -13
- package/dist/templates/native-go-vue/frontend/src/context.ts +31 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/ExecutorSettings.vue +54 -9
- package/dist/templates/native-go-vue/frontend/src/delegate/catalogs.ts +11 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/copy.test.ts +12 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/copy.ts +4 -4
- package/dist/templates/native-go-vue/frontend/src/delegate/executor-settings.test.ts +23 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/executor-setup.ts +8 -2
- package/dist/templates/native-go-vue/frontend/src/delegate/facade.test.ts +100 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/facade.ts +85 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/de.json +47 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/en.json +13 -2
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/es.json +47 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/fr.json +47 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/it.json +47 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/ja.json +47 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/ko.json +47 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/de.json +42 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/en.json +42 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/es.json +42 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/fr.json +42 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/it.json +42 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/ja.json +42 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/ko.json +42 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-CN.json +42 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-TW.json +42 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/zh-CN.json +47 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/zh-TW.json +16 -5
- package/dist/templates/native-go-vue/frontend/src/delegate/long-running.ts +57 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/save-copy.test.ts +12 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/save-copy.ts +24 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/scoped-picker.test.ts +12 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/scoped-picker.ts +21 -0
- package/dist/templates/native-go-vue/frontend/src/files/files.test.ts +7 -0
- package/dist/templates/native-go-vue/frontend/src/i18n/de.json +20 -0
- package/dist/templates/native-go-vue/frontend/src/i18n/en.json +20 -0
- package/dist/templates/native-go-vue/frontend/src/i18n/es.json +20 -0
- package/dist/templates/native-go-vue/frontend/src/i18n/fr.json +20 -0
- package/dist/templates/native-go-vue/frontend/src/i18n/it.json +20 -0
- package/dist/templates/native-go-vue/frontend/src/i18n/ja.json +20 -0
- package/dist/templates/native-go-vue/frontend/src/i18n/ko.json +20 -0
- package/dist/templates/native-go-vue/frontend/src/i18n/zh-CN.json +20 -0
- package/dist/templates/native-go-vue/frontend/src/i18n/zh-TW.json +20 -0
- package/dist/templates/native-go-vue/frontend/src/i18n.ts +13 -0
- package/dist/templates/native-go-vue/frontend/src/identity/LoginPage.vue +16 -14
- package/dist/templates/native-go-vue/frontend/src/identity/i18n/de.json +27 -0
- package/dist/templates/native-go-vue/frontend/src/identity/i18n/en.json +27 -0
- package/dist/templates/native-go-vue/frontend/src/identity/i18n/es.json +27 -0
- package/dist/templates/native-go-vue/frontend/src/identity/i18n/fr.json +27 -0
- package/dist/templates/native-go-vue/frontend/src/identity/i18n/it.json +27 -0
- package/dist/templates/native-go-vue/frontend/src/identity/i18n/ja.json +27 -0
- package/dist/templates/native-go-vue/frontend/src/identity/i18n/ko.json +27 -0
- package/dist/templates/native-go-vue/frontend/src/identity/i18n/zh-CN.json +27 -0
- package/dist/templates/native-go-vue/frontend/src/identity/i18n/zh-TW.json +27 -0
- package/dist/templates/native-go-vue/frontend/src/identity/i18n.ts +14 -0
- package/dist/templates/native-go-vue/frontend/src/identity/session.test.ts +4 -0
- package/dist/templates/native-go-vue/frontend/src/identity/session.ts +9 -8
- package/dist/templates/native-go-vue/frontend/src/nas/NasPicker.vue +31 -12
- package/dist/templates/native-go-vue/frontend/src/nas/destination.test.ts +48 -0
- package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/de.json +1 -1
- package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/en.json +1 -1
- package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/es.json +10 -10
- package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/fr.json +1 -1
- package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/it.json +12 -12
- package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/ja.json +15 -15
- package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/ko.json +12 -12
- package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/zh-CN.json +1 -1
- package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/zh-TW.json +1 -1
- package/dist/templates/native-go-vue/frontend/src/portal/PortalSettings.vue +39 -0
- package/dist/templates/native-go-vue/frontend/src/shell/i18n/fr.json +1 -1
- package/dist/templates/native-go-vue/frontend/src/shell/i18n/ja.json +1 -1
- package/dist/templates/native-go-vue/frontend/test/apps.test.ts +14 -0
- package/dist/templates/native-go-vue/frontend/test/portal-settings.test.ts +71 -0
- package/dist/templates/native-go-vue/frontend/test/shell-i18n.test.ts +50 -0
- package/package.json +5 -1
- package/schema/package-metadata.schema.json +101 -0
- package/schema/synology-app.schema.json +112 -0
- package/skills/SKILL.md +106 -4
- package/skills/reference/create.md +101 -2
- package/skills/reference/deploy.md +2 -2
- package/skills/reference/files.md +113 -2
- package/skills/reference/identity.md +70 -1
- package/skills/reference/portal.md +17 -0
- package/skills/reference/ui-vue.md +12 -0
- package/templates/_daemon/go.mod +1 -1
- package/templates/_daemon/main.go +6 -3
- package/templates/_daemon/main_test.go +82 -9
- package/templates/_daemon/testdata/child.go +3 -0
- package/templates/_executor/README.md +130 -16
- package/templates/_executor/anchor.go +156 -0
- package/templates/_executor/anchor_test.go +161 -0
- package/templates/_executor/apppriv_review_test.go +1 -1
- package/templates/_executor/cancel_contract_test.go +236 -0
- package/templates/_executor/copy.go +64 -0
- package/templates/_executor/copy_test.go +88 -0
- package/templates/_executor/digest_review_test.go +2 -2
- package/templates/_executor/dsm.go +63 -13
- package/templates/_executor/dsm_test.go +95 -1
- package/templates/_executor/executor.go +260 -49
- package/templates/_executor/executor_test.go +36 -6
- package/templates/_executor/go.mod +2 -0
- package/templates/_executor/identity.go +27 -1
- package/templates/_executor/identity_kernel_linux_test.go +60 -0
- package/templates/_executor/identity_linux.go +82 -0
- package/templates/_executor/identity_security_test.go +43 -0
- package/templates/_executor/internal/lifecycle/lifecycle.go +5 -0
- package/templates/_executor/internal/lifecycle/lifecycle_test.go +61 -3
- package/templates/_executor/internal/taskscript/boot.sh +9 -9
- package/templates/_executor/internal/taskscript/cmd/main.go +2 -2
- package/templates/_executor/internal/taskscript/control.sh +15 -10
- package/templates/_executor/internal/taskscript/tasks.go +5 -3
- package/templates/_executor/lifecycle_test.go +11 -0
- package/templates/_executor/link_linux.go +28 -0
- package/templates/_executor/long_running_test.go +530 -0
- package/templates/_executor/main.go +83 -25
- package/templates/_executor/managed.go +3 -3
- package/templates/_executor/managed_test.go +3 -3
- package/templates/_executor/move_linux.go +490 -0
- package/templates/_executor/move_linux_test.go +460 -0
- package/templates/_executor/move_validation.go +46 -0
- package/templates/_executor/move_validation_test.go +187 -0
- package/templates/_executor/operations.go +926 -0
- package/templates/_executor/path_test.go +18 -0
- package/templates/_executor/pid.go +37 -0
- package/templates/_executor/pid_test.go +113 -0
- package/templates/_executor/platform_linux.go +24 -173
- package/templates/_executor/platform_linux_test.go +9 -65
- package/templates/_executor/platform_other.go +38 -4
- package/templates/_executor/process.go +3 -0
- package/templates/_executor/purge_log.go +18 -0
- package/templates/_executor/records.go +242 -0
- package/templates/_executor/scope_cancel_contract_test.go +91 -0
- package/templates/_executor/scope_client.go +37 -0
- package/templates/_executor/scope_contract.go +12 -0
- package/templates/_executor/selected_scope.go +146 -0
- package/templates/_executor/selected_scope_test.go +186 -0
- package/templates/_executor/status_disabled_test.go +33 -0
- package/templates/_executor/store.go +4 -3
- package/templates/_executor/supervise.go +109 -0
- package/templates/_executor/supervise_test.go +130 -0
- package/templates/_executor/task_race_review_test.go +1 -1
- package/templates/_executor/tasks.go +52 -15
- package/templates/_executor/tasks_dsm71_test.go +114 -0
- package/templates/_executor/tasks_review_test.go +1 -1
- package/templates/_executor/tasks_test.go +62 -5
- package/templates/_executor/upgrade_watch_test.go +13 -5
- package/templates/_executor/watch.go +8 -0
- package/templates/_executor/watch_test.go +23 -2
- package/templates/_executor/write_review_linux_test.go +4 -12
- package/templates/_gateway/go.mod +2 -0
- package/templates/_shared/AGENTS.md +129 -0
- package/templates/binary/dsm-package.yaml +24 -0
- package/templates/binary/i18n/messages/de.json +4 -0
- package/templates/binary/i18n/messages/en.json +4 -0
- package/templates/binary/i18n/messages/es.json +4 -0
- package/templates/binary/i18n/messages/fr.json +4 -0
- package/templates/binary/i18n/messages/it.json +4 -0
- package/templates/binary/i18n/messages/ja.json +4 -0
- package/templates/binary/i18n/messages/ko.json +4 -0
- package/templates/binary/i18n/messages/zh-CN.json +4 -0
- package/templates/binary/i18n/messages/zh-TW.json +4 -0
- package/templates/binary/ui/app.js +19 -4
- package/templates/native-authorization/AuthorizationGate.vue +5 -3
- package/templates/native-authorization/i18n/de.json +1 -0
- package/templates/native-authorization/i18n/en.json +1 -0
- package/templates/native-authorization/i18n/es.json +2 -1
- package/templates/native-authorization/i18n/fr.json +1 -0
- package/templates/native-authorization/i18n/it.json +11 -10
- package/templates/native-authorization/i18n/ja.json +1 -0
- package/templates/native-authorization/i18n/ko.json +10 -9
- package/templates/native-authorization/i18n/zh-CN.json +6 -5
- package/templates/native-authorization/i18n/zh-TW.json +6 -5
- package/templates/native-go/README.md +43 -10
- package/templates/native-go/backend/cmd/app/main.go +27 -7
- package/templates/native-go/backend/go.mod +1 -1
- package/templates/native-go/backend/internal/api/entry_test.go +47 -0
- package/templates/native-go/backend/internal/api/permissions_test.go +3 -3
- package/templates/native-go/backend/internal/api/registry.go +11 -0
- package/templates/native-go/backend/internal/api/sdk-permissions.json +14 -1
- package/templates/native-go/backend/internal/api/server.go +18 -5
- package/templates/native-go/backend/internal/app/delegate_config.go +57 -0
- package/templates/native-go/backend/internal/app/delegate_config_test.go +28 -0
- package/templates/native-go/backend/internal/app/portal.go +30 -0
- package/templates/native-go/backend/internal/app/portal_test.go +55 -0
- package/templates/native-go/backend/internal/app/routes.go +8 -2
- package/templates/native-go/backend/internal/background/runner.go +1 -1
- package/templates/native-go/backend/internal/background/store.go +16 -14
- package/templates/native-go/backend/internal/dsm/admin.go +8 -2
- package/templates/native-go/backend/internal/dsm/admin_linux.go +43 -13
- package/templates/native-go/backend/internal/dsm/admin_linux_test.go +56 -0
- package/templates/native-go/backend/internal/dsm/admin_other.go +4 -1
- package/templates/native-go/backend/internal/dsm/apppriv.go +30 -0
- package/templates/native-go/backend/internal/dsm/bridge_file.go +75 -0
- package/templates/native-go/backend/internal/dsm/bridge_file_test.go +57 -0
- package/templates/native-go/backend/internal/dsm/entry.go +177 -0
- package/templates/native-go/backend/internal/dsm/entry_test.go +315 -0
- package/templates/native-go/backend/internal/dsm/native/admin-armv7.elf +0 -0
- package/templates/native-go/backend/internal/dsm/native/apppriv-aarch64.elf +0 -0
- package/templates/native-go/backend/internal/dsm/native/apppriv-armv7.elf +0 -0
- package/templates/native-go/backend/internal/dsm/native/apppriv-x86_64.elf +0 -0
- package/templates/native-go/backend/internal/dsm/native/apppriv_check.c +33 -0
- package/templates/native-go/backend/internal/dsm/native/build.sh +13 -4
- package/templates/native-go/backend/internal/dsm/native/provenance.json +9 -3
- package/templates/native-go/backend/internal/filestation/check_permission.go +71 -0
- package/templates/native-go/backend/internal/filestation/check_permission_test.go +77 -0
- package/templates/native-go/backend/internal/filestation/client.go +39 -3
- package/templates/native-go/backend/internal/filestation/download_dsm71_test.go +82 -0
- package/templates/native-go/backend/internal/filestation/sdk_policy_test.go +39 -0
- package/templates/native-go/backend/internal/filestation/service.go +1 -1
- package/templates/native-go/backend/internal/identity/README.md +5 -1
- package/templates/native-go/backend/internal/identity/dsm.go +24 -5
- package/templates/native-go/backend/internal/identity/http.go +1 -1
- package/templates/native-go/backend/internal/identity/identity_test.go +48 -1
- package/templates/native-go/backend/internal/identity/service.go +1 -1
- package/templates/native-go/backend/internal/portal/portal.go +138 -0
- package/templates/native-go/backend/internal/portal/portal_test.go +89 -0
- package/templates/native-go/dsm-package.yaml +20 -1
- package/templates/native-go/i18n/glossary.json +2 -1
- package/templates/native-go/i18n/messages/de.json +1 -0
- package/templates/native-go/i18n/messages/en.json +1 -0
- package/templates/native-go/i18n/messages/es.json +1 -0
- package/templates/native-go/i18n/messages/fr.json +1 -0
- package/templates/native-go/i18n/messages/it.json +1 -0
- package/templates/native-go/i18n/messages/ja.json +1 -0
- package/templates/native-go/i18n/messages/ko.json +1 -0
- package/templates/native-go/i18n/messages/zh-CN.json +1 -0
- package/templates/native-go/i18n/messages/zh-TW.json +1 -0
- package/templates/native-go/ui/app.css +10 -0
- package/templates/native-go/ui/app.js +11 -1
- package/templates/native-go-delegate/DELEGATE.md +126 -5
- package/templates/native-go-delegate/backend/cmd/background-worker/main.go +7 -0
- package/templates/native-go-delegate/backend/internal/app/delegate.go +11 -1
- package/templates/native-go-delegate/backend/internal/app/delegate_routes_test.go +14 -0
- package/templates/native-go-delegate/backend/internal/background/delegated_moves.go +270 -0
- package/templates/native-go-delegate/backend/internal/delegate/admission_recovery_test.go +410 -0
- package/templates/native-go-delegate/backend/internal/delegate/cancel_contract_test.go +95 -0
- package/templates/native-go-delegate/backend/internal/delegate/client.go +8 -0
- package/templates/native-go-delegate/backend/internal/delegate/destination.go +110 -0
- package/templates/native-go-delegate/backend/internal/delegate/destination_state.go +117 -0
- package/templates/native-go-delegate/backend/internal/delegate/destination_state_test.go +60 -0
- package/templates/native-go-delegate/backend/internal/delegate/destination_test.go +116 -0
- package/templates/native-go-delegate/backend/internal/delegate/example.go +81 -0
- package/templates/native-go-delegate/backend/internal/delegate/example_test.go +91 -0
- package/templates/native-go-delegate/backend/internal/delegate/executor_contract_test.go +511 -0
- package/templates/native-go-delegate/backend/internal/delegate/files.go +1 -1
- package/templates/native-go-delegate/backend/internal/delegate/long_running_routes.go +92 -0
- package/templates/native-go-delegate/backend/internal/delegate/long_running_routes_test.go +99 -0
- package/templates/native-go-delegate/backend/internal/delegate/move.go +348 -0
- package/templates/native-go-delegate/backend/internal/delegate/move_test.go +351 -0
- package/templates/native-go-delegate/backend/internal/delegate/operations_list.go +65 -0
- package/templates/native-go-delegate/backend/internal/delegate/operations_list_test.go +114 -0
- package/templates/native-go-delegate/backend/internal/delegate/retry_test.go +1 -1
- package/templates/native-go-delegate/backend/internal/delegate/service.go +58 -17
- package/templates/native-go-delegate/backend/internal/delegate/staging.go +121 -0
- package/templates/native-go-delegate/backend/internal/delegate/status_disabled_test.go +51 -0
- package/templates/native-go-delegate/backend/internal/delegate/testdata/executor_contract.txt +112 -0
- package/templates/native-go-delegate/backend/internal/delegate/transient_contract_test.go +137 -0
- package/templates/native-go-delegate/backend/internal/filestation/files.go +1 -31
- package/templates/native-go-delegate/frontend/src/CapabilityPanels.vue +30 -3
- package/templates/native-go-delegate/frontend/src/delegate/BackgroundSavePanel.vue +113 -0
- package/templates/native-go-delegate/frontend/src/delegate/copy.ts +5 -6
- package/templates/native-go-delegate/frontend/test/background-save.test.ts +121 -0
- package/templates/native-go-delegate/frontend/test/delegate.test.ts +15 -0
- package/templates/native-go-delegate/frontend/test/scope-settings.test.ts +49 -0
- package/templates/native-go-delegate/test.sh +1 -0
- package/templates/native-go-jobs/frontend/src/background/i18n/de.json +2 -2
- package/templates/native-go-jobs/frontend/src/background/i18n/en.json +2 -2
- package/templates/native-go-jobs/frontend/src/background/i18n/es.json +12 -12
- package/templates/native-go-jobs/frontend/src/background/i18n/fr.json +4 -4
- package/templates/native-go-jobs/frontend/src/background/i18n/it.json +17 -17
- package/templates/native-go-jobs/frontend/src/background/i18n/ja.json +7 -7
- package/templates/native-go-jobs/frontend/src/background/i18n/ko.json +15 -15
- package/templates/native-go-jobs/frontend/src/background/i18n/zh-CN.json +3 -3
- package/templates/native-go-jobs/frontend/src/background/i18n/zh-TW.json +2 -2
- package/templates/native-go-jobs/frontend/test/background.test.ts +25 -28
- package/templates/native-go-root-ops/backend/internal/app/executor.go +30 -2
- package/templates/native-go-root-ops/backend/internal/app/executor_test.go +64 -0
- package/templates/native-go-vue/README.md +4 -0
- package/templates/native-go-vue/frontend/package-lock.json +4 -4
- package/templates/native-go-vue/frontend/package.json +1 -1
- package/templates/native-go-vue/frontend/src/App.vue +18 -13
- package/templates/native-go-vue/frontend/src/context.ts +31 -0
- package/templates/native-go-vue/frontend/src/delegate/ExecutorSettings.vue +54 -9
- package/templates/native-go-vue/frontend/src/delegate/catalogs.ts +11 -0
- package/templates/native-go-vue/frontend/src/delegate/copy.test.ts +12 -0
- package/templates/native-go-vue/frontend/src/delegate/copy.ts +4 -4
- package/templates/native-go-vue/frontend/src/delegate/executor-settings.test.ts +23 -0
- package/templates/native-go-vue/frontend/src/delegate/executor-setup.ts +8 -2
- package/templates/native-go-vue/frontend/src/delegate/facade.test.ts +100 -0
- package/templates/native-go-vue/frontend/src/delegate/facade.ts +85 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/de.json +47 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/en.json +13 -2
- package/templates/native-go-vue/frontend/src/delegate/i18n/es.json +47 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/fr.json +47 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/it.json +47 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/ja.json +47 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/ko.json +47 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/save/de.json +42 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/save/en.json +42 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/save/es.json +42 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/save/fr.json +42 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/save/it.json +42 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/save/ja.json +42 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/save/ko.json +42 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-CN.json +42 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-TW.json +42 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/zh-CN.json +47 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/zh-TW.json +16 -5
- package/templates/native-go-vue/frontend/src/delegate/long-running.ts +57 -0
- package/templates/native-go-vue/frontend/src/delegate/save-copy.test.ts +12 -0
- package/templates/native-go-vue/frontend/src/delegate/save-copy.ts +24 -0
- package/templates/native-go-vue/frontend/src/delegate/scoped-picker.test.ts +12 -0
- package/templates/native-go-vue/frontend/src/delegate/scoped-picker.ts +21 -0
- package/templates/native-go-vue/frontend/src/files/files.test.ts +7 -0
- package/templates/native-go-vue/frontend/src/i18n/de.json +20 -0
- package/templates/native-go-vue/frontend/src/i18n/en.json +20 -0
- package/templates/native-go-vue/frontend/src/i18n/es.json +20 -0
- package/templates/native-go-vue/frontend/src/i18n/fr.json +20 -0
- package/templates/native-go-vue/frontend/src/i18n/it.json +20 -0
- package/templates/native-go-vue/frontend/src/i18n/ja.json +20 -0
- package/templates/native-go-vue/frontend/src/i18n/ko.json +20 -0
- package/templates/native-go-vue/frontend/src/i18n/zh-CN.json +20 -0
- package/templates/native-go-vue/frontend/src/i18n/zh-TW.json +20 -0
- package/templates/native-go-vue/frontend/src/i18n.ts +13 -0
- package/templates/native-go-vue/frontend/src/identity/LoginPage.vue +16 -14
- package/templates/native-go-vue/frontend/src/identity/i18n/de.json +27 -0
- package/templates/native-go-vue/frontend/src/identity/i18n/en.json +27 -0
- package/templates/native-go-vue/frontend/src/identity/i18n/es.json +27 -0
- package/templates/native-go-vue/frontend/src/identity/i18n/fr.json +27 -0
- package/templates/native-go-vue/frontend/src/identity/i18n/it.json +27 -0
- package/templates/native-go-vue/frontend/src/identity/i18n/ja.json +27 -0
- package/templates/native-go-vue/frontend/src/identity/i18n/ko.json +27 -0
- package/templates/native-go-vue/frontend/src/identity/i18n/zh-CN.json +27 -0
- package/templates/native-go-vue/frontend/src/identity/i18n/zh-TW.json +27 -0
- package/templates/native-go-vue/frontend/src/identity/i18n.ts +14 -0
- package/templates/native-go-vue/frontend/src/identity/session.test.ts +4 -0
- package/templates/native-go-vue/frontend/src/identity/session.ts +9 -8
- package/templates/native-go-vue/frontend/src/nas/NasPicker.vue +31 -12
- package/templates/native-go-vue/frontend/src/nas/destination.test.ts +48 -0
- package/templates/native-go-vue/frontend/src/nas/i18n/files/de.json +1 -1
- package/templates/native-go-vue/frontend/src/nas/i18n/files/en.json +1 -1
- package/templates/native-go-vue/frontend/src/nas/i18n/files/es.json +10 -10
- package/templates/native-go-vue/frontend/src/nas/i18n/files/fr.json +1 -1
- package/templates/native-go-vue/frontend/src/nas/i18n/files/it.json +12 -12
- package/templates/native-go-vue/frontend/src/nas/i18n/files/ja.json +15 -15
- package/templates/native-go-vue/frontend/src/nas/i18n/files/ko.json +12 -12
- package/templates/native-go-vue/frontend/src/nas/i18n/files/zh-CN.json +1 -1
- package/templates/native-go-vue/frontend/src/nas/i18n/files/zh-TW.json +1 -1
- package/templates/native-go-vue/frontend/src/portal/PortalSettings.vue +39 -0
- package/templates/native-go-vue/frontend/src/shell/i18n/fr.json +1 -1
- package/templates/native-go-vue/frontend/src/shell/i18n/ja.json +1 -1
- package/templates/native-go-vue/frontend/test/apps.test.ts +14 -0
- package/templates/native-go-vue/frontend/test/portal-settings.test.ts +71 -0
- package/templates/native-go-vue/frontend/test/shell-i18n.test.ts +50 -0
|
@@ -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
|
+
}
|
|
@@ -21,6 +21,9 @@ const MaxBytes = 512 * 1024
|
|
|
21
21
|
const maxMetadata = 1024 * 1024
|
|
22
22
|
const PageSize = 100
|
|
23
23
|
|
|
24
|
+
// downloadPeek bounds how much of a DSM 7.1 download is inspected for an error envelope.
|
|
25
|
+
const downloadPeek = 4096
|
|
26
|
+
|
|
24
27
|
// Client has no cookie jar or credentials. The destination is startup-owned,
|
|
25
28
|
// literal loopback only; proxies, redirects and automatic retries are disabled.
|
|
26
29
|
type Client struct {
|
|
@@ -269,15 +272,48 @@ func (c *Client) DownloadStream(ctx context.Context, r *http.Request, p string)
|
|
|
269
272
|
if e != nil {
|
|
270
273
|
return Download{}, e
|
|
271
274
|
}
|
|
272
|
-
|
|
273
|
-
|
|
275
|
+
body := resp.Body
|
|
276
|
+
raw := resp.Header.Values("Content-Disposition")
|
|
277
|
+
if len(raw) == 0 {
|
|
278
|
+
// DSM 7.1 sends no Content-Disposition at all. Only a short body that is
|
|
279
|
+
// a complete DSM envelope is an error; everything else is the file.
|
|
280
|
+
if body, e = unlessEnvelope(resp); e != nil {
|
|
281
|
+
return Download{}, e
|
|
282
|
+
}
|
|
283
|
+
} else if disposition, _, _ := mime.ParseMediaType(raw[0]); len(raw) != 1 || disposition != "attachment" {
|
|
274
284
|
e = decode(resp, nil)
|
|
275
285
|
if e == nil {
|
|
276
286
|
e = fail("unavailable")
|
|
277
287
|
}
|
|
278
288
|
return Download{}, e
|
|
279
289
|
}
|
|
280
|
-
return Download{Body:
|
|
290
|
+
return Download{Body: body, ContentType: resp.Header.Get("Content-Type"), ContentDisposition: resp.Header.Get("Content-Disposition"), ContentLength: resp.ContentLength}, nil
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// unlessEnvelope returns the whole body as a stream unless it fits in the peek
|
|
294
|
+
// and decodes as a DSM envelope, which is refused and never becomes content.
|
|
295
|
+
func unlessEnvelope(resp *http.Response) (io.ReadCloser, error) {
|
|
296
|
+
head, e := io.ReadAll(io.LimitReader(resp.Body, downloadPeek+1))
|
|
297
|
+
if e != nil {
|
|
298
|
+
resp.Body.Close()
|
|
299
|
+
return nil, fail("read_failed")
|
|
300
|
+
}
|
|
301
|
+
if len(head) <= downloadPeek {
|
|
302
|
+
var envelope struct {
|
|
303
|
+
Success *bool `json:"success"`
|
|
304
|
+
}
|
|
305
|
+
if json.Unmarshal(head, &envelope) == nil && envelope.Success != nil {
|
|
306
|
+
resp.Body = io.NopCloser(bytes.NewReader(head))
|
|
307
|
+
if e = decode(resp, nil); e == nil {
|
|
308
|
+
e = fail("unavailable")
|
|
309
|
+
}
|
|
310
|
+
return nil, e
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return struct {
|
|
314
|
+
io.Reader
|
|
315
|
+
io.Closer
|
|
316
|
+
}{io.MultiReader(bytes.NewReader(head), resp.Body), resp.Body}, nil
|
|
281
317
|
}
|
|
282
318
|
|
|
283
319
|
// Read is the bounded package-private job import, using the same stream.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
package filestation
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"fmt"
|
|
6
|
+
"io"
|
|
7
|
+
"net/http"
|
|
8
|
+
"strings"
|
|
9
|
+
"testing"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
// DSM 7.1.1-42962 (vt-62, t-0231) answers Download v2 mode=download with the
|
|
13
|
+
// file bytes and no Content-Disposition header. Its Content-Type was not
|
|
14
|
+
// recorded, so every plausible type is covered.
|
|
15
|
+
func dsm71Download(contentType, body string) http.HandlerFunc {
|
|
16
|
+
return func(w http.ResponseWriter, r *http.Request) {
|
|
17
|
+
if contentType != "" {
|
|
18
|
+
w.Header().Set("Content-Type", contentType)
|
|
19
|
+
}
|
|
20
|
+
fmt.Fprint(w, body)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
func TestDSM71DownloadWithoutDispositionReturnsFileBytes(t *testing.T) {
|
|
25
|
+
for _, tt := range []struct{ contentType, body string }{
|
|
26
|
+
{"text/plain", "t0231 fixture a.txt"},
|
|
27
|
+
{"application/octet-stream", "\x00\x01binary"},
|
|
28
|
+
{"", "no type"},
|
|
29
|
+
{"application/json", `{"name":"a user file"}`},
|
|
30
|
+
{"application/json", `[{"success":false}]`},
|
|
31
|
+
{"text/plain", strings.Repeat("y", downloadPeek+7)},
|
|
32
|
+
} {
|
|
33
|
+
t.Run(tt.contentType, func(t *testing.T) {
|
|
34
|
+
c := fakeClient(t, dsm71Download(tt.contentType, tt.body))
|
|
35
|
+
b, err := c.Read(context.Background(), sessionRequest(), "/home/a")
|
|
36
|
+
if err != nil || string(b) != tt.body {
|
|
37
|
+
t.Fatalf("%v %q", err, b)
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
func TestDSM71DownloadStreamsBeyondThePeek(t *testing.T) {
|
|
44
|
+
body := strings.Repeat("z", MaxBytes*3)
|
|
45
|
+
c := fakeClient(t, dsm71Download("application/octet-stream", body))
|
|
46
|
+
d, err := c.DownloadStream(context.Background(), sessionRequest(), "/home/a")
|
|
47
|
+
if err != nil {
|
|
48
|
+
t.Fatal(err)
|
|
49
|
+
}
|
|
50
|
+
defer d.Body.Close()
|
|
51
|
+
b, err := io.ReadAll(d.Body)
|
|
52
|
+
if err != nil || string(b) != body || d.ContentType != "application/octet-stream" {
|
|
53
|
+
t.Fatal(err, len(b))
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
func TestDSM71DownloadEnvelopeWithoutDispositionIsNeverContent(t *testing.T) {
|
|
58
|
+
for _, tt := range []struct{ contentType, body, want string }{
|
|
59
|
+
{"application/json; charset=utf-8", `{"success":false,"error":{"code":408}}`, "denied"},
|
|
60
|
+
{"text/plain", `{"success":false,"error":{"code":119}}`, "session_required"},
|
|
61
|
+
{"", ` {"error":{"code":407},"success":false}` + "\n", "denied"},
|
|
62
|
+
{"application/json", `{"success":true,"data":{}}`, "unavailable"},
|
|
63
|
+
} {
|
|
64
|
+
t.Run(tt.want, func(t *testing.T) {
|
|
65
|
+
c := fakeClient(t, dsm71Download(tt.contentType, tt.body))
|
|
66
|
+
b, err := c.Read(context.Background(), sessionRequest(), "/home/a")
|
|
67
|
+
if err == nil || len(b) != 0 || !strings.Contains(err.Error(), tt.want) {
|
|
68
|
+
t.Fatalf("%v %q", err, b)
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
func TestNonAttachmentDispositionStaysRefused(t *testing.T) {
|
|
75
|
+
c := fakeClient(t, func(w http.ResponseWriter, r *http.Request) {
|
|
76
|
+
w.Header().Set("Content-Disposition", `inline; filename="a"`)
|
|
77
|
+
fmt.Fprint(w, "abc")
|
|
78
|
+
})
|
|
79
|
+
if b, err := c.Read(context.Background(), sessionRequest(), "/home/a"); err == nil || len(b) != 0 {
|
|
80
|
+
t.Fatal("inline disposition admitted")
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -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
|
|
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")
|
|
@@ -16,7 +16,11 @@ The transport accepts same-origin JSON POST requests:
|
|
|
16
16
|
- `identity.logout {}` removes the app session and clears the cookie.
|
|
17
17
|
|
|
18
18
|
NormalUser.get is the only DSM cookie identity check; anonymous Initdata success
|
|
19
|
-
cannot establish identity.
|
|
19
|
+
cannot establish identity. DSM web logins enable SynoToken, and DSM refuses
|
|
20
|
+
their cookie alone (119), so the check also forwards the request's single
|
|
21
|
+
`X-SYNO-TOKEN` header, which the DSM window's `ctx.call` sends. Sessions without
|
|
22
|
+
SynoToken still verify with the cookie alone. The token is never put in a URL,
|
|
23
|
+
logged or stored. Local `user.Lookup` binds the username to its UID.
|
|
20
24
|
Auth login creates a temporary SID, then immediately logs it out before issuing
|
|
21
25
|
an app session. A logout failure refuses the app login. No password, OTP, DSM SID
|
|
22
26
|
or DSM cookie is logged or persisted. Only hashes of app bearer IDs are stored.
|
|
@@ -9,6 +9,7 @@ import (
|
|
|
9
9
|
"net/url"
|
|
10
10
|
"strings"
|
|
11
11
|
"time"
|
|
12
|
+
"unicode"
|
|
12
13
|
)
|
|
13
14
|
|
|
14
15
|
type dsmReply struct {
|
|
@@ -25,7 +26,9 @@ type dsmReply struct {
|
|
|
25
26
|
} `json:"error"`
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
// dsmCall sends token only as X-SYNO-TOKEN: DSM web logins enable SynoToken,
|
|
30
|
+
// and their cookie alone is refused with 119.
|
|
31
|
+
func (s *Service) dsmCall(ctx context.Context, form url.Values, cookie, token string) (dsmReply, error) {
|
|
29
32
|
var out dsmReply
|
|
30
33
|
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
|
31
34
|
defer cancel()
|
|
@@ -37,6 +40,9 @@ func (s *Service) dsmCall(ctx context.Context, form url.Values, cookie string) (
|
|
|
37
40
|
if cookie != "" {
|
|
38
41
|
req.AddCookie(&http.Cookie{Name: "id", Value: cookie})
|
|
39
42
|
}
|
|
43
|
+
if token != "" {
|
|
44
|
+
req.Header.Set("X-SYNO-TOKEN", token)
|
|
45
|
+
}
|
|
40
46
|
res, err := s.cfg.Client.Do(req)
|
|
41
47
|
if err != nil {
|
|
42
48
|
return out, errors.New("DSM unavailable")
|
|
@@ -48,19 +54,32 @@ func (s *Service) dsmCall(ctx context.Context, form url.Values, cookie string) (
|
|
|
48
54
|
}
|
|
49
55
|
return out, nil
|
|
50
56
|
}
|
|
51
|
-
|
|
52
|
-
|
|
57
|
+
|
|
58
|
+
// verify checks the caller's DSM session with the request's cookie and, when the
|
|
59
|
+
// browser sent one, its SynoToken. Neither is stored or logged.
|
|
60
|
+
func (s *Service) verify(r *http.Request, cookie string) (Subject, error) {
|
|
61
|
+
token := synoToken(r)
|
|
62
|
+
out, err := s.dsmCall(r.Context(), url.Values{"api": {"SYNO.Core.NormalUser"}, "method": {"get"}, "version": {"1"}}, cookie, token)
|
|
53
63
|
if err != nil || !out.Success {
|
|
54
64
|
return Subject{}, errors.New("DSM session refused")
|
|
55
65
|
}
|
|
56
66
|
return s.resolve(out.Data.Username)
|
|
57
67
|
}
|
|
68
|
+
|
|
69
|
+
// synoToken returns the single, well-formed X-SYNO-TOKEN header or "".
|
|
70
|
+
func synoToken(r *http.Request) string {
|
|
71
|
+
values := r.Header.Values("X-SYNO-TOKEN")
|
|
72
|
+
if len(values) != 1 || len(values[0]) > 4096 || strings.ContainsFunc(values[0], unicode.IsControl) {
|
|
73
|
+
return ""
|
|
74
|
+
}
|
|
75
|
+
return values[0]
|
|
76
|
+
}
|
|
58
77
|
func (s *Service) login(ctx context.Context, account, password, otp string) (Subject, string) {
|
|
59
78
|
form := url.Values{"api": {"SYNO.API.Auth"}, "method": {"login"}, "version": {"7"}, "account": {account}, "passwd": {password}, "format": {"sid"}}
|
|
60
79
|
if otp != "" {
|
|
61
80
|
form.Set("otp_code", otp)
|
|
62
81
|
}
|
|
63
|
-
out, err := s.dsmCall(ctx, form, "")
|
|
82
|
+
out, err := s.dsmCall(ctx, form, "", "")
|
|
64
83
|
if err != nil {
|
|
65
84
|
return Subject{}, "auth_unavailable"
|
|
66
85
|
}
|
|
@@ -84,7 +103,7 @@ func (s *Service) login(ctx context.Context, account, password, otp string) (Sub
|
|
|
84
103
|
}
|
|
85
104
|
// The temporary DSM session exists only to prove identity. Dispose of it
|
|
86
105
|
// before issuing an application session, including when local lookup fails.
|
|
87
|
-
loggedOut, err := s.dsmCall(context.WithoutCancel(ctx), url.Values{"api": {"SYNO.API.Auth"}, "method": {"logout"}, "version": {"7"}, "_sid": {out.Data.SID}}, "")
|
|
106
|
+
loggedOut, err := s.dsmCall(context.WithoutCancel(ctx), url.Values{"api": {"SYNO.API.Auth"}, "method": {"logout"}, "version": {"7"}, "_sid": {out.Data.SID}}, "", "")
|
|
88
107
|
if err != nil || !loggedOut.Success {
|
|
89
108
|
return Subject{}, "auth_unavailable"
|
|
90
109
|
}
|
|
@@ -237,7 +237,7 @@ func (s *Service) handle(w http.ResponseWriter, r *http.Request, req request) {
|
|
|
237
237
|
code = "session_required"
|
|
238
238
|
return nil
|
|
239
239
|
}
|
|
240
|
-
sub, err := s.verify(r
|
|
240
|
+
sub, err := s.verify(r, dc.Value)
|
|
241
241
|
if err != nil {
|
|
242
242
|
code = "session_required"
|
|
243
243
|
return nil
|
|
@@ -27,6 +27,11 @@ type fixture struct {
|
|
|
27
27
|
logout int
|
|
28
28
|
calls []string
|
|
29
29
|
dir string
|
|
30
|
+
// synoToken makes the fake DSM session a SynoToken session: NormalUser.get
|
|
31
|
+
// then needs the cookie and this X-SYNO-TOKEN header, as DSM web logins do.
|
|
32
|
+
synoToken string
|
|
33
|
+
// token is the X-SYNO-TOKEN the browser sends with each App request.
|
|
34
|
+
token string
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
func setup(t *testing.T) *fixture {
|
|
@@ -40,7 +45,10 @@ func setup(t *testing.T) *fixture {
|
|
|
40
45
|
switch name {
|
|
41
46
|
case "SYNO.Core.NormalUser.get":
|
|
42
47
|
c, err := r.Cookie("id")
|
|
43
|
-
if
|
|
48
|
+
if r.Form.Has("SynoToken") {
|
|
49
|
+
t.Error("SynoToken must travel only in the header")
|
|
50
|
+
}
|
|
51
|
+
if f.valid && err == nil && c.Value == "valid-dsm-cookie" && (f.synoToken == "" || r.Header.Get("X-SYNO-TOKEN") == f.synoToken) {
|
|
44
52
|
fmt.Fprint(w, `{"success":true,"data":{"username":"alice"}}`)
|
|
45
53
|
} else {
|
|
46
54
|
fmt.Fprint(w, `{"success":false,"error":{"code":119}}`)
|
|
@@ -93,6 +101,9 @@ func (f *fixture) call(method, params string, appCookie *http.Cookie, dsmCookie
|
|
|
93
101
|
if dsmCookie {
|
|
94
102
|
req.AddCookie(&http.Cookie{Name: "id", Value: "valid-dsm-cookie"})
|
|
95
103
|
}
|
|
104
|
+
if f.token != "" {
|
|
105
|
+
req.Header.Set("X-SYNO-TOKEN", f.token)
|
|
106
|
+
}
|
|
96
107
|
out := httptest.NewRecorder()
|
|
97
108
|
f.service.Wrap(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
98
109
|
id, err := f.service.Authenticate(r)
|
|
@@ -148,6 +159,42 @@ func TestCookieExchangeAndNoInitdataShortcut(t *testing.T) {
|
|
|
148
159
|
}
|
|
149
160
|
}
|
|
150
161
|
}
|
|
162
|
+
func TestSynoTokenSessionBootstrapsAndRevalidates(t *testing.T) {
|
|
163
|
+
f := setup(t)
|
|
164
|
+
f.synoToken = "secret-syno-token"
|
|
165
|
+
if _, out := f.call("identity.session", `{"bootstrap":true}`, nil, true); errorCode(out) != "session_required" {
|
|
166
|
+
t.Fatalf("cookie alone must not pass a SynoToken session: %v", out)
|
|
167
|
+
}
|
|
168
|
+
f.token = "secret-syno-token"
|
|
169
|
+
rec, out := f.call("identity.session", `{"bootstrap":true}`, nil, true)
|
|
170
|
+
if errorCode(out) != "" {
|
|
171
|
+
t.Fatalf("bootstrap with SynoToken: %v", out)
|
|
172
|
+
}
|
|
173
|
+
c := sessionCookie(t, rec)
|
|
174
|
+
if _, out = f.call("greeting.set", `{"text":"x"}`, c, true); errorCode(out) != "" {
|
|
175
|
+
t.Fatalf("live revalidation with SynoToken: %v", out)
|
|
176
|
+
}
|
|
177
|
+
filepath.WalkDir(f.dir, func(path string, d os.DirEntry, err error) error {
|
|
178
|
+
if err != nil {
|
|
179
|
+
t.Fatal(err)
|
|
180
|
+
}
|
|
181
|
+
if raw, _ := os.ReadFile(path); !d.IsDir() && strings.Contains(string(raw), "secret-syno-token") {
|
|
182
|
+
t.Fatalf("SynoToken written to %s", path)
|
|
183
|
+
}
|
|
184
|
+
return nil
|
|
185
|
+
})
|
|
186
|
+
f.token = ""
|
|
187
|
+
if _, out = f.call("greeting.set", `{"text":"x"}`, c, true); errorCode(out) != "session_required" {
|
|
188
|
+
t.Fatalf("revalidation without SynoToken: %v", out)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
func TestTokenlessSessionStillVerifiesWhenBrowserSendsToken(t *testing.T) {
|
|
192
|
+
f := setup(t)
|
|
193
|
+
f.token = "unused-token"
|
|
194
|
+
if _, out := f.call("identity.session", `{"bootstrap":true}`, nil, true); errorCode(out) != "" {
|
|
195
|
+
t.Fatal(out)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
151
198
|
func TestLoginOTPLogoutAndNoCredentialsOnDisk(t *testing.T) {
|
|
152
199
|
f := setup(t)
|
|
153
200
|
f.otp = true
|
|
@@ -145,7 +145,7 @@ func (s *Service) current(st *state, r *http.Request, revalidate bool) (string,
|
|
|
145
145
|
if err != nil || dc.Value == "" {
|
|
146
146
|
return "", record{}, errRevalidation
|
|
147
147
|
}
|
|
148
|
-
live, err := s.verify(r
|
|
148
|
+
live, err := s.verify(r, dc.Value)
|
|
149
149
|
if err != nil || live != sub {
|
|
150
150
|
delete(st.Sessions, key)
|
|
151
151
|
return "", record{}, api.ErrNoSession
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// Package portal reads the DSM Application Portal alias an administrator set
|
|
2
|
+
// for this package. DSM owns the setting: the package never writes it, and
|
|
3
|
+
// anything this reader does not fully understand reads as Unavailable.
|
|
4
|
+
package portal
|
|
5
|
+
|
|
6
|
+
import (
|
|
7
|
+
"bytes"
|
|
8
|
+
"encoding/json"
|
|
9
|
+
"errors"
|
|
10
|
+
"io"
|
|
11
|
+
"io/fs"
|
|
12
|
+
"os"
|
|
13
|
+
"regexp"
|
|
14
|
+
"syscall"
|
|
15
|
+
"unicode/utf8"
|
|
16
|
+
|
|
17
|
+
"dsmapp/internal/jsonlimits"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
// MaxConfigBytes bounds DSM's saved setting; its real size is a few hundred bytes.
|
|
21
|
+
const MaxConfigBytes = 64 << 10
|
|
22
|
+
|
|
23
|
+
const (
|
|
24
|
+
// Active means users can open the app at /<alias>/ after DSM sign-in.
|
|
25
|
+
Active = "active"
|
|
26
|
+
// Inactive means no administrator has set an alias yet.
|
|
27
|
+
Inactive = "inactive"
|
|
28
|
+
// Unavailable means the setting could not be read safely.
|
|
29
|
+
Unavailable = "unavailable"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
// Status is what portal.status returns; Alias is set only when Active.
|
|
33
|
+
type Status struct {
|
|
34
|
+
State string `json:"state"`
|
|
35
|
+
Alias string `json:"alias,omitempty"`
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// A single URL path segment; DSM's own validation is looser than this.
|
|
39
|
+
var aliasPattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$`)
|
|
40
|
+
|
|
41
|
+
// ConfigPath is where DSM saves the package's portal setting
|
|
42
|
+
// (/var/packages/<package>/etc is the package's @appconf directory).
|
|
43
|
+
func ConfigPath(packageID string) string {
|
|
44
|
+
return "/var/packages/" + packageID + "/etc/AppPortal.json"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Read returns the saved alias when the setting belongs to appID.
|
|
48
|
+
func Read(path, appID string) Status {
|
|
49
|
+
raw, err := readBounded(path)
|
|
50
|
+
if errors.Is(err, fs.ErrNotExist) {
|
|
51
|
+
return Status{State: Inactive}
|
|
52
|
+
}
|
|
53
|
+
if err != nil {
|
|
54
|
+
return Status{State: Unavailable}
|
|
55
|
+
}
|
|
56
|
+
id, alias, ok := parse(raw)
|
|
57
|
+
if !ok || id != appID {
|
|
58
|
+
return Status{State: Unavailable}
|
|
59
|
+
}
|
|
60
|
+
if alias == "" {
|
|
61
|
+
return Status{State: Inactive}
|
|
62
|
+
}
|
|
63
|
+
if !aliasPattern.MatchString(alias) {
|
|
64
|
+
return Status{State: Unavailable}
|
|
65
|
+
}
|
|
66
|
+
return Status{State: Active, Alias: alias}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
func readBounded(path string) ([]byte, error) {
|
|
70
|
+
f, err := os.OpenFile(path, os.O_RDONLY|syscall.O_NOFOLLOW|syscall.O_NONBLOCK, 0)
|
|
71
|
+
if err != nil {
|
|
72
|
+
return nil, err
|
|
73
|
+
}
|
|
74
|
+
defer f.Close()
|
|
75
|
+
info, err := f.Stat()
|
|
76
|
+
if err != nil {
|
|
77
|
+
return nil, err
|
|
78
|
+
}
|
|
79
|
+
if !info.Mode().IsRegular() {
|
|
80
|
+
return nil, errors.New("not a regular file")
|
|
81
|
+
}
|
|
82
|
+
raw, err := io.ReadAll(io.LimitReader(f, MaxConfigBytes+1))
|
|
83
|
+
if err != nil {
|
|
84
|
+
return nil, err
|
|
85
|
+
}
|
|
86
|
+
if len(raw) > MaxConfigBytes {
|
|
87
|
+
return nil, errors.New("too large")
|
|
88
|
+
}
|
|
89
|
+
return raw, nil
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// parse accepts exactly one JSON object with unique top-level keys, a string
|
|
93
|
+
// id and a string or null alias; other DSM fields are skipped unread.
|
|
94
|
+
func parse(raw []byte) (id, alias string, ok bool) {
|
|
95
|
+
if !utf8.Valid(raw) || !jsonlimits.WithinLimits(raw) {
|
|
96
|
+
return "", "", false
|
|
97
|
+
}
|
|
98
|
+
d := json.NewDecoder(bytes.NewReader(raw))
|
|
99
|
+
if token, err := d.Token(); err != nil || token != json.Delim('{') {
|
|
100
|
+
return "", "", false
|
|
101
|
+
}
|
|
102
|
+
seen := map[string]bool{}
|
|
103
|
+
hasID := false
|
|
104
|
+
for d.More() {
|
|
105
|
+
token, err := d.Token()
|
|
106
|
+
key, isKey := token.(string)
|
|
107
|
+
if err != nil || !isKey || seen[key] {
|
|
108
|
+
return "", "", false
|
|
109
|
+
}
|
|
110
|
+
seen[key] = true
|
|
111
|
+
var value json.RawMessage
|
|
112
|
+
if err := d.Decode(&value); err != nil {
|
|
113
|
+
return "", "", false
|
|
114
|
+
}
|
|
115
|
+
switch key {
|
|
116
|
+
case "id":
|
|
117
|
+
if json.Unmarshal(value, &id) != nil || id == "" {
|
|
118
|
+
return "", "", false
|
|
119
|
+
}
|
|
120
|
+
hasID = true
|
|
121
|
+
case "alias":
|
|
122
|
+
var text *string
|
|
123
|
+
if json.Unmarshal(value, &text) != nil {
|
|
124
|
+
return "", "", false
|
|
125
|
+
}
|
|
126
|
+
if text != nil {
|
|
127
|
+
alias = *text
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if token, err := d.Token(); err != nil || token != json.Delim('}') {
|
|
132
|
+
return "", "", false
|
|
133
|
+
}
|
|
134
|
+
if _, err := d.Token(); err != io.EOF {
|
|
135
|
+
return "", "", false
|
|
136
|
+
}
|
|
137
|
+
return id, alias, hasID
|
|
138
|
+
}
|