diff --git a/.github/workflows/at_server.yaml b/.github/workflows/at_server.yaml index 7a8f4bbed..298d6cc82 100644 --- a/.github/workflows/at_server.yaml +++ b/.github/workflows/at_server.yaml @@ -43,11 +43,11 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 # v1.6.5 + - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 # v1.7.0 with: sdk: ${{ matrix.dart-channel}} - - uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0 + - uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 with: go-version: 'stable' cache-dependency-path: tools/osv-scanner/go.sum @@ -179,7 +179,7 @@ jobs: run: | echo "hashes=$(cat checksums.txt | base64 -w0)" >> "$GITHUB_OUTPUT" - if: ${{ matrix.dart-channel == 'stable' && startsWith(github.ref, 'refs/tags/') }} - uses: actions/attest-build-provenance@ef244123eb79f2f7a7e75d99086184180e6d0018 # v1.4.4 + uses: actions/attest-build-provenance@7668571508540a607bdfd90a87a560489fe372eb # v2.1.0 with: subject-path: 'sboms/**' @@ -218,7 +218,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 # v1.6.5 + - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 # v1.7.0 with: sdk: ${{ matrix.dart-channel}} @@ -312,7 +312,7 @@ jobs: grep version pubspec.yaml | head -1 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 - name: Login to DockerHub uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 @@ -356,7 +356,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 # v 1.6.5 + - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 # v 1.7.0 with: sdk: stable @@ -385,7 +385,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 # v1.6.5 + - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 # v1.7.0 with: sdk: stable @@ -414,7 +414,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 # v1.6.5 + - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 # v1.7.0 with: sdk: stable @@ -467,7 +467,7 @@ jobs: fi - name: Install Dart - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 # v1.6.5 + uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 # v1.7.0 with: sdk: stable @@ -671,7 +671,7 @@ jobs: uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 - name: Login to DockerHub uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 @@ -725,7 +725,7 @@ jobs: uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 - name: Login to DockerHub uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 @@ -780,7 +780,7 @@ jobs: uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 - name: Login to DockerHub uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 @@ -837,7 +837,7 @@ jobs: uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 - name: Login to DockerHub uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 @@ -902,7 +902,7 @@ jobs: uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 - name: Login to DockerHub uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 @@ -967,7 +967,7 @@ jobs: uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 - name: Login to DockerHub uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 @@ -1022,7 +1022,7 @@ jobs: uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 - name: Login to DockerHub uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 diff --git a/.github/workflows/at_server_dev_deploy.yaml b/.github/workflows/at_server_dev_deploy.yaml index 20fecd102..10281fb37 100644 --- a/.github/workflows/at_server_dev_deploy.yaml +++ b/.github/workflows/at_server_dev_deploy.yaml @@ -21,7 +21,7 @@ jobs: run: echo "BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV - name: Set up Docker Buildx - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 - name: Login to DockerHub uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 @@ -61,7 +61,7 @@ jobs: uses: google-github-actions/setup-gcloud@6189d56e4096ee891640bb02ac264be376592d6a # v2.1.2 # Get the GKE credentials so we can deploy to the cluster - - uses: google-github-actions/get-gke-credentials@206d64b64b0eba0a6e2f25113d044c31776ca8d6 # v2.2.2 + - uses: google-github-actions/get-gke-credentials@9025e8f90f2d8e0c3dafc3128cc705a26d992a6a # v2.3.0 with: cluster_name: ${{ secrets.GKE_DEV_CLUSTER }} location: ${{ secrets.GKE_DEV_ZONE }} diff --git a/.github/workflows/at_server_prod_deploy.yaml b/.github/workflows/at_server_prod_deploy.yaml index 2ecc320e6..e43231c7d 100644 --- a/.github/workflows/at_server_prod_deploy.yaml +++ b/.github/workflows/at_server_prod_deploy.yaml @@ -26,7 +26,7 @@ jobs: run: echo "BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV - name: Set up Docker Buildx - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 - name: Login to DockerHub uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 @@ -82,7 +82,7 @@ jobs: uses: google-github-actions/setup-gcloud@6189d56e4096ee891640bb02ac264be376592d6a # v2.1.2 # Get the GKE credentials so we can deploy to the cluster - - uses: google-github-actions/get-gke-credentials@206d64b64b0eba0a6e2f25113d044c31776ca8d6 # v2.2.2 + - uses: google-github-actions/get-gke-credentials@9025e8f90f2d8e0c3dafc3128cc705a26d992a6a # v2.3.0 with: cluster_name: ${{ secrets.GKE_PROD_CLUSTER }} location: ${{ secrets.GKE_PROD_ZONE }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 68ee484a2..83beb12f3 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -50,7 +50,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3.27.5 + uses: github/codeql-action/init@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -60,7 +60,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3.27.5 + uses: github/codeql-action/autobuild@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 # ℹī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -73,6 +73,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3.27.5 + uses: github/codeql-action/analyze@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/melos_bootstrap.yaml b/.github/workflows/melos_bootstrap.yaml index 4c86ca544..4bb62bff4 100644 --- a/.github/workflows/melos_bootstrap.yaml +++ b/.github/workflows/melos_bootstrap.yaml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: subosito/flutter-action@74af56c5ed2697ba4621264652728e8d217e53d3 # v2.17.0 + - uses: subosito/flutter-action@f2c4f6686ca8e8d6e6d0f28410eeef506ed66aff # v2.18.0 with: channel: "stable" - name: flutter pub get diff --git a/.github/workflows/promote_canary.yaml b/.github/workflows/promote_canary.yaml index d879865a9..7eea459cb 100644 --- a/.github/workflows/promote_canary.yaml +++ b/.github/workflows/promote_canary.yaml @@ -29,7 +29,7 @@ jobs: uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 - name: Login to DockerHub uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 @@ -90,7 +90,7 @@ jobs: uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 - name: Login to DockerHub uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 @@ -164,7 +164,7 @@ jobs: working-directory: sboms run: | echo "hashes=$(cat checksums.txt | base64 -w0)" >> "$GITHUB_OUTPUT" - - uses: actions/attest-build-provenance@ef244123eb79f2f7a7e75d99086184180e6d0018 # v1.4.4 + - uses: actions/attest-build-provenance@7668571508540a607bdfd90a87a560489fe372eb # v2.1.0 with: subject-path: 'sboms/**' diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index d9afeba4a..707003c17 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3.27.5 + uses: github/codeql-action/upload-sarif@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 with: sarif_file: results.sarif diff --git a/.github/workflows/ve_base.yaml b/.github/workflows/ve_base.yaml index 167b44ff8..a9ee477ed 100644 --- a/.github/workflows/ve_base.yaml +++ b/.github/workflows/ve_base.yaml @@ -15,7 +15,7 @@ jobs: uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 - name: Login to DockerHub uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 diff --git a/.github/workflows/vip_rebuild.yaml b/.github/workflows/vip_rebuild.yaml index 1b67d13d8..a075aa9d9 100644 --- a/.github/workflows/vip_rebuild.yaml +++ b/.github/workflows/vip_rebuild.yaml @@ -23,7 +23,7 @@ jobs: uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 - name: Login to DockerHub uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 @@ -74,7 +74,7 @@ jobs: uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 - name: Login to DockerHub uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 diff --git a/packages/at_persistence_secondary_server/CHANGELOG.md b/packages/at_persistence_secondary_server/CHANGELOG.md index cff1223c2..59297900e 100644 --- a/packages/at_persistence_secondary_server/CHANGELOG.md +++ b/packages/at_persistence_secondary_server/CHANGELOG.md @@ -1,3 +1,13 @@ +## 3.1.0 +- feat: commit log changes for sync skipDeletesUntil feature +- build[deps]: Upgraded the following package: + - at_commons to v5.1.0 +## 3.0.66 +- feat: Add "PublicKeyHash" to the "AtMetadata" which holds the hash value of encryption public key +- build[deps]: Upgraded the following packages: + - at_commons to v5.0.2 + - lints to v5.0.0 + - test to v1.25.8 ## 3.0.65 - fix: Modified checks in commit log keystore _alwaysIncludeInSync method to match only reserved shared_key, encryption public key and public key without namespace. diff --git a/packages/at_persistence_secondary_server/lib/src/config/at_config.dart b/packages/at_persistence_secondary_server/lib/src/config/at_config.dart index cb71d5c00..06737cca1 100644 --- a/packages/at_persistence_secondary_server/lib/src/config/at_config.dart +++ b/packages/at_persistence_secondary_server/lib/src/config/at_config.dart @@ -26,7 +26,8 @@ class AtConfig { persistenceManager = SecondaryPersistenceStoreFactory.getInstance() .getSecondaryPersistenceStore(_atSign)! .getHivePersistenceManager()!; - configKey = HiveKeyStoreHelper.getInstance().prepareKey('private:blocklist$_atSign'); + configKey = HiveKeyStoreHelper.getInstance() + .prepareKey('private:blocklist$_atSign'); } ///Returns 'success' on adding unique [blockList] into blocklist. diff --git a/packages/at_persistence_secondary_server/lib/src/keystore/hive_manager.dart b/packages/at_persistence_secondary_server/lib/src/keystore/hive_manager.dart index ca5391338..c25785eae 100644 --- a/packages/at_persistence_secondary_server/lib/src/keystore/hive_manager.dart +++ b/packages/at_persistence_secondary_server/lib/src/keystore/hive_manager.dart @@ -31,6 +31,9 @@ class HivePersistenceManager with HiveBase { if (!Hive.isAdapterRegistered(AtMetaDataAdapter().typeId)) { Hive.registerAdapter(AtMetaDataAdapter()); } + if (!Hive.isAdapterRegistered(PublicKeyHashAdapater().typeId)) { + Hive.registerAdapter(PublicKeyHashAdapater()); + } var secret = await _getHiveSecretFromFile(_atsign!, storagePath); _boxName = AtUtils.getShaForAtSign(_atsign!); @@ -113,10 +116,11 @@ class HivePersistenceManager with HiveBase { } //TODO change into to Duration and construct cron string dynamically - void scheduleKeyExpireTask(int? runFrequencyMins, {Duration? runTimeInterval, bool skipCommits = false}) { + void scheduleKeyExpireTask(int? runFrequencyMins, + {Duration? runTimeInterval, bool skipCommits = false}) { logger.finest('scheduleKeyExpireTask starting cron job.'); Schedule schedule; - if(runTimeInterval != null){ + if (runTimeInterval != null) { schedule = Schedule(seconds: runTimeInterval.inSeconds); } else { schedule = Schedule.parse('*/$runFrequencyMins * * * *'); diff --git a/packages/at_persistence_secondary_server/lib/src/log/commitlog/at_commit_log.dart b/packages/at_persistence_secondary_server/lib/src/log/commitlog/at_commit_log.dart index de249cb53..9ddfac83f 100644 --- a/packages/at_persistence_secondary_server/lib/src/log/commitlog/at_commit_log.dart +++ b/packages/at_persistence_secondary_server/lib/src/log/commitlog/at_commit_log.dart @@ -127,12 +127,13 @@ class AtCommitLog extends BaseAtCommitLog { /// Returns the Iterator of [_commitLogCacheMap] from the commitId specified. @server Iterator> getEntries(int commitId, - {String? regex, int limit = 25}) { + {String? regex, int limit = 25, int? skipDeletesUntil}) { // If regex is null or isEmpty set regex to match all keys if (regex == null || regex.isEmpty) { regex = '.*'; } - return _commitLogKeyStore.getEntries(commitId, regex: regex, limit: limit); + return _commitLogKeyStore.getEntries(commitId, + regex: regex, limit: limit, skipDeletesUntil: skipDeletesUntil); } Future _publishChangeEvent(CommitEntry commitEntry) async { diff --git a/packages/at_persistence_secondary_server/lib/src/log/commitlog/commit_log_keystore.dart b/packages/at_persistence_secondary_server/lib/src/log/commitlog/commit_log_keystore.dart index b5a7c98ea..3dd8d96a2 100644 --- a/packages/at_persistence_secondary_server/lib/src/log/commitlog/commit_log_keystore.dart +++ b/packages/at_persistence_secondary_server/lib/src/log/commitlog/commit_log_keystore.dart @@ -4,6 +4,9 @@ import 'package:at_persistence_secondary_server/src/keystore/hive_base.dart'; import 'package:at_utils/at_utils.dart'; import 'package:hive/hive.dart'; import 'package:meta/meta.dart'; +import 'package:at_persistence_secondary_server/src/log/commitlog/sync/fetch_all_keys_strategy.dart'; +import 'package:at_persistence_secondary_server/src/log/commitlog/sync/skip_deletes_strategy.dart'; +import 'package:at_persistence_secondary_server/src/log/commitlog/sync/sync_keys_fetch_strategy.dart'; @server class CommitLogKeyStore extends BaseCommitLogKeyStore { @@ -12,8 +15,11 @@ class CommitLogKeyStore extends BaseCommitLogKeyStore { int get latestCommitId => commitLogCache.latestCommitId; + late SyncKeysFetchStrategy _syncKeysFetchStrategy; + CommitLogKeyStore(String currentAtSign) : super(currentAtSign) { commitLogCache = CommitLogCache(this); + _syncKeysFetchStrategy = FetchAllKeysStrategy(); } @override @@ -184,51 +190,8 @@ class CommitLogKeyStore extends BaseCommitLogKeyStore { /// if regex is passed, key has to match the regex or it has to be a special key. bool _shouldIncludeKeyInSyncResponse(String atKey, String regex, {List? enrolledNamespace}) { - return _isNamespaceAuthorised(atKey, enrolledNamespace) && - (_keyMatchesRegex(atKey, regex) || _alwaysIncludeInSync(atKey)); - } - - bool _isNamespaceAuthorised( - String atKeyAsString, List? enrolledNamespace) { - // This is work-around for : https://github.com/atsign-foundation/at_server/issues/1570 - if (atKeyAsString.toLowerCase() == 'configkey') { - return true; - } - late AtKey atKey; - try { - atKey = AtKey.fromString(atKeyAsString); - } on InvalidSyntaxException catch (_) { - _logger.warning( - '_isNamespaceAuthorized found an invalid key "$atKeyAsString" in the commit log. Returning false'); - return false; - } - String? keyNamespace = atKey.namespace; - // If enrolledNamespace is null or keyNamespace is null, fallback to - // existing behaviour - the key is authorized for the client to receive. So return true. - if (enrolledNamespace == null || - enrolledNamespace.isEmpty || - (keyNamespace == null || keyNamespace.isEmpty)) { - return true; - } - if (enrolledNamespace.contains('*') || - enrolledNamespace.contains(keyNamespace)) { - return true; - } - return false; - } - - bool _keyMatchesRegex(String atKey, String regex) { - return RegExp(regex).hasMatch(atKey); - } - - /// match keys which have to included in sync irrespective of whether regex matches - /// e.g @bob:shared_key@alice, shared_key.bob@alice, public:publickey@alice, - /// public:phone@alice (public key without namespace) - bool _alwaysIncludeInSync(String atKey) { - return (atKey.contains(AtConstants.atEncryptionSharedKey) && - RegexUtil.keyType(atKey, false) == KeyType.reservedKey) || - atKey.startsWith(AtConstants.atEncryptionPublicKey) || - (atKey.startsWith('public:') && !atKey.contains('.')); + return _syncKeysFetchStrategy.shouldIncludeKeyInSyncResponse(atKey, regex, + enrolledNamespace: enrolledNamespace); } /// Returns the latest commitEntry of the key. @@ -238,13 +201,16 @@ class CommitLogKeyStore extends BaseCommitLogKeyStore { /// Returns the Iterator of entries as Key value pairs after the given the [commitId] for the keys that matches the [regex] Iterator> getEntries(int commitId, - {String regex = '.*', int limit = 25}) { + {String regex = '.*', int limit = 25, int? skipDeletesUntil}) { + SyncKeysFetchStrategy syncKeysFetchStrategy = skipDeletesUntil != null + ? SkipDeleteStrategy(skipDeletesUntil, commitLogCache.latestCommitId) + : _syncKeysFetchStrategy; Iterable> commitEntriesIterable = commitLogCache .entriesList() .where((element) => - element.value.commitId! >= commitId && - _shouldIncludeKeyInSyncResponse(element.value.atKey!, regex)) + syncKeysFetchStrategy.shouldIncludeEntryInSyncResponse( + element.value, commitId, regex)) .take(limit); return commitEntriesIterable.iterator; } diff --git a/packages/at_persistence_secondary_server/lib/src/log/commitlog/sync/fetch_all_keys_strategy.dart b/packages/at_persistence_secondary_server/lib/src/log/commitlog/sync/fetch_all_keys_strategy.dart new file mode 100644 index 000000000..5859fe457 --- /dev/null +++ b/packages/at_persistence_secondary_server/lib/src/log/commitlog/sync/fetch_all_keys_strategy.dart @@ -0,0 +1,14 @@ +import 'package:at_persistence_secondary_server/src/log/commitlog/commit_entry.dart'; +import 'package:at_persistence_secondary_server/src/log/commitlog/sync/sync_keys_fetch_strategy.dart'; + +/// Returns the commit entries which have to be synced from server to client +class FetchAllKeysStrategy extends SyncKeysFetchStrategy { + @override + bool shouldIncludeEntryInSyncResponse( + CommitEntry commitEntry, int commitId, String regex, + {List? enrolledNamespace}) { + return commitEntry.commitId! >= commitId && + super.shouldIncludeKeyInSyncResponse(commitEntry.atKey!, regex, + enrolledNamespace: enrolledNamespace); + } +} diff --git a/packages/at_persistence_secondary_server/lib/src/log/commitlog/sync/skip_deletes_strategy.dart b/packages/at_persistence_secondary_server/lib/src/log/commitlog/sync/skip_deletes_strategy.dart new file mode 100644 index 000000000..dcaef7ee8 --- /dev/null +++ b/packages/at_persistence_secondary_server/lib/src/log/commitlog/sync/skip_deletes_strategy.dart @@ -0,0 +1,24 @@ +import 'package:at_persistence_secondary_server/src/log/commitlog/commit_entry.dart'; +import 'package:at_persistence_secondary_server/src/log/commitlog/sync/sync_keys_fetch_strategy.dart'; + +/// Returns the commit entries to be returned in sync response from server to client except delete commit entries. +class SkipDeleteStrategy extends SyncKeysFetchStrategy { + late int skipDeletesUntil; + late int latestCommitId; + SkipDeleteStrategy(this.skipDeletesUntil, this.latestCommitId); + @override + bool shouldIncludeEntryInSyncResponse( + CommitEntry commitEntry, int commitId, String regex, + {List? enrolledNamespace}) { + // do not include delete commit entries between commitId and skipDeletesUntil, except when delete is the last commit entry + if (commitEntry.operation == CommitOp.DELETE && + commitEntry.commitId! <= skipDeletesUntil && + commitEntry.commitId! >= commitId && + commitEntry.commitId != latestCommitId) { + return false; + } + return commitEntry.commitId! >= commitId && + super.shouldIncludeKeyInSyncResponse(commitEntry.atKey!, regex, + enrolledNamespace: enrolledNamespace); + } +} diff --git a/packages/at_persistence_secondary_server/lib/src/log/commitlog/sync/sync_keys_fetch_strategy.dart b/packages/at_persistence_secondary_server/lib/src/log/commitlog/sync/sync_keys_fetch_strategy.dart new file mode 100644 index 000000000..17324fda0 --- /dev/null +++ b/packages/at_persistence_secondary_server/lib/src/log/commitlog/sync/sync_keys_fetch_strategy.dart @@ -0,0 +1,66 @@ +import 'package:at_commons/at_commons.dart'; +import 'package:at_persistence_secondary_server/at_persistence_secondary_server.dart'; +import 'package:at_utils/at_utils.dart'; + +abstract class SyncKeysFetchStrategy { + final _logger = AtSignLogger('SyncKeysFetchStrategy'); + + /// Returns true if the commit entry should be included in sync response, false otherwise + bool shouldIncludeEntryInSyncResponse( + CommitEntry commitEntry, int commitId, String regex, + {List? enrolledNamespace}); + + /// if enrolledNamespace is passed, key namespace should be in enrolledNamespace list and + /// atKey should match regex or should be a special key that is always included in sync. + bool shouldIncludeKeyInSyncResponse(String atKey, String regex, + {List? enrolledNamespace}) { + return isNamespaceAuthorised(atKey, enrolledNamespace) && + (keyMatchesRegex(atKey, regex) || alwaysIncludeInSync(atKey)); + } + + /// Returns true if atKey namespace is empty or null/ enrolledNamespace is empty or null + /// if enrolledNamespace contains atKey namespace, return true. false otherwise + bool isNamespaceAuthorised( + String atKeyAsString, List? enrolledNamespace) { + // This is work-around for : https://github.com/atsign-foundation/at_server/issues/1570 + if (atKeyAsString.toLowerCase() == 'configkey') { + return true; + } + late AtKey atKey; + try { + atKey = AtKey.fromString(atKeyAsString); + } on InvalidSyntaxException catch (_) { + _logger.warning( + '_isNamespaceAuthorized found an invalid key "$atKeyAsString" in the commit log. Returning false'); + return false; + } + String? keyNamespace = atKey.namespace; + // If enrolledNamespace is null or keyNamespace is null, fallback to + // existing behaviour - the key is authorized for the client to receive. So return true. + if (enrolledNamespace == null || + enrolledNamespace.isEmpty || + (keyNamespace == null || keyNamespace.isEmpty)) { + return true; + } + if (enrolledNamespace.contains('*') || + enrolledNamespace.contains(keyNamespace)) { + return true; + } + return false; + } + + /// Returns true if atKey matches regex. false otherwise + bool keyMatchesRegex(String atKey, String regex) { + return RegExp(regex).hasMatch(atKey); + } + + /// match keys which have to included in sync irrespective of whether regex matches + /// e.g @bob:shared_key@alice, shared_key.bob@alice, public:publickey@alice, + /// public:phone@alice (public key without namespace) + bool alwaysIncludeInSync(String atKey) { + return (atKey.contains(AtConstants.atEncryptionSharedKey) && + RegexUtil.keyType(atKey, false) == KeyType.reservedKey) || + atKey.startsWith(AtConstants.atEncryptionPublicKey) || + (atKey.startsWith('public:') && !atKey.contains('.')); + } +} diff --git a/packages/at_persistence_secondary_server/lib/src/model/at_meta_data.dart b/packages/at_persistence_secondary_server/lib/src/model/at_meta_data.dart index 571f80850..d48460013 100644 --- a/packages/at_persistence_secondary_server/lib/src/model/at_meta_data.dart +++ b/packages/at_persistence_secondary_server/lib/src/model/at_meta_data.dart @@ -77,6 +77,9 @@ class AtMetaData extends HiveObject { @HiveField(23) String? skeEncAlgo; + @HiveField(24) + PublicKeyHash? pubKeyHash; + @override String toString() { return toJson().toString(); @@ -100,7 +103,8 @@ class AtMetaData extends HiveObject { ..encAlgo = encAlgo ..ivNonce = ivNonce ..skeEncKeyName = skeEncKeyName - ..skeEncAlgo = skeEncAlgo; + ..skeEncAlgo = skeEncAlgo + ..pubKeyHash = pubKeyHash; } factory AtMetaData.fromCommonsMetadata(Metadata metadata) { @@ -120,7 +124,8 @@ class AtMetaData extends HiveObject { ..encAlgo = metadata.encAlgo ..ivNonce = metadata.ivNonce ..skeEncKeyName = metadata.skeEncKeyName - ..skeEncAlgo = metadata.skeEncAlgo; + ..skeEncAlgo = metadata.skeEncAlgo + ..pubKeyHash = metadata.pubKeyHash; return AtMetadataBuilder(newAtMetaData: atMetadata).build(); } @@ -151,6 +156,7 @@ class AtMetaData extends HiveObject { map[AtConstants.ivOrNonce] = ivNonce; map[AtConstants.sharedKeyEncryptedEncryptingKeyName] = skeEncKeyName; map[AtConstants.sharedKeyEncryptedEncryptingAlgo] = skeEncAlgo; + map[AtConstants.sharedWithPublicKeyHash] = pubKeyHash?.toJson(); return map; } @@ -205,6 +211,8 @@ class AtMetaData extends HiveObject { ivNonce = json[AtConstants.ivOrNonce]; skeEncKeyName = json[AtConstants.sharedKeyEncryptedEncryptingKeyName]; skeEncAlgo = json[AtConstants.sharedKeyEncryptedEncryptingAlgo]; + pubKeyHash = + PublicKeyHash.fromJson(json[AtConstants.sharedWithPublicKeyHash]); return this; } @@ -301,13 +309,14 @@ class AtMetaDataAdapter extends TypeAdapter { ..encAlgo = fields[20] ..ivNonce = fields[21] ..skeEncKeyName = fields[22] - ..skeEncAlgo = fields[23]; + ..skeEncAlgo = fields[23] + ..pubKeyHash = fields[24]; } @override void write(BinaryWriter writer, AtMetaData obj) { writer - ..writeByte(24) + ..writeByte(25) ..writeByte(0) ..write(obj.createdBy) ..writeByte(1) @@ -355,6 +364,33 @@ class AtMetaDataAdapter extends TypeAdapter { ..writeByte(22) ..write(obj.skeEncKeyName) ..writeByte(23) - ..write(obj.skeEncAlgo); + ..write(obj.skeEncAlgo) + ..writeByte(24) + ..write(obj.pubKeyHash); + } +} + +@HiveType(typeId: 11) +class PublicKeyHashAdapater extends TypeAdapter { + @override + final int typeId = typeAdapterMap['PublicKeyHashAdapater']; + + @override + PublicKeyHash read(BinaryReader reader) { + var numOfFields = reader.readByte(); + var fields = { + for (var i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return PublicKeyHash(fields[0] as String, fields[1] as String); + } + + @override + void write(BinaryWriter writer, PublicKeyHash obj) { + writer + ..writeByte(2) + ..writeByte(0) + ..write(obj.hash) + ..writeByte(1) + ..write(obj.hashingAlgo); } } diff --git a/packages/at_persistence_secondary_server/lib/src/model/at_metadata_builder.dart b/packages/at_persistence_secondary_server/lib/src/model/at_metadata_builder.dart index 5588a41ad..f34e1447d 100644 --- a/packages/at_persistence_secondary_server/lib/src/model/at_metadata_builder.dart +++ b/packages/at_persistence_secondary_server/lib/src/model/at_metadata_builder.dart @@ -1,3 +1,4 @@ +import 'package:at_commons/at_commons.dart'; import 'package:at_persistence_secondary_server/at_persistence_secondary_server.dart'; import 'package:at_utils/at_logger.dart'; @@ -18,26 +19,26 @@ class AtMetadataBuilder { /// ttb : Time to birth of the key. If ttb is null, atMetadata's ttb is assigned to ttb. /// ttr : Time to refresh of the key. If ttr is null, atMetadata's ttr is assigned to ttr. /// ccd : Cascade delete. If ccd is null, atMetadata's ccd is assigned to ccd. - AtMetadataBuilder({ - String? atSign, - AtMetaData? newAtMetaData, - AtMetaData? existingMetaData, - int? ttl, - int? ttb, - int? ttr, - bool? ccd, - bool? isBinary, - bool? isEncrypted, - String? dataSignature, - String? sharedKeyEncrypted, - String? publicKeyChecksum, - String? encoding, - String? encKeyName, - String? encAlgo, - String? ivNonce, - String? skeEncKeyName, - String? skeEncAlgo, - }) { + AtMetadataBuilder( + {String? atSign, + AtMetaData? newAtMetaData, + AtMetaData? existingMetaData, + int? ttl, + int? ttb, + int? ttr, + bool? ccd, + bool? isBinary, + bool? isEncrypted, + String? dataSignature, + String? sharedKeyEncrypted, + String? publicKeyChecksum, + String? encoding, + String? encKeyName, + String? encAlgo, + String? ivNonce, + String? skeEncKeyName, + String? skeEncAlgo, + PublicKeyHash? publicKeyHash}) { newAtMetaData ??= AtMetaData(); atMetaData = newAtMetaData; // createdAt indicates the date and time of the key created. @@ -85,6 +86,7 @@ class AtMetadataBuilder { ivNonce ??= newAtMetaData.ivNonce; skeEncKeyName ??= newAtMetaData.skeEncKeyName; skeEncAlgo ??= newAtMetaData.skeEncAlgo; + publicKeyHash ??= newAtMetaData.pubKeyHash; if (ttl != null && ttl >= 0) { setTTL(ttl, ttb: ttb); @@ -110,6 +112,7 @@ class AtMetadataBuilder { atMetaData.ivNonce = ivNonce; atMetaData.skeEncKeyName = skeEncKeyName; atMetaData.skeEncAlgo = skeEncAlgo; + atMetaData.pubKeyHash = publicKeyHash; } void setTTL(int? ttl, {int? ttb}) { diff --git a/packages/at_persistence_secondary_server/lib/src/notification/at_notification_keystore.dart b/packages/at_persistence_secondary_server/lib/src/notification/at_notification_keystore.dart index 9ffd0b7e5..2712c9a05 100644 --- a/packages/at_persistence_secondary_server/lib/src/notification/at_notification_keystore.dart +++ b/packages/at_persistence_secondary_server/lib/src/notification/at_notification_keystore.dart @@ -1,10 +1,10 @@ // ignore_for_file: non_constant_identifier_names +import 'package:at_persistence_secondary_server/at_persistence_secondary_server.dart'; +import 'package:at_persistence_secondary_server/src/keystore/hive_base.dart'; import 'package:at_utf7/at_utf7.dart'; import 'package:at_utils/at_utils.dart'; import 'package:hive/hive.dart'; -import 'package:at_persistence_secondary_server/at_persistence_secondary_server.dart'; -import 'package:at_persistence_secondary_server/src/keystore/hive_base.dart'; /// Class to initialize, put and get entries into [AtNotificationKeystore] class AtNotificationKeystore @@ -42,6 +42,9 @@ class AtNotificationKeystore if (!Hive.isAdapterRegistered(AtMetaDataAdapter().typeId)) { Hive.registerAdapter(AtMetaDataAdapter()); } + if (!Hive.isAdapterRegistered(PublicKeyHashAdapater().typeId)) { + Hive.registerAdapter(PublicKeyHashAdapater()); + } _register = true; } await super.openBox(_boxName); diff --git a/packages/at_persistence_secondary_server/lib/src/utils/type_adapter_util.dart b/packages/at_persistence_secondary_server/lib/src/utils/type_adapter_util.dart index 8c8da9e70..bde94e0ae 100644 --- a/packages/at_persistence_secondary_server/lib/src/utils/type_adapter_util.dart +++ b/packages/at_persistence_secondary_server/lib/src/utils/type_adapter_util.dart @@ -10,5 +10,6 @@ final Map typeAdapterMap = { 'NotificationTypeAdapter': 7, 'OperationTypeAdapter': 8, 'NotificationPriorityAdapter': 9, - 'MessageTypeAdapter': 10 + 'MessageTypeAdapter': 10, + 'PublicKeyHashAdapater': 11 }; diff --git a/packages/at_persistence_secondary_server/pubspec.yaml b/packages/at_persistence_secondary_server/pubspec.yaml index b1ea0cd42..5eb05242c 100644 --- a/packages/at_persistence_secondary_server/pubspec.yaml +++ b/packages/at_persistence_secondary_server/pubspec.yaml @@ -1,6 +1,6 @@ name: at_persistence_secondary_server description: A Dart library with the implementation classes for the persistence layer of the secondary server. -version: 3.0.65 +version: 3.1.0 repository: https://github.com/atsign-foundation/at_server homepage: https://docs.atsign.com/ @@ -14,13 +14,13 @@ dependencies: crypto: ^3.0.3 uuid: ^3.0.6 at_utf7: ^1.0.0 - at_commons: ^5.0.1 + at_commons: ^5.1.0 at_utils: ^3.0.19 at_persistence_spec: ^2.0.14 meta: ^1.8.0 dev_dependencies: - lints: ^2.0.1 - test: ^1.22.1 + lints: ^5.0.0 + test: ^1.25.8 coverage: ^1.6.1 collection: ^1.17.1 diff --git a/packages/at_persistence_secondary_server/test/at_config_test.dart b/packages/at_persistence_secondary_server/test/at_config_test.dart index 3fc81677a..1819b007c 100644 --- a/packages/at_persistence_secondary_server/test/at_config_test.dart +++ b/packages/at_persistence_secondary_server/test/at_config_test.dart @@ -137,7 +137,8 @@ Future tearDownFunc() async { // closes the instance of hive keystore await SecondaryPersistenceStoreFactory.getInstance() .getSecondaryPersistenceStore('@test_user_1')! - .getHivePersistenceManager()?.close(); + .getHivePersistenceManager() + ?.close(); var isExists = await Directory('test/hive/').exists(); if (isExists) { diff --git a/packages/at_persistence_secondary_server/test/commit_log_test.dart b/packages/at_persistence_secondary_server/test/commit_log_test.dart index 3f92ab455..e2136b974 100644 --- a/packages/at_persistence_secondary_server/test/commit_log_test.dart +++ b/packages/at_persistence_secondary_server/test/commit_log_test.dart @@ -840,6 +840,92 @@ void main() async { expect(commitEntriesMap.containsKey('public:phone.wavi@alice'), false); expect(commitEntriesMap.containsKey('public:location@alice'), true); }); + test( + 'A test to verify delete commit entries are NOT returned when skipDeletesUntil is set', + () async { + var commitLogInstance = + await (AtCommitLogManagerImpl.getInstance().getCommitLog('@alice')); + var commitLogKeystore = commitLogInstance!.commitLogKeyStore; + await commitLogKeystore.add(CommitEntry( + 'test_key_true_1@alice', CommitOp.UPDATE, DateTime.now())); + await commitLogKeystore.add(CommitEntry( + 'test_key_true_2@alice', CommitOp.DELETE, DateTime.now())); + await commitLogKeystore.add(CommitEntry( + 'test_key_true_3@alice', CommitOp.DELETE, DateTime.now())); + await commitLogKeystore.add(CommitEntry( + 'test_key_true_4@alice', CommitOp.UPDATE, DateTime.now())); + Iterator>? changes = commitLogInstance + .commitLogKeyStore + .getEntries(-1, skipDeletesUntil: 25); + Map commitEntriesMap = {}; + while (changes.moveNext()) { + var commitEntry = changes.current.value; + commitEntriesMap[commitEntry.atKey] = commitEntry; + } + expect(commitEntriesMap.containsKey('test_key_true_1@alice'), true); + expect(commitEntriesMap.containsKey('test_key_true_2@alice'), false); + expect(commitEntriesMap.containsKey('test_key_true_3@alice'), false); + expect(commitEntriesMap.containsKey('test_key_true_4@alice'), true); + }); + test( + 'A test to verify correct commit entries are returned when skipDeletesUntil is set and regex is passed', + () async { + var commitLogInstance = + await (AtCommitLogManagerImpl.getInstance().getCommitLog('@alice')); + var commitLogKeystore = commitLogInstance!.commitLogKeyStore; + await commitLogKeystore.add(CommitEntry( + 'test_key_true_1.wavi@alice', CommitOp.UPDATE, DateTime.now())); + await commitLogKeystore.add(CommitEntry( + 'test_key_true_2.buzz@alice', CommitOp.DELETE, DateTime.now())); + await commitLogKeystore.add(CommitEntry( + 'test_key_true_3.wavi@alice', CommitOp.DELETE, DateTime.now())); + await commitLogKeystore.add(CommitEntry( + 'test_key_true_4.buzz@alice', CommitOp.UPDATE, DateTime.now())); + Iterator>? changes = commitLogInstance + .commitLogKeyStore + .getEntries(-1, skipDeletesUntil: 25, regex: '.buzz'); + Map commitEntriesMap = {}; + while (changes.moveNext()) { + var commitEntry = changes.current.value; + commitEntriesMap[commitEntry.atKey] = commitEntry; + } + expect( + commitEntriesMap.containsKey('test_key_true_1.wavi@alice'), false); + expect( + commitEntriesMap.containsKey('test_key_true_2.buzz@alice'), false); + expect( + commitEntriesMap.containsKey('test_key_true_3.wavi@alice'), false); + expect( + commitEntriesMap.containsKey('test_key_true_4.buzz@alice'), true); + }); + test( + 'A test to verify last delete commit entry is returned when its commitId is equal to latest commitId', + () async { + var commitLogInstance = + await (AtCommitLogManagerImpl.getInstance().getCommitLog('@alice')); + var commitLogKeystore = commitLogInstance!.commitLogKeyStore; + await commitLogKeystore.add(CommitEntry( + 'test_key_true_1@alice', CommitOp.UPDATE, DateTime.now())); + await commitLogKeystore.add(CommitEntry( + 'test_key_true_2@alice', CommitOp.DELETE, DateTime.now())); + await commitLogKeystore.add(CommitEntry( + 'test_key_true_3@alice', CommitOp.DELETE, DateTime.now())); + await commitLogKeystore.add(CommitEntry( + 'test_key_true_4@alice', CommitOp.DELETE, DateTime.now())); + int? latestCommitId = commitLogInstance.lastCommittedSequenceNumber(); + Iterator>? changes = commitLogInstance + .commitLogKeyStore + .getEntries(-1, skipDeletesUntil: latestCommitId); + Map commitEntriesMap = {}; + while (changes.moveNext()) { + var commitEntry = changes.current.value; + commitEntriesMap[commitEntry.atKey] = commitEntry; + } + expect(commitEntriesMap.containsKey('test_key_true_1@alice'), true); + expect(commitEntriesMap.containsKey('test_key_true_2@alice'), false); + expect(commitEntriesMap.containsKey('test_key_true_3@alice'), false); + expect(commitEntriesMap.containsKey('test_key_true_4@alice'), true); + }); }); tearDown(() async => await tearDownFunc()); }); diff --git a/packages/at_persistence_secondary_server/test/hive_keystore_impl_test.dart b/packages/at_persistence_secondary_server/test/hive_keystore_impl_test.dart index 60d125010..1eaf54b88 100644 --- a/packages/at_persistence_secondary_server/test/hive_keystore_impl_test.dart +++ b/packages/at_persistence_secondary_server/test/hive_keystore_impl_test.dart @@ -84,7 +84,8 @@ void main() async { ..encAlgo = 'AES/CTR/PKCS7Padding' ..ivNonce = 'someIvNonce' ..skeEncKeyName = 'someSkeEncKeyName' - ..skeEncAlgo = 'someSkeEncAlgo'; + ..skeEncAlgo = 'someSkeEncAlgo' + ..pubKeyHash = PublicKeyHash('someHashValue', 'sha512'); var atMetaData = AtMetaData.fromCommonsMetadata(commonsMetadata); atData.metaData = atMetaData; await keyStore.create(key, atData); @@ -92,6 +93,8 @@ void main() async { var dataFromHive = await (keyStore.get(key)); expect(dataFromHive?.data, 'india'); expect(dataFromHive?.metaData, atMetaData); + expect(dataFromHive?.metaData?.pubKeyHash?.hash, 'someHashValue'); + expect(dataFromHive?.metaData?.pubKeyHash?.hashingAlgo, 'sha512'); var updateData = AtData(); var updateMetaData = diff --git a/packages/at_persistence_secondary_server/test/skip_delete_strategy_test.dart b/packages/at_persistence_secondary_server/test/skip_delete_strategy_test.dart new file mode 100644 index 000000000..b4de3e14f --- /dev/null +++ b/packages/at_persistence_secondary_server/test/skip_delete_strategy_test.dart @@ -0,0 +1,116 @@ +import 'package:at_persistence_secondary_server/at_persistence_secondary_server.dart'; +import 'package:at_persistence_secondary_server/src/log/commitlog/sync/skip_deletes_strategy.dart'; +import 'package:test/test.dart'; + +void main() async { + group('A group of tests to verify skip deletes strategy', () { + test( + 'Test to verify shouldInclude returns true when last commit entry is delete', + () { + int skipDeletesUntil = 15; + int latestCommitId = 15; + var skipStrategy = SkipDeleteStrategy(skipDeletesUntil, latestCommitId); + final commitEntry = + CommitEntry('phone@alice', CommitOp.DELETE, DateTime.now()) + ..commitId = 15; + bool shouldInclude = + skipStrategy.shouldIncludeEntryInSyncResponse(commitEntry, 5, '.*'); + expect(shouldInclude, true); + }); + test( + 'Test to verify shouldInclude returns false when commitOp is DELETE and commit entry commitId is in between passed commitId and skipDeletesUntil value', + () { + int skipDeletesUntil = 10; + int latestCommitId = 15; + var skipStrategy = SkipDeleteStrategy(skipDeletesUntil, latestCommitId); + final commitEntry = + CommitEntry('phone@alice', CommitOp.DELETE, DateTime.now()) + ..commitId = 5; + bool shouldInclude = + skipStrategy.shouldIncludeEntryInSyncResponse(commitEntry, -1, '.*'); + expect(shouldInclude, false); + }); + test( + 'Test to verify shouldInclude returns true when commitOp is UPDATE and commit entry commitId is in between passed commitId and skipDeletesUntil value', + () { + int skipDeletesUntil = 10; + int latestCommitId = 15; + var skipStrategy = SkipDeleteStrategy(skipDeletesUntil, latestCommitId); + final commitEntry = + CommitEntry('phone@alice', CommitOp.UPDATE, DateTime.now()) + ..commitId = 5; + bool shouldInclude = + skipStrategy.shouldIncludeEntryInSyncResponse(commitEntry, -1, '.*'); + expect(shouldInclude, true); + }); + test( + 'Test to verify shouldInclude returns false when commitOp is UPDATE and key regex does not match passed regex', + () { + int skipDeletesUntil = 10; + int latestCommitId = 15; + var skipStrategy = SkipDeleteStrategy(skipDeletesUntil, latestCommitId); + final commitEntry = + CommitEntry('phone.buzz@alice', CommitOp.UPDATE, DateTime.now()) + ..commitId = 5; + bool shouldInclude = skipStrategy.shouldIncludeEntryInSyncResponse( + commitEntry, -1, '.wavi'); + expect(shouldInclude, false); + }); + test( + 'Test to verify shouldInclude returns true when commitOp is UPDATE and key regex matches passed regex', + () { + int skipDeletesUntil = 10; + int latestCommitId = 15; + var skipStrategy = SkipDeleteStrategy(skipDeletesUntil, latestCommitId); + final commitEntry = + CommitEntry('phone.buzz@alice', CommitOp.UPDATE, DateTime.now()) + ..commitId = 5; + bool shouldInclude = skipStrategy.shouldIncludeEntryInSyncResponse( + commitEntry, -1, '.buzz'); + expect(shouldInclude, true); + }); + test( + 'Test to verify shouldInclude returns true when commitOp is DELETE and commit entry commitId is greater than skipDeletesUntil values', + () { + int skipDeletesUntil = 10; + int latestCommitId = 15; + var skipStrategy = SkipDeleteStrategy(skipDeletesUntil, latestCommitId); + final commitEntry = + CommitEntry('phone.buzz@alice', CommitOp.DELETE, DateTime.now()) + ..commitId = 25; + bool shouldInclude = skipStrategy.shouldIncludeEntryInSyncResponse( + commitEntry, -1, '.buzz'); + expect(shouldInclude, true); + }); + test( + 'Test to verify shouldInclude returns true when commitOp is UPDATE and key regex matches passed regex, key namespace is in enrolled namespace', + () { + int skipDeletesUntil = 10; + int latestCommitId = 15; + var skipStrategy = SkipDeleteStrategy(skipDeletesUntil, latestCommitId); + final commitEntry = + CommitEntry('phone.buzz@alice', CommitOp.UPDATE, DateTime.now()) + ..commitId = 5; + var enrolledNamespaces = ['buzz', 'contacts']; + bool shouldInclude = skipStrategy.shouldIncludeEntryInSyncResponse( + commitEntry, -1, '.buzz', + enrolledNamespace: enrolledNamespaces); + expect(shouldInclude, true); + }); + test( + 'Test to verify shouldInclude returns false when commitOp is UPDATE and key regex matches passed regex, key namespace is NOT in enrolled namespace', + () { + int skipDeletesUntil = 10; + int latestCommitId = 15; + var skipStrategy = SkipDeleteStrategy(skipDeletesUntil, latestCommitId); + final commitEntry = + CommitEntry('phone.buzz@alice', CommitOp.UPDATE, DateTime.now()) + ..commitId = 5; + var enrolledNamespaces = ['wavi', 'contacts']; + bool shouldInclude = skipStrategy.shouldIncludeEntryInSyncResponse( + commitEntry, -1, '.buzz', + enrolledNamespace: enrolledNamespaces); + expect(shouldInclude, false); + }); + }); +} diff --git a/packages/at_root_server/Dockerfile b/packages/at_root_server/Dockerfile index 3ee200cf5..37bf0785f 100644 --- a/packages/at_root_server/Dockerfile +++ b/packages/at_root_server/Dockerfile @@ -1,4 +1,4 @@ -FROM dart:3.5.4@sha256:6489a269e878813bf5a8fe66256c1a3e552d3a3c53eef9c1b224e2d8d737ca6d AS buildimage +FROM dart:3.6.0@sha256:b677df29e01bca3a2d5bdaffe204498834d56a4ed38ff56c0ac6a42d6541b58e AS buildimage ENV HOMEDIR=/atsign ENV BINARYDIR=/usr/local/at ENV USER_ID=1024 diff --git a/packages/at_secondary_server/CHANGELOG.md b/packages/at_secondary_server/CHANGELOG.md index 7458df73a..e7dae789d 100644 --- a/packages/at_secondary_server/CHANGELOG.md +++ b/packages/at_secondary_server/CHANGELOG.md @@ -1,7 +1,23 @@ +# 3.1.1 +- fix: Store "publicKeyHash" value in the keystore +- fix: add limit param in SyncProgressiveVerbHandler +- build[deps]: Upgraded the following package: + at_commons to v5.1.2 +# 3.1.0 +- feat: sync skip deletes until changes +- fix: Enable persistence of the Initialization Vector for "defaultEncryptionPrivateKey" and "selfEncryptionKey" in + the APKAM flow. +- build[deps]: Upgraded the following package: + - at_commons to v5.1.0 + - at_persistence_secondary_server to v3.1.0 # 3.0.52 - build[deps]: Upgraded the following package: - - at_commons to v5.0.1 - - at_persistence_secondary_server to v3.0.65 + - at_commons to v5.0.2 + - at_chops to v2.2.0 + - meta to v1.16.0 + - test to v1.25.9 + - args to v2.6.0 + - at_persistence_secondary_server to v3.0.66 to consume publicKeyHash changes. ## 3.0.51 - feat: Introduce option to unrevoke revoked enrollments - feat: Introduce option to delete enrollments that are denied/revoked diff --git a/packages/at_secondary_server/lib/src/notification/resource_manager.dart b/packages/at_secondary_server/lib/src/notification/resource_manager.dart index 43e018780..0487a13a5 100644 --- a/packages/at_secondary_server/lib/src/notification/resource_manager.dart +++ b/packages/at_secondary_server/lib/src/notification/resource_manager.dart @@ -227,6 +227,10 @@ class ResourceManager { commandBody = '${AtConstants.encryptingKeyName}:${atNotification.atMetadata!.encKeyName}:$commandBody'; } + if (atNotification.atMetadata!.pubKeyHash != null) { + commandBody = + '${AtConstants.sharedWithPublicKeyHash}:${atNotification.atMetadata!.pubKeyHash?.hash}:${AtConstants.sharedWithPublicKeyHashingAlgo}:${atNotification.atMetadata!.pubKeyHash?.hashingAlgo}:$commandBody'; + } if (atNotification.atMetadata!.pubKeyCS != null) { commandBody = '${AtConstants.sharedWithPublicKeyCheckSum}:${atNotification.atMetadata!.pubKeyCS}:$commandBody'; diff --git a/packages/at_secondary_server/lib/src/verb/handler/abstract_update_verb_handler.dart b/packages/at_secondary_server/lib/src/verb/handler/abstract_update_verb_handler.dart index d3aade830..1e53d6aeb 100644 --- a/packages/at_secondary_server/lib/src/verb/handler/abstract_update_verb_handler.dart +++ b/packages/at_secondary_server/lib/src/verb/handler/abstract_update_verb_handler.dart @@ -191,6 +191,13 @@ abstract class AbstractUpdateVerbHandler extends ChangeVerbHandler { verbParams[AtConstants.sharedKeyEncryptedEncryptingKeyName]; metadata.skeEncAlgo = verbParams[AtConstants.sharedKeyEncryptedEncryptingAlgo]; + if (verbParams[AtConstants.sharedWithPublicKeyHash].isNotNullOrEmpty && + verbParams[AtConstants.sharedWithPublicKeyHashingAlgo] + .isNotNullOrEmpty) { + metadata.pubKeyHash = PublicKeyHash( + verbParams[AtConstants.sharedWithPublicKeyHash]!, + verbParams[AtConstants.sharedWithPublicKeyHashingAlgo]!); + } updateParams.metadata = metadata; return updateParams; diff --git a/packages/at_secondary_server/lib/src/verb/handler/enroll_verb_handler.dart b/packages/at_secondary_server/lib/src/verb/handler/enroll_verb_handler.dart index 0677432e0..d74b98f36 100644 --- a/packages/at_secondary_server/lib/src/verb/handler/enroll_verb_handler.dart +++ b/packages/at_secondary_server/lib/src/verb/handler/enroll_verb_handler.dart @@ -249,9 +249,6 @@ class EnrollVerbHandler extends AbstractVerbHandler { final inboundConnectionMetadata = atConnection.metaData as InboundConnectionMetadata; inboundConnectionMetadata.enrollmentId = newEnrollmentId; - // Store default encryption private key and self encryption key(both encrypted) - // for future retrieval - await _storeEncryptionKeys(newEnrollmentId, enrollParams, currentAtSign); // store this apkam as default pkam public key for old clients // The keys with AT_PKAM_PUBLIC_KEY does not sync to client. await keyStore.put(AtConstants.atPkamPublicKey, @@ -409,12 +406,18 @@ class EnrollVerbHandler extends AbstractVerbHandler { String newEnrollmentId, EnrollParams enrollParams, String atSign) async { var privateKeyJson = {}; privateKeyJson['value'] = enrollParams.encryptedDefaultEncryptionPrivateKey; + if (enrollParams.encPrivateKeyIV != null) { + privateKeyJson['iv'] = enrollParams.encPrivateKeyIV; + } await keyStore.put( '$newEnrollmentId.${AtConstants.defaultEncryptionPrivateKey}.$enrollManageNamespace$atSign', AtData()..data = jsonEncode(privateKeyJson), skipCommit: true); var selfKeyJson = {}; selfKeyJson['value'] = enrollParams.encryptedDefaultSelfEncryptionKey; + if (enrollParams.selfEncKeyIV != null) { + selfKeyJson['iv'] = enrollParams.selfEncKeyIV; + } await keyStore.put( '$newEnrollmentId.${AtConstants.defaultSelfEncryptionKey}.$enrollManageNamespace$atSign', AtData()..data = jsonEncode(selfKeyJson), diff --git a/packages/at_secondary_server/lib/src/verb/handler/monitor_verb_handler.dart b/packages/at_secondary_server/lib/src/verb/handler/monitor_verb_handler.dart index 5d77a768f..c5f383674 100644 --- a/packages/at_secondary_server/lib/src/verb/handler/monitor_verb_handler.dart +++ b/packages/at_secondary_server/lib/src/verb/handler/monitor_verb_handler.dart @@ -140,6 +140,12 @@ class MonitorVerbHandler extends AbstractVerbHandler { "sharedKeyEnc": atNotification.atMetadata?.sharedKeyEnc, }; + notification.metadata?.putIfAbsent( + "pubKeyHash", + () => (atNotification.atMetadata?.pubKeyHash != null) + ? jsonEncode(atNotification.atMetadata?.pubKeyHash?.toJson()) + : null); + await _checkAndSend(notification); } } @@ -224,6 +230,11 @@ class Notification { (atNotification.atMetadata?.expiresAt ?? atNotification.expiresAt) .toString() }; + metadata?.putIfAbsent( + "pubKeyHash", + () => (atNotification.atMetadata?.pubKeyHash != null) + ? jsonEncode(atNotification.atMetadata?.pubKeyHash?.toJson()) + : null); } Map toJson() => { diff --git a/packages/at_secondary_server/lib/src/verb/handler/notify_verb_handler.dart b/packages/at_secondary_server/lib/src/verb/handler/notify_verb_handler.dart index 2f7e10075..15fc6d1ca 100644 --- a/packages/at_secondary_server/lib/src/verb/handler/notify_verb_handler.dart +++ b/packages/at_secondary_server/lib/src/verb/handler/notify_verb_handler.dart @@ -358,6 +358,13 @@ class NotifyVerbHandler extends AbstractVerbHandler { if (verbParams[AtConstants.sharedWithPublicKeyCheckSum] != null) { atMetadata.pubKeyCS = verbParams[AtConstants.sharedWithPublicKeyCheckSum]; } + if (verbParams[AtConstants.sharedWithPublicKeyHash].isNotNullOrEmpty && + verbParams[AtConstants.sharedWithPublicKeyHashingAlgo] + .isNotNullOrEmpty) { + atMetadata.pubKeyHash = PublicKeyHash( + verbParams[AtConstants.sharedWithPublicKeyHash]!, + verbParams[AtConstants.sharedWithPublicKeyHashingAlgo]!); + } if (verbParams[AtConstants.encryptingKeyName] != null) { atMetadata.encKeyName = verbParams[AtConstants.encryptingKeyName]; } diff --git a/packages/at_secondary_server/lib/src/verb/handler/sync_progressive_verb_handler.dart b/packages/at_secondary_server/lib/src/verb/handler/sync_progressive_verb_handler.dart index 01149568a..08afdabeb 100644 --- a/packages/at_secondary_server/lib/src/verb/handler/sync_progressive_verb_handler.dart +++ b/packages/at_secondary_server/lib/src/verb/handler/sync_progressive_verb_handler.dart @@ -40,13 +40,25 @@ class SyncProgressiveVerbHandler extends AbstractVerbHandler { // Get Commit Log Instance. var atCommitLog = await (AtCommitLogManagerImpl.getInstance() .getCommitLog(AtSecondaryServerImpl.getInstance().currentAtSign)); + int? skipDeletesUntil = verbParams[AtConstants.skipDeletesUntil] != null + ? int.parse(verbParams[AtConstants.skipDeletesUntil]!) + : null; + int? syncLimit = verbParams[AtConstants.syncLimit] != null + ? int.parse(verbParams[AtConstants.syncLimit]!) + : null; // Get entries to sync - var commitEntryIterator = atCommitLog!.getEntries( + Iterator> commitEntryIterator; + // if client doesn't pass syncLimit set the default value from server + syncLimit ??= AtSecondaryConfig.syncPageLimit; + commitEntryIterator = atCommitLog!.getEntries( int.parse(verbParams[AtConstants.fromCommitSequence]!) + 1, - regex: verbParams['regex']); + regex: verbParams['regex'], + skipDeletesUntil: skipDeletesUntil, + limit: syncLimit); List syncResponse = []; - await prepareResponse(capacity, syncResponse, commitEntryIterator, + await prepareResponse( + capacity, syncLimit, syncResponse, commitEntryIterator, enrollmentId: (atConnection.metaData as InboundConnectionMetadata).enrollmentId); @@ -57,8 +69,11 @@ class SyncProgressiveVerbHandler extends AbstractVerbHandler { /// 1. there is at least one item in [syncResponse], and the response length is greater than [desiredMaxSyncResponseLength], or /// 2. there are [AtSecondaryConfig.syncPageLimit] items in the [syncResponse] @visibleForTesting - Future prepareResponse(int desiredMaxSyncResponseLength, - List syncResponse, Iterator commitEntryIterator, + Future prepareResponse( + int desiredMaxSyncResponseLength, + int syncPageLimit, + List syncResponse, + Iterator commitEntryIterator, {String? enrollmentId}) async { int currentResponseLength = 0; Map enrolledNamespaces = {}; @@ -69,9 +84,8 @@ class SyncProgressiveVerbHandler extends AbstractVerbHandler { .get(enrollmentId)) .namespaces; } - - while (commitEntryIterator.moveNext() && - syncResponse.length < AtSecondaryConfig.syncPageLimit) { + while ( + commitEntryIterator.moveNext() && syncResponse.length < syncPageLimit) { var atKeyType = AtKey.getKeyType(commitEntryIterator.current.key, enforceNameSpace: false); if (atKeyType == KeyType.invalidKey) { @@ -150,11 +164,24 @@ class SyncProgressiveVerbHandler extends AbstractVerbHandler { if (metaData == null) { return metaDataMap; } - metaData.toJson().forEach((key, value) { - if (value != null) { - metaDataMap[key] = value.toString(); + Iterator itr = metaData.toJson().entries.iterator; + while (itr.moveNext()) { + // The value of [AtConstants.sharedWithPublicKeyHash] stores a Map containing + // the hash value and the hashing algorithm used for hashing the data. + // For example, {"hash":"dummy_value", "hashingAlgo":"sha512"}. + // Using toString() will not allow convert this into a Map, which is necessary + // for constructing the PublicKeyHash type on the client side. + // Therefore, a JSON-encoded string is used here, and on the client side, + // "jsonDecode" will be used to retrieve the Map and build the PublicKeyHash instance. + if (itr.current.key == AtConstants.sharedWithPublicKeyHash && + itr.current.value != null) { + metaDataMap[itr.current.key] = jsonEncode(itr.current.value); + continue; } - }); + if (itr.current.value != null) { + metaDataMap[itr.current.key] = itr.current.value.toString(); + } + } return metaDataMap; } diff --git a/packages/at_secondary_server/pubspec.yaml b/packages/at_secondary_server/pubspec.yaml index b40672b63..61f0dee81 100644 --- a/packages/at_secondary_server/pubspec.yaml +++ b/packages/at_secondary_server/pubspec.yaml @@ -1,6 +1,6 @@ name: at_secondary description: Implementation of secondary server. -version: 3.0.52 +version: 3.1.1 repository: https://github.com/atsign-foundation/at_server homepage: https://www.example.com publish_to: none @@ -9,7 +9,7 @@ environment: sdk: '>=3.0.0 <4.0.0' dependencies: - args: 2.5.0 + args: 2.6.0 uuid: 3.0.7 convert: 3.1.1 cron: 0.5.1 @@ -19,17 +19,17 @@ dependencies: basic_utils: 5.7.0 ecdsa: 0.1.0 encrypt: 5.0.3 - at_commons: 5.0.1 + at_commons: 5.1.2 at_utils: 3.0.19 - at_chops: 2.0.1 + at_chops: 2.2.0 at_lookup: 3.0.49 at_server_spec: 5.0.2 at_persistence_spec: 2.0.14 - at_persistence_secondary_server: 3.0.65 + at_persistence_secondary_server: 3.1.0 intl: ^0.19.0 json_annotation: ^4.8.0 version: 3.0.2 - meta: 1.15.0 + meta: 1.16.0 mutex: 3.1.0 yaml: 3.1.2 logging: 1.2.0 @@ -43,7 +43,7 @@ dependency_overrides: dev_dependencies: build_runner: ^2.3.3 - test: ^1.24.4 + test: ^1.25.9 coverage: ^1.6.1 lints: ^5.0.0 mocktail: ^1.0.3 diff --git a/packages/at_secondary_server/test/monitor_verb_test.dart b/packages/at_secondary_server/test/monitor_verb_test.dart index a99eca11b..17ddbe476 100644 --- a/packages/at_secondary_server/test/monitor_verb_test.dart +++ b/packages/at_secondary_server/test/monitor_verb_test.dart @@ -1,6 +1,7 @@ import 'dart:collection'; import 'dart:convert'; +import 'package:at_chops/at_chops.dart'; import 'package:at_commons/at_commons.dart'; import 'package:at_persistence_secondary_server/at_persistence_secondary_server.dart'; import 'package:at_secondary/src/connection/inbound/dummy_inbound_connection.dart'; @@ -105,6 +106,69 @@ void main() { expect(notificationMap['messageType'], 'MessageType.key'); expect(notificationMap['operation'], 'update'); }); + + test( + 'A test to verify publicKeyHash is written on InboundConnection when populated', + () async { + HashMap verbParams = HashMap(); + verbParams[AtConstants.regex] = 'wavi'; + inboundConnection.metaData.isAuthenticated = true; + MonitorVerbHandler monitorVerbHandler = + MonitorVerbHandler(secondaryKeyStore); + await monitorVerbHandler.processVerb( + Response(), verbParams, inboundConnection); + + var atNotification = (AtNotificationBuilder() + ..id = 'abc' + ..fromAtSign = '@bob' + ..notificationDateTime = DateTime.now() + ..toAtSign = alice + ..notification = 'phone.wavi' + ..type = NotificationType.received + ..opType = OperationType.update + ..messageType = MessageType.key + ..atMetaData = (AtMetaData() + ..pubKeyHash = + PublicKeyHash('dummy_hash', HashingAlgoType.sha512.name))) + .build(); + await monitorVerbHandler.processAtNotification(atNotification); + inboundConnection.lastWrittenData = inboundConnection.lastWrittenData + ?.replaceAll('notification:', '') + .trim(); + Map notificationMap = jsonDecode(inboundConnection.lastWrittenData!); + expect(notificationMap['metadata']['pubKeyHash'], + '{"hash":"dummy_hash","hashingAlgo":"sha512"}'); + }); + + test( + 'A test to verify publicKeyHash is set to null on InboundConnection when not populated', + () async { + HashMap verbParams = HashMap(); + verbParams[AtConstants.regex] = 'wavi'; + inboundConnection.metaData.isAuthenticated = true; + MonitorVerbHandler monitorVerbHandler = + MonitorVerbHandler(secondaryKeyStore); + await monitorVerbHandler.processVerb( + Response(), verbParams, inboundConnection); + + var atNotification = (AtNotificationBuilder() + ..id = 'abc' + ..fromAtSign = '@bob' + ..notificationDateTime = DateTime.now() + ..toAtSign = alice + ..notification = 'phone.wavi' + ..type = NotificationType.received + ..opType = OperationType.update + ..messageType = MessageType.key + ..atMetaData = (AtMetaData()..encKeyName = 'encKeyName')) + .build(); + await monitorVerbHandler.processAtNotification(atNotification); + inboundConnection.lastWrittenData = inboundConnection.lastWrittenData + ?.replaceAll('notification:', '') + .trim(); + var notificationMap = jsonDecode(inboundConnection.lastWrittenData!); + expect(notificationMap['metadata']['pubKeyHash'], null); + }); tearDown(() async { await verbTestsTearDown(); AtNotificationCallback.getInstance().callbackMethods.clear(); diff --git a/packages/at_secondary_server/test/sync_unit_test.dart b/packages/at_secondary_server/test/sync_unit_test.dart index 568fcbb4b..a1e8e5608 100644 --- a/packages/at_secondary_server/test/sync_unit_test.dart +++ b/packages/at_secondary_server/test/sync_unit_test.dart @@ -469,6 +469,144 @@ void main() { expect(syncResponse[3]['operation'], '*'); }); + test( + 'test to verify last delete commit entry is sent when skipDeletesUntil flag is set', + () async { + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.put('test_key_1@alice', AtData()..data = 'alice'); + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.remove('test_key_1@alice'); + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.put('test_key_2@alice', AtData()..data = 'alice'); + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.remove('test_key_2@alice'); + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.put('test_key_3@alice', AtData()..data = 'alice'); + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.remove('test_key_3@alice'); + var syncProgressiveVerbHandler = SyncProgressiveVerbHandler( + secondaryPersistenceStore!.getSecondaryKeyStore()!); + var response = Response(); + var inBoundSessionId = '_6665436c-29ff-481b-8dc6-129e89199718'; + var atConnection = InboundConnectionImpl(mockSocket, inBoundSessionId); + atConnection.metaData.isAuthenticated = true; + var syncVerbParams = HashMap(); + syncVerbParams.putIfAbsent(AtConstants.fromCommitSequence, () => '-1'); + syncVerbParams.putIfAbsent('skipDeletesUntil', () => '15'); + await syncProgressiveVerbHandler.processVerb( + response, syncVerbParams, atConnection); + List syncResponse = jsonDecode(response.data!); + var syncResponseList = []; + for (var entry in syncResponse) { + syncResponseList.add(entry['atKey']); + } + expect(syncResponseList.contains('test_key_1@alice'), false); + expect(syncResponseList.contains('test_key_2@alice'), false); + expect(syncResponseList.contains('test_key_3@alice'), true); + }); + + test( + 'test to verify delete commit entries are not sent when skipDeletesUntil flag is set and only matching keys are sent when regex is set', + () async { + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.put('test_key_1.wavi@alice', AtData()..data = 'alice'); + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.remove('test_key_1.wavi@alice'); + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.put('test_key_2.buzz@alice', AtData()..data = 'alice'); + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.remove('test_key_2.buzz@alice'); + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.put('test_key_3.buzz@alice', AtData()..data = 'alice'); + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.put('test_key_4.wavi@alice', AtData()..data = 'alice'); + var syncProgressiveVerbHandler = SyncProgressiveVerbHandler( + secondaryPersistenceStore!.getSecondaryKeyStore()!); + var response = Response(); + var inBoundSessionId = '_6665436c-29ff-481b-8dc6-129e89199718'; + var atConnection = InboundConnectionImpl(mockSocket, inBoundSessionId); + atConnection.metaData.isAuthenticated = true; + var syncVerbParams = HashMap(); + syncVerbParams.putIfAbsent(AtConstants.fromCommitSequence, () => '-1'); + syncVerbParams.putIfAbsent('regex', () => 'buzz'); + syncVerbParams.putIfAbsent('skipDeletesUntil', () => '15'); + await syncProgressiveVerbHandler.processVerb( + response, syncVerbParams, atConnection); + List syncResponse = jsonDecode(response.data!); + var syncResponseList = []; + for (var entry in syncResponse) { + syncResponseList.add(entry['atKey']); + } + expect(syncResponseList.contains('test_key_1.wavi@alice'), false); + expect(syncResponseList.contains('test_key_2.buzz@alice'), false); + expect(syncResponseList.contains('test_key_3.buzz@alice'), true); + expect(syncResponseList.contains('test_key_4.wavi@alice'), false); + }); + + test( + 'test to verify last delete commit entry is NOT sent when skipDeletesUntil flag is set and key does not match regex', + () async { + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.put('test_key_1.wavi@alice', AtData()..data = 'alice'); + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.remove('test_key_1.wavi@alice'); + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.put('test_key_2.buzz@alice', AtData()..data = 'alice'); + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.remove('test_key_2.buzz@alice'); + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.put('test_key_3.buzz@alice', AtData()..data = 'alice'); + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.put('test_key_4.wavi@alice', AtData()..data = 'alice'); + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.put('test_key_5.buzz@alice', AtData()..data = 'alice'); + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.remove('test_key_4.wavi@alice'); + var syncProgressiveVerbHandler = SyncProgressiveVerbHandler( + secondaryPersistenceStore!.getSecondaryKeyStore()!); + var response = Response(); + var inBoundSessionId = '_6665436c-29ff-481b-8dc6-129e89199718'; + var atConnection = InboundConnectionImpl(mockSocket, inBoundSessionId); + atConnection.metaData.isAuthenticated = true; + var syncVerbParams = HashMap(); + syncVerbParams.putIfAbsent(AtConstants.fromCommitSequence, () => '-1'); + syncVerbParams.putIfAbsent('regex', () => 'buzz'); + syncVerbParams.putIfAbsent('skipDeletesUntil', () => '20'); + await syncProgressiveVerbHandler.processVerb( + response, syncVerbParams, atConnection); + List syncResponse = jsonDecode(response.data!); + var syncResponseList = []; + for (var entry in syncResponse) { + syncResponseList.add(entry['atKey']); + } + expect(syncResponseList.contains('test_key_1.wavi@alice'), false); + expect(syncResponseList.contains('test_key_2.buzz@alice'), false); + expect(syncResponseList.contains('test_key_3.buzz@alice'), true); + expect(syncResponseList.contains('test_key_5.buzz@alice'), true); + // last commit entry should not be included since regex doesn't match + expect(syncResponseList.contains('test_key_4.wavi@alice'), false); + }); + test( 'test to verify only entries matching the regex are added to sync response', () async { @@ -925,6 +1063,597 @@ void main() { }); tearDown(() async => await tearDownMethod()); }); + group('A group of tests to verify skip deletes feature', () { + setUp(() async => await setUpMethod()); + test( + 'test to verify delete commit entries are not sent when skipDeletesUntil flag is set', + () async { + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.put('test_key_1@alice', AtData()..data = 'alice'); + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.remove('test_key_1@alice'); + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.put('test_key_2@alice', AtData()..data = 'alice'); + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.remove('test_key_2@alice'); + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.put('test_key_3@alice', AtData()..data = 'alice'); + var syncProgressiveVerbHandler = SyncProgressiveVerbHandler( + secondaryPersistenceStore!.getSecondaryKeyStore()!); + var response = Response(); + var inBoundSessionId = '_6665436c-29ff-481b-8dc6-129e89199718'; + var atConnection = InboundConnectionImpl(mockSocket, inBoundSessionId); + atConnection.metaData.isAuthenticated = true; + var syncVerbParams = HashMap(); + syncVerbParams.putIfAbsent(AtConstants.fromCommitSequence, () => '-1'); + syncVerbParams.putIfAbsent('skipDeletesUntil', () => '15'); + await syncProgressiveVerbHandler.processVerb( + response, syncVerbParams, atConnection); + List syncResponse = jsonDecode(response.data!); + var syncResponseList = []; + for (var entry in syncResponse) { + syncResponseList.add(entry['atKey']); + } + expect( + syncResponseList.contains('test_key_1@alice'), false); //deleted key + expect( + syncResponseList.contains('test_key_2@alice'), false); //deleted key + expect(syncResponseList.contains('test_key_3@alice'), true); + }); + + test( + 'a test to verify sync response with skip deletes when first batch has all deletes', + () async { + for (int i = 0; i < 25; i++) { + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.put('test_key_$i@alice', AtData()..data = 'alice'); + } + for (int i = 0; i < 25; i++) { + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.remove('test_key_$i@alice'); + } + var syncProgressiveVerbHandler = SyncProgressiveVerbHandler( + secondaryPersistenceStore!.getSecondaryKeyStore()!); + var response = Response(); + var inBoundSessionId = '_6665436c-29ff-481b-8dc6-129e89199718'; + var atConnection = InboundConnectionImpl(mockSocket, inBoundSessionId); + atConnection.metaData.isAuthenticated = true; + var syncVerbParams = HashMap(); + syncVerbParams.putIfAbsent(AtConstants.fromCommitSequence, () => '-1'); + syncVerbParams.putIfAbsent('skipDeletesUntil', () => '50'); + await syncProgressiveVerbHandler.processVerb( + response, syncVerbParams, atConnection); + List syncResponse = jsonDecode(response.data!); + Map commitIdMap = {}; + for (var entry in syncResponse) { + commitIdMap[entry['commitId']] = entry['operation']; + } + // deletes from commitID 25-49 should be skipped + for (int i = 25; i < 49; i++) { + expect(commitIdMap.containsKey(i), false); + } + // last delete should be sent + expect(commitIdMap.containsKey(49), true); + expect(commitIdMap[49], '-'); + }); + test( + 'a test to verify sync response with skip deletes when commit log has only deletes in second batch', + () async { + for (int i = 0; i < 25; i++) { + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.put('test_key_$i@alice', AtData()..data = 'alice'); + } + for (int i = 25; i < 50; i++) { + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.put('test_key_$i@alice', AtData()..data = 'alice'); + } + for (int i = 0; i < 25; i++) { + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.remove('test_key_$i@alice'); + } + + var syncProgressiveVerbHandler = SyncProgressiveVerbHandler( + secondaryPersistenceStore!.getSecondaryKeyStore()!); + var response = Response(); + var inBoundSessionId = '_6665436c-29ff-481b-8dc6-129e89199718'; + var atConnection = InboundConnectionImpl(mockSocket, inBoundSessionId); + atConnection.metaData.isAuthenticated = true; + var syncVerbParams = HashMap(); + syncVerbParams.putIfAbsent(AtConstants.fromCommitSequence, () => '-1'); + syncVerbParams.putIfAbsent('skipDeletesUntil', () => '100'); + await syncProgressiveVerbHandler.processVerb( + response, syncVerbParams, atConnection); + List syncResponse = jsonDecode(response.data!); + Map commitIdMap = {}; + for (var entry in syncResponse) { + commitIdMap[entry['commitId']] = entry['operation']; + } + for (int i = 25; i < 50; i++) { + expect(commitIdMap.containsKey(i), true); + expect(commitIdMap[i], '+'); + } + response = Response(); + syncVerbParams[AtConstants.fromCommitSequence] = '49'; + syncVerbParams.putIfAbsent('skipDeletesUntil', () => '100'); + await syncProgressiveVerbHandler.processVerb( + response, syncVerbParams, atConnection); + syncResponse = jsonDecode(response.data!); + for (var entry in syncResponse) { + commitIdMap[entry['commitId']] = entry['operation']; + } + //deletes from commitID 50-74 should be skipped + for (int i = 50; i < 74; i++) { + expect(commitIdMap.containsKey(i), false); + } + // last delete should be sent + expect(commitIdMap.containsKey(74), true); + expect(commitIdMap[74], '-'); + }); + test( + 'a test to verify sync response with skip deletes when last entry in first batch is delete', + () async { + for (int i = 0; i < 25; i++) { + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.put('test_key_$i@alice', AtData()..data = 'alice'); + } + for (int i = 0; i < 1; i++) { + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.remove('test_key_$i@alice'); + } + + var syncProgressiveVerbHandler = SyncProgressiveVerbHandler( + secondaryPersistenceStore!.getSecondaryKeyStore()!); + var response = Response(); + var inBoundSessionId = '_6665436c-29ff-481b-8dc6-129e89199718'; + var atConnection = InboundConnectionImpl(mockSocket, inBoundSessionId); + atConnection.metaData.isAuthenticated = true; + var syncVerbParams = HashMap(); + syncVerbParams.putIfAbsent(AtConstants.fromCommitSequence, () => '-1'); + syncVerbParams.putIfAbsent('skipDeletesUntil', () => '100'); + await syncProgressiveVerbHandler.processVerb( + response, syncVerbParams, atConnection); + List syncResponse = jsonDecode(response.data!); + Map commitIdMap = {}; + for (var entry in syncResponse) { + commitIdMap[entry['commitId']] = entry['operation']; + } + for (int i = 1; i < 25; i++) { + expect(commitIdMap.containsKey(i), true); + expect(commitIdMap[i], '+'); + } + // last delete should be sent + expect(commitIdMap.containsKey(25), true); + expect(commitIdMap[25], '-'); + }); + test( + 'a test to verify sync response - deletes after skipDeletesUntil should be sent', + () async { + for (int i = 0; i < 25; i++) { + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.put('test_key_$i@alice', AtData()..data = 'alice'); + } + for (int i = 0; i < 10; i++) { + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.remove('test_key_$i@alice'); + } + + var syncProgressiveVerbHandler = SyncProgressiveVerbHandler( + secondaryPersistenceStore!.getSecondaryKeyStore()!); + var response = Response(); + var inBoundSessionId = '_6665436c-29ff-481b-8dc6-129e89199718'; + var atConnection = InboundConnectionImpl(mockSocket, inBoundSessionId); + atConnection.metaData.isAuthenticated = true; + var syncVerbParams = HashMap(); + syncVerbParams.putIfAbsent(AtConstants.fromCommitSequence, () => '-1'); + syncVerbParams.putIfAbsent('skipDeletesUntil', () => '30'); + await syncProgressiveVerbHandler.processVerb( + response, syncVerbParams, atConnection); + List syncResponse = jsonDecode(response.data!); + Map commitIdMap = {}; + for (var entry in syncResponse) { + commitIdMap[entry['commitId']] = entry['operation']; + } + for (int i = 10; i < 25; i++) { + expect(commitIdMap.containsKey(i), true); + expect(commitIdMap[i], '+'); + } + // deletes till 30 should not be sent + for (int i = 25; i <= 30; i++) { + expect(commitIdMap.containsKey(i), false); + } + // rest of the deletes should be sent + for (int i = 31; i <= 34; i++) { + expect(commitIdMap.containsKey(i), true); + expect(commitIdMap[i], '-'); + } + }); + test( + 'a test to verify sync response with skip deletes when first entry in second batch is delete', + () async { + for (int i = 0; i < 25; i++) { + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.put('test_key_$i@alice', AtData()..data = 'alice'); + } + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.put('test_key_25@alice', AtData()..data = 'alice'); + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.remove('test_key_25@alice'); + + var syncProgressiveVerbHandler = SyncProgressiveVerbHandler( + secondaryPersistenceStore!.getSecondaryKeyStore()!); + var response = Response(); + var inBoundSessionId = '_6665436c-29ff-481b-8dc6-129e89199718'; + var atConnection = InboundConnectionImpl(mockSocket, inBoundSessionId); + atConnection.metaData.isAuthenticated = true; + var syncVerbParams = HashMap(); + syncVerbParams.putIfAbsent(AtConstants.fromCommitSequence, () => '-1'); + syncVerbParams.putIfAbsent('skipDeletesUntil', () => '50'); + await syncProgressiveVerbHandler.processVerb( + response, syncVerbParams, atConnection); + List syncResponse = jsonDecode(response.data!); + Map commitIdMap = {}; + for (var entry in syncResponse) { + commitIdMap[entry['commitId']] = entry['operation']; + } + for (int i = 0; i < 25; i++) { + expect(commitIdMap.containsKey(i), true); + expect(commitIdMap[i], '+'); + } + //get the next batch + syncVerbParams[AtConstants.fromCommitSequence] = '24'; + syncVerbParams.putIfAbsent('skipDeletesUntil', () => '50'); + response = Response(); + await syncProgressiveVerbHandler.processVerb( + response, syncVerbParams, atConnection); + syncResponse = jsonDecode(response.data!); + for (var entry in syncResponse) { + commitIdMap[entry['commitId']] = entry['operation']; + } + // test_key_25 commitId should not be present in second batch + expect(commitIdMap.containsKey(25), false); + // last delete should be sent + expect(commitIdMap.containsKey(26), true); + expect(commitIdMap[26], '-'); + }); + + test( + 'a test to verify sync response skipDeletesUntil is (last commit ID in batch - 1)', + () async { + for (int i = 0; i < 25; i++) { + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.put('test_key_$i@alice', AtData()..data = 'alice'); + } + for (int i = 0; i < 10; i++) { + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.remove('test_key_$i@alice'); + } + + var syncProgressiveVerbHandler = SyncProgressiveVerbHandler( + secondaryPersistenceStore!.getSecondaryKeyStore()!); + var response = Response(); + var inBoundSessionId = '_6665436c-29ff-481b-8dc6-129e89199718'; + var atConnection = InboundConnectionImpl(mockSocket, inBoundSessionId); + atConnection.metaData.isAuthenticated = true; + var syncVerbParams = HashMap(); + syncVerbParams.putIfAbsent(AtConstants.fromCommitSequence, () => '-1'); + syncVerbParams.putIfAbsent('skipDeletesUntil', () => '33'); + await syncProgressiveVerbHandler.processVerb( + response, syncVerbParams, atConnection); + List syncResponse = jsonDecode(response.data!); + Map commitIdMap = {}; + for (var entry in syncResponse) { + commitIdMap[entry['commitId']] = entry['operation']; + } + for (int i = 10; i < 25; i++) { + expect(commitIdMap.containsKey(i), true); + expect(commitIdMap[i], '+'); + } + // deletes till 33 should not be sent + for (int i = 25; i <= 33; i++) { + expect(commitIdMap.containsKey(i), false); + } + // last delete should be sent + expect(commitIdMap.containsKey(34), true); + expect(commitIdMap[34], '-'); + }); + test( + 'a test to verify sync response skipDeletesUntil is (last commit ID in batch)', + () async { + for (int i = 0; i < 25; i++) { + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.put('test_key_$i@alice', AtData()..data = 'alice'); + } + for (int i = 0; i < 10; i++) { + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.remove('test_key_$i@alice'); + } + + var syncProgressiveVerbHandler = SyncProgressiveVerbHandler( + secondaryPersistenceStore!.getSecondaryKeyStore()!); + var response = Response(); + var inBoundSessionId = '_6665436c-29ff-481b-8dc6-129e89199718'; + var atConnection = InboundConnectionImpl(mockSocket, inBoundSessionId); + atConnection.metaData.isAuthenticated = true; + var syncVerbParams = HashMap(); + syncVerbParams.putIfAbsent(AtConstants.fromCommitSequence, () => '-1'); + syncVerbParams.putIfAbsent('skipDeletesUntil', () => '34'); + await syncProgressiveVerbHandler.processVerb( + response, syncVerbParams, atConnection); + List syncResponse = jsonDecode(response.data!); + Map commitIdMap = {}; + for (var entry in syncResponse) { + commitIdMap[entry['commitId']] = entry['operation']; + } + for (int i = 10; i < 25; i++) { + expect(commitIdMap.containsKey(i), true); + expect(commitIdMap[i], '+'); + } + // deletes till 33 should not be sent + for (int i = 25; i <= 33; i++) { + expect(commitIdMap.containsKey(i), false); + } + // last delete should be sent even though skipDeleteUntil is 34 + expect(commitIdMap.containsKey(34), true); + expect(commitIdMap[34], '-'); + }); + test( + 'a test to verify sync response skipDeletesUntil is (last commit ID in batch + 1)', + () async { + for (int i = 0; i < 25; i++) { + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.put('test_key_$i@alice', AtData()..data = 'alice'); + } + for (int i = 0; i < 10; i++) { + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.remove('test_key_$i@alice'); + } + for (int i = 25; i < 30; i++) { + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.put('test_key_$i@alice', AtData()..data = 'alice'); + } + + var syncProgressiveVerbHandler = SyncProgressiveVerbHandler( + secondaryPersistenceStore!.getSecondaryKeyStore()!); + var response = Response(); + var inBoundSessionId = '_6665436c-29ff-481b-8dc6-129e89199718'; + var atConnection = InboundConnectionImpl(mockSocket, inBoundSessionId); + atConnection.metaData.isAuthenticated = true; + var syncVerbParams = HashMap(); + syncVerbParams.putIfAbsent(AtConstants.fromCommitSequence, () => '-1'); + syncVerbParams.putIfAbsent('skipDeletesUntil', () => '36'); + await syncProgressiveVerbHandler.processVerb( + response, syncVerbParams, atConnection); + List syncResponse = jsonDecode(response.data!); + Map commitIdMap = {}; + for (var entry in syncResponse) { + commitIdMap[entry['commitId']] = entry['operation']; + } + for (int i = 10; i < 25; i++) { + expect(commitIdMap.containsKey(i), true); + expect(commitIdMap[i], '+'); + } + // deletes till 34 should not be sent + for (int i = 25; i <= 34; i++) { + expect(commitIdMap.containsKey(i), false); + } + //updates from 35-39 should be sent + for (int i = 35; i <= 39; i++) { + expect(commitIdMap.containsKey(i), true); + expect(commitIdMap[i], '+'); + } + }); + test( + 'a test to verify sync response when skipDeletesUntil is accidentally set to a higher value greater than latest server commit id', + () async { + //1. creates commitId from 0-29 + for (int i = 0; i < 30; i++) { + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.put('test_key_$i@alice', AtData()..data = 'alice'); + } + //2. 10-29 commit id will be updates. 30-39 will be deletes since only one key entry will be maintained for update followed by delete. + for (int i = 0; i < 10; i++) { + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.remove('test_key_$i@alice'); + } + var syncProgressiveVerbHandler = SyncProgressiveVerbHandler( + secondaryPersistenceStore!.getSecondaryKeyStore()!); + var response = Response(); + var inBoundSessionId = '_6665436c-29ff-481b-8dc6-129e89199718'; + var atConnection = InboundConnectionImpl(mockSocket, inBoundSessionId); + atConnection.metaData.isAuthenticated = true; + var syncVerbParams = HashMap(); + syncVerbParams.putIfAbsent(AtConstants.fromCommitSequence, () => '-1'); + syncVerbParams.putIfAbsent( + 'skipDeletesUntil', () => '100'); //set to a higher value + await syncProgressiveVerbHandler.processVerb( + response, syncVerbParams, atConnection); + List syncResponse = jsonDecode(response.data!); + var syncResponseList = []; + var commitIdList = []; + for (var entry in syncResponse) { + syncResponseList.add(entry); + commitIdList.add(entry['commitId']); + } + // should contain updates from commit id 10-29 and last commit id 39 which is delete. 30-38 will be skipped. + // assert all update commits are present + for (int i = 10; i < 30; i++) { + expect(commitIdList.contains(i), true); + } + // assert deletes in between are skipped + for (int i = 30; i < 39; i++) { + expect(commitIdList.contains(i), false); + } + // assert last delete is present + expect(commitIdList.contains(39), true); + + expect(syncResponseList[0]['atKey'], 'test_key_10@alice'); + expect(syncResponseList[0]['operation'], '+'); + expect(syncResponseList[0]['commitId'], 10); + expect(syncResponseList[19]['atKey'], 'test_key_29@alice'); + expect(syncResponseList[19]['operation'], '+'); + expect(syncResponseList[19]['commitId'], 29); + expect(syncResponseList[20]['atKey'], 'test_key_9@alice'); + expect(syncResponseList[20]['operation'], '-'); + expect(syncResponseList[20]['commitId'], 39); + }); + test( + 'a test to verify sync response with skip deletes when commit log has less entries than default entry buffer count(25)', + () async { + for (int i = 0; i < 10; i++) { + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.put('test_key_$i@alice', AtData()..data = 'alice'); + } + for (int i = 0; i < 5; i++) { + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.remove('test_key_$i@alice'); + } + var syncProgressiveVerbHandler = SyncProgressiveVerbHandler( + secondaryPersistenceStore!.getSecondaryKeyStore()!); + var response = Response(); + var inBoundSessionId = '_6665436c-29ff-481b-8dc6-129e89199718'; + var atConnection = InboundConnectionImpl(mockSocket, inBoundSessionId); + atConnection.metaData.isAuthenticated = true; + var syncVerbParams = HashMap(); + syncVerbParams.putIfAbsent(AtConstants.fromCommitSequence, () => '-1'); + syncVerbParams.putIfAbsent('skipDeletesUntil', () => '15'); + await syncProgressiveVerbHandler.processVerb( + response, syncVerbParams, atConnection); + List syncResponse = jsonDecode(response.data!); + var syncResponseList = []; + var commitIdList = []; + for (var entry in syncResponse) { + syncResponseList.add(entry); + commitIdList.add(entry['commitId']); + } + + // update commits from 5-9 should be present + for (int i = 5; i < 10; i++) { + expect(commitIdList.contains(i), true); + } + // delete commits from 10-13 should be skipped + for (int i = 10; i < 14; i++) { + expect(commitIdList.contains(i), false); + } + + // last commit delete should be present + expect(commitIdList.contains(14), true); + }); + test( + 'a test to verify sync response with skip deletes when commit log has greater entries than default entry buffer count(25)', + () async { + for (int i = 0; i < 10; i++) { + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.put('test_key_$i@alice', AtData()..data = 'alice'); + } + for (int i = 0; i < 5; i++) { + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.remove('test_key_$i@alice'); + } + for (int i = 10; i < 20; i++) { + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.put('test_key_$i@alice', AtData()..data = 'alice'); + } + for (int i = 10; i < 15; i++) { + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.remove('test_key_$i@alice'); + } + for (int i = 20; i < 50; i++) { + await secondaryPersistenceStore! + .getSecondaryKeyStore() + ?.put('test_key_$i@alice', AtData()..data = 'alice'); + } + + var syncProgressiveVerbHandler = SyncProgressiveVerbHandler( + secondaryPersistenceStore!.getSecondaryKeyStore()!); + var response = Response(); + var inBoundSessionId = '_6665436c-29ff-481b-8dc6-129e89199718'; + var atConnection = InboundConnectionImpl(mockSocket, inBoundSessionId); + atConnection.metaData.isAuthenticated = true; + var syncVerbParams = HashMap(); + syncVerbParams.putIfAbsent(AtConstants.fromCommitSequence, () => '-1'); + syncVerbParams.putIfAbsent('skipDeletesUntil', () => '70'); + await syncProgressiveVerbHandler.processVerb( + response, syncVerbParams, atConnection); + List syncResponse = jsonDecode(response.data!); + Map commitIdMap = {}; + for (var entry in syncResponse) { + commitIdMap[entry['commitId']] = entry['operation']; + } + // commit log will have + // 5-9 updates + // 10-14 deletes + // 20-24 updates + // 25-29 deletes + // 30-59 updates + // total entries in commit log 50 + for (int i = 5; i < 10; i++) { + expect(commitIdMap.containsKey(i), true); + expect(commitIdMap[i], '+'); + } + for (int i = 10; i < 15; i++) { + expect(commitIdMap.containsKey(i), false); + } + for (int i = 20; i < 25; i++) { + expect(commitIdMap.containsKey(i), true); + expect(commitIdMap[i], '+'); + } + for (int i = 25; i < 30; i++) { + expect(commitIdMap.containsKey(i), false); + } + for (int i = 30; i < 45; i++) { + expect(commitIdMap.containsKey(i), true); + expect(commitIdMap[i], '+'); + } + // get the remaining updates in next sync batch + syncVerbParams[AtConstants.fromCommitSequence] = '44'; + syncVerbParams.putIfAbsent('skipDeletesUntil', () => '70'); + response = Response(); + await syncProgressiveVerbHandler.processVerb( + response, syncVerbParams, atConnection); + syncResponse = jsonDecode(response.data!); + for (var entry in syncResponse) { + commitIdMap[entry['commitId']] = entry['operation']; + } + for (int i = 45; i < 60; i++) { + expect(commitIdMap.containsKey(i), true); + expect(commitIdMap[i], '+'); + } + }); + + tearDown(() async => await tearDownMethod()); + }); }); } diff --git a/packages/at_secondary_server/test/sync_verb_test.dart b/packages/at_secondary_server/test/sync_verb_test.dart index bbae7fc34..57ac9bb0a 100644 --- a/packages/at_secondary_server/test/sync_verb_test.dart +++ b/packages/at_secondary_server/test/sync_verb_test.dart @@ -2,16 +2,18 @@ import 'dart:collection'; import 'dart:convert'; import 'dart:io'; +import 'package:at_chops/at_chops.dart'; import 'package:at_commons/at_commons.dart'; import 'package:at_persistence_secondary_server/at_persistence_secondary_server.dart'; import 'package:at_secondary/src/connection/inbound/inbound_connection_impl.dart'; +import 'package:at_secondary/src/server/at_secondary_config.dart'; import 'package:at_secondary/src/server/at_secondary_impl.dart'; import 'package:at_secondary/src/utils/handler_util.dart'; import 'package:at_secondary/src/utils/secondary_util.dart'; import 'package:at_secondary/src/verb/handler/sync_progressive_verb_handler.dart'; import 'package:at_server_spec/at_verb_spec.dart'; -import 'package:test/test.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:test/test.dart'; import 'test_utils.dart'; @@ -115,7 +117,10 @@ void main() { ..ttb = 1000 ..ttr = 100 ..isBinary = false - ..encoding = 'base64')); + ..encoding = 'base64' + ..pubKeyHash = + PublicKeyHash('dummy_hash', HashingAlgoType.sha512.name) + ..pubKeyCS = 'dummy_pub_key_cs')); verbHandler = SyncProgressiveVerbHandler(keyStoreManager.getKeyStore()); var response = Response(); @@ -136,6 +141,9 @@ void main() { expect(syncResponseMap['metadata']['ttr'], '100'); expect(syncResponseMap['metadata']['isBinary'], 'false'); expect(syncResponseMap['metadata']['encoding'], 'base64'); + expect(syncResponseMap['metadata']['pubKeyCS'], 'dummy_pub_key_cs'); + expect(syncResponseMap['metadata']['pubKeyHash'], + '{"hash":"dummy_hash","hashingAlgo":"sha512"}'); }); when(() => mockKeyStore.isKeyExists(any())).thenReturn(true); @@ -154,8 +162,8 @@ void main() { assert(atCommitLog.entriesCount() > 0); List syncResponse = []; - await verbHandler.prepareResponse( - 0, syncResponse, atCommitLog.getEntries(0)); + await verbHandler.prepareResponse(0, AtSecondaryConfig.syncPageLimit, + syncResponse, atCommitLog.getEntries(0)); expect(syncResponse.length, 1); expect(syncResponse[0].key, 'test_key_alpha@alice'); }); @@ -185,19 +193,19 @@ void main() { // Since syncResponse already has an entry, and the 'capacity' is 0, then the next entry // should not be added to the syncResponse - await verbHandler.prepareResponse( - 0, syncResponse, atCommitLog.getEntries(0)); + await verbHandler.prepareResponse(0, AtSecondaryConfig.syncPageLimit, + syncResponse, atCommitLog.getEntries(0)); expect(syncResponse, [entry]); syncResponse.clear(); - await verbHandler.prepareResponse( - 0, syncResponse, atCommitLog.getEntries(0)); + await verbHandler.prepareResponse(0, AtSecondaryConfig.syncPageLimit, + syncResponse, atCommitLog.getEntries(0)); expect(syncResponse.length, 1); expect(syncResponse[0].key, 'test_key_alpha@alice'); syncResponse.clear(); - await verbHandler.prepareResponse( - 0, syncResponse, atCommitLog.getEntries(1)); + await verbHandler.prepareResponse(0, AtSecondaryConfig.syncPageLimit, + syncResponse, atCommitLog.getEntries(1)); expect(syncResponse.length, 1); expect(syncResponse[0].key, 'test_key2_beta@alice'); }); @@ -229,7 +237,10 @@ void main() { syncResponse.add(entry); await verbHandler.prepareResponse( - 10 * 1024 * 1024, syncResponse, atCommitLog.getEntries(0)); + 10 * 1024 * 1024, + AtSecondaryConfig.syncPageLimit, + syncResponse, + atCommitLog.getEntries(0)); // Expecting that all the entries in the commitLog have been // added to syncResponse @@ -255,24 +266,151 @@ void main() { assert(atCommitLog.entriesCount() == 2); List syncResponse = []; - await verbHandler.prepareResponse( - 0, syncResponse, atCommitLog.getEntries(0)); + await verbHandler.prepareResponse(0, AtSecondaryConfig.syncPageLimit, + syncResponse, atCommitLog.getEntries(0)); expect(syncResponse.length, 1); expect(syncResponse[0].key, 'test_key1@alice'); syncResponse.clear(); - await verbHandler.prepareResponse( - 0, syncResponse, atCommitLog.getEntries(1)); + await verbHandler.prepareResponse(0, AtSecondaryConfig.syncPageLimit, + syncResponse, atCommitLog.getEntries(1)); expect(syncResponse.length, 1); expect(syncResponse[0].key, 'test_key2@alice'); // test with empty iterator syncResponse.clear(); await verbHandler.prepareResponse( - 10 * 1024 * 1024, syncResponse, atCommitLog.getEntries(2)); + 10 * 1024 * 1024, + AtSecondaryConfig.syncPageLimit, + syncResponse, + atCommitLog.getEntries(2)); expect(syncResponse.length, 0); }); + test( + 'A test to verify sync returns default number of entries when limit is not passed', + () async { + // Add data to commit log + var atCommitLog = + await AtCommitLogManagerImpl.getInstance().getCommitLog('@alice'); + await atCommitLog?.commit('phone.wavi@alice', CommitOp.UPDATE); + //Add data to keystore + var secondaryKeyStore = SecondaryPersistenceStoreFactory.getInstance() + .getSecondaryPersistenceStore('@alice'); + var metadata = (AtMetaData() + ..ttl = 10000 + ..ttb = 1000 + ..ttr = 100 + ..isBinary = false + ..encoding = 'base64' + ..pubKeyHash = PublicKeyHash('dummy_hash', HashingAlgoType.sha512.name) + ..pubKeyCS = 'dummy_pub_key_cs'); + for (int i = 1; i <= 40; i++) { + await secondaryKeyStore?.getSecondaryKeyStore()?.put( + 'random_$i.wavi@alice', + AtData() + ..data = i.toString() + ..metaData = metadata); + } + + verbHandler = SyncProgressiveVerbHandler(keyStoreManager.getKeyStore()); + var response = Response(); + var verbParams = HashMap(); + verbParams.putIfAbsent(AtConstants.fromCommitSequence, () => '0'); + var inBoundSessionId = '123'; + var atConnection = InboundConnectionImpl(mockSocket, inBoundSessionId); + await verbHandler.processVerb(response, verbParams, atConnection); + + var syncResponseList = jsonDecode(response.data!); + expect(syncResponseList.length, 25); + for (int i = 0; i < syncResponseList.length; i++) { + expect(syncResponseList[i]['atKey'], 'random_${i + 1}.wavi@alice'); + } + }); + + test( + 'A test to verify sync returns correct number of entries when limit (less than default size) is passed', + () async { + // Add data to commit log + var atCommitLog = + await AtCommitLogManagerImpl.getInstance().getCommitLog('@alice'); + await atCommitLog?.commit('phone.wavi@alice', CommitOp.UPDATE); + //Add data to keystore + var secondaryKeyStore = SecondaryPersistenceStoreFactory.getInstance() + .getSecondaryPersistenceStore('@alice'); + var metadata = (AtMetaData() + ..ttl = 10000 + ..ttb = 1000 + ..ttr = 100 + ..isBinary = false + ..encoding = 'base64' + ..pubKeyHash = PublicKeyHash('dummy_hash', HashingAlgoType.sha512.name) + ..pubKeyCS = 'dummy_pub_key_cs'); + for (int i = 1; i <= 40; i++) { + await secondaryKeyStore?.getSecondaryKeyStore()?.put( + 'random_$i.wavi@alice', + AtData() + ..data = i.toString() + ..metaData = metadata); + } + + verbHandler = SyncProgressiveVerbHandler(keyStoreManager.getKeyStore()); + var response = Response(); + var verbParams = HashMap(); + verbParams.putIfAbsent(AtConstants.fromCommitSequence, () => '0'); + verbParams.putIfAbsent(AtConstants.syncLimit, () => '12'); + var inBoundSessionId = '123'; + var atConnection = InboundConnectionImpl(mockSocket, inBoundSessionId); + await verbHandler.processVerb(response, verbParams, atConnection); + + var syncResponseList = jsonDecode(response.data!); + expect(syncResponseList.length, 12); + for (int i = 0; i < syncResponseList.length; i++) { + expect(syncResponseList[i]['atKey'], 'random_${i + 1}.wavi@alice'); + } + }); + test( + 'A test to verify sync returns correct number of entries when limit (greater than default size) is passed', + () async { + // Add data to commit log + var atCommitLog = + await AtCommitLogManagerImpl.getInstance().getCommitLog('@alice'); + await atCommitLog?.commit('phone.wavi@alice', CommitOp.UPDATE); + //Add data to keystore + var secondaryKeyStore = SecondaryPersistenceStoreFactory.getInstance() + .getSecondaryPersistenceStore('@alice'); + var metadata = (AtMetaData() + ..ttl = 10000 + ..ttb = 1000 + ..ttr = 100 + ..isBinary = false + ..encoding = 'base64' + ..pubKeyHash = PublicKeyHash('dummy_hash', HashingAlgoType.sha512.name) + ..pubKeyCS = 'dummy_pub_key_cs'); + for (int i = 1; i <= 40; i++) { + await secondaryKeyStore?.getSecondaryKeyStore()?.put( + 'random_$i.wavi@alice', + AtData() + ..data = i.toString() + ..metaData = metadata); + } + + verbHandler = SyncProgressiveVerbHandler(keyStoreManager.getKeyStore()); + var response = Response(); + var verbParams = HashMap(); + verbParams.putIfAbsent(AtConstants.fromCommitSequence, () => '0'); + verbParams.putIfAbsent(AtConstants.syncLimit, () => '35'); + var inBoundSessionId = '123'; + var atConnection = InboundConnectionImpl(mockSocket, inBoundSessionId); + await verbHandler.processVerb(response, verbParams, atConnection); + + var syncResponseList = jsonDecode(response.data!); + expect(syncResponseList.length, 35); + for (int i = 0; i < syncResponseList.length; i++) { + expect(syncResponseList[i]['atKey'], 'random_${i + 1}.wavi@alice'); + } + }); + tearDown(() async => await tearDownFunc()); }); } diff --git a/tests/at_end2end_test/pubspec.yaml b/tests/at_end2end_test/pubspec.yaml index d8b2fb94d..d577e21dd 100644 --- a/tests/at_end2end_test/pubspec.yaml +++ b/tests/at_end2end_test/pubspec.yaml @@ -9,9 +9,10 @@ environment: dependencies: encrypt: 5.0.3 at_demo_data: ^1.0.3 - at_lookup: ^3.0.48 + at_lookup: ^3.0.49 + at_commons: ^5.1.2 dev_dependencies: - lints: ^1.0.1 - test: ^1.14.4 + lints: ^5.0.0 + test: ^1.25.9 version: ^3.0.2 diff --git a/tests/at_end2end_test/test/lookup_verb_test.dart b/tests/at_end2end_test/test/lookup_verb_test.dart index 831f03498..14dc155d1 100644 --- a/tests/at_end2end_test/test/lookup_verb_test.dart +++ b/tests/at_end2end_test/test/lookup_verb_test.dart @@ -1,6 +1,9 @@ +import 'dart:convert'; import 'dart:math'; import 'package:test/test.dart'; +import 'package:uuid/uuid.dart'; +import 'package:version/version.dart'; import 'e2e_test_utils.dart' as e2e; @@ -34,16 +37,54 @@ void main() { ///Update verb on bob atsign var lastValue = Random().nextInt(5); var value = 'Q7878R$lastValue'; - await sh1.writeCommand('update:$atSign_2:special-code$atSign_1 $value'); + var randomId = Uuid().v4(); + await sh1.writeCommand( + 'update:$atSign_2:special-code-$randomId$atSign_1 $value'); String response = await sh1.read(); print('update verb response : $response'); assert( (!response.contains('Invalid syntax')) && (!response.contains('null'))); ///lookup verb alice atsign - await sh2.writeCommand('lookup:special-code$atSign_1'); + await sh2.writeCommand('lookup:special-code-$randomId$atSign_1'); response = await sh2.read(timeoutMillis: 4000); print('lookup verb response : $response'); expect(response, contains('data:$value')); }, timeout: Timeout(Duration(minutes: 3))); + + test('A test to verify lookup metadata contains public key hash value', + () async { + Version atSign1ServerVersion = Version.parse(await sh1.getVersion()); + if (atSign1ServerVersion < Version(3, 1, 1)) { + print( + 'Found $atSign_1 with server version: $atSign1ServerVersion. This test is only applicable for server version least 3.1.1. Skipping the test'); + return; + } + Version atSign2ServerVersion = Version.parse(await sh2.getVersion()); + if (atSign2ServerVersion < Version(3, 1, 1)) { + print( + 'Found $atSign_2 with server version: $atSign2ServerVersion. This test is only applicable for server version least 3.1.1. Skipping the test'); + return; + } + var lastValue = Random().nextInt(5); + var randomHashValue = Uuid().v4().hashCode; + var value = 'Q7878R$lastValue'; + await sh1.writeCommand( + 'update:pubKeyHash:hashedValue-$randomHashValue:hashingAlgo:sha512:$atSign_2:special-code-$randomHashValue$atSign_1 $value'); + String response = await sh1.read(); + assert( + (!response.contains('Invalid syntax')) && (!response.contains('null'))); + + ///lookup verb alice atsign + await sh2.writeCommand('lookup:all:special-code-$randomHashValue$atSign_1'); + response = await sh2.read(timeoutMillis: 4000); + response = response.replaceAll('data:', ''); + var decodedResponse = jsonDecode(response); + expect(decodedResponse['key'], + '$atSign_2:special-code-$randomHashValue$atSign_1'); + expect(decodedResponse['metaData']['pubKeyHash']['hash'], + 'hashedValue-$randomHashValue'); + expect(decodedResponse['metaData']['pubKeyHash']['hashingAlgo'], 'sha512'); + expect(decodedResponse['data'], value); + }); } diff --git a/tests/at_functional_test/pubspec.yaml b/tests/at_functional_test/pubspec.yaml index 3bb74939e..44cd762c8 100644 --- a/tests/at_functional_test/pubspec.yaml +++ b/tests/at_functional_test/pubspec.yaml @@ -9,13 +9,14 @@ environment: dependencies: hex: ^0.2.0 at_demo_data: ^1.1.0 - at_chops: ^2.0.0 - at_lookup: ^3.0.48 - at_commons: ^4.1.2 + at_chops: ^2.2.0 + at_lookup: ^3.0.49 + at_commons: ^5.1.2 uuid: ^3.0.7 elliptic: ^0.3.8 + dev_dependencies: - lints: ^4.0.0 - test: ^1.25.8 + lints: ^5.0.0 + test: ^1.25.9 version: ^3.0.2 diff --git a/tests/at_functional_test/test/enroll_verb_test.dart b/tests/at_functional_test/test/enroll_verb_test.dart index 004a31a10..aea2bf7fd 100644 --- a/tests/at_functional_test/test/enroll_verb_test.dart +++ b/tests/at_functional_test/test/enroll_verb_test.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:io'; +import 'package:at_chops/at_chops.dart'; import 'package:at_commons/at_commons.dart'; import 'package:at_demo_data/at_demo_data.dart' as at_demos; import 'package:at_demo_data/at_demo_data.dart'; @@ -274,35 +275,6 @@ void main() { expect(llookupResponseMap['errorCode'], 'AT0009'); expect(llookupResponseMap['errorDescription'], 'UnAuthorized client in request : Connection with enrollment ID $enrollmentId is not authorized to llookup key: $enrollmentKey'); - - // keys:get:self should return default self encryption key - var selfKey = '$enrollmentId.default_self_enc_key.__manage$firstAtSign'; - String selfKeyResponse = - await socketConnection2.sendRequestToServer('keys:get:self'); - expect(selfKeyResponse.contains(selfKey), true); - - // keys:get:private should return private encryption key - var privateKey = - '$enrollmentId.default_enc_private_key.__manage$firstAtSign'; - String privateKeyResponse = - await socketConnection2.sendRequestToServer('keys:get:private'); - expect(privateKeyResponse.contains(privateKey), true); - - // keys:get:keyName should return the enrollment key with __manage namespace - String selfKeyGetResponse = await socketConnection2 - .sendRequestToServer('keys:get:keyName:$selfKey'); - expect( - selfKeyGetResponse - .contains('${apkamEncryptedKeysMap['encryptedSelfEncKey']}'), - true); - - // keys:get:keyName should return the enrollment key with __manage namespace - String privateKeyGetResponse = await socketConnection2 - .sendRequestToServer('keys:get:keyName:$privateKey'); - expect( - privateKeyGetResponse.contains( - '${apkamEncryptedKeysMap['encryptedDefaultEncPrivateKey']}'), - true); }); test( @@ -387,8 +359,12 @@ void main() { var secondEnrollId = enrollJson['enrollmentId']; // connect to the first client to approve the enroll request + final encryptionPrivateKeyIV = + base64Encode(AtChopsUtil.generateRandomIV(16).ivBytes); + final selfEncryptionKeyIV = + base64Encode(AtChopsUtil.generateRandomIV(16).ivBytes); String approveResponse = (await firstAtSignConnection.sendRequestToServer( - 'enroll:approve:{"enrollmentId":"$secondEnrollId","encryptedDefaultEncryptionPrivateKey":"${apkamEncryptedKeysMap['encryptedDefaultEncPrivateKey']}","encryptedDefaultSelfEncryptionKey": "${apkamEncryptedKeysMap['encryptedSelfEncKey']}"}')) + 'enroll:approve:{"enrollmentId":"$secondEnrollId","encryptedDefaultEncryptionPrivateKey":"${apkamEncryptedKeysMap['encryptedDefaultEncPrivateKey']}","encPrivateKeyIV":"$encryptionPrivateKeyIV","encryptedDefaultSelfEncryptionKey": "${apkamEncryptedKeysMap['encryptedSelfEncKey']}","selfEncKeyIV":"$selfEncryptionKeyIV"}')) .replaceFirst('data:', ''); var approveJson = jsonDecode(approveResponse); expect(approveJson['status'], 'approved'); @@ -406,12 +382,27 @@ void main() { await socketConnection2.sendRequestToServer('keys:get:self'); expect(selfKeyResponse.contains(selfKey), true); + String selfKeyGetResponse = await socketConnection2 + .sendRequestToServer('keys:get:keyName:$selfKey'); + selfKeyGetResponse = selfKeyGetResponse.replaceFirst('data:', ''); + var selfKeyResponseJson = jsonDecode(selfKeyGetResponse); + expect(selfKeyResponseJson['value'], + apkamEncryptedKeysMap['encryptedSelfEncKey']); + expect(selfKeyResponseJson['iv'], selfEncryptionKeyIV); + // keys:get:private should return private encryption key var privateKey = '$secondEnrollId.default_enc_private_key.__manage$firstAtSign'; String privateKeyResponse = await socketConnection2.sendRequestToServer('keys:get:private'); expect(privateKeyResponse.contains(privateKey), true); + String privateKeyGetResponse = await socketConnection2 + .sendRequestToServer('keys:get:keyName:$privateKey'); + privateKeyGetResponse = privateKeyGetResponse.replaceFirst('data:', ''); + var privateKeyResponseJson = jsonDecode(privateKeyGetResponse); + expect(privateKeyResponseJson['value'], + apkamEncryptedKeysMap['encryptedDefaultEncPrivateKey']); + expect(privateKeyResponseJson['iv'], encryptionPrivateKeyIV); }); test( diff --git a/tests/at_functional_test/test/sync_verb_test.dart b/tests/at_functional_test/test/sync_verb_test.dart index 98a1ed35f..062889bac 100644 --- a/tests/at_functional_test/test/sync_verb_test.dart +++ b/tests/at_functional_test/test/sync_verb_test.dart @@ -70,6 +70,26 @@ void main() { assert((response.contains('Invalid syntax'))); }); + test('A test to verify publicKeyHash in set in sync response', () async { + String namespace = '.func.test'; + String randomId = Uuid().v4(); + + var response = await firstAtSignConnection.sendRequestToServer( + 'update:pubKeyHash:dummy_hash:hashingAlgo:sha512:$secondAtSign:twitter-$randomId$namespace$firstAtSign bob_tweet'); + assert( + (!response.contains('Invalid syntax')) && (!response.contains('null'))); + String commitId = response.replaceAll('data:', ''); + int syncId = int.parse(commitId); + // sync with regex + response = await firstAtSignConnection + .sendRequestToServer('sync:from:${syncId - 1}:limit:5:$namespace'); + response = response.replaceAll('data:', ''); + var responseMap = jsonDecode(response); + var publicKeyHashMap = jsonDecode(responseMap[0]['metadata']['pubKeyHash']); + expect(publicKeyHashMap['hash'], 'dummy_hash'); + expect(publicKeyHashMap['hashingAlgo'], 'sha512'); + }); + group('A group of tests to verify sync entries', () { late OutboundConnectionFactory authenticatedSocket = OutboundConnectionFactory(); diff --git a/tools/build_secondary/Dockerfile b/tools/build_secondary/Dockerfile index 54c081e66..9b3da4e87 100644 --- a/tools/build_secondary/Dockerfile +++ b/tools/build_secondary/Dockerfile @@ -1,4 +1,4 @@ -FROM dart:3.5.4@sha256:6489a269e878813bf5a8fe66256c1a3e552d3a3c53eef9c1b224e2d8d737ca6d AS buildimage +FROM dart:3.6.0@sha256:b677df29e01bca3a2d5bdaffe204498834d56a4ed38ff56c0ac6a42d6541b58e AS buildimage ENV HOMEDIR=/atsign ENV USER_ID=1024 ENV GROUP_ID=1024 diff --git a/tools/build_secondary/Dockerfile.observe b/tools/build_secondary/Dockerfile.observe index e0c3faf0c..52a2328f9 100644 --- a/tools/build_secondary/Dockerfile.observe +++ b/tools/build_secondary/Dockerfile.observe @@ -1,4 +1,4 @@ -FROM dart:3.5.4@sha256:6489a269e878813bf5a8fe66256c1a3e552d3a3c53eef9c1b224e2d8d737ca6d AS buildimage +FROM dart:3.6.0@sha256:b677df29e01bca3a2d5bdaffe204498834d56a4ed38ff56c0ac6a42d6541b58e AS buildimage ENV HOMEDIR=/atsign ENV USER_ID=1024 ENV GROUP_ID=1024 diff --git a/tools/build_virtual_environment/ve/Dockerfile.vip b/tools/build_virtual_environment/ve/Dockerfile.vip index 9645a5b77..5bb7281f3 100644 --- a/tools/build_virtual_environment/ve/Dockerfile.vip +++ b/tools/build_virtual_environment/ve/Dockerfile.vip @@ -1,4 +1,4 @@ -FROM dart:3.5.4@sha256:6489a269e878813bf5a8fe66256c1a3e552d3a3c53eef9c1b224e2d8d737ca6d AS buildimage +FROM dart:3.6.0@sha256:b677df29e01bca3a2d5bdaffe204498834d56a4ed38ff56c0ac6a42d6541b58e AS buildimage ENV USER_ID=1024 ENV GROUP_ID=1024 WORKDIR /app diff --git a/tools/build_virtual_environment/ve_base/Dockerfile b/tools/build_virtual_environment/ve_base/Dockerfile index 39aa6e997..822c3c165 100644 --- a/tools/build_virtual_environment/ve_base/Dockerfile +++ b/tools/build_virtual_environment/ve_base/Dockerfile @@ -1,4 +1,4 @@ -FROM dart:3.5.4@sha256:6489a269e878813bf5a8fe66256c1a3e552d3a3c53eef9c1b224e2d8d737ca6d AS buildimage +FROM dart:3.6.0@sha256:b677df29e01bca3a2d5bdaffe204498834d56a4ed38ff56c0ac6a42d6541b58e AS buildimage ENV USER_ID=1024 ENV GROUP_ID=1024 WORKDIR /app @@ -17,7 +17,7 @@ RUN \ dart pub update ; \ dart compile exe bin/install_PKAM_Keys.dart -o install_PKAM_Keys -FROM debian:stable-20241111-slim@sha256:32f6d6f046ee9b4c31b359e695a1f0174e85846148f058f3fecad9233e88ff6a +FROM debian:stable-20241202-slim@sha256:4d63ef53faef7bd35c92fbefb1e9e2e7b6777e3cbec6c34f640e96b925e430eb # was debian:stable-20221114-slim USER root diff --git a/tools/build_virtual_environment/ve_base/contents/atsign/root/certs/cert.pem b/tools/build_virtual_environment/ve_base/contents/atsign/root/certs/cert.pem index 977346257..0804758f7 100644 --- a/tools/build_virtual_environment/ve_base/contents/atsign/root/certs/cert.pem +++ b/tools/build_virtual_environment/ve_base/contents/atsign/root/certs/cert.pem @@ -1,29 +1,29 @@ -----BEGIN CERTIFICATE----- -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 -u9qdIVKAbK+1cN15HYJ5jG0pXrOsBTbr +MAowCAYGZ4EMAQIBMIIBBAYKKwYBBAHWeQIEAgSB9QSB8gDwAHYAzPsPaoVxCWX+ +lZtTzumyfCLphVwNl422qX5UwP5MDbAAAAGTyUNR5wAABAMARzBFAiEA+qSCaeFM +SdWNtOBMFlHJqd8KuaXE03TW0lkAwXmilvYCIGsvyis+wdeiI6U/0fXO3aSfQLw1 +BVUuEdbQzTxrAvN1AHYAE0rfGrWYQgl4DG/vTHqRpBa3I0nOWFdq367ap8Kr4CIA +AAGTyUNSnAAABAMARzBFAiBtvdHsFEXPsU2E2SgLoZhhuOK8K3tDRuBtQ3PqZJMq +ogIhAI8jQXRm3dg0WRvPoxnp72gcg0yKRHvAH6L/9qwo067JMA0GCSqGSIb3DQEB +CwUAA4IBAQBrbAzdOo8akOc1s0jevoTFyn11cPSkkjnic0x93tcuoGkbs5oqJpVh +PuLDSXOy+xJ/TXUIDGFbCLA/SWC7/b1vViqwasEBGohJJ7ZM/27j/fIL+6Nc/u7Y +f37L5J32WcGRKKoxxA9MoaKqn5GVZTW8UIy/K+llfRaSptQCloyOPcq0h9uTyFci +sIDhVb5qj9hMmgkcz0BfZZHqH+45vtnFV0v+jeLGRkijcxN2LxpXIHCFTDivl0tS +/o2XXHuXZCkZ6q1eeDF7kC6fccXLOJnsbiNwmwiEQb/0WeX/lDQzA31sv4yOVfZr +Ne1IRWUsZfbo8jKSnnUOCwCKgOoNq81z -----END CERTIFICATE----- diff --git a/tools/build_virtual_environment/ve_base/contents/atsign/root/certs/fullchain.pem b/tools/build_virtual_environment/ve_base/contents/atsign/root/certs/fullchain.pem index e3980496f..9b21b4421 100644 --- a/tools/build_virtual_environment/ve_base/contents/atsign/root/certs/fullchain.pem +++ b/tools/build_virtual_environment/ve_base/contents/atsign/root/certs/fullchain.pem @@ -1,31 +1,31 @@ -----BEGIN CERTIFICATE----- -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 -u9qdIVKAbK+1cN15HYJ5jG0pXrOsBTbr +MAowCAYGZ4EMAQIBMIIBBAYKKwYBBAHWeQIEAgSB9QSB8gDwAHYAzPsPaoVxCWX+ +lZtTzumyfCLphVwNl422qX5UwP5MDbAAAAGTyUNR5wAABAMARzBFAiEA+qSCaeFM +SdWNtOBMFlHJqd8KuaXE03TW0lkAwXmilvYCIGsvyis+wdeiI6U/0fXO3aSfQLw1 +BVUuEdbQzTxrAvN1AHYAE0rfGrWYQgl4DG/vTHqRpBa3I0nOWFdq367ap8Kr4CIA +AAGTyUNSnAAABAMARzBFAiBtvdHsFEXPsU2E2SgLoZhhuOK8K3tDRuBtQ3PqZJMq +ogIhAI8jQXRm3dg0WRvPoxnp72gcg0yKRHvAH6L/9qwo067JMA0GCSqGSIb3DQEB +CwUAA4IBAQBrbAzdOo8akOc1s0jevoTFyn11cPSkkjnic0x93tcuoGkbs5oqJpVh +PuLDSXOy+xJ/TXUIDGFbCLA/SWC7/b1vViqwasEBGohJJ7ZM/27j/fIL+6Nc/u7Y +f37L5J32WcGRKKoxxA9MoaKqn5GVZTW8UIy/K+llfRaSptQCloyOPcq0h9uTyFci +sIDhVb5qj9hMmgkcz0BfZZHqH+45vtnFV0v+jeLGRkijcxN2LxpXIHCFTDivl0tS +/o2XXHuXZCkZ6q1eeDF7kC6fccXLOJnsbiNwmwiEQb/0WeX/lDQzA31sv4yOVfZr +Ne1IRWUsZfbo8jKSnnUOCwCKgOoNq81z -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- diff --git a/tools/build_virtual_environment/ve_base/contents/atsign/root/certs/privkey.pem b/tools/build_virtual_environment/ve_base/contents/atsign/root/certs/privkey.pem index 83d6618ac..a3392bf71 100644 --- a/tools/build_virtual_environment/ve_base/contents/atsign/root/certs/privkey.pem +++ b/tools/build_virtual_environment/ve_base/contents/atsign/root/certs/privkey.pem @@ -1,28 +1,28 @@ -----BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC1lHvQhYRRAyRL -Yy6HhDhB9AuhS7Cj22+AUB2vTQa/ANgYM5on6sfGTH3b1g59cGccgabd2449l46t -X/w6D33yYctlaCv55j0lA+XMAyFtxe1cvGSPTqX5rUwCJDFORz7q+JcHKfNW60Lt -1bWSwF/z+ojSSP4zlJj1Z5MP5POAEsYjWmoZEichrqq4XAl0JJUmaa9K9EK7Biek -x3fUIN0gcviz6oiIiUXUWQbiX/WJsqEIFWvbbYh4lNG83LMY/yA7iA6f+w8k65td -LpnL/KUueomOZll2LBqoiYW9A+KNraSzh8+j3EGkY5e8OZqNGJXcKVq0oje0LJ4v -acWTi77pAgMBAAECggEAC9zO9/D5E3fSk7A/lm/fYwOoDNoOiK3py2awgcbU9X5/ -H5mjbQ8sDPrrUATcfnps6hJYTe8tjD2N/yqhpkNj1YVpDc9v7AwMG5J1HU5pDSxc -lO3j3e7j7ftR12P46qXB6QTjjP7DHH8TKMmI6rTxr9VCwgYtwT3ofkPNekDZXZkA -LPHIZ7YqFC2QnTK/ojt4EhEf8e15fR04SBuaxcvPpnVQ0RqykkMI5/jIAcVLrN1M -Ig1A6OAhTLooWVoa45q0eIEBlsExQh6bfwhmL2brvK4vFIZjfpXNJUDKGHD1y7pB -3gDOKP05621JCyxAoR4F69YMa1LWsBmk/r20eLPg2QKBgQDL9+2m6EyNrWxTQ8MM -mm78DafBpWuLjo7nL5rNn5DKhHhWB419V9akWz+xPpfeZXAeZECG+s0NvX9VW7OA -34uj4+7FqFzhpArOwzcJvxxbpvcejMVPvt2J0GeTN/igLlsqGXI2CYWUKg5gKypz -n+EBtBvdIHJ5RLnGd0rLybZLrQKBgQDj5nytGQAWsru7aYOujcRoixkqEDznc1ZA -tQoNaAnuqHpGct9Zo9I4iZcjcPCxRrOjHbmC9cp5EK3NP3DfXrIsW5OKGF39eMEh -4vMYW7xQXpciQbpjfthuwm6Ue5fvm9vJLt3k1WuFSdRG74r+TkAoFzFws4g0peY8 -WzErc4hnrQKBgQDJuXqhCD4uIMZx0yKk5Fe/kQHI0wF6FJPgsYQ4uQdKQ5l//BhD -TwxJoLVtZqpsmYY5iI0I8iK65B97HCZ73psMi/EqK0kxIDxQIWHdkdVKD+d3n2HF -bM9N7X+CamjKgw5GpMQADYq+7pkpI4fossamGKs2F1Ef/oh9cJ3YzrYsQQKBgAqc -EVPRpSJpYboA2IOI0Ar55ssBhIRV5z8N7pDfNg8pQsAC2wkGSmQda67XtCzcmMJQ -QPGq1ak7MIwslumfM/ZCsSStq7qAI6WbNiv9IVAwhyKuIqo9HoBRhupcsp8SB3Jf -Py9HDEFhRYOj5TmflPcI1WFkykOBMa2/pRNbxo7lAoGADm5gZl+2U2E8eKzjOPcr -Gm6eKDyLfZnD67SxB+nCu+7IdHA5whPEAL4GUr3lxqQsDCxlSKJ06vTXs1H/BEbN -tJ6RiHR8oAebnw4RWuPRe96rUE4J/jmfmlkw2hPxrZAH/gfRTJ+U2KwATV5mSDPp -M7v1j1qt497Qk4s5Aw3LBLs= +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC6L8igR+Q3HtmI +95PTifuFGVGS6ri6NjSl3GnPNc27GKOLJ9QKBvrBosFGF3Hg37D4mbbB4aZTof+p +vXsBur5sqLSGhA9lUqoIixTew/HFow4hBGQz7e+RtUdCUqxdecBil6twpM5x1I2F +79hOFlEiXjtikEiabiEcZfF59mJliyGf4X+e7BOFoJyEsE6/RHax9QKkKV5o2aTn +ktzmYaePAi7W7GaV67ArRKIB373/12QSrTprb30lBSIpLNvnbXWOsP+JHGtCfaYc +1XokOLeGoNYViScMHaqNdzxbW68olAvobUN6MpmLfActB0Mw0gObZ9cUq43OMgQ7 +1TiIQj4ZAgMBAAECggEAA30w/yjtusc0GCQck/CgYyGC9PP7V3j70lZfsLt/Qhfv +e6R4zaHNGesXAF1g29ARUM1PnpFAFM3ivL9xYkpJtn9QV7t6juFmUI/rqZOGMLgS +JBYBGzSEomhBYDmso3lMPMS/GJasDDfJmaaRmQADz/cwWXPe+m7hJbc219xhQmoZ +bgoNQ0VqGE9jwb7VHRM3iG2xVg4PMP4R6J7A0kQtJ0zWyqit/ivDKjNFtXJQX5d1 +bwcLWslSeQuYL0BTOomA+ItTG6Uvc1x9B1urRoeY6dVFlnJlv+qQ8tzLvLy2OlkF +zOxlHJ+tABJp9lnftA5fba4vum63f94c1sNziGDfsQKBgQD22k7D5cLXPpdNsXGe +v0O/tYCSIprreMGHYTIvnyysdpZimC0qOY5or4LH2x6CRT+WMlmPf1hxkosMEHqL +GZGZWmy/gk/5fg8robyhJfo+bjIr5o6289bdn1779hNjtNGcxzVG7JVzhCllorA5 +aoRQXveGjUuSmmFc6NyYplNXqQKBgQDBFfxad5flpYtq4qbCJ3yj/LHFrv5pjALF +ISR5RC9X6WQ/nxT6/c5ez7JXfLVxAclvgR9zMpzd5co2cCNtx/KhpXhok3CTvA3i +w0hzOqsWEjfpYDN8TUK85PnteodU2JHpvACpj5zCBrcfLeAbezG+yzpEd1X9bPVg +I5EliHn48QKBgApu+7lH8s5KNPbxsw20o9Zsg3ypvcaKGhPwxcBBiA/aC00rhW0t +dZlzIaH03vs+lgtxIVvxU/mo6fuejq9ma0MCEWqexWaUL2v2sBRXmJYe7jvwyMNu +1vwTCl637eXe9KUdvmC7Cy4QvfcvwycKG4GSIgAr5K/Tmp3ksb45+XfRAoGAHzxZ +SKFUN1hih3YHGB2fMkAs2hgHHDgEMrO73MpFBmnBR/GKSVhJzFi5NyH7kD6DwcAX +wGENs2/7GHdno9jWzYOhSPmhGqUvceG5lLqG9tkXpm9jpNYy17ALJnnN7tHiOPeq ++8M5zsGbWjXnrUpur7jcwjqz9H1t7JGUZ4f+g2ECgYEA3wWMXOhH5hbhLCP3RYNC +V1C6DomNzSENOhQuwI1hOVFSGG66QQz5ZrLMcqGZNvHQGuzZYpf2hxdW1hXp5018 +iBhfiA/1ENgVoH15enikEPY66jFxexlzzsMIAtWBuDlyx0+t7AQqPnleGSsfjFz7 +aeOFRx5/WPqd2nCBYe9u93Q= -----END PRIVATE KEY----- diff --git a/tools/build_virtual_environment/ve_base/contents/atsign/secondary/base/certs/cert.pem b/tools/build_virtual_environment/ve_base/contents/atsign/secondary/base/certs/cert.pem index 977346257..0804758f7 100644 --- a/tools/build_virtual_environment/ve_base/contents/atsign/secondary/base/certs/cert.pem +++ b/tools/build_virtual_environment/ve_base/contents/atsign/secondary/base/certs/cert.pem @@ -1,29 +1,29 @@ -----BEGIN CERTIFICATE----- -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 -u9qdIVKAbK+1cN15HYJ5jG0pXrOsBTbr +MAowCAYGZ4EMAQIBMIIBBAYKKwYBBAHWeQIEAgSB9QSB8gDwAHYAzPsPaoVxCWX+ +lZtTzumyfCLphVwNl422qX5UwP5MDbAAAAGTyUNR5wAABAMARzBFAiEA+qSCaeFM +SdWNtOBMFlHJqd8KuaXE03TW0lkAwXmilvYCIGsvyis+wdeiI6U/0fXO3aSfQLw1 +BVUuEdbQzTxrAvN1AHYAE0rfGrWYQgl4DG/vTHqRpBa3I0nOWFdq367ap8Kr4CIA +AAGTyUNSnAAABAMARzBFAiBtvdHsFEXPsU2E2SgLoZhhuOK8K3tDRuBtQ3PqZJMq +ogIhAI8jQXRm3dg0WRvPoxnp72gcg0yKRHvAH6L/9qwo067JMA0GCSqGSIb3DQEB +CwUAA4IBAQBrbAzdOo8akOc1s0jevoTFyn11cPSkkjnic0x93tcuoGkbs5oqJpVh +PuLDSXOy+xJ/TXUIDGFbCLA/SWC7/b1vViqwasEBGohJJ7ZM/27j/fIL+6Nc/u7Y +f37L5J32WcGRKKoxxA9MoaKqn5GVZTW8UIy/K+llfRaSptQCloyOPcq0h9uTyFci +sIDhVb5qj9hMmgkcz0BfZZHqH+45vtnFV0v+jeLGRkijcxN2LxpXIHCFTDivl0tS +/o2XXHuXZCkZ6q1eeDF7kC6fccXLOJnsbiNwmwiEQb/0WeX/lDQzA31sv4yOVfZr +Ne1IRWUsZfbo8jKSnnUOCwCKgOoNq81z -----END CERTIFICATE----- diff --git a/tools/build_virtual_environment/ve_base/contents/atsign/secondary/base/certs/fullchain.pem b/tools/build_virtual_environment/ve_base/contents/atsign/secondary/base/certs/fullchain.pem index e3980496f..9b21b4421 100644 --- a/tools/build_virtual_environment/ve_base/contents/atsign/secondary/base/certs/fullchain.pem +++ b/tools/build_virtual_environment/ve_base/contents/atsign/secondary/base/certs/fullchain.pem @@ -1,31 +1,31 @@ -----BEGIN CERTIFICATE----- -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 -u9qdIVKAbK+1cN15HYJ5jG0pXrOsBTbr +MAowCAYGZ4EMAQIBMIIBBAYKKwYBBAHWeQIEAgSB9QSB8gDwAHYAzPsPaoVxCWX+ +lZtTzumyfCLphVwNl422qX5UwP5MDbAAAAGTyUNR5wAABAMARzBFAiEA+qSCaeFM +SdWNtOBMFlHJqd8KuaXE03TW0lkAwXmilvYCIGsvyis+wdeiI6U/0fXO3aSfQLw1 +BVUuEdbQzTxrAvN1AHYAE0rfGrWYQgl4DG/vTHqRpBa3I0nOWFdq367ap8Kr4CIA +AAGTyUNSnAAABAMARzBFAiBtvdHsFEXPsU2E2SgLoZhhuOK8K3tDRuBtQ3PqZJMq +ogIhAI8jQXRm3dg0WRvPoxnp72gcg0yKRHvAH6L/9qwo067JMA0GCSqGSIb3DQEB +CwUAA4IBAQBrbAzdOo8akOc1s0jevoTFyn11cPSkkjnic0x93tcuoGkbs5oqJpVh +PuLDSXOy+xJ/TXUIDGFbCLA/SWC7/b1vViqwasEBGohJJ7ZM/27j/fIL+6Nc/u7Y +f37L5J32WcGRKKoxxA9MoaKqn5GVZTW8UIy/K+llfRaSptQCloyOPcq0h9uTyFci +sIDhVb5qj9hMmgkcz0BfZZHqH+45vtnFV0v+jeLGRkijcxN2LxpXIHCFTDivl0tS +/o2XXHuXZCkZ6q1eeDF7kC6fccXLOJnsbiNwmwiEQb/0WeX/lDQzA31sv4yOVfZr +Ne1IRWUsZfbo8jKSnnUOCwCKgOoNq81z -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- diff --git a/tools/build_virtual_environment/ve_base/contents/atsign/secondary/base/certs/privkey.pem b/tools/build_virtual_environment/ve_base/contents/atsign/secondary/base/certs/privkey.pem index 83d6618ac..a3392bf71 100644 --- a/tools/build_virtual_environment/ve_base/contents/atsign/secondary/base/certs/privkey.pem +++ b/tools/build_virtual_environment/ve_base/contents/atsign/secondary/base/certs/privkey.pem @@ -1,28 +1,28 @@ -----BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC1lHvQhYRRAyRL -Yy6HhDhB9AuhS7Cj22+AUB2vTQa/ANgYM5on6sfGTH3b1g59cGccgabd2449l46t -X/w6D33yYctlaCv55j0lA+XMAyFtxe1cvGSPTqX5rUwCJDFORz7q+JcHKfNW60Lt -1bWSwF/z+ojSSP4zlJj1Z5MP5POAEsYjWmoZEichrqq4XAl0JJUmaa9K9EK7Biek -x3fUIN0gcviz6oiIiUXUWQbiX/WJsqEIFWvbbYh4lNG83LMY/yA7iA6f+w8k65td -LpnL/KUueomOZll2LBqoiYW9A+KNraSzh8+j3EGkY5e8OZqNGJXcKVq0oje0LJ4v -acWTi77pAgMBAAECggEAC9zO9/D5E3fSk7A/lm/fYwOoDNoOiK3py2awgcbU9X5/ -H5mjbQ8sDPrrUATcfnps6hJYTe8tjD2N/yqhpkNj1YVpDc9v7AwMG5J1HU5pDSxc -lO3j3e7j7ftR12P46qXB6QTjjP7DHH8TKMmI6rTxr9VCwgYtwT3ofkPNekDZXZkA -LPHIZ7YqFC2QnTK/ojt4EhEf8e15fR04SBuaxcvPpnVQ0RqykkMI5/jIAcVLrN1M -Ig1A6OAhTLooWVoa45q0eIEBlsExQh6bfwhmL2brvK4vFIZjfpXNJUDKGHD1y7pB -3gDOKP05621JCyxAoR4F69YMa1LWsBmk/r20eLPg2QKBgQDL9+2m6EyNrWxTQ8MM -mm78DafBpWuLjo7nL5rNn5DKhHhWB419V9akWz+xPpfeZXAeZECG+s0NvX9VW7OA -34uj4+7FqFzhpArOwzcJvxxbpvcejMVPvt2J0GeTN/igLlsqGXI2CYWUKg5gKypz -n+EBtBvdIHJ5RLnGd0rLybZLrQKBgQDj5nytGQAWsru7aYOujcRoixkqEDznc1ZA -tQoNaAnuqHpGct9Zo9I4iZcjcPCxRrOjHbmC9cp5EK3NP3DfXrIsW5OKGF39eMEh -4vMYW7xQXpciQbpjfthuwm6Ue5fvm9vJLt3k1WuFSdRG74r+TkAoFzFws4g0peY8 -WzErc4hnrQKBgQDJuXqhCD4uIMZx0yKk5Fe/kQHI0wF6FJPgsYQ4uQdKQ5l//BhD -TwxJoLVtZqpsmYY5iI0I8iK65B97HCZ73psMi/EqK0kxIDxQIWHdkdVKD+d3n2HF -bM9N7X+CamjKgw5GpMQADYq+7pkpI4fossamGKs2F1Ef/oh9cJ3YzrYsQQKBgAqc -EVPRpSJpYboA2IOI0Ar55ssBhIRV5z8N7pDfNg8pQsAC2wkGSmQda67XtCzcmMJQ -QPGq1ak7MIwslumfM/ZCsSStq7qAI6WbNiv9IVAwhyKuIqo9HoBRhupcsp8SB3Jf -Py9HDEFhRYOj5TmflPcI1WFkykOBMa2/pRNbxo7lAoGADm5gZl+2U2E8eKzjOPcr -Gm6eKDyLfZnD67SxB+nCu+7IdHA5whPEAL4GUr3lxqQsDCxlSKJ06vTXs1H/BEbN -tJ6RiHR8oAebnw4RWuPRe96rUE4J/jmfmlkw2hPxrZAH/gfRTJ+U2KwATV5mSDPp -M7v1j1qt497Qk4s5Aw3LBLs= +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC6L8igR+Q3HtmI +95PTifuFGVGS6ri6NjSl3GnPNc27GKOLJ9QKBvrBosFGF3Hg37D4mbbB4aZTof+p +vXsBur5sqLSGhA9lUqoIixTew/HFow4hBGQz7e+RtUdCUqxdecBil6twpM5x1I2F +79hOFlEiXjtikEiabiEcZfF59mJliyGf4X+e7BOFoJyEsE6/RHax9QKkKV5o2aTn +ktzmYaePAi7W7GaV67ArRKIB373/12QSrTprb30lBSIpLNvnbXWOsP+JHGtCfaYc +1XokOLeGoNYViScMHaqNdzxbW68olAvobUN6MpmLfActB0Mw0gObZ9cUq43OMgQ7 +1TiIQj4ZAgMBAAECggEAA30w/yjtusc0GCQck/CgYyGC9PP7V3j70lZfsLt/Qhfv +e6R4zaHNGesXAF1g29ARUM1PnpFAFM3ivL9xYkpJtn9QV7t6juFmUI/rqZOGMLgS +JBYBGzSEomhBYDmso3lMPMS/GJasDDfJmaaRmQADz/cwWXPe+m7hJbc219xhQmoZ +bgoNQ0VqGE9jwb7VHRM3iG2xVg4PMP4R6J7A0kQtJ0zWyqit/ivDKjNFtXJQX5d1 +bwcLWslSeQuYL0BTOomA+ItTG6Uvc1x9B1urRoeY6dVFlnJlv+qQ8tzLvLy2OlkF +zOxlHJ+tABJp9lnftA5fba4vum63f94c1sNziGDfsQKBgQD22k7D5cLXPpdNsXGe +v0O/tYCSIprreMGHYTIvnyysdpZimC0qOY5or4LH2x6CRT+WMlmPf1hxkosMEHqL +GZGZWmy/gk/5fg8robyhJfo+bjIr5o6289bdn1779hNjtNGcxzVG7JVzhCllorA5 +aoRQXveGjUuSmmFc6NyYplNXqQKBgQDBFfxad5flpYtq4qbCJ3yj/LHFrv5pjALF +ISR5RC9X6WQ/nxT6/c5ez7JXfLVxAclvgR9zMpzd5co2cCNtx/KhpXhok3CTvA3i +w0hzOqsWEjfpYDN8TUK85PnteodU2JHpvACpj5zCBrcfLeAbezG+yzpEd1X9bPVg +I5EliHn48QKBgApu+7lH8s5KNPbxsw20o9Zsg3ypvcaKGhPwxcBBiA/aC00rhW0t +dZlzIaH03vs+lgtxIVvxU/mo6fuejq9ma0MCEWqexWaUL2v2sBRXmJYe7jvwyMNu +1vwTCl637eXe9KUdvmC7Cy4QvfcvwycKG4GSIgAr5K/Tmp3ksb45+XfRAoGAHzxZ +SKFUN1hih3YHGB2fMkAs2hgHHDgEMrO73MpFBmnBR/GKSVhJzFi5NyH7kD6DwcAX +wGENs2/7GHdno9jWzYOhSPmhGqUvceG5lLqG9tkXpm9jpNYy17ALJnnN7tHiOPeq ++8M5zsGbWjXnrUpur7jcwjqz9H1t7JGUZ4f+g2ECgYEA3wWMXOhH5hbhLCP3RYNC +V1C6DomNzSENOhQuwI1hOVFSGG66QQz5ZrLMcqGZNvHQGuzZYpf2hxdW1hXp5018 +iBhfiA/1ENgVoH15enikEPY66jFxexlzzsMIAtWBuDlyx0+t7AQqPnleGSsfjFz7 +aeOFRx5/WPqd2nCBYe9u93Q= -----END PRIVATE KEY-----