From b3016e6c99ee82e46ac298eeae0bcef836c492c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D1=83=D1=80=D0=BD=D0=B0=D1=82=20=D0=90=D0=BD=D0=B4?= =?UTF-8?q?=D1=80=D0=B5=D0=B9?= Date: Wed, 10 Jun 2026 07:56:02 +0300 Subject: [PATCH] Audit sharing controls coverage --- PRODUCTION_READINESS.md | 3 +- .../docs/sharing-controls-production-audit.md | 33 ++++++++ scripts/verify-sharing-controls-coverage.ps1 | 82 +++++++++++++++++++ 3 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 Server/docs/sharing-controls-production-audit.md create mode 100644 scripts/verify-sharing-controls-coverage.ps1 diff --git a/PRODUCTION_READINESS.md b/PRODUCTION_READINESS.md index 1d3408ca..9c9cb05f 100644 --- a/PRODUCTION_READINESS.md +++ b/PRODUCTION_READINESS.md @@ -38,6 +38,7 @@ Date: 2026-06-08. - Android photo/video backup settings now show a compact overall backup status: enabled state, selected phone-folder count, network/roaming/charging conditions, and last backup scan. - Android auto-upload now stores per-source folder sync cursors in Room schema version `52`, keeps the legacy `lastSyncTimestamp` as a migration fallback, starts newly added source folders at timestamp `0`, and preserves per-source cursors when a metered run stops at the daily mobile-data limit. `:qsferaDomain:testDebugUnitTest --tests "eu.qsfera.android.domain.automaticuploads.model.FolderBackUpConfigurationTest"`, `:qsferaApp:testOriginalDebugUnitTest --tests "eu.qsfera.android.workers.AutomaticUploadsWorkerTest"`, and `:qsferaData:compileDebugKotlin` passed on 2026-06-10. - Android public-link rows now show the effective access mode, password state, and expiration state directly in the share list, so users can review link safety without reopening each link. `:qsferaApp:testOriginalDebugUnitTest --tests "eu.qsfera.android.presentation.sharing.shares.PublicShareStatusFormatterTest"` and `:qsferaApp:compileOriginalDebugKotlin` passed on 2026-06-10. +- Sharing controls were audited against the Google Drive and Yandex Disk benchmarks in `Server/docs/sharing-controls-production-audit.md`, and `scripts/verify-sharing-controls-coverage.ps1` now checks local Android UI, Graph link types, server link handling, and acceptance-feature coverage for password, expiration, access roles, internal links, secure-view/no-download behavior, and role-based resharing. - Server `qsfera_full` compose files now require explicit admin, Keycloak, and S3/MinIO secrets instead of demo defaults. - Server `qsfera_full/env.production.example` provides a tracked production-safe starting point with TLS validation enabled and demo users disabled. - Server `qsfera_full/validate-production-env.sh` checks production `.env` invariants before compose validation: TLS validation enabled, demo users disabled, pinned production image, required domains, required secrets, and no public-production MinIO. @@ -91,4 +92,4 @@ Date: 2026-06-08. - Extend Android backup status UX beyond the new overall settings summary with per-item status, queued/paused/permanent-failure states, and low-storage messaging. - Configure `QSFERA_ALERT_WEBHOOK_URL` in `/etc/qsfera/rpi-production-check.env` if off-host alert delivery is required; the installed failure service currently logs through `systemd-cat` and journal without an external endpoint. -- Verify sharing controls against Google Drive and Yandex Disk expectations: link expiration, password, disable downloads, organization-only access, and role inheritance. +- Decide and implement Android-facing controls for Graph `internal` links and `blocksDownload` secure-view links, or explicitly keep them server-managed/custom-access states. Server/API coverage is documented in `Server/docs/sharing-controls-production-audit.md`; the Android public-link dialog still exposes only password, expiration, read-only, read/write, and upload-only OCS controls. diff --git a/Server/docs/sharing-controls-production-audit.md b/Server/docs/sharing-controls-production-audit.md new file mode 100644 index 00000000..e668234d --- /dev/null +++ b/Server/docs/sharing-controls-production-audit.md @@ -0,0 +1,33 @@ +# QSfera Sharing Controls Production Audit + +Date: 2026-06-10. + +## External Benchmarks + +- Google Drive documents file-sharing roles, per-link/general access, download control for viewers/commenters, and folder permission inheritance on its Android sharing help page: . +- Yandex Disk documents mobile link security settings for view-only access, disabling downloads, link password, expiration date, and organization-only links on its Android sharing help page: . + +## Verified QSfera Coverage + +| Benchmark control | QSfera evidence | Status | +| --- | --- | --- | +| Public-link password | Android exposes `shareViaLinkPasswordSwitch`; Graph `CreateLink` stores `Password`; acceptance tests create and update password-protected link shares and verify old passwords fail with HTTP `401`. | Covered for Android OCS links and Graph links. | +| Public-link expiration | Android exposes `shareViaLinkExpirationSwitch`; Graph `CreateLink` reads `expirationDateTime`; acceptance tests create, update, and remove expiration dates. | Covered for Android OCS links and Graph links. | +| Link access role | Android exposes read-only, read/write, and upload-only radio options; Graph link types include `view`, `edit`, `upload`, and `createOnly`; acceptance tests cover those roles for files, folders, and project-space links. | Covered, with different Android OCS labels and Graph role names. | +| Disable downloads / secure view | Graph link types include `blocksDownload`; acceptance tests also verify the `Secure Viewer` role cannot download files. Android public-link rows now display non-standard link permissions as `Custom access`. | Server/API coverage exists; Android does not expose a dedicated `blocksDownload` switch. | +| Organization-only access | Graph link types include `internal`; frontend config documents default link permission `0` as an internal/member-only link; server rejects redundant passwords on internal links; acceptance tests create and update internal links. | Server/API coverage exists; Android does not expose a separate organization-only/internal link choice. | +| Role inheritance / resharing | Space sharing acceptance tests verify managers can reshare spaces, editors/viewers cannot, and project-space public-link creation requires manager rights. | Covered by server acceptance scenarios. | + +## Repeatable Local Check + +Run from the repository root: + +```powershell +.\scripts\verify-sharing-controls-coverage.ps1 +``` + +The script is a static coverage gate over Android UI files, Graph link type code, server link creation code, and acceptance feature files. It does not replace a full server acceptance run. + +## Remaining Product Gap + +The Android public-link dialog currently exposes the OCS link controls already present in the client: password, expiration, read-only, read/write, and upload-only. It does not expose dedicated Graph `internal` or `blocksDownload` controls. If QSfera wants Android parity with Yandex Disk's organization-only and disable-download controls, the next client/server integration step is to add Android Graph-link creation/editing paths for those two link types or define an explicit product decision that Android continues to show them as server-managed/custom access states. diff --git a/scripts/verify-sharing-controls-coverage.ps1 b/scripts/verify-sharing-controls-coverage.ps1 new file mode 100644 index 00000000..cbd9596b --- /dev/null +++ b/scripts/verify-sharing-controls-coverage.ps1 @@ -0,0 +1,82 @@ +param( + [string]$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path +) + +$ErrorActionPreference = "Stop" + +function Fail($Message) { + Write-Error $Message + exit 1 +} + +function Assert-FileContains($Name, $RelativePath, $Pattern) { + $path = Join-Path $RepoRoot $RelativePath + if (-not (Test-Path -LiteralPath $path)) { + Fail "$Name source file is missing: $RelativePath" + } + + $content = Get-Content -LiteralPath $path -Raw + if ($content -notmatch $Pattern) { + Fail "$Name was not found in $RelativePath. Pattern: $Pattern" + } + + Write-Host "OK: $Name" +} + +Write-Host "Checking QSfera sharing-controls coverage..." + +Assert-FileContains "Android public-link dialog exposes password control" ` + "Android/qsferaApp/src/main/java/eu/qsfera/android/presentation/sharing/shares/PublicShareDialogFragment.kt" ` + "shareViaLinkPasswordSwitch" +Assert-FileContains "Android public-link dialog exposes expiration control" ` + "Android/qsferaApp/src/main/java/eu/qsfera/android/presentation/sharing/shares/PublicShareDialogFragment.kt" ` + "shareViaLinkExpirationSwitch" +Assert-FileContains "Android public-link dialog exposes upload-only mode" ` + "Android/qsferaApp/src/main/java/eu/qsfera/android/presentation/sharing/shares/PublicShareDialogFragment.kt" ` + "shareViaLinkEditPermissionUploadFiles" +Assert-FileContains "Android public-link rows show safety status" ` + "Android/qsferaApp/src/main/java/eu/qsfera/android/presentation/sharing/shares/SharePublicLinkListAdapter.kt" ` + "PublicShareStatusFormatter\.statusFor" + +Assert-FileContains "Graph API declares internal link type" ` + "Server/vendor/github.com/opencloud-eu/libre-graph-api-go/model_sharing_link_type.go" ` + "INTERNAL SharingLinkType = `"internal`"" +Assert-FileContains "Graph API declares secure-view link type" ` + "Server/vendor/github.com/opencloud-eu/libre-graph-api-go/model_sharing_link_type.go" ` + "BLOCKS_DOWNLOAD SharingLinkType = `"blocksDownload`"" +Assert-FileContains "Graph API declares file-drop link type" ` + "Server/vendor/github.com/opencloud-eu/libre-graph-api-go/model_sharing_link_type.go" ` + "CREATE_ONLY SharingLinkType = `"createOnly`"" +Assert-FileContains "Server maps internal link to no public-share permissions" ` + "Server/services/graph/pkg/linktype/linktype.go" ` + "func NewInternalLinkPermissionSet\(\) \*LinkType[\s\S]*ResourcePermissions\{\}" +Assert-FileContains "Server rejects passwords on internal links" ` + "Server/services/graph/pkg/service/v0/api_driveitem_permissions_links.go" ` + "password is redundant for the internal link" +Assert-FileContains "Server stores link expiration during creation" ` + "Server/services/graph/pkg/service/v0/api_driveitem_permissions_links.go" ` + "GetExpirationDateTimeOk" + +Assert-FileContains "Acceptance tests create link shares with password and expiration" ` + "Server/tests/acceptance/features/apiSharingNgLinkSharePermission/createLinkShare.feature" ` + "expirationDateTime[\s\S]*hasPassword" +Assert-FileContains "Acceptance tests update link-share expiration" ` + "Server/tests/acceptance/features/apiSharingNgLinkSharePermission/updateLinkShare.feature" ` + "update expiration date of a file's link share" +Assert-FileContains "Acceptance tests update link-share password and reject old password" ` + "Server/tests/acceptance/features/apiSharingNgLinkSharePermission/updateLinkShare.feature" ` + "public download of file[\s\S]*should fail with HTTP status code `"401`"" +Assert-FileContains "Acceptance tests update public links to internal links" ` + "Server/tests/acceptance/features/apiSharingNgLinkSharePermission/updateLinkShare.feature" ` + "permissionsRole \| internal" +Assert-FileContains "Acceptance tests include secure viewer no-download behavior" ` + "Server/tests/acceptance/features/apiSharingNg1/enableDisablePermissionsRole.feature" ` + "Secure Viewer[\s\S]*should not be able to download" +Assert-FileContains "Acceptance tests enforce space-manager sharing rights" ` + "Server/tests/acceptance/features/apiSpacesShares/shareSpaces.feature" ` + "manager role can share[\s\S]*editor or viewer role cannot share" +Assert-FileContains "Acceptance tests enforce project-space public-link sharing rights" ` + "Server/tests/acceptance/features/apiSpacesShares/shareSubItemOfSpaceViaPublicLink.feature" ` + "space manager role can share[\s\S]*without space manager role cannot share" + +Write-Host "QSfera sharing-controls coverage check passed."