@synopackageland/cli 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +613 -0
- package/README.md +81 -89
- package/dist/add.js +39 -16
- 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 +39 -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.d.ts +1 -1
- package/dist/build-spk/gateway.js +2 -2
- package/dist/build-spk/generator.js +30 -5
- 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 +15 -7
- 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/plan.js +1 -1
- package/dist/deploy/spk-version.js +1 -1
- package/dist/error-registry.js +43 -15
- package/dist/host/dsm-client.d.ts +8 -4
- package/dist/host/dsm-client.js +10 -4
- package/dist/host/types.d.ts +2 -1
- package/dist/info.js +18 -2
- package/dist/init.d.ts +3 -0
- package/dist/init.js +49 -18
- 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/executor-build.d.ts +1 -1
- package/dist/runtime/executor-build.js +5 -4
- 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 -1
- package/dist/standalone/daemon.js +2 -1
- package/dist/standalone/dsm-window.d.ts +3 -1
- package/dist/standalone/dsm-window.js +72 -15
- package/dist/standalone/inspect-spk.d.ts +8 -0
- package/dist/standalone/inspect-spk.js +105 -26
- 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 +2 -0
- package/dist/standalone/native-layout.d.ts +3 -0
- package/dist/standalone/native-layout.js +37 -5
- 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.js +62 -15
- package/dist/standalone/spk.d.ts +1 -1
- package/dist/standalone/spk.js +25 -7
- package/dist/templates/_daemon/main_test.go +61 -8
- package/dist/templates/_executor/README.md +127 -16
- package/dist/templates/_executor/anchor.go +146 -0
- package/dist/templates/_executor/anchor_test.go +126 -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/dsm.go +23 -10
- 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/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 +72 -20
- 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/platform_linux.go +24 -173
- package/dist/templates/_executor/platform_linux_test.go +9 -65
- package/dist/templates/_executor/platform_other.go +30 -4
- package/dist/templates/_executor/process.go +3 -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 +7 -8
- package/dist/templates/_executor/tasks_review_test.go +1 -1
- package/dist/templates/_executor/tasks_test.go +4 -4
- 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/_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/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/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/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/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-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 +52 -8
- 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-setup.ts +4 -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 +46 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/en.json +12 -2
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/es.json +46 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/fr.json +46 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/it.json +46 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/ja.json +46 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/ko.json +46 -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 +46 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/zh-TW.json +15 -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/main_test.go +61 -8
- package/templates/_executor/README.md +127 -16
- package/templates/_executor/anchor.go +146 -0
- package/templates/_executor/anchor_test.go +126 -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/dsm.go +23 -10
- 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/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 +72 -20
- 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/platform_linux.go +24 -173
- package/templates/_executor/platform_linux_test.go +9 -65
- package/templates/_executor/platform_other.go +30 -4
- package/templates/_executor/process.go +3 -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 +7 -8
- package/templates/_executor/tasks_review_test.go +1 -1
- package/templates/_executor/tasks_test.go +4 -4
- 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/_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/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/sdk_policy_test.go +39 -0
- package/templates/native-go/backend/internal/filestation/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/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-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 +52 -8
- 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-setup.ts +4 -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 +46 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/en.json +12 -2
- package/templates/native-go-vue/frontend/src/delegate/i18n/es.json +46 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/fr.json +46 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/it.json +46 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/ja.json +46 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/ko.json +46 -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 +46 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/zh-TW.json +15 -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
|
@@ -135,6 +135,9 @@ func (d *liveDSM) call(ctx context.Context, api, method, version, runner string,
|
|
|
135
135
|
}
|
|
136
136
|
b, err := d.Command.Exec(ctx, "/usr/syno/bin/synowebapi", args)
|
|
137
137
|
if err != nil {
|
|
138
|
+
if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
|
|
139
|
+
return nil, err
|
|
140
|
+
}
|
|
138
141
|
return nil, errors.New("DSM command")
|
|
139
142
|
}
|
|
140
143
|
return decodeEnvelope(b)
|
|
@@ -161,13 +164,21 @@ func (d *liveDSM) Account(ctx context.Context, name string) (Subject, error) {
|
|
|
161
164
|
s.Expired = out.Users[0].Expired
|
|
162
165
|
return s, nil
|
|
163
166
|
}
|
|
164
|
-
|
|
167
|
+
|
|
168
|
+
// Allow is the executor's offline App Privileges decision. It follows the CGI
|
|
169
|
+
// EntryGate: administrators pass, admin-only refuses everyone else, and
|
|
170
|
+
// app-privilege requires DSM's own allow. Preview lists only effective allows
|
|
171
|
+
// (user, group and everyone rules), so an absent App is allowed only when the
|
|
172
|
+
// package declares no entry mode and DSM therefore never manages its App.
|
|
173
|
+
func (d *liveDSM) Allow(ctx context.Context, s Subject, app, entry string) error {
|
|
174
|
+
if !contains([]string{"", "admin-only", "app-privilege"}, entry) {
|
|
175
|
+
return errors.New("entry mode")
|
|
176
|
+
}
|
|
165
177
|
data, err := d.call(ctx, "SYNO.Core.AppPriv.App", "preview", "2", "", map[string]any{"username": s.Username})
|
|
166
178
|
if err != nil {
|
|
167
179
|
return err
|
|
168
180
|
}
|
|
169
|
-
//
|
|
170
|
-
// An explicit deny/custom entry or malformed/ambiguous response still refuses.
|
|
181
|
+
// An explicit deny/custom entry or malformed/ambiguous response refuses.
|
|
171
182
|
var out struct {
|
|
172
183
|
Apps *[]json.RawMessage `json:"applications"`
|
|
173
184
|
}
|
|
@@ -193,7 +204,10 @@ func (d *liveDSM) Allow(ctx context.Context, s Subject, app string) error {
|
|
|
193
204
|
if count > 1 {
|
|
194
205
|
return errors.New("app ambiguous")
|
|
195
206
|
}
|
|
196
|
-
|
|
207
|
+
if entry == "" || s.Admin || entry == "app-privilege" && count == 1 {
|
|
208
|
+
return nil
|
|
209
|
+
}
|
|
210
|
+
return errors.New("app denied")
|
|
197
211
|
}
|
|
198
212
|
func (d *liveDSM) ScopeRoot(ctx context.Context, s Subject, scope string) (string, error) {
|
|
199
213
|
if scope == "home" {
|
|
@@ -210,16 +224,15 @@ func (d *liveDSM) ScopeRoot(ctx context.Context, s Subject, scope string) (strin
|
|
|
210
224
|
if err != nil {
|
|
211
225
|
return "", err
|
|
212
226
|
}
|
|
227
|
+
// DSM Share.get returns one share and its volume, not Share.list's array.
|
|
213
228
|
var out struct {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
Path string `json:"path"`
|
|
217
|
-
} `json:"shares"`
|
|
229
|
+
Name string `json:"name"`
|
|
230
|
+
Volume string `json:"vol_path"`
|
|
218
231
|
}
|
|
219
|
-
if json.Unmarshal(data, &out) != nil ||
|
|
232
|
+
if json.Unmarshal(data, &out) != nil || out.Name != name || !validSelectedScope(scope) || !cleanPath(out.Volume) || out.Volume == "/" {
|
|
220
233
|
return "", errors.New("share")
|
|
221
234
|
}
|
|
222
|
-
return filepath.EvalSymlinks(out.
|
|
235
|
+
return filepath.EvalSymlinks(filepath.Join(out.Volume, out.Name))
|
|
223
236
|
}
|
|
224
237
|
func (d *liveDSM) API(ctx context.Context, runner, op string, p map[string]any) (json.RawMessage, error) {
|
|
225
238
|
if runner == "" {
|
|
@@ -5,7 +5,10 @@ import (
|
|
|
5
5
|
"errors"
|
|
6
6
|
"net/http"
|
|
7
7
|
"net/http/httptest"
|
|
8
|
+
"os"
|
|
9
|
+
"path/filepath"
|
|
8
10
|
"reflect"
|
|
11
|
+
"strconv"
|
|
9
12
|
"strings"
|
|
10
13
|
"testing"
|
|
11
14
|
)
|
|
@@ -24,6 +27,25 @@ type cmdStub struct {
|
|
|
24
27
|
err error
|
|
25
28
|
}
|
|
26
29
|
|
|
30
|
+
func TestDSMCommandDeadlineStaysDistinctFromDenial(t *testing.T) {
|
|
31
|
+
for _, cause := range []error{context.DeadlineExceeded, errors.New("explicit DSM refusal")} {
|
|
32
|
+
d := &liveDSM{Command: &cmdStub{err: cause}, Accounts: accountsStub{s: Subject{Username: "alice", UID: 1200, GID: 100, Groups: []int{100}}}}
|
|
33
|
+
_, err := d.ScopeRoot(context.Background(), Subject{Username: "alice"}, "shared:reports")
|
|
34
|
+
if errors.Is(err, context.DeadlineExceeded) != errors.Is(cause, context.DeadlineExceeded) {
|
|
35
|
+
t.Fatal("lost deadline identity", err)
|
|
36
|
+
}
|
|
37
|
+
e := &Executor{DSM: d, Policy: Policy{App: "app"}}
|
|
38
|
+
_, code := e.eligible(context.Background(), "alice", 1200, true)
|
|
39
|
+
want := "INELIGIBLE"
|
|
40
|
+
if errors.Is(cause, context.DeadlineExceeded) {
|
|
41
|
+
want = "EXECUTOR_PAUSED"
|
|
42
|
+
}
|
|
43
|
+
if code != want {
|
|
44
|
+
t.Fatal(code, want)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
27
49
|
func (c *cmdStub) Exec(ctx context.Context, p string, args []string) ([]byte, error) {
|
|
28
50
|
c.path = p
|
|
29
51
|
c.args = args
|
|
@@ -69,7 +91,7 @@ func TestDSMAppPrivilege(t *testing.T) {
|
|
|
69
91
|
t.Run(tc.name, func(t *testing.T) {
|
|
70
92
|
c := &cmdStub{reply: tc.reply}
|
|
71
93
|
d := &liveDSM{Command: c}
|
|
72
|
-
err := d.Allow(context.Background(), Subject{Username: "alice"}, "app")
|
|
94
|
+
err := d.Allow(context.Background(), Subject{Username: "alice"}, "app", "")
|
|
73
95
|
if (err == nil) != tc.pass {
|
|
74
96
|
t.Fatal(err)
|
|
75
97
|
}
|
|
@@ -79,6 +101,41 @@ func TestDSMAppPrivilege(t *testing.T) {
|
|
|
79
101
|
})
|
|
80
102
|
}
|
|
81
103
|
}
|
|
104
|
+
|
|
105
|
+
// Preview lists only effective allows, so an absent App means "not allowed"
|
|
106
|
+
// unless DSM never manages the App (no entry mode). Mirrors the CGI EntryGate.
|
|
107
|
+
func TestDSMAppPrivilegeByEntryMode(t *testing.T) {
|
|
108
|
+
const (
|
|
109
|
+
absent = `{"success":true,"data":{"applications":[{"app_id":"other","privilelge":"allow"}]}}`
|
|
110
|
+
allowed = `{"success":true,"data":{"applications":[{"app_id":"app","privilelge":"allow"}]}}`
|
|
111
|
+
denied = `{"success":true,"data":{"applications":[{"app_id":"app","privilelge":"deny"}]}}`
|
|
112
|
+
allowAll = `{"success":true,"data":{"applications":[{"app_id":"SYNO.ALLOW.ALL.APPLICATIONS","privilelge":"allow"}]}}`
|
|
113
|
+
malformed = `{"success":true,"data":{}}`
|
|
114
|
+
)
|
|
115
|
+
user, admin := Subject{Username: "alice"}, Subject{Username: "root-admin", Admin: true}
|
|
116
|
+
for _, tc := range []struct {
|
|
117
|
+
name, entry, reply string
|
|
118
|
+
s Subject
|
|
119
|
+
pass bool
|
|
120
|
+
}{
|
|
121
|
+
{"legacy absent", "", absent, user, true},
|
|
122
|
+
{"app-privilege absent", "app-privilege", absent, user, false},
|
|
123
|
+
{"app-privilege allowed", "app-privilege", allowed, user, true},
|
|
124
|
+
{"app-privilege denied", "app-privilege", denied, user, false},
|
|
125
|
+
{"app-privilege admin", "app-privilege", allowAll, admin, true},
|
|
126
|
+
{"app-privilege admin malformed", "app-privilege", malformed, admin, false},
|
|
127
|
+
{"admin-only user allowed rule", "admin-only", allowed, user, false},
|
|
128
|
+
{"admin-only admin", "admin-only", allowAll, admin, true},
|
|
129
|
+
{"unknown mode", "everyone", allowed, user, false},
|
|
130
|
+
} {
|
|
131
|
+
t.Run(tc.name, func(t *testing.T) {
|
|
132
|
+
d := &liveDSM{Command: &cmdStub{reply: tc.reply}}
|
|
133
|
+
if err := d.Allow(context.Background(), tc.s, "app", tc.entry); (err == nil) != tc.pass {
|
|
134
|
+
t.Fatal(err)
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
}
|
|
82
139
|
func TestDSMVerify(t *testing.T) {
|
|
83
140
|
for _, reply := range []string{`{"success":true,"data":{"username":"alice"}}`, `{"success":true,"data":{}}`, `{"success":false}`, strings.Repeat("x", maxRequest+1)} {
|
|
84
141
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
@@ -194,3 +251,40 @@ func TestDefaultAppPrivilegeNeverBypassesExplicitDenialOrDisabledAccount(t *test
|
|
|
194
251
|
})
|
|
195
252
|
}
|
|
196
253
|
}
|
|
254
|
+
|
|
255
|
+
func TestDSMSharedScopeUsesMeasuredGetVolumeContract(t *testing.T) {
|
|
256
|
+
volume := t.TempDir()
|
|
257
|
+
volume, err := filepath.EvalSymlinks(volume)
|
|
258
|
+
if err != nil {
|
|
259
|
+
t.Fatal(err)
|
|
260
|
+
}
|
|
261
|
+
share := filepath.Join(volume, "reports")
|
|
262
|
+
if err := os.Mkdir(share, 0700); err != nil {
|
|
263
|
+
t.Fatal(err)
|
|
264
|
+
}
|
|
265
|
+
for _, tc := range []struct {
|
|
266
|
+
name, data string
|
|
267
|
+
pass bool
|
|
268
|
+
}{
|
|
269
|
+
{"DSM single share", `{"name":"reports","vol_path":` + strconv.Quote(volume) + `,"desc":"test","uuid":"fixture"}`, true},
|
|
270
|
+
{"different share", `{"name":"other","vol_path":` + strconv.Quote(volume) + `}`, false},
|
|
271
|
+
{"missing volume", `{"name":"reports"}`, false},
|
|
272
|
+
{"relative volume", `{"name":"reports","vol_path":"relative"}`, false},
|
|
273
|
+
{"unclean volume", `{"name":"reports","vol_path":"/volume1/../etc"}`, false},
|
|
274
|
+
{"root volume", `{"name":"reports","vol_path":"/"}`, false},
|
|
275
|
+
{"null", `null`, false},
|
|
276
|
+
{"invented list contract", `{"shares":[{"name":"reports","path":` + strconv.Quote(share) + `}]}`, false},
|
|
277
|
+
} {
|
|
278
|
+
t.Run(tc.name, func(t *testing.T) {
|
|
279
|
+
cmd := &cmdStub{reply: `{"success":true,"data":` + tc.data + `}`}
|
|
280
|
+
d := &liveDSM{Command: cmd}
|
|
281
|
+
root, err := d.ScopeRoot(context.Background(), Subject{}, "shared:reports")
|
|
282
|
+
if (err == nil) != tc.pass || tc.pass && root != share {
|
|
283
|
+
t.Fatalf("root=%q err=%v", root, err)
|
|
284
|
+
}
|
|
285
|
+
if !reflect.DeepEqual(cmd.args, []string{"--exec", "api=SYNO.Core.Share", "method=get", "version=1", `name="reports"`}) {
|
|
286
|
+
t.Fatal(cmd.args)
|
|
287
|
+
}
|
|
288
|
+
})
|
|
289
|
+
}
|
|
290
|
+
}
|
|
@@ -22,17 +22,21 @@ import (
|
|
|
22
22
|
const version = "1"
|
|
23
23
|
const maxRequest = 64 << 10
|
|
24
24
|
|
|
25
|
-
var allOps = []string{"status", "delegate.create", "delegate.list", "delegate.revoke", "files.list", "files.info", "files.createFolder", "files.checkPermission", "files.write", "root.run"}
|
|
25
|
+
var allOps = []string{"status", "delegate.create", "delegate.list", "delegate.revoke", "files.list", "files.info", "files.createFolder", "files.checkPermission", "files.write", "files.moveFromStaging", "files.operationStatus", "files.cancelOperation", "delegate.disable", "delegate.enable", "delegate.audit", "root.run"}
|
|
26
26
|
var tokenRE = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$`)
|
|
27
|
+
var destinationRE = regexp.MustCompile(`^[a-z][a-z0-9_-]{0,63}$`)
|
|
27
28
|
var idRE = regexp.MustCompile(`^[a-f0-9]{64}$`)
|
|
28
29
|
|
|
29
30
|
type Policy struct {
|
|
30
|
-
Version
|
|
31
|
-
Package
|
|
32
|
-
App
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
Version int `json:"v"`
|
|
32
|
+
Package string `json:"package"`
|
|
33
|
+
App string `json:"app"`
|
|
34
|
+
Entry string `json:"entry,omitempty"`
|
|
35
|
+
Scope string `json:"scope,omitempty"`
|
|
36
|
+
Operations []string `json:"operations"`
|
|
37
|
+
Destinations []string `json:"destinations,omitempty"`
|
|
38
|
+
Lifetime string `json:"lifetime,omitempty"`
|
|
39
|
+
Root map[string]string `json:"root,omitempty"`
|
|
36
40
|
}
|
|
37
41
|
type Subject struct {
|
|
38
42
|
Username string `json:"username"`
|
|
@@ -44,16 +48,20 @@ type Subject struct {
|
|
|
44
48
|
Admin bool `json:"admin"`
|
|
45
49
|
}
|
|
46
50
|
type Delegation struct {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
ScopeGeneration uint64 `json:"scope_generation,omitempty"`
|
|
52
|
+
Lifetime string `json:"lifetime,omitempty"`
|
|
53
|
+
DestinationKey string `json:"destination,omitempty"`
|
|
54
|
+
Directory directoryIdentity `json:"directory,omitempty"`
|
|
55
|
+
ID string `json:"delegation_id"`
|
|
56
|
+
Package string `json:"package"`
|
|
57
|
+
Subject Subject `json:"subject"`
|
|
58
|
+
Job string `json:"job_id"`
|
|
59
|
+
Ops []string `json:"ops"`
|
|
60
|
+
Scope string `json:"scope"`
|
|
61
|
+
Path string `json:"path"`
|
|
62
|
+
Root string `json:"root"`
|
|
63
|
+
Expires time.Time `json:"expires_at"`
|
|
64
|
+
Revoked bool `json:"revoked"`
|
|
57
65
|
}
|
|
58
66
|
type Auth struct {
|
|
59
67
|
Cookie string `json:"dsm_cookie,omitempty"`
|
|
@@ -95,20 +103,23 @@ type Response struct {
|
|
|
95
103
|
Message string `json:"message,omitempty"`
|
|
96
104
|
}
|
|
97
105
|
type Params struct {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
Lifetime string `json:"lifetime,omitempty"`
|
|
107
|
+
DestinationKey string `json:"destination,omitempty"`
|
|
108
|
+
Size int64 `json:"size,omitempty"`
|
|
109
|
+
Job string `json:"job_id,omitempty"`
|
|
110
|
+
Ops []string `json:"ops,omitempty"`
|
|
111
|
+
Scope string `json:"scope,omitempty"`
|
|
112
|
+
Path string `json:"path,omitempty"`
|
|
113
|
+
TTL int64 `json:"ttl_seconds,omitempty"`
|
|
114
|
+
ID string `json:"delegation_id,omitempty"`
|
|
115
|
+
Staging string `json:"staging,omitempty"`
|
|
116
|
+
Dest string `json:"dest_dir,omitempty"`
|
|
117
|
+
Name string `json:"name,omitempty"`
|
|
107
118
|
}
|
|
108
119
|
type DSM interface {
|
|
109
120
|
Verify(context.Context, string, string) (string, error)
|
|
110
121
|
Account(context.Context, string) (Subject, error)
|
|
111
|
-
Allow(context.Context, Subject, string) error
|
|
122
|
+
Allow(context.Context, Subject, string, string) error
|
|
112
123
|
ScopeRoot(context.Context, Subject, string) (string, error)
|
|
113
124
|
API(context.Context, string, string, map[string]any) (json.RawMessage, error)
|
|
114
125
|
}
|
|
@@ -128,7 +139,10 @@ type Executor struct {
|
|
|
128
139
|
Store *Store
|
|
129
140
|
Now func() time.Time
|
|
130
141
|
ExecutorDigest, PolicyDigest string
|
|
131
|
-
|
|
142
|
+
legacyCalls map[string][]chan struct{}
|
|
143
|
+
copySlot chan struct{}
|
|
144
|
+
commitMu sync.Mutex // Durable admission/revoke/disable and bounded publish only.
|
|
145
|
+
async *operationRuntime
|
|
132
146
|
}
|
|
133
147
|
|
|
134
148
|
func deny(code string) Response {
|
|
@@ -231,7 +245,7 @@ func uniqueValue(d *json.Decoder, canonical bool) error {
|
|
|
231
245
|
}
|
|
232
246
|
func (e *Executor) Connection(parent context.Context, c net.Conn, uid int, peer func(net.Conn) (int, error)) {
|
|
233
247
|
got, err := peer(c)
|
|
234
|
-
if err != nil || got != uid {
|
|
248
|
+
if err != nil || (got != uid && got != 0) {
|
|
235
249
|
return
|
|
236
250
|
}
|
|
237
251
|
c.SetDeadline(time.Now().Add(15 * time.Second))
|
|
@@ -249,22 +263,36 @@ func (e *Executor) Connection(parent context.Context, c net.Conn, uid int, peer
|
|
|
249
263
|
} else {
|
|
250
264
|
ctx, cancel := context.WithTimeout(parent, 30*time.Second)
|
|
251
265
|
defer cancel()
|
|
252
|
-
out = e.
|
|
266
|
+
out = e.handlePeer(ctx, req, got, uid)
|
|
253
267
|
}
|
|
254
268
|
}
|
|
255
269
|
c.SetWriteDeadline(time.Now().Add(5 * time.Second))
|
|
256
270
|
json.NewEncoder(c).Encode(out)
|
|
257
271
|
}
|
|
258
272
|
func (e *Executor) eligible(ctx context.Context, name string, uid int, files bool) (Subject, string) {
|
|
273
|
+
moveValidationStage(ctx, "account")
|
|
259
274
|
s, err := e.DSM.Account(ctx, name)
|
|
260
|
-
if
|
|
275
|
+
if moveValidationDeadline(ctx, err) {
|
|
276
|
+
return s, "EXECUTOR_PAUSED"
|
|
277
|
+
}
|
|
278
|
+
if err != nil || s.Username != name || s.Expired != "normal" || s.UID < 1024 || s.GID < 0 || len(s.Groups) == 0 || (uid >= 0 && s.UID != uid) {
|
|
261
279
|
return Subject{}, "INELIGIBLE"
|
|
262
280
|
}
|
|
263
|
-
|
|
281
|
+
moveValidationStage(ctx, "app-privilege")
|
|
282
|
+
if err := e.DSM.Allow(ctx, s, e.Policy.App, e.Policy.Entry); err != nil {
|
|
283
|
+
if moveValidationDeadline(ctx, err) {
|
|
284
|
+
return s, "EXECUTOR_PAUSED"
|
|
285
|
+
}
|
|
264
286
|
return Subject{}, "APP_DENIED"
|
|
265
287
|
}
|
|
266
|
-
|
|
267
|
-
|
|
288
|
+
moveValidationStage(ctx, "file-station-privilege")
|
|
289
|
+
if files {
|
|
290
|
+
if err := e.DSM.Allow(ctx, s, "SYNO.SDS.App.FileStation3.Instance", ""); err != nil {
|
|
291
|
+
if moveValidationDeadline(ctx, err) {
|
|
292
|
+
return s, "EXECUTOR_PAUSED"
|
|
293
|
+
}
|
|
294
|
+
return Subject{}, "APP_DENIED"
|
|
295
|
+
}
|
|
268
296
|
}
|
|
269
297
|
return s, ""
|
|
270
298
|
}
|
|
@@ -283,8 +311,9 @@ func (e *Executor) verified(ctx context.Context, auth Auth) (Subject, string) {
|
|
|
283
311
|
return s, ""
|
|
284
312
|
}
|
|
285
313
|
func (e *Executor) Handle(ctx context.Context, r Request) Response {
|
|
286
|
-
|
|
287
|
-
|
|
314
|
+
if isExtension(r.Op) {
|
|
315
|
+
return e.handleExtension(ctx, r)
|
|
316
|
+
}
|
|
288
317
|
if ctx.Err() != nil {
|
|
289
318
|
return deny("EXECUTOR_PAUSED")
|
|
290
319
|
}
|
|
@@ -309,17 +338,55 @@ func (e *Executor) Handle(ctx context.Context, r Request) Response {
|
|
|
309
338
|
if json.Unmarshal(r.Params, &keys) != nil {
|
|
310
339
|
return deny("INVALID_REQUEST")
|
|
311
340
|
}
|
|
312
|
-
allowed := map[string]string{"status": "", "delegate.create": "job_id ops scope path ttl_seconds", "delegate.list": "", "delegate.revoke": "delegation_id job_id", "files.list": "delegation_id job_id path", "files.info": "delegation_id job_id path", "files.createFolder": "delegation_id job_id path name", "files.checkPermission": "delegation_id job_id path", "files.write": "delegation_id job_id staging dest_dir name", "root.run": "name"}
|
|
341
|
+
allowed := map[string]string{"status": "", "delegate.create": "job_id ops scope path ttl_seconds destination lifetime", "delegate.list": "", "delegate.revoke": "delegation_id job_id", "files.list": "delegation_id job_id path", "files.info": "delegation_id job_id path", "files.createFolder": "delegation_id job_id path name", "files.checkPermission": "delegation_id job_id path", "files.write": "delegation_id job_id staging dest_dir name", "root.run": "name"}
|
|
313
342
|
for k := range keys {
|
|
314
343
|
if !contains(strings.Fields(allowed[r.Op]), k) {
|
|
315
344
|
return deny("INVALID_REQUEST")
|
|
316
345
|
}
|
|
317
346
|
}
|
|
347
|
+
if r.Op == "delegate.create" || strings.HasPrefix(r.Op, "files.") {
|
|
348
|
+
e.commitMu.Lock()
|
|
349
|
+
disabled, err := e.Store.Disabled()
|
|
350
|
+
e.commitMu.Unlock()
|
|
351
|
+
if err != nil {
|
|
352
|
+
return deny("STORAGE_ERROR")
|
|
353
|
+
}
|
|
354
|
+
if disabled {
|
|
355
|
+
return deny("disabled")
|
|
356
|
+
}
|
|
357
|
+
}
|
|
318
358
|
switch r.Op {
|
|
319
359
|
case "status":
|
|
320
|
-
|
|
360
|
+
e.commitMu.Lock()
|
|
361
|
+
defer e.commitMu.Unlock()
|
|
362
|
+
disabled, err := e.Store.Disabled()
|
|
363
|
+
out := map[string]any{"executor_digest": e.ExecutorDigest, "policy_digest": e.PolicyDigest, "version": version, "ready": err == nil, "disabled": disabled}
|
|
364
|
+
if e.Policy.Scope == installSelectedScope {
|
|
365
|
+
state, code := e.selectionLocked()
|
|
366
|
+
out["scope_state"] = code
|
|
367
|
+
out["selection"] = nil
|
|
368
|
+
if code == "" {
|
|
369
|
+
out["selection"] = map[string]any{"scope": state.Scope, "root": scopeVirtual(state.Scope), "generation": state.Generation}
|
|
370
|
+
}
|
|
371
|
+
if code == "STORAGE_ERROR" {
|
|
372
|
+
out["ready"] = false
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return success(out)
|
|
321
376
|
case "delegate.create":
|
|
322
|
-
|
|
377
|
+
long := p.Lifetime == "until-revoked"
|
|
378
|
+
if long {
|
|
379
|
+
if _, ok := keys["job_id"]; ok {
|
|
380
|
+
return deny("INVALID_REQUEST")
|
|
381
|
+
}
|
|
382
|
+
if _, ok := keys["ttl_seconds"]; ok {
|
|
383
|
+
return deny("INVALID_REQUEST")
|
|
384
|
+
}
|
|
385
|
+
if !contains(e.Policy.Destinations, p.DestinationKey) {
|
|
386
|
+
return deny("invalid-destination")
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (long && (e.Policy.Lifetime != p.Lifetime || !contains(e.Policy.Destinations, p.DestinationKey) || p.Job != "" || p.TTL != 0 || len(p.Ops) != 1 || p.Ops[0] != "files.moveFromStaging")) || (!long && (p.Lifetime != "" || p.DestinationKey != "" || !tokenRE.MatchString(p.Job) || p.TTL <= 0 || p.TTL > 86400*30 || contains(p.Ops, "files.moveFromStaging"))) || len(p.Ops) == 0 || p.Scope != e.Policy.Scope || !cleanPath(p.Path) {
|
|
323
390
|
return deny("INVALID_REQUEST")
|
|
324
391
|
}
|
|
325
392
|
for _, op := range p.Ops {
|
|
@@ -335,17 +402,35 @@ func (e *Executor) Handle(ctx context.Context, r Request) Response {
|
|
|
335
402
|
if code != "" {
|
|
336
403
|
return deny(code)
|
|
337
404
|
}
|
|
338
|
-
|
|
405
|
+
selection, selectionCode := e.selection()
|
|
406
|
+
if selectionCode != "" {
|
|
407
|
+
return deny(selectionCode)
|
|
408
|
+
}
|
|
409
|
+
root, err := e.DSM.ScopeRoot(ctx, s, selection.Scope)
|
|
339
410
|
if err != nil {
|
|
340
411
|
return deny("SCOPE_DENIED")
|
|
341
412
|
}
|
|
342
|
-
d := Delegation{Package: e.Policy.Package, Subject: s, Job: p.Job, Ops: p.Ops, Scope:
|
|
343
|
-
real
|
|
413
|
+
d := Delegation{Package: e.Policy.Package, Subject: s, Job: p.Job, Ops: p.Ops, Scope: selection.Scope, ScopeGeneration: selection.Generation, Path: scopeVirtual(selection.Scope), Root: root, Expires: e.Now().UTC().Add(time.Duration(p.TTL) * time.Second)}
|
|
414
|
+
var real string
|
|
415
|
+
if long {
|
|
416
|
+
real, err = mappedDirectory(d, p.Path)
|
|
417
|
+
} else {
|
|
418
|
+
real, err = scopedPath(d, p.Path)
|
|
419
|
+
}
|
|
344
420
|
if err != nil {
|
|
345
421
|
return deny("SCOPE_DENIED")
|
|
346
422
|
}
|
|
347
423
|
d.Root = real
|
|
348
424
|
d.Path = p.Path
|
|
425
|
+
if long {
|
|
426
|
+
d.Lifetime = p.Lifetime
|
|
427
|
+
d.DestinationKey = p.DestinationKey
|
|
428
|
+
d.Expires = time.Time{}
|
|
429
|
+
d.Directory, err = bindDirectory(real)
|
|
430
|
+
if err != nil {
|
|
431
|
+
return deny("SCOPE_DENIED")
|
|
432
|
+
}
|
|
433
|
+
}
|
|
349
434
|
// Permission creation is checked as the subject, never SYSTEM_ADMIN.
|
|
350
435
|
if _, err = e.DSM.API(ctx, s.Username, "files.checkPermission", map[string]any{"path": p.Path}); err != nil {
|
|
351
436
|
return deny("PERMISSION_DENIED")
|
|
@@ -355,9 +440,49 @@ func (e *Executor) Handle(ctx context.Context, r Request) Response {
|
|
|
355
440
|
return deny("INTERNAL")
|
|
356
441
|
}
|
|
357
442
|
d.ID = hex.EncodeToString(id)
|
|
358
|
-
|
|
443
|
+
e.commitMu.Lock()
|
|
444
|
+
disabled, disableErr := e.Store.Disabled()
|
|
445
|
+
if !e.scopeMatchesLocked(d) {
|
|
446
|
+
e.commitMu.Unlock()
|
|
447
|
+
return deny("SCOPE_DENIED")
|
|
448
|
+
}
|
|
449
|
+
if disableErr != nil || disabled {
|
|
450
|
+
e.commitMu.Unlock()
|
|
451
|
+
return deny("disabled")
|
|
452
|
+
}
|
|
453
|
+
putErr := e.auditLocked(s, "delegate.create", "", d.Path, "admitting")
|
|
454
|
+
// A destination key denotes one current choice for this person. Changing
|
|
455
|
+
// it cancels its previous grant under the same publish fence.
|
|
456
|
+
if putErr == nil && long {
|
|
457
|
+
var previous []Delegation
|
|
458
|
+
previous, putErr = e.Store.List()
|
|
459
|
+
for _, prior := range previous {
|
|
460
|
+
if putErr != nil {
|
|
461
|
+
break
|
|
462
|
+
}
|
|
463
|
+
if prior.Package != d.Package || prior.Subject.UID != s.UID || prior.Subject.Username != s.Username || prior.Lifetime != "until-revoked" || prior.DestinationKey != d.DestinationKey || prior.Revoked {
|
|
464
|
+
continue
|
|
465
|
+
}
|
|
466
|
+
putErr = e.auditLocked(s, "delegate.revoke", prior.ID, prior.Path, "destination-replaced")
|
|
467
|
+
if putErr == nil {
|
|
468
|
+
prior.Revoked = true
|
|
469
|
+
putErr = e.Store.Put(prior)
|
|
470
|
+
}
|
|
471
|
+
if putErr == nil {
|
|
472
|
+
e.cancelGrantLocked(prior.ID, "destination-replaced")
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
if putErr == nil {
|
|
477
|
+
putErr = e.Store.Put(d)
|
|
478
|
+
}
|
|
479
|
+
e.commitMu.Unlock()
|
|
480
|
+
if putErr != nil {
|
|
359
481
|
return deny("STORAGE_ERROR")
|
|
360
482
|
}
|
|
483
|
+
if long {
|
|
484
|
+
return success(publicDelegation(d))
|
|
485
|
+
}
|
|
361
486
|
return success(map[string]any{"delegation_id": d.ID, "subject": publicSubject(s), "expires_at": d.Expires})
|
|
362
487
|
case "delegate.list":
|
|
363
488
|
s, code := e.verified(ctx, r.Auth)
|
|
@@ -371,6 +496,9 @@ func (e *Executor) Handle(ctx context.Context, r Request) Response {
|
|
|
371
496
|
out := []any{}
|
|
372
497
|
for _, d := range ds {
|
|
373
498
|
if d.Package == e.Policy.Package && d.Subject.UID == s.UID && d.Subject.Username == s.Username {
|
|
499
|
+
if !e.scopeMatches(d) {
|
|
500
|
+
d.Revoked = true
|
|
501
|
+
}
|
|
374
502
|
out = append(out, publicDelegation(d))
|
|
375
503
|
}
|
|
376
504
|
}
|
|
@@ -385,16 +513,37 @@ func (e *Executor) Handle(ctx context.Context, r Request) Response {
|
|
|
385
513
|
if code != "" {
|
|
386
514
|
return deny(code)
|
|
387
515
|
}
|
|
388
|
-
if !s.Admin && (s.UID != d.Subject.UID || s.Username != d.Subject.Username) {
|
|
516
|
+
if (d.Lifetime != "" || !s.Admin) && (s.UID != d.Subject.UID || s.Username != d.Subject.Username) {
|
|
389
517
|
return deny("DELEGATION_DENIED")
|
|
390
518
|
}
|
|
391
|
-
} else if p.Job == "" || p.Job != d.Job {
|
|
519
|
+
} else if d.Lifetime != "" || p.Job == "" || p.Job != d.Job {
|
|
392
520
|
return deny("DELEGATION_DENIED")
|
|
393
521
|
}
|
|
522
|
+
e.commitMu.Lock()
|
|
523
|
+
locked := true
|
|
524
|
+
defer func() {
|
|
525
|
+
if locked {
|
|
526
|
+
e.commitMu.Unlock()
|
|
527
|
+
}
|
|
528
|
+
}()
|
|
529
|
+
d, err = e.Store.Get(p.ID)
|
|
530
|
+
if err != nil {
|
|
531
|
+
return deny("STORAGE_ERROR")
|
|
532
|
+
}
|
|
394
533
|
d.Revoked = true
|
|
395
534
|
if e.Store.Put(d) != nil {
|
|
396
535
|
return deny("STORAGE_ERROR")
|
|
397
536
|
}
|
|
537
|
+
e.cancelGrantLocked(d.ID, "grant-revoked")
|
|
538
|
+
if e.auditLocked(d.Subject, "delegate.revoke", "", d.Path, "revoked") != nil {
|
|
539
|
+
return deny("STORAGE_ERROR")
|
|
540
|
+
}
|
|
541
|
+
pending := e.legacyPendingLocked(d.ID)
|
|
542
|
+
e.commitMu.Unlock()
|
|
543
|
+
locked = false
|
|
544
|
+
if waitLegacy(ctx, pending) != nil {
|
|
545
|
+
return deny("EXECUTOR_PAUSED")
|
|
546
|
+
}
|
|
398
547
|
return success(map[string]any{"revoked": true})
|
|
399
548
|
case "root.run":
|
|
400
549
|
if !tokenRE.MatchString(p.Name) {
|
|
@@ -411,7 +560,7 @@ func (e *Executor) Handle(ctx context.Context, r Request) Response {
|
|
|
411
560
|
if !s.Admin {
|
|
412
561
|
return deny("ADMIN_REQUIRED")
|
|
413
562
|
}
|
|
414
|
-
if e.DSM.Allow(ctx, s, e.Policy.App) != nil {
|
|
563
|
+
if e.DSM.Allow(ctx, s, e.Policy.App, e.Policy.Entry) != nil {
|
|
415
564
|
return deny("APP_DENIED")
|
|
416
565
|
}
|
|
417
566
|
if e.Runner == nil {
|
|
@@ -424,7 +573,7 @@ func (e *Executor) Handle(ctx context.Context, r Request) Response {
|
|
|
424
573
|
return success(map[string]any{"output": out})
|
|
425
574
|
}
|
|
426
575
|
d, err := e.Store.Get(p.ID)
|
|
427
|
-
if err != nil || d.Package != e.Policy.Package || d.Revoked || !e.Now().Before(d.Expires) || d.Job != p.Job || !contains(d.Ops, r.Op) ||
|
|
576
|
+
if err != nil || d.Package != e.Policy.Package || d.Revoked || (d.Lifetime != "" || !e.Now().Before(d.Expires)) || d.Job != p.Job || !contains(d.Ops, r.Op) || !e.scopeMatches(d) {
|
|
428
577
|
return deny("DELEGATION_DENIED")
|
|
429
578
|
}
|
|
430
579
|
s, code := e.eligible(ctx, d.Subject.Username, d.Subject.UID, true)
|
|
@@ -461,7 +610,43 @@ func (e *Executor) Handle(ctx context.Context, r Request) Response {
|
|
|
461
610
|
if !validName(p.Name) || !validRelative(p.Staging) {
|
|
462
611
|
return deny("INVALID_REQUEST")
|
|
463
612
|
}
|
|
464
|
-
|
|
613
|
+
release, err := e.acquireCopy(ctx)
|
|
614
|
+
if err != nil {
|
|
615
|
+
return deny("EXECUTOR_PAUSED")
|
|
616
|
+
}
|
|
617
|
+
defer release()
|
|
618
|
+
e.commitMu.Lock()
|
|
619
|
+
disabled, x := e.Store.Disabled()
|
|
620
|
+
fresh, x2 := e.Store.Get(d.ID)
|
|
621
|
+
e.commitMu.Unlock()
|
|
622
|
+
if x != nil || x2 != nil {
|
|
623
|
+
return deny("STORAGE_ERROR")
|
|
624
|
+
}
|
|
625
|
+
if disabled {
|
|
626
|
+
return deny("disabled")
|
|
627
|
+
}
|
|
628
|
+
if fresh.Revoked || ctx.Err() != nil {
|
|
629
|
+
return deny("DELEGATION_DENIED")
|
|
630
|
+
}
|
|
631
|
+
writerCtx := context.WithValue(ctx, writeJobKey{}, p.Job)
|
|
632
|
+
writerCtx = context.WithValue(writerCtx, legacyPublishKey{}, func(publish func() (MoveReceipt, error)) (MoveReceipt, error) {
|
|
633
|
+
current, c := e.eligible(ctx, d.Subject.Username, d.Subject.UID, true)
|
|
634
|
+
if c != "" || !sameIdentity(s, current) {
|
|
635
|
+
return MoveReceipt{}, errors.New("identity changed")
|
|
636
|
+
}
|
|
637
|
+
if _, x := e.DSM.API(ctx, s.Username, "files.checkPermission", map[string]any{"path": p.Dest}); x != nil {
|
|
638
|
+
return MoveReceipt{}, x
|
|
639
|
+
}
|
|
640
|
+
e.commitMu.Lock()
|
|
641
|
+
defer e.commitMu.Unlock()
|
|
642
|
+
disabled, x := e.Store.Disabled()
|
|
643
|
+
fresh, x2 := e.Store.Get(d.ID)
|
|
644
|
+
if x != nil || x2 != nil || disabled || fresh.Revoked || !e.Now().Before(fresh.Expires) || ctx.Err() != nil {
|
|
645
|
+
return MoveReceipt{}, errors.New("write fenced")
|
|
646
|
+
}
|
|
647
|
+
return publish()
|
|
648
|
+
})
|
|
649
|
+
if err = e.Writer.Write(writerCtx, s, p.Staging, real, p.Name, d.Root); err != nil {
|
|
465
650
|
return deny("WRITE_FAILED")
|
|
466
651
|
}
|
|
467
652
|
return success(map[string]any{"path": strings.TrimSuffix(p.Dest, "/") + "/" + p.Name})
|
|
@@ -473,6 +658,11 @@ func (e *Executor) Handle(ctx context.Context, r Request) Response {
|
|
|
473
658
|
}
|
|
474
659
|
args["name"] = p.Name
|
|
475
660
|
}
|
|
661
|
+
done, code := e.beginLegacy(d)
|
|
662
|
+
if code != "" {
|
|
663
|
+
return deny(code)
|
|
664
|
+
}
|
|
665
|
+
defer done()
|
|
476
666
|
out, err := e.DSM.API(ctx, s.Username, r.Op, args)
|
|
477
667
|
if err != nil {
|
|
478
668
|
return deny("DSM_FAILED")
|
|
@@ -481,7 +671,11 @@ func (e *Executor) Handle(ctx context.Context, r Request) Response {
|
|
|
481
671
|
}
|
|
482
672
|
func publicSubject(s Subject) any { return map[string]any{"username": s.Username, "uid": s.UID} }
|
|
483
673
|
func publicDelegation(d Delegation) any {
|
|
484
|
-
|
|
674
|
+
out := map[string]any{"delegation_id": d.ID, "subject": publicSubject(d.Subject), "job_id": d.Job, "ops": d.Ops, "scope": d.Scope, "path": d.Path, "expires_at": d.Expires, "revoked": d.Revoked, "destination": d.DestinationKey, "lifetime": d.Lifetime}
|
|
675
|
+
if d.ScopeGeneration != 0 {
|
|
676
|
+
out["scope_generation"] = d.ScopeGeneration
|
|
677
|
+
}
|
|
678
|
+
return out
|
|
485
679
|
}
|
|
486
680
|
func scopeVirtual(scope string) string {
|
|
487
681
|
if scope == "home" {
|
|
@@ -522,3 +716,20 @@ func validRelative(p string) bool {
|
|
|
522
716
|
}
|
|
523
717
|
return true
|
|
524
718
|
}
|
|
719
|
+
|
|
720
|
+
// Permanent destinations reject every symlink component; legacy File Station
|
|
721
|
+
// lookup retains its historical canonical-path semantics.
|
|
722
|
+
func mappedDirectory(d Delegation, p string) (string, error) {
|
|
723
|
+
base := d.Path
|
|
724
|
+
if base == "" {
|
|
725
|
+
base = scopeVirtual(d.Scope)
|
|
726
|
+
}
|
|
727
|
+
if base == "" || !cleanPath(d.Root) || !cleanPath(p) || !within(base, p) {
|
|
728
|
+
return "", errors.New("scope")
|
|
729
|
+
}
|
|
730
|
+
real := filepath.Join(d.Root, strings.TrimPrefix(strings.TrimPrefix(p, base), "/"))
|
|
731
|
+
if _, err := bindDirectory(real); err != nil {
|
|
732
|
+
return "", err
|
|
733
|
+
}
|
|
734
|
+
return real, nil
|
|
735
|
+
}
|