@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,177 @@
|
|
|
1
|
+
package dsm
|
|
2
|
+
|
|
3
|
+
// The entry gate decides who may use the app at all, before any route policy.
|
|
4
|
+
// It wraps the session authenticator, so every API method, transfer and the
|
|
5
|
+
// window's own calls share one decision; apps never implement it themselves.
|
|
6
|
+
|
|
7
|
+
import (
|
|
8
|
+
"context"
|
|
9
|
+
"crypto/sha256"
|
|
10
|
+
"encoding/hex"
|
|
11
|
+
"encoding/json"
|
|
12
|
+
"errors"
|
|
13
|
+
"fmt"
|
|
14
|
+
"net"
|
|
15
|
+
"net/http"
|
|
16
|
+
"os"
|
|
17
|
+
"path/filepath"
|
|
18
|
+
"regexp"
|
|
19
|
+
"sort"
|
|
20
|
+
"time"
|
|
21
|
+
|
|
22
|
+
"dsmapp/internal/api"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
const (
|
|
26
|
+
// EntryAdminOnly admits only DSM administrators.
|
|
27
|
+
EntryAdminOnly = "admin-only"
|
|
28
|
+
// EntryAppPrivilege admits administrators and any account that DSM's
|
|
29
|
+
// Control Panel > Application Privileges allows for this app.
|
|
30
|
+
EntryAppPrivilege = "app-privilege"
|
|
31
|
+
|
|
32
|
+
// EntryCacheTTL bounds how long a privilege change can take to apply.
|
|
33
|
+
EntryCacheTTL = 30 * time.Second
|
|
34
|
+
// EntryCacheMaxEntries bounds the cache file whatever the traffic.
|
|
35
|
+
EntryCacheMaxEntries = 256
|
|
36
|
+
|
|
37
|
+
entryCacheFile = "entry-cache.json"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
var packageName = regexp.MustCompile(`^[A-Za-z][A-Za-z0-9]{1,63}$`)
|
|
41
|
+
|
|
42
|
+
// EntryGate is an api.Authenticator that admits a verified caller only when
|
|
43
|
+
// the app's entry mode allows it.
|
|
44
|
+
type EntryGate struct {
|
|
45
|
+
next api.Authenticator
|
|
46
|
+
mode string
|
|
47
|
+
app string
|
|
48
|
+
dir string
|
|
49
|
+
now func() time.Time
|
|
50
|
+
allowed func(ctx context.Context, principal, app, addr string) (bool, error)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// NewEntryGate wraps next for the build-time entry mode. An empty mode keeps
|
|
54
|
+
// next unchanged: the route policy alone decides, as before entry modes.
|
|
55
|
+
// dataDir holds the short-lived decision cache shared by CGI processes.
|
|
56
|
+
func NewEntryGate(next api.Authenticator, mode, packageID, dataDir string) (api.Authenticator, error) {
|
|
57
|
+
if next == nil {
|
|
58
|
+
return nil, errors.New("entry gate needs an authenticator")
|
|
59
|
+
}
|
|
60
|
+
switch mode {
|
|
61
|
+
case "":
|
|
62
|
+
return next, nil
|
|
63
|
+
case EntryAdminOnly, EntryAppPrivilege:
|
|
64
|
+
default:
|
|
65
|
+
return nil, fmt.Errorf("unknown entry mode %q", mode)
|
|
66
|
+
}
|
|
67
|
+
if !packageName.MatchString(packageID) {
|
|
68
|
+
return nil, fmt.Errorf("invalid package ID %q", packageID)
|
|
69
|
+
}
|
|
70
|
+
return &EntryGate{next: next, mode: mode, app: "SYNO.SDS." + packageID + ".Instance", dir: filepath.Join(dataDir, "entry"), now: time.Now, allowed: AppPrivilege}, nil
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func (g *EntryGate) Authenticate(r *http.Request) (api.Identity, error) {
|
|
74
|
+
id, err := g.next.Authenticate(r)
|
|
75
|
+
if err != nil || id.Admin {
|
|
76
|
+
return id, err
|
|
77
|
+
}
|
|
78
|
+
if g.mode == EntryAdminOnly {
|
|
79
|
+
return api.Identity{}, api.ErrAdminRequired
|
|
80
|
+
}
|
|
81
|
+
addr, err := clientAddress(r)
|
|
82
|
+
if err != nil {
|
|
83
|
+
return api.Identity{}, err
|
|
84
|
+
}
|
|
85
|
+
allowed, err := g.decide(r.Context(), id.User, addr)
|
|
86
|
+
if err != nil {
|
|
87
|
+
return api.Identity{}, err
|
|
88
|
+
}
|
|
89
|
+
if !allowed {
|
|
90
|
+
return api.Identity{}, api.ErrAppPrivilegeDenied
|
|
91
|
+
}
|
|
92
|
+
return id, nil
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// clientAddress is the browser's address as DSM's web server reported it
|
|
96
|
+
// (REMOTE_ADDR); DSM's IP rules are evaluated against it.
|
|
97
|
+
func clientAddress(r *http.Request) (string, error) {
|
|
98
|
+
host, _, err := net.SplitHostPort(r.RemoteAddr)
|
|
99
|
+
if err != nil {
|
|
100
|
+
host = r.RemoteAddr
|
|
101
|
+
}
|
|
102
|
+
ip := net.ParseIP(host)
|
|
103
|
+
if ip == nil {
|
|
104
|
+
return "", errors.New("client address unavailable")
|
|
105
|
+
}
|
|
106
|
+
return ip.String(), nil
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
type cachedDecision struct {
|
|
110
|
+
Allowed bool `json:"allowed"`
|
|
111
|
+
At int64 `json:"at"`
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
func (g *EntryGate) decide(ctx context.Context, principal, addr string) (bool, error) {
|
|
115
|
+
sum := sha256.Sum256([]byte(principal + "\x00" + g.app + "\x00" + addr))
|
|
116
|
+
key := hex.EncodeToString(sum[:])
|
|
117
|
+
now := g.now().Unix()
|
|
118
|
+
entries, _ := g.readCache()
|
|
119
|
+
if d, ok := entries[key]; ok && d.At <= now && now-d.At < int64(EntryCacheTTL/time.Second) {
|
|
120
|
+
return d.Allowed, nil
|
|
121
|
+
}
|
|
122
|
+
allowed, err := g.allowed(ctx, principal, g.app, addr)
|
|
123
|
+
if err != nil {
|
|
124
|
+
return false, err
|
|
125
|
+
}
|
|
126
|
+
if entries == nil {
|
|
127
|
+
entries = map[string]cachedDecision{}
|
|
128
|
+
}
|
|
129
|
+
entries[key] = cachedDecision{Allowed: allowed, At: now}
|
|
130
|
+
g.writeCache(entries, now)
|
|
131
|
+
return allowed, nil
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
func (g *EntryGate) readCache() (map[string]cachedDecision, error) {
|
|
135
|
+
data, err := os.ReadFile(filepath.Join(g.dir, entryCacheFile))
|
|
136
|
+
if err != nil {
|
|
137
|
+
return nil, err
|
|
138
|
+
}
|
|
139
|
+
var entries map[string]cachedDecision
|
|
140
|
+
if err := json.Unmarshal(data, &entries); err != nil {
|
|
141
|
+
return nil, err
|
|
142
|
+
}
|
|
143
|
+
return entries, nil
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// writeCache keeps only fresh entries, newest first, up to the bound. A failed
|
|
147
|
+
// write only costs the next request a fresh lookup.
|
|
148
|
+
func (g *EntryGate) writeCache(entries map[string]cachedDecision, now int64) {
|
|
149
|
+
keys := make([]string, 0, len(entries))
|
|
150
|
+
for k, d := range entries {
|
|
151
|
+
if d.At <= now && now-d.At < int64(EntryCacheTTL/time.Second) {
|
|
152
|
+
keys = append(keys, k)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
sort.Slice(keys, func(i, j int) bool { return entries[keys[i]].At > entries[keys[j]].At })
|
|
156
|
+
if len(keys) > EntryCacheMaxEntries {
|
|
157
|
+
keys = keys[:EntryCacheMaxEntries]
|
|
158
|
+
}
|
|
159
|
+
kept := make(map[string]cachedDecision, len(keys))
|
|
160
|
+
for _, k := range keys {
|
|
161
|
+
kept[k] = entries[k]
|
|
162
|
+
}
|
|
163
|
+
data, err := json.Marshal(kept)
|
|
164
|
+
if err != nil || os.MkdirAll(g.dir, 0o700) != nil {
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
tmp, err := os.CreateTemp(g.dir, entryCacheFile+".*")
|
|
168
|
+
if err != nil {
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
defer os.Remove(tmp.Name())
|
|
172
|
+
if _, err := tmp.Write(data); err != nil || tmp.Close() != nil {
|
|
173
|
+
tmp.Close()
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
os.Rename(tmp.Name(), filepath.Join(g.dir, entryCacheFile))
|
|
177
|
+
}
|
|
@@ -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
|
+
}
|
|
Binary file
|
|
@@ -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
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
"
|
|
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
|
+
}
|