From 330f387edb4605e0e5c171105182f54abd1016b5 Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Tue, 28 Jul 2026 16:12:20 +1000 Subject: [PATCH 1/7] revokes public/auth before private sharing if required --- lib/src/solid/grant_permission.dart | 65 ++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/lib/src/solid/grant_permission.dart b/lib/src/solid/grant_permission.dart index 4b868b70..3968331e 100644 --- a/lib/src/solid/grant_permission.dart +++ b/lib/src/solid/grant_permission.dart @@ -31,6 +31,7 @@ library; import 'dart:core'; import 'package:flutter/material.dart' hide Key; +import 'package:rdflib/rdflib.dart'; import 'package:solidpod/src/solid/api/common_permission.dart'; import 'package:solidpod/src/solid/api/grant_permission_api.dart'; @@ -38,13 +39,14 @@ import 'package:solidpod/src/solid/api/rest_api.dart'; import 'package:solidpod/src/solid/constants/common.dart'; import 'package:solidpod/src/solid/constants/web_acl.dart'; import 'package:solidpod/src/solid/models/log_entry.dart'; +import 'package:solidpod/src/solid/revoke_permission.dart' show revokePermission; import 'package:solidpod/src/solid/solid_func_call_status.dart'; import 'package:solidpod/src/solid/utils/exceptions.dart'; import 'package:solidpod/src/solid/utils/get_url_helper.dart'; import 'package:solidpod/src/solid/utils/key_helper.dart' show RecipientPubKey; import 'package:solidpod/src/solid/utils/key_manager.dart' show KeyManager; import 'package:solidpod/src/solid/utils/misc.dart'; -import 'package:solidpod/src/solid/utils/permission.dart' show genAclTurtle; +import 'package:solidpod/src/solid/utils/permission.dart' show genAclTurtle, readAcl; /// Grant access permissions to [fileName] to the type of recipient /// or specific recipients, if recipient type is individual or group, @@ -70,6 +72,17 @@ import 'package:solidpod/src/solid/utils/permission.dart' show genAclTurtle; /// - [isFile] Optional flag describing whether the resources is a file or /// not. /// - [groupName] - Optional name of the group permission. +/// - [revokePublicAccessOnSpecificGrant] - When [recipientType] is +/// individual or group and the resource currently has a Public or +/// Authenticated User class grant (and is therefore plaintext on the +/// server, per the decryption step this function performs for those +/// recipient classes), revoke that grant and re-encrypt the resource +/// before proceeding. Without this, the resource would end up both still +/// readable by the previously-granted class *and* holding a stale +/// individual key that does not match the (still plaintext) content. +/// Defaults to `true`; set to `false` to keep today's behaviour where +/// granting to a specific recipient never touches an existing +/// public/authUser grant. Future grantPermission({ required String fileName, @@ -81,6 +94,7 @@ Future grantPermission({ bool isFile = true, bool isExternalRes = false, String? groupName, + bool revokePublicAccessOnSpecificGrant = true, }) async { if (!await isUserLoggedIn()) { throw NotLoggedInException( @@ -150,6 +164,55 @@ Future grantPermission({ // if recipient pods have been initialised if (allRecipientsInitialised || !hasSpecificRecipients) { if (resStatus == ResourceStatus.exist) { + // Sharing to a specific individual/group assumes the resource is + // ciphertext under an individual key (see the `fileHasIndKey` + // branch below). If it's currently also granted to the Public or + // Authenticated User class, it's plaintext on the server (that + // class has no key of its own to decrypt with) — so revoke that + // grant and re-encrypt first. Otherwise the resource would end up + // both still openly readable *and* holding a stale individual key + // that doesn't match the (still plaintext) bytes. Must run before + // `setPermissionAcl` below, so `revokePermission`'s own ACL read + // still sees the pre-existing grant. + if (hasSpecificRecipients && revokePublicAccessOnSpecificGrant) { + final currentPermMap = extractAclPerm(await readAcl(resourceUrl)); + for (final classGrant in <(RecipientType, URIRef)>[ + (RecipientType.public, publicAgent), + (RecipientType.authUser, authenticatedAgent), + ]) { + final (classType, classAgent) = classGrant; + String? receiverId; + for (final id in currentPermMap.keys) { + if (id is String && + currentPermMap[id][agentStr] == agentClassPred && + URIRef(id) == classAgent) { + receiverId = id; + break; + } + } + if (receiverId == null) continue; + + final grantedPerms = + (currentPermMap[receiverId][permStr] as List) + .cast(); + debugPrint( + '[grantPermission] revoking existing $classType access on ' + '"$resourceUrl" before granting to $recipientType', + ); + await revokePermission( + fileName: resourceUrl, + isFileUrl: true, + permissionList: grantedPerms, + recipientIndOrGroupWebId: classAgent.value, + recipientType: classType, + ownerWebId: ownerWebId, + granterWebId: granterWebId, + isFile: isFile, + isExternalRes: isExternalRes, + ); + } + } + // Add the permission line to the relevant ACL file await setPermissionAcl( resourceUrl, From 1dad7d977d22446af060c9821a7a1a925629cb8b Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Tue, 28 Jul 2026 21:51:30 +1000 Subject: [PATCH 2/7] call dialog to confirm --- lib/src/solid/grant_permission.dart | 37 +++++++++--------------- lib/src/solid/read_permission.dart | 44 +++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 23 deletions(-) diff --git a/lib/src/solid/grant_permission.dart b/lib/src/solid/grant_permission.dart index 3968331e..8b43bcf6 100644 --- a/lib/src/solid/grant_permission.dart +++ b/lib/src/solid/grant_permission.dart @@ -31,7 +31,6 @@ library; import 'dart:core'; import 'package:flutter/material.dart' hide Key; -import 'package:rdflib/rdflib.dart'; import 'package:solidpod/src/solid/api/common_permission.dart'; import 'package:solidpod/src/solid/api/grant_permission_api.dart'; @@ -39,6 +38,8 @@ import 'package:solidpod/src/solid/api/rest_api.dart'; import 'package:solidpod/src/solid/constants/common.dart'; import 'package:solidpod/src/solid/constants/web_acl.dart'; import 'package:solidpod/src/solid/models/log_entry.dart'; +import 'package:solidpod/src/solid/read_permission.dart' + show getUserClassPermissions; import 'package:solidpod/src/solid/revoke_permission.dart' show revokePermission; import 'package:solidpod/src/solid/solid_func_call_status.dart'; import 'package:solidpod/src/solid/utils/exceptions.dart'; @@ -46,7 +47,7 @@ import 'package:solidpod/src/solid/utils/get_url_helper.dart'; import 'package:solidpod/src/solid/utils/key_helper.dart' show RecipientPubKey; import 'package:solidpod/src/solid/utils/key_manager.dart' show KeyManager; import 'package:solidpod/src/solid/utils/misc.dart'; -import 'package:solidpod/src/solid/utils/permission.dart' show genAclTurtle, readAcl; +import 'package:solidpod/src/solid/utils/permission.dart' show genAclTurtle; /// Grant access permissions to [fileName] to the type of recipient /// or specific recipients, if recipient type is individual or group, @@ -175,26 +176,16 @@ Future grantPermission({ // `setPermissionAcl` below, so `revokePermission`'s own ACL read // still sees the pre-existing grant. if (hasSpecificRecipients && revokePublicAccessOnSpecificGrant) { - final currentPermMap = extractAclPerm(await readAcl(resourceUrl)); - for (final classGrant in <(RecipientType, URIRef)>[ - (RecipientType.public, publicAgent), - (RecipientType.authUser, authenticatedAgent), - ]) { - final (classType, classAgent) = classGrant; - String? receiverId; - for (final id in currentPermMap.keys) { - if (id is String && - currentPermMap[id][agentStr] == agentClassPred && - URIRef(id) == classAgent) { - receiverId = id; - break; - } - } - if (receiverId == null) continue; - - final grantedPerms = - (currentPermMap[receiverId][permStr] as List) - .cast(); + final existingClassPerms = await getUserClassPermissions( + fileName: resourceUrl, + isFile: isFile, + isFileUrl: true, + isExternalRes: isExternalRes, + ); + for (final classType in existingClassPerms.keys) { + final classAgent = classType == RecipientType.public + ? publicAgent + : authenticatedAgent; debugPrint( '[grantPermission] revoking existing $classType access on ' '"$resourceUrl" before granting to $recipientType', @@ -202,7 +193,7 @@ Future grantPermission({ await revokePermission( fileName: resourceUrl, isFileUrl: true, - permissionList: grantedPerms, + permissionList: existingClassPerms[classType]!, recipientIndOrGroupWebId: classAgent.value, recipientType: classType, ownerWebId: ownerWebId, diff --git a/lib/src/solid/read_permission.dart b/lib/src/solid/read_permission.dart index 70f8eb50..16bcc76d 100644 --- a/lib/src/solid/read_permission.dart +++ b/lib/src/solid/read_permission.dart @@ -30,6 +30,12 @@ library; import 'dart:core'; +import 'package:rdflib/rdflib.dart'; + +import 'package:solidpod/src/solid/constants/common.dart' + show agentClassPred, agentStr, permStr; +import 'package:solidpod/src/solid/constants/web_acl.dart' + show RecipientType, authenticatedAgent, publicAgent; import 'package:solidpod/src/solid/utils/get_url_helper.dart'; import 'package:solidpod/src/solid/utils/misc.dart'; import 'package:solidpod/src/solid/utils/permission.dart'; @@ -70,3 +76,41 @@ Future> readPermission({ return permMap; } + +/// The Public/Authenticated-User access modes currently granted on +/// [fileName], keyed by [RecipientType.public]/[RecipientType.authUser]. +/// A class with no current grant is omitted from the result. +/// +/// Single source of truth for "does this resource currently have a +/// Public/AuthenticatedUser grant" — used both by [grantPermission] (to +/// decide whether an individual/group grant must first revoke and +/// re-encrypt) and by solidui's confirmation dialog for the same action. + +Future>> getUserClassPermissions({ + required String fileName, + required bool isFile, + bool isFileUrl = false, + bool isExternalRes = false, +}) async { + final permMap = await readPermission( + fileName: fileName, + isFile: isFile, + isFileUrl: isFileUrl, + isExternalRes: isExternalRes, + ); + + final result = >{}; + for (final receiverId in permMap.keys) { + if (receiverId is! String || + permMap[receiverId][agentStr] != agentClassPred) { + continue; + } + final perms = (permMap[receiverId][permStr] as List).cast(); + if (URIRef(receiverId) == publicAgent) { + result[RecipientType.public] = perms; + } else if (URIRef(receiverId) == authenticatedAgent) { + result[RecipientType.authUser] = perms; + } + } + return result; +} From 8449b83abb33ca672472298b8e456fa41b53c8de Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Tue, 28 Jul 2026 22:17:52 +1000 Subject: [PATCH 3/7] update changelog and bump minor version --- CHANGELOG.md | 7 ++++--- pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f69e097..7550c801 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,8 +11,9 @@ Visit the package at [pub.dev](https://pub.dev/packages/solidpod). ## 1.0 ++ Switch public to private sharing [1.0.14 20260728 jesscmoore] + Add load test to the example app [1.0.13 20260702 tonypioneer] -+ Migrate TEMPALTE to solidui [1.0.12 20260629 tonypioneer] ++ Migrate TEMPLATE to solidui [1.0.12 20260629 tonypioneer] + Support profile editing [1.0.11 20260626 tonypioneer] + Bug fix template for dart run [1.0.10 20260622 tonypioneer] + Add app template for a 'create' experience [1.0.9 20260619 tonypioneer] @@ -32,8 +33,8 @@ Visit the package at [pub.dev](https://pub.dev/packages/solidpod). + Check missing resources [0.12.9 20260520 tonypioneer] + Support checking webID [0.12.8 20260520 tonypioneer] + Update Try Another WebID workflow [0.12.7 20260520 tonypioneer] -+ Bug fix to ttl rdf for special chars #628 [0.12.6 20260518 tonypioneer] -+ Upgrade solidauth and fix key file saving edge cases [0.12.5 20260427 jesscmoore] ++ Bug fix to ttl RDF for special chars #628 [0.12.6 20260518 tonypioneer] ++ Upgrade solid_auth and fix key file saving edge cases [0.12.5 20260427 jesscmoore] + Support user profile. [0.12.4 20260421 tonypioneer] + Key map + paths updates. Update file_picker. [0.12.3 20260420 jesscmoore] + Add silentLogout() [0.12.2 20260325 tonypioneer] diff --git a/pubspec.yaml b/pubspec.yaml index cbdcb728..9f662d6a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: solidpod description: Support access to private data from PODs on Solid servers. -version: 1.0.13 +version: 1.0.14 homepage: https://github.com/anusii/solidpod environment: From 5a7de56712faff0723e27b185679b8f330c00e61 Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Mon, 3 Aug 2026 13:01:24 +1000 Subject: [PATCH 4/7] dart format --- lib/src/solid/grant_permission.dart | 3 ++- lib/src/solid/write_external_pod.dart | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/src/solid/grant_permission.dart b/lib/src/solid/grant_permission.dart index 8b43bcf6..68f68022 100644 --- a/lib/src/solid/grant_permission.dart +++ b/lib/src/solid/grant_permission.dart @@ -40,7 +40,8 @@ import 'package:solidpod/src/solid/constants/web_acl.dart'; import 'package:solidpod/src/solid/models/log_entry.dart'; import 'package:solidpod/src/solid/read_permission.dart' show getUserClassPermissions; -import 'package:solidpod/src/solid/revoke_permission.dart' show revokePermission; +import 'package:solidpod/src/solid/revoke_permission.dart' + show revokePermission; import 'package:solidpod/src/solid/solid_func_call_status.dart'; import 'package:solidpod/src/solid/utils/exceptions.dart'; import 'package:solidpod/src/solid/utils/get_url_helper.dart'; diff --git a/lib/src/solid/write_external_pod.dart b/lib/src/solid/write_external_pod.dart index cf9043e9..74970dd6 100644 --- a/lib/src/solid/write_external_pod.dart +++ b/lib/src/solid/write_external_pod.dart @@ -34,7 +34,8 @@ import 'dart:convert'; import 'package:flutter/material.dart' hide Key; import 'package:solidpod/src/solid/api/rest_api.dart'; -import 'package:solidpod/src/solid/check_encryption.dart' show isContentEncrypted; +import 'package:solidpod/src/solid/check_encryption.dart' + show isContentEncrypted; import 'package:solidpod/src/solid/common_func.dart'; import 'package:solidpod/src/solid/constants/common.dart'; import 'package:solidpod/src/solid/utils/exceptions.dart'; From 3fe223aad76b45cae9a2f2f38815220292b92c7b Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Mon, 3 Aug 2026 13:03:20 +1000 Subject: [PATCH 5/7] markdownlint fix --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 313e5d1d..b9bd84cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ Visit the package at [pub.dev](https://pub.dev/packages/solidpod). ## 1.0 + Switch public to private sharing [1.0.15 20260728 jesscmoore] -+ Preserve encryption state on overwrite in writePod/writeExternalPod [1.0.14 20260730 jesscmoore] ++ Preserve encryption state on overwrite [1.0.14 20260730 jesscmoore] + Add load test to the example app [1.0.13 20260702 tonypioneer] + Migrate TEMPLATE to solidui [1.0.12 20260629 tonypioneer] + Support profile editing [1.0.11 20260626 tonypioneer] From 1e68400d7c81aa430350f27185f1d215b9b41b50 Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Mon, 3 Aug 2026 13:20:53 +1000 Subject: [PATCH 6/7] exclude-file replaced by exclude-path --- .github/workflows/ci.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 86cc5434..0071c02b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -16,7 +16,6 @@ env: FLUTTER_VERSION: '3.44.2' jobs: - analyze: runs-on: ubuntu-latest if: github.event.repository.private == false @@ -103,8 +102,7 @@ jobs: id: lychee uses: lycheeverse/lychee-action@v2 with: # Don't fail for now but then create an issue - useful? - args: - --exclude-file .lycheeignore + args: --exclude-path .lycheeignore --no-progress '*.md' './**/*.dart' From 43a70f97df04769f7fb4d7eb62134825eb4a8b57 Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Wed, 12 Aug 2026 09:42:42 +1000 Subject: [PATCH 7/7] add example links in documentation to lycheeignore file --- .lycheeignore | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.lycheeignore b/.lycheeignore index 49be24e1..4a7c4d8b 100644 --- a/.lycheeignore +++ b/.lycheeignore @@ -79,12 +79,14 @@ https://securetoken.google.com/ file:///home/runner/work/solidui/solidui/lib/src/utils/scheme https://pods.example.au/john-doe/profile/card#me +https://anushkavidanage.github.io/solidpod/example/client-profile.jsonld +https://anushkavidanage.github.io/solidpod/example/redirect.html +https://anusii.github.io/solidpodeg/client-profile.jsonld https://github.com/gjwgit/myapp/blob/main/README.md https://github.com/anusii/solidpod/blob/main/solidpodeg/README.md https://server/POD_NAME/APP_NAME/data/FILE_PATH https://server/alice/ https://server/alice/myapp/ -https://anusii.github.io/solidpodeg/client-profile.jsonld # 20260605 gjw Failing solid servers @@ -97,4 +99,4 @@ https://api.open-meteo.com/v1/forecast # 20260619 gjw konapod failures -https://blog.kumo.dev/2024/05/22/reverse_engineering_hkg_apps.html \ No newline at end of file +https://blog.kumo.dev/2024/05/22/reverse_engineering_hkg_apps.html