From a88a22c5db688f2e2650f07ca1b64e24f559dd93 Mon Sep 17 00:00:00 2001
From: idimov-keeper <78815270+idimov-keeper@users.noreply.github.com>
Date: Fri, 31 May 2024 13:45:40 -0500
Subject: [PATCH 1/9] KSM-515 - .NET SDK: update to bouncy castle 2 4 0 (#604)
* Updated Package Version to 16.6.6
* Update to Bouncy Castle 2.4.0
---
sdk/dotNet/README.md | 4 ++++
sdk/dotNet/SecretsManager/SecretsManager.csproj | 8 ++++----
2 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/sdk/dotNet/README.md b/sdk/dotNet/README.md
index 20220ace..c0617be6 100644
--- a/sdk/dotNet/README.md
+++ b/sdk/dotNet/README.md
@@ -3,6 +3,10 @@
# Change Log
+## 16.6.6
+
+* KSM-515 - Update to Bouncy Castle 2.4.0
+
## 16.6.5
* KSM-476 - fix public key parsing
diff --git a/sdk/dotNet/SecretsManager/SecretsManager.csproj b/sdk/dotNet/SecretsManager/SecretsManager.csproj
index cc475f05..69535524 100644
--- a/sdk/dotNet/SecretsManager/SecretsManager.csproj
+++ b/sdk/dotNet/SecretsManager/SecretsManager.csproj
@@ -5,9 +5,9 @@
9
Keeper Security Inc.
SecretsManager .Net SDK
- 16.6.5
- 16.6.5
- 16.6.5
+ 16.6.6
+ 16.6.6
+ 16.6.6
en-US
Keeper.SecretsManager
Sergey Aldoukhov
@@ -22,7 +22,7 @@
-
+
From 3090e94461622696b5b7bd642724260edafea272 Mon Sep 17 00:00:00 2001
From: idimov-keeper <78815270+idimov-keeper@users.noreply.github.com>
Date: Fri, 7 Jun 2024 14:12:46 -0500
Subject: [PATCH 2/9] KSM-490 - .NET SDK: Switch some internal classes to
public - for use in plugins (#563)
* KSM-490 Switch some internal classes to public - for use in plugins
* Updated readme
---
sdk/dotNet/README.md | 1 +
sdk/dotNet/SecretsManager/Notation.cs | 4 ++--
sdk/dotNet/SecretsManager/SecretsManagerClient.cs | 5 +----
3 files changed, 4 insertions(+), 6 deletions(-)
diff --git a/sdk/dotNet/README.md b/sdk/dotNet/README.md
index c0617be6..9deb1b5c 100644
--- a/sdk/dotNet/README.md
+++ b/sdk/dotNet/README.md
@@ -5,6 +5,7 @@
## 16.6.6
+* KSM-490 - Switch some internal classes to public - for use in plugins
* KSM-515 - Update to Bouncy Castle 2.4.0
## 16.6.5
diff --git a/sdk/dotNet/SecretsManager/Notation.cs b/sdk/dotNet/SecretsManager/Notation.cs
index 5dd84098..07f1b98f 100644
--- a/sdk/dotNet/SecretsManager/Notation.cs
+++ b/sdk/dotNet/SecretsManager/Notation.cs
@@ -108,7 +108,7 @@ KeeperRecordField FindField(string fieldName)
}
// data class to represent parsed notation section
- internal class NotationSection
+ public class NotationSection
{
public string Section = null; // section name - ex. prefix
public bool IsPresent = false; // presence flag
@@ -281,7 +281,7 @@ private static NotationSection ParseSection(string notation, string section, int
return result;
}
- internal static List ParseNotation(string notation, bool legacyMode = false)
+ public static List ParseNotation(string notation, bool legacyMode = false)
{
if (string.IsNullOrEmpty(notation))
throw new Exception("Keeper notation is missing or invalid.");
diff --git a/sdk/dotNet/SecretsManager/SecretsManagerClient.cs b/sdk/dotNet/SecretsManager/SecretsManagerClient.cs
index 44202c7a..bcb6eec6 100644
--- a/sdk/dotNet/SecretsManager/SecretsManagerClient.cs
+++ b/sdk/dotNet/SecretsManager/SecretsManagerClient.cs
@@ -5,14 +5,11 @@
using System.Linq;
using System.Net;
using System.Reflection;
-using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
-[assembly: InternalsVisibleTo("SecretsManager.Test.Core")]
-
namespace SecretsManager
{
using GetRandomBytesFunction = Func;
@@ -1412,7 +1409,7 @@ private static TransmissionKey GenerateTransmissionKey(IKeyValueStorage storage)
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
- internal static GetRandomBytesFunction TransmissionKeyStub { get; set; }
+ public static GetRandomBytesFunction TransmissionKeyStub { get; set; }
private static EncryptedPayload EncryptAndSignPayload(IKeyValueStorage storage,
TransmissionKey transmissionKey, T payload)
From b0387386c99cbd01e1d1137f78b1a3a371f03868 Mon Sep 17 00:00:00 2001
From: idimov-keeper <78815270+idimov-keeper@users.noreply.github.com>
Date: Fri, 7 Jun 2024 14:26:38 -0500
Subject: [PATCH 3/9] KSM-360 - .Net SDK: GHA to build and release strong named
assemblies (#564)
* KSM-360 .Net SDK GHA to build and release strong named assemblies
- updated copyright year to 2024
- updated to the publish.nuget.strong.named.yml
- updates to the publish.nuget.yml
---
.../workflows/publish.nuget.strong.named.yml | 72 +++++++++++
.github/workflows/publish.nuget.yml | 114 +++++++++++++++++-
sdk/dotNet/README.md | 1 +
.../SecretsManager.Test.Core.csproj | 5 +
.../SecretsManager/SecretsManager.csproj | 7 +-
5 files changed, 195 insertions(+), 4 deletions(-)
create mode 100644 .github/workflows/publish.nuget.strong.named.yml
diff --git a/.github/workflows/publish.nuget.strong.named.yml b/.github/workflows/publish.nuget.strong.named.yml
new file mode 100644
index 00000000..9f1d3f5e
--- /dev/null
+++ b/.github/workflows/publish.nuget.strong.named.yml
@@ -0,0 +1,72 @@
+name: Publish strong-named assemblies to NuGet
+
+on:
+ workflow_dispatch:
+ inputs:
+ publish:
+ description: 'Publish to NuGet (uncheck to build only)'
+ required: false
+ default: 'true'
+ type: boolean
+
+
+jobs:
+ publish-nuget:
+ environment: prod
+ runs-on: ubuntu-latest
+
+ defaults:
+ run:
+ shell: bash
+ working-directory: ./sdk/dotNet
+
+ steps:
+ - name: Get the source code
+ uses: actions/checkout@v4
+
+ - name: Setup .NET 6
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 6.0.x
+
+ - name: Retrieve secrets from KSM
+ id: ksmsecrets
+ uses: Keeper-Security/ksm-action@master
+ with:
+ keeper-secret-config: ${{ secrets.KSM_KSM_CONFIG }}
+ secrets: |
+ Sq4nnb5HXXNp1l6KryXynw/field/password > NUGET_AUTH_TOKEN
+ Sq4nnb5HXXNp1l6KryXynw/file/sgKSM.snk > file:${{ github.workspace }}/sdk/dotNet/SecretsManager/sgKSM.snk
+
+ - name: Install dependencies
+ run: dotnet restore
+
+ - name: "Preparing package for strong naming"
+ working-directory: ./SecretsManager
+ run: |
+ pwd
+ ls -lah
+ cp -f SecretsManager.csproj SecretsManager.StrongName.csproj
+ ls -lah
+ sed -i 's/Keeper.SecretsManager<\/PackageId>/Keeper.SecretsManager.StrongName<\/PackageId>/g' SecretsManager.StrongName.csproj
+ cat SecretsManager.StrongName.csproj
+
+ - name: Build
+ working-directory: ./SecretsManager
+ run: |
+ pwd
+ ls -lah
+ dotnet build SecretsManager.StrongName.csproj --configuration Release --no-restore -p:SignKSM=True
+
+ - name: Cleanup temp files
+ working-directory: ./SecretsManager
+ run: |
+ ls -lah
+ rm -f sgKSM.snk
+
+ - name: Publish package
+ if: ${{ github.event.inputs.publish == 'true' }}
+ working-directory: ./SecretsManager
+ run: |
+ ls -lah ./bin/Release/
+ dotnet nuget push bin/Release/*.nupkg --api-key ${{steps.ksmsecrets.outputs.NUGET_AUTH_TOKEN}} --source https://api.nuget.org/v3/index.json
diff --git a/.github/workflows/publish.nuget.yml b/.github/workflows/publish.nuget.yml
index 00e8c482..d4f0450a 100644
--- a/.github/workflows/publish.nuget.yml
+++ b/.github/workflows/publish.nuget.yml
@@ -1,6 +1,13 @@
name: Publish to NuGet
+
on:
workflow_dispatch:
+ inputs:
+ publish:
+ description: 'Publish to NuGet (uncheck to build only)'
+ required: false
+ default: 'true'
+ type: boolean
jobs:
publish-nuget:
@@ -9,23 +16,62 @@ jobs:
defaults:
run:
+
working-directory: ./sdk/dotNet
steps:
- name: Get the source code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Setup .NET 6
- uses: actions/setup-dotnet@v1
+ uses: actions/setup-dotnet@v4
with:
dotnet-version: 6.0.x
+ - name: Retrieve secrets from KSM
+ id: ksmsecrets
+ uses: Keeper-Security/ksm-action@master
+ with:
+ keeper-secret-config: ${{ secrets.KSM_KSM_CONFIG }}
+ secrets: |
+ Sq4nnb5HXXNp1l6KryXynw/field/password > NUGET_AUTH_TOKEN
+
- name: Install dependencies
run: dotnet restore
- name: Build
run: dotnet build --configuration Release --no-restore
+ - name: Publish package
+ if: ${{ github.event.inputs.publish == 'true' }}
+ run: dotnet nuget push ./SecretsManager/bin/Release/*.nupkg --api-key ${{steps.ksmsecrets.outputs.NUGET_AUTH_TOKEN}} --source https://api.nuget.org/v3/index.json
+
+ - name: Upload non-strong-named binaries
+ if: ${{ github.event.inputs.publish == 'false' }}
+ uses: actions/upload-artifact@v4
+ with:
+ name: non-strong-named-binaries-${{ github.run_number }}
+ path: |
+ ${{ github.workspace }}/sdk/dotNet/SecretsManager/bin/Release/*.nupkg
+
+ publish-nuget-strongname:
+ environment: prod
+ runs-on: windows-latest
+
+ defaults:
+ run:
+ shell: powershell
+ working-directory: .\sdk\dotNet
+
+ steps:
+ - name: Get the source code
+ uses: actions/checkout@v4
+
+ - name: Setup .NET 6
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 6.0.x
+
- name: Retrieve secrets from KSM
id: ksmsecrets
uses: Keeper-Security/ksm-action@master
@@ -33,6 +79,68 @@ jobs:
keeper-secret-config: ${{ secrets.KSM_KSM_CONFIG }}
secrets: |
Sq4nnb5HXXNp1l6KryXynw/field/password > NUGET_AUTH_TOKEN
+ Sq4nnb5HXXNp1l6KryXynw/file/sgKSM.snk > file:${{ github.workspace }}\sdk\dotNet\SecretsManager\sgKSM.snk
+
+ - name: Extract and Update Public Key in SecretsManagerClient.cs
+ run: |
+ $snPath = "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\sn.exe"
+ $snkPath = "${{ github.workspace }}\sdk\dotNet\SecretsManager\sgKSM.snk"
+ $publicKeyPath = "${{ github.workspace }}\sdk\dotNet\SecretsManager\sgKSM.pub"
+ & $snPath -p $snkPath $publicKeyPath
+ $publicKeyInfo = & $snPath -tp $publicKeyPath
+ # Filter and join the lines of the public key
+ $publicKeyLines = $publicKeyInfo -split "`n" | Where-Object { $_ -match "^[a-f0-9\s]+$" }
+ $publicKey = $publicKeyLines -join "" -replace "\s", ""
+
+ if (-not $publicKey) {
+ Write-Error "Failed to extract the full public key."
+ exit 1
+ }
+
+ Write-Output "Extracted Public Key: $publicKey"
+ $filePath = "${{ github.workspace }}\sdk\dotNet\SecretsManager\SecretsManagerClient.cs"
+ (Get-Content $filePath) -replace '\[assembly: InternalsVisibleTo\("SecretsManager.Test.Core"\)\]', "[assembly: InternalsVisibleTo(`"SecretsManager.Test.Core, PublicKey=$publicKey`")]" | Set-Content $filePath
+ Write-Output "First 20 lines of the modified SecretsManagerClient.cs:"
+ Get-Content $filePath -Head 20
+
+ - name: Install dependencies
+ run: dotnet restore
+
+ - name: "Preparing package for strong naming"
+ working-directory: ${{ github.workspace }}\sdk\dotNet\SecretsManager\
+ run: |
+ pwd
+ Get-ChildItem
+ Copy-Item -Path "SecretsManager.csproj" -Destination "SecretsManager.StrongName.csproj"
+ (Get-Content -Path "SecretsManager.StrongName.csproj") -replace 'Keeper.SecretsManager', 'Keeper.SecretsManager.StrongName' | Set-Content -Path "SecretsManager.StrongName.csproj"
+ Get-Content "SecretsManager.StrongName.csproj"
+ Copy-Item -Path "${{ github.workspace }}\sdk\dotNet\SecretsManager\sgKSM.snk" -Destination "${{ github.workspace }}\sdk\dotNet\SecretsManager.Test.Core\sgKSM.snk"
+ Get-ChildItem "${{ github.workspace }}\sdk\dotNet\SecretsManager.Test.Core\"
+
+ - name: Build
+ working-directory: ${{ github.workspace }}\sdk\dotNet\SecretsManager\
+ run: |
+ pwd
+ Get-ChildItem
+ dotnet build "SecretsManager.StrongName.csproj" --configuration Release --no-restore -p:SignKSM=True
+
+ - name: Cleanup secret files
+ working-directory: ${{ github.workspace }}\sdk\dotNet\SecretsManager\
+ run: |
+ Remove-Item -Path "${{ github.workspace }}\sdk\dotNet\SecretsManager\sgKSM.snk"
+ Remove-Item -Path "${{ github.workspace }}\sdk\dotNet\SecretsManager.Test.Core\sgKSM.snk"
- name: Publish package
- run: dotnet nuget push ./SecretsManager/bin/Release/*.nupkg --api-key ${{steps.ksmsecrets.outputs.NUGET_AUTH_TOKEN}} --source https://api.nuget.org/v3/index.json
+ if: ${{ github.event.inputs.publish == 'true' }}
+ working-directory: ${{ github.workspace }}\sdk\dotNet\SecretsManager\
+ run: |
+ Get-ChildItem ".\bin\Release\"
+ dotnet nuget push ".\bin\Release\*.nupkg" --api-key ${{steps.ksmsecrets.outputs.NUGET_AUTH_TOKEN}} --source https://api.nuget.org/v3/index.json
+
+ - name: Upload strong-named binaries
+ if: ${{ github.event.inputs.publish == 'false' }}
+ uses: actions/upload-artifact@v4
+ with:
+ name: strong-named-binaries-${{ github.run_number }}
+ path: |
+ ${{ github.workspace }}\sdk\dotNet\SecretsManager\bin\Release\*.nupkg
\ No newline at end of file
diff --git a/sdk/dotNet/README.md b/sdk/dotNet/README.md
index 9deb1b5c..8fe9b97a 100644
--- a/sdk/dotNet/README.md
+++ b/sdk/dotNet/README.md
@@ -5,6 +5,7 @@
## 16.6.6
+* KSM-360 - GHA to build and release strong named assemblies
* KSM-490 - Switch some internal classes to public - for use in plugins
* KSM-515 - Update to Bouncy Castle 2.4.0
diff --git a/sdk/dotNet/SecretsManager.Test.Core/SecretsManager.Test.Core.csproj b/sdk/dotNet/SecretsManager.Test.Core/SecretsManager.Test.Core.csproj
index 598ad363..b70d7b7b 100644
--- a/sdk/dotNet/SecretsManager.Test.Core/SecretsManager.Test.Core.csproj
+++ b/sdk/dotNet/SecretsManager.Test.Core/SecretsManager.Test.Core.csproj
@@ -19,4 +19,9 @@
+
+ True
+ sgKSM.snk
+
+
diff --git a/sdk/dotNet/SecretsManager/SecretsManager.csproj b/sdk/dotNet/SecretsManager/SecretsManager.csproj
index 69535524..d66ed58b 100644
--- a/sdk/dotNet/SecretsManager/SecretsManager.csproj
+++ b/sdk/dotNet/SecretsManager/SecretsManager.csproj
@@ -16,7 +16,7 @@
https://github.com/Keeper-Security/secrets-manager
GitHub
keeper secrets manager passwords
- © 2023 Keeper Security, Inc.
+ © 2024 Keeper Security, Inc.
https://raw.githubusercontent.com/Keeper-Security/secrets-manager/master/LICENSE?token=AACNMRVMD5L3PYT3C5MTNF3BEAFZY
true
@@ -26,4 +26,9 @@
+
+
+ True
+ sgKSM.snk
+
From 0004afeab8d30252ad7bf040cec394d21a08ca1a Mon Sep 17 00:00:00 2001
From: idimov-keeper <78815270+idimov-keeper@users.noreply.github.com>
Date: Fri, 14 Jun 2024 11:04:29 -0500
Subject: [PATCH 4/9] KSM-517 Added netstandard2.0 target (#609)
---
examples/dotNet/HelloSecret/HelloSecret.csproj | 2 +-
sdk/dotNet/SecretsManager/SecretsManager.csproj | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/examples/dotNet/HelloSecret/HelloSecret.csproj b/examples/dotNet/HelloSecret/HelloSecret.csproj
index 51758ca3..7c938c57 100644
--- a/examples/dotNet/HelloSecret/HelloSecret.csproj
+++ b/examples/dotNet/HelloSecret/HelloSecret.csproj
@@ -6,7 +6,7 @@
-
+
diff --git a/sdk/dotNet/SecretsManager/SecretsManager.csproj b/sdk/dotNet/SecretsManager/SecretsManager.csproj
index d66ed58b..563789cf 100644
--- a/sdk/dotNet/SecretsManager/SecretsManager.csproj
+++ b/sdk/dotNet/SecretsManager/SecretsManager.csproj
@@ -1,7 +1,7 @@
- net47;net48;netstandard2.1
+ net47;net48;netstandard2.0;netstandard2.1
9
Keeper Security Inc.
SecretsManager .Net SDK
From 2f58d42a5970a06c7059ab660241d1b095d3235c Mon Sep 17 00:00:00 2001
From: idimov-keeper <78815270+idimov-keeper@users.noreply.github.com>
Date: Tue, 23 Jul 2024 11:29:18 -0500
Subject: [PATCH 5/9] Ksm 536 ksm net update to system text json 8 0 4 (#625)
* upgraded to System.Text.Json 8.0.4
* added .editorconfig
* upgraded test dependencies to latest versions
switched from classic to the constraint-based Assert model in tests
* updated README
---
sdk/dotNet/.editorconfig | 10 +++
sdk/dotNet/README.md | 1 +
.../CryptoUtils.Test.cs | 72 +++++++++----------
.../JsonUtils.Test.cs | 31 ++++----
.../SecretsManager.Test.Core/Notation.Test.cs | 60 ++++++++--------
.../SecretsManager.Test.Core.csproj | 11 +--
.../SecretsManagerClient.Test.cs | 28 ++++----
sdk/dotNet/SecretsManager.sln | 5 ++
sdk/dotNet/SecretsManager/CryptoUtils.cs | 9 ++-
sdk/dotNet/SecretsManager/JsonUtils.cs | 2 +-
.../SecretsManager/LocalConfigStorage.cs | 2 +-
sdk/dotNet/SecretsManager/RecordData.cs | 4 +-
.../SecretsManager/SecretsManager.csproj | 2 +-
13 files changed, 128 insertions(+), 109 deletions(-)
create mode 100644 sdk/dotNet/.editorconfig
diff --git a/sdk/dotNet/.editorconfig b/sdk/dotNet/.editorconfig
new file mode 100644
index 00000000..692b3332
--- /dev/null
+++ b/sdk/dotNet/.editorconfig
@@ -0,0 +1,10 @@
+[*]
+
+# Indentation and spacing
+indent_size = 4
+indent_style = space
+tab_width = 4
+
+# New line preferences
+end_of_line = lf
+insert_final_newline = true
diff --git a/sdk/dotNet/README.md b/sdk/dotNet/README.md
index 8fe9b97a..112b04fc 100644
--- a/sdk/dotNet/README.md
+++ b/sdk/dotNet/README.md
@@ -8,6 +8,7 @@
* KSM-360 - GHA to build and release strong named assemblies
* KSM-490 - Switch some internal classes to public - for use in plugins
* KSM-515 - Update to Bouncy Castle 2.4.0
+* KSM-536 - Update to System.Text.Json 8.0.4
## 16.6.5
diff --git a/sdk/dotNet/SecretsManager.Test.Core/CryptoUtils.Test.cs b/sdk/dotNet/SecretsManager.Test.Core/CryptoUtils.Test.cs
index 63c08e5c..da61fdd0 100644
--- a/sdk/dotNet/SecretsManager.Test.Core/CryptoUtils.Test.cs
+++ b/sdk/dotNet/SecretsManager.Test.Core/CryptoUtils.Test.cs
@@ -12,33 +12,33 @@ public void PrivateKeyIsCompatible()
const string fakeExternalPrivateKey64String = "MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg34GXYbMpXKaHcHZW4dIMO3WYU8zTjB6t+41SRsY1rwqgCgYIKoZIzj0DAQehRANCAAQGH/4ZwpGR9B7AlMjVY7ekpjOcaD3rhuO25CmOZxI3wqRDdhXQIdDnuWvQPCZ3ymtjL3C8JrVIcloklwYI9T7+";
var fakeExternalPrivateKey64Bytes = CryptoUtils.Base64ToBytes(fakeExternalPrivateKey64String);
var fakeExportedPublicKey = CryptoUtils.ExportPublicKey(fakeExternalPrivateKey64Bytes);
- Assert.AreEqual("BAYf/hnCkZH0HsCUyNVjt6SmM5xoPeuG47bkKY5nEjfCpEN2FdAh0Oe5a9A8JnfKa2MvcLwmtUhyWiSXBgj1Pv4=", CryptoUtils.BytesToBase64(fakeExportedPublicKey));
+ Assert.That("BAYf/hnCkZH0HsCUyNVjt6SmM5xoPeuG47bkKY5nEjfCpEN2FdAh0Oe5a9A8JnfKa2MvcLwmtUhyWiSXBgj1Pv4=", Is.EqualTo(CryptoUtils.BytesToBase64(fakeExportedPublicKey)));
}
[Test]
public void PrivateKeySizeIs150()
{
var privateKey = CryptoUtils.GenerateKeyPair();
- Assert.AreEqual(150, privateKey.Length);
+ Assert.That(150, Is.EqualTo(privateKey.Length));
}
[Test]
public void TestPasswordGeneration()
{
var password = CryptoUtils.GeneratePassword();
- Assert.AreEqual(32, password?.Length);
+ Assert.That(32, Is.EqualTo(password?.Length));
password = CryptoUtils.GeneratePassword(32, 32);
- Assert.IsTrue(Regex.IsMatch(password, @"^[a-z]{32}$"));
+ Assert.That(Regex.IsMatch(password, @"^[a-z]{32}$"));
password = CryptoUtils.GeneratePassword(32, 0, 32);
- Assert.IsTrue(Regex.IsMatch(password, @"^[A-Z]{32}$"));
+ Assert.That(Regex.IsMatch(password, @"^[A-Z]{32}$"));
password = CryptoUtils.GeneratePassword(32, 0, 0, 32);
- Assert.IsTrue(Regex.IsMatch(password, @"^[0-9]{32}$"));
+ Assert.That(Regex.IsMatch(password, @"^[0-9]{32}$"));
password = CryptoUtils.GeneratePassword(32, 0, 0, 0, 32);
- Assert.IsTrue(Regex.IsMatch(password, @"^[""!@#$%()+;<>=?[\\\]{}^.,]{32}$"));
+ Assert.That(Regex.IsMatch(password, @"^[""!@#$%()+;<>=?[\\\]{}^.,]{32}$"));
password = CryptoUtils.GeneratePassword(64, 16, 16, 16, 16);
var chars = password.ToCharArray();
@@ -50,7 +50,7 @@ public void TestPasswordGeneration()
return groupCompare == 0 ? x.CompareTo(y) : groupCompare;
});
password = new string(chars);
- Assert.IsTrue(Regex.IsMatch(password, @"^[a-z]{16}[A-Z]{16}[0-9]{16}[""!@#$%()+;<>=?[\\\]{}^.,]{16}$"));
+ Assert.That(Regex.IsMatch(password, @"^[a-z]{16}[A-Z]{16}[0-9]{16}[""!@#$%()+;<>=?[\\\]{}^.,]{16}$"));
}
[Test]
@@ -59,7 +59,7 @@ public void TestTotpDefaultAlgorithm()
// {Algorithm: "", Period: 30, UnixTime: 20000000000, Secret: "12345678901234567890", Digits: 8}, Output: "65353130"}
const string url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=ACME&algorithm=&digits=8&period=30";
var totp = CryptoUtils.GetTotpCode(url, 20000000000);
- Assert.AreEqual("65353130", totp?.Code); // using default algorithm SHA1
+ Assert.That("65353130", Is.EqualTo(totp?.Code)); // using default algorithm SHA1
}
[Test]
@@ -68,7 +68,7 @@ public void TestTotpDefaultDigits()
// { Algorithm: "SHA1", Period: 30, UnixTime: 20000000000, Secret: "12345678901234567890", Digits: 0}, Output: "353130"}
const string url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=ACME&algorithm=SHA1&digits=0&period=30";
var totp = CryptoUtils.GetTotpCode(url, 20000000000);
- Assert.AreEqual("353130", totp?.Code); // using default digits = 6
+ Assert.That("353130", Is.EqualTo(totp?.Code)); // using default digits = 6
}
[Test]
@@ -77,7 +77,7 @@ public void TestTotpDefaultPeriod()
// {Algorithm: "SHA1", Period: 0, UnixTime: 20000000000, Secret: "12345678901234567890", Digits: 8}, Output: "65353130"}
const string url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=ACME&algorithm=SHA1&digits=8&period=0";
var totp = CryptoUtils.GetTotpCode(url, 20000000000);
- Assert.AreEqual("65353130", totp?.Code); // using default period = 30
+ Assert.That("65353130", Is.EqualTo(totp?.Code)); // using default period = 30
}
[Test]
@@ -86,7 +86,7 @@ public void TestTotpEmptySecret()
// {Algorithm: "SHA1", Period: 30, UnixTime: 0, Secret: "", Digits: 8}, Output: "no secret key provided"}
const string url = "otpauth://totp/ACME:john.doe@email.com?secret=&issuer=ACME&algorithm=SHA1&digits=8&period=30";
var totp = CryptoUtils.GetTotpCode(url);
- Assert.Null(totp, "Empty secret shouldn't produce valid TOTP");
+ Assert.That(totp, Is.Null, "Empty secret shouldn't produce valid TOTP");
}
[Test]
@@ -95,7 +95,7 @@ public void TestTotpInvalidAlgorithm()
// { Algorithm: "SHA1024", Period: 30, UnixTime: 0, Secret: "12345678901234567890", Digits: 8}, Output: "invalid algorithm - use one of SHA1/SHA256/SHA512"}
const string url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=ACME&algorithm=SHA1024&digits=8&period=30";
var totp = CryptoUtils.GetTotpCode(url);
- Assert.Null(totp, "SHA1024 is unsupported algorithm for TOTP");
+ Assert.That(totp, Is.Null, "SHA1024 is unsupported algorithm for TOTP");
}
[Test]
@@ -104,7 +104,7 @@ public void TestTotpInvalidSecret()
// { Algorithm: "SHA1", Period: 30, UnixTime: 0, Secret: "1NVAL1D", Digits: 8}, Output: "bad secret key"}
const string url = "otpauth://totp/ACME:john.doe@email.com?secret=1NVAL1D&issuer=ACME&algorithm=SHA1&digits=8&period=30";
var totp = CryptoUtils.GetTotpCode(url);
- Assert.Null(totp, "Invalid secret shouldn't produce valid TOTP");
+ Assert.That(totp, Is.Null, "Invalid secret shouldn't produce valid TOTP");
}
[Test]
@@ -114,86 +114,86 @@ public void TestTotp()
// {Algorithm: "SHA1", Period: 30, UnixTime: 59, Secret: "12345678901234567890", Digits: 8}, Output: "94287082"}
string url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=ACME&algorithm=SHA1&digits=8&period=30";
var totp = CryptoUtils.GetTotpCode(url, 59);
- Assert.AreEqual("94287082", totp?.Code);
- Assert.AreEqual(1, totp?.TimeLeft);
+ Assert.That("94287082", Is.EqualTo(totp?.Code));
+ Assert.That(1, Is.EqualTo(totp?.TimeLeft));
// {Algorithm: "SHA256", Period: 30, UnixTime: 59, Secret: "12345678901234567890123456789012", Digits: 8}, Output: "46119246"}
url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZA&issuer=ACME&algorithm=SHA256&digits=8&period=30";
totp = CryptoUtils.GetTotpCode(url, 59);
- Assert.AreEqual("46119246", totp?.Code);
- Assert.AreEqual(1, totp?.TimeLeft);
+ Assert.That("46119246", Is.EqualTo(totp?.Code));
+ Assert.That(1, Is.EqualTo(totp?.TimeLeft));
// {Algorithm: "SHA512", Period: 30, UnixTime: 59, Secret: "1234567890123456789012345678901234567890123456789012345678901234", Digits: 8}, Output: "90693936"}
url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNA=&issuer=ACME&algorithm=SHA512&digits=8&period=30";
totp = CryptoUtils.GetTotpCode(url, 59);
- Assert.AreEqual("90693936", totp?.Code);
- Assert.AreEqual(1, totp?.TimeLeft);
+ Assert.That("90693936", Is.EqualTo(totp?.Code));
+ Assert.That(1, Is.EqualTo(totp?.TimeLeft));
// Check different periods - 1 sec. before split
// {Algorithm: "SHA1", Period: 30, UnixTime: 1111111109, Secret: "12345678901234567890", Digits: 8}, Output: "07081804"}
url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=ACME&algorithm=SHA1&digits=8&period=30";
totp = CryptoUtils.GetTotpCode(url, 1111111109);
- Assert.AreEqual("07081804", totp?.Code);
+ Assert.That("07081804", Is.EqualTo(totp?.Code));
// {Algorithm: "SHA256", Period: 30, UnixTime: 1111111109, Secret: "12345678901234567890123456789012", Digits: 8}, Output: "68084774"}
url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZA&issuer=ACME&algorithm=SHA256&digits=8&period=30";
totp = CryptoUtils.GetTotpCode(url, 1111111109);
- Assert.AreEqual("68084774", totp?.Code);
+ Assert.That("68084774", Is.EqualTo(totp?.Code));
// {Algorithm: "SHA512", Period: 30, UnixTime: 1111111109, Secret: "1234567890123456789012345678901234567890123456789012345678901234", Digits: 8}, Output: "25091201"}
url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNA=&issuer=ACME&algorithm=SHA512&digits=8&period=30";
totp = CryptoUtils.GetTotpCode(url, 1111111109);
- Assert.AreEqual("25091201", totp?.Code);
+ Assert.That("25091201", Is.EqualTo(totp?.Code));
// Check different periods - 1 sec. after split
// {Algorithm: "SHA1", Period: 30, UnixTime: 1111111111, Secret: "12345678901234567890", Digits: 8}, Output: "14050471"}
url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=ACME&algorithm=SHA1&digits=8&period=30";
totp = CryptoUtils.GetTotpCode(url, 1111111111);
- Assert.AreEqual("14050471", totp?.Code);
+ Assert.That("14050471", Is.EqualTo(totp?.Code));
// {Algorithm: "SHA256", Period: 30, UnixTime: 1111111111, Secret: "12345678901234567890123456789012", Digits: 8}, Output: "67062674"}
url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZA&issuer=ACME&algorithm=SHA256&digits=8&period=30";
totp = CryptoUtils.GetTotpCode(url, 1111111111);
- Assert.AreEqual("67062674", totp?.Code);
+ Assert.That("67062674", Is.EqualTo(totp?.Code));
// {Algorithm: "SHA512", Period: 30, UnixTime: 1111111111, Secret: "1234567890123456789012345678901234567890123456789012345678901234", Digits: 8}, Output: "99943326"}
url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNA=&issuer=ACME&algorithm=SHA512&digits=8&period=30";
totp = CryptoUtils.GetTotpCode(url, 1111111111);
- Assert.AreEqual("99943326", totp?.Code);
+ Assert.That("99943326", Is.EqualTo(totp?.Code));
// Check different time periods
// {Algorithm: "SHA1", Period: 30, UnixTime: 1234567890, Secret: "12345678901234567890", Digits: 8}, Output: "89005924"}
url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=ACME&algorithm=SHA1&digits=8&period=30";
totp = CryptoUtils.GetTotpCode(url, 1234567890);
- Assert.AreEqual("89005924", totp?.Code);
+ Assert.That("89005924", Is.EqualTo(totp?.Code));
// {Algorithm: "SHA256", Period: 30, UnixTime: 1234567890, Secret: "12345678901234567890123456789012", Digits: 8}, Output: "91819424"}
url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZA&issuer=ACME&algorithm=SHA256&digits=8&period=30";
totp = CryptoUtils.GetTotpCode(url, 1234567890);
- Assert.AreEqual("91819424", totp?.Code);
+ Assert.That("91819424", Is.EqualTo(totp?.Code));
// {Algorithm: "SHA512", Period: 30, UnixTime: 1234567890, Secret: "1234567890123456789012345678901234567890123456789012345678901234", Digits: 8}, Output: "93441116"}
url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNA=&issuer=ACME&algorithm=SHA512&digits=8&period=30";
totp = CryptoUtils.GetTotpCode(url, 1234567890);
- Assert.AreEqual("93441116", totp?.Code);
+ Assert.That("93441116", Is.EqualTo(totp?.Code));
// {Algorithm: "SHA1", Period: 30, UnixTime: 2000000000, Secret: "12345678901234567890", Digits: 8}, Output: "69279037"}
url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=ACME&algorithm=SHA1&digits=8&period=30";
totp = CryptoUtils.GetTotpCode(url, 2000000000);
- Assert.AreEqual("69279037", totp?.Code);
+ Assert.That("69279037", Is.EqualTo(totp?.Code));
// {Algorithm: "SHA256", Period: 30, UnixTime: 2000000000, Secret: "12345678901234567890123456789012", Digits: 8}, Output: "90698825"}
url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZA&issuer=ACME&algorithm=SHA256&digits=8&period=30";
totp = CryptoUtils.GetTotpCode(url, 2000000000);
- Assert.AreEqual("90698825", totp?.Code);
+ Assert.That("90698825", Is.EqualTo(totp?.Code));
// {Algorithm: "SHA512", Period: 30, UnixTime: 2000000000, Secret: "1234567890123456789012345678901234567890123456789012345678901234", Digits: 8}, Output: "38618901"}
url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNA=&issuer=ACME&algorithm=SHA512&digits=8&period=30";
totp = CryptoUtils.GetTotpCode(url, 2000000000);
- Assert.AreEqual("38618901", totp?.Code);
+ Assert.That("38618901", Is.EqualTo(totp?.Code));
// {Algorithm: "SHA1", Period: 30, UnixTime: 20000000000, Secret: "12345678901234567890", Digits: 8}, Output: "65353130"}
url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=ACME&algorithm=SHA1&digits=8&period=30";
totp = CryptoUtils.GetTotpCode(url, 20000000000);
- Assert.AreEqual("65353130", totp?.Code);
+ Assert.That("65353130", Is.EqualTo(totp?.Code));
// {Algorithm: "SHA256", Period: 30, UnixTime: 20000000000, Secret: "12345678901234567890123456789012", Digits: 8}, Output: "77737706"}
url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZA&issuer=ACME&algorithm=SHA256&digits=8&period=30";
totp = CryptoUtils.GetTotpCode(url, 20000000000);
- Assert.AreEqual("77737706", totp?.Code);
+ Assert.That("77737706", Is.EqualTo(totp?.Code));
// {Algorithm: "SHA512", Period: 30, UnixTime: 20000000000, Secret: "1234567890123456789012345678901234567890123456789012345678901234", Digits: 8}, Output: "47863826"}
url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNA=&issuer=ACME&algorithm=SHA512&digits=8&period=30";
totp = CryptoUtils.GetTotpCode(url, 20000000000);
- Assert.AreEqual("47863826", totp?.Code);
+ Assert.That("47863826", Is.EqualTo(totp?.Code));
}
}
-}
\ No newline at end of file
+}
diff --git a/sdk/dotNet/SecretsManager.Test.Core/JsonUtils.Test.cs b/sdk/dotNet/SecretsManager.Test.Core/JsonUtils.Test.cs
index 94567399..7ac408fd 100644
--- a/sdk/dotNet/SecretsManager.Test.Core/JsonUtils.Test.cs
+++ b/sdk/dotNet/SecretsManager.Test.Core/JsonUtils.Test.cs
@@ -7,26 +7,27 @@ public class JsonUtilsTests
[Test]
public void ParseAndSerializeShouldNotChangeTheData()
{
- var rec = new KeeperRecordData {
- type = "Login2",
+ var rec = new KeeperRecordData
+ {
+ type = "Login2",
title = "MyHomeLogin",
notes = "MyNotes",
- fields = new KeeperRecordField[] {
- new KeeperRecordField { type = "login", label = "Login", value = new string[] { "Login 1" }, required=true, privacyScreen=true },
- new KeeperRecordField { type = "password", label = "Password", value = new string[] { "3[OJ%sc7n].wX6+k5GY)6" }, required=true, privacyScreen=true, enforceGeneration=true, complexity=new PasswordComplexity{ length=21, caps=5, lowercase=5, digits=5, special=5} },
- new KeeperRecordField { type = "url", label = "URL", value = new string[] { "https://asdfjkasdfkdsa.com" }, required=true, privacyScreen=true },
- new KeeperRecordField { type = "securityQuestion", label = "Security Question & Answer", value = new SecurityQuestion[] { new SecurityQuestion { question= "asdf", answer= "asdf" } }, required=true, privacyScreen=true },
- new KeeperRecordField { type = "fileRef", label = "File or Photo", value = new object[] { } },
- new KeeperRecordField { type = "oneTimeCode", label = "Two-Factor Code", value = new object[] { } },
- },
- custom = new KeeperRecordField[] { }
+ fields = new KeeperRecordField[] {
+ new KeeperRecordField { type = "login", label = "Login", value = new string[] { "Login 1" }, required=true, privacyScreen=true },
+ new KeeperRecordField { type = "password", label = "Password", value = new string[] { "3[OJ%sc7n].wX6+k5GY)6" }, required=true, privacyScreen=true, enforceGeneration=true, complexity=new PasswordComplexity{ length=21, caps=5, lowercase=5, digits=5, special=5} },
+ new KeeperRecordField { type = "url", label = "URL", value = new string[] { "https://asdfjkasdfkdsa.com" }, required=true, privacyScreen=true },
+ new KeeperRecordField { type = "securityQuestion", label = "Security Question & Answer", value = new SecurityQuestion[] { new SecurityQuestion { question= "asdf", answer= "asdf" } }, required=true, privacyScreen=true },
+ new KeeperRecordField { type = "fileRef", label = "File or Photo", value = new object[] { } },
+ new KeeperRecordField { type = "oneTimeCode", label = "Two-Factor Code", value = new object[] { } },
+ },
+ custom = new KeeperRecordField[] { }
};
var jsonIn = CryptoUtils.BytesToString(JsonUtils.SerializeJson(rec));
//const string jsonIn = "{\"title\":\"MyHomeLogin\",\"type\":\"Login2\",\"fields\":[{\"type\":\"login\",\"label\":\"Login\",\"value\":[\"Login 1\"],\"required\":true,\"privacyScreen\":true},{\"type\":\"password\",\"label\":\"Password\",\"value\":[\"3[OJ%sc7n].wX6+k5GY)6\"],\"required\":true,\"privacyScreen\":true,\"enforceGeneration\":true,\"complexity\":{\"length\":21,\"caps\":5,\"lowercase\":5,\"digits\":5,\"special\":5}},{\"type\":\"url\",\"label\":\"URL\",\"value\":[\"https://asdfjkasdfkdsa.com\"],\"required\":true,\"privacyScreen\":true},{\"type\":\"securityQuestion\",\"label\":\"Security Question & Answer\",\"value\":[{\"question\":\"asdf\",\"answer\":\"asdf\"}],\"required\":true,\"privacyScreen\":true},{\"type\":\"fileRef\",\"label\":\"File or Photo\",\"value\":[]},{\"type\":\"oneTimeCode\",\"label\":\"Two-Factor Code\",\"value\":[]}],\"custom\":[],\"notes\":\"MyNotes\"}";
var recordData = JsonUtils.ParseJson(CryptoUtils.StringToBytes(jsonIn));
var jsonOut = CryptoUtils.BytesToString(JsonUtils.SerializeJson(recordData));
- Assert.AreEqual(jsonIn, jsonOut);
- Assert.AreEqual(recordData.fields[1].value[0].ToString(), rec.fields[1].value[0]);
+ Assert.That(jsonIn, Is.EqualTo(jsonOut));
+ Assert.That(recordData.fields[1].value[0].ToString(), Is.EqualTo(rec.fields[1].value[0]));
}
[Test]
public void ParseAndSerializeShouldPreserveDiacritics()
@@ -34,7 +35,7 @@ public void ParseAndSerializeShouldPreserveDiacritics()
string recordTitle = "MySpéciàlHomèL°gin";
var krdin = new KeeperRecordData { title = recordTitle, type = "login" };
var krdout = JsonUtils.ParseJson(JsonUtils.SerializeJson(krdin));
- Assert.AreEqual(krdin.title, krdout.title);
+ Assert.That(krdin.title, Is.EqualTo(krdout.title));
}
}
-}
\ No newline at end of file
+}
diff --git a/sdk/dotNet/SecretsManager.Test.Core/Notation.Test.cs b/sdk/dotNet/SecretsManager.Test.Core/Notation.Test.cs
index 50e8ecca..3f016ba2 100644
--- a/sdk/dotNet/SecretsManager.Test.Core/Notation.Test.cs
+++ b/sdk/dotNet/SecretsManager.Test.Core/Notation.Test.cs
@@ -19,13 +19,13 @@ public void NotationsWork()
});
var value = Notation.GetValue(secrets, $"keeper://{RecordUid}/field/login");
- Assert.AreEqual("My Login 1", value);
+ Assert.That("My Login 1", Is.EqualTo(value));
value = Notation.GetValue(secrets, $"{RecordUid}/field/login");
- Assert.AreEqual("My Login 1", value);
+ Assert.That("My Login 1", Is.EqualTo(value));
value = Notation.GetValue(secrets, $"keeper://{RecordUid}/field/login[0]");
- Assert.AreEqual("My Login 1", value);
+ Assert.That("My Login 1", Is.EqualTo(value));
try
{
@@ -38,34 +38,34 @@ public void NotationsWork()
}
value = Notation.GetValue(secrets, $"keeper://{RecordUid}/field/login[]");
- Assert.AreEqual("[\"My Login 1\"]", value);
+ Assert.That("[\"My Login 1\"]", Is.EqualTo(value));
value = Notation.GetValue(secrets, $"keeper://{RecordUid}/custom_field/My Custom 1");
- Assert.AreEqual("custom1", value);
+ Assert.That("custom1", Is.EqualTo(value));
value = Notation.GetValue(secrets, $"keeper://{RecordUid}/custom_field/My Custom 2");
- Assert.AreEqual("one", value);
+ Assert.That("one", Is.EqualTo(value));
value = Notation.GetValue(secrets, $"keeper://{RecordUid}/custom_field/My Custom 2[1]");
- Assert.AreEqual("two", value);
+ Assert.That("two", Is.EqualTo(value));
value = Notation.GetValue(secrets, $"keeper://{RecordUid}/custom_field/My Custom 2[]");
- Assert.AreEqual("[\"one\",\"two\",\"three\"]", value);
+ Assert.That("[\"one\",\"two\",\"three\"]", Is.EqualTo(value));
value = Notation.GetValue(secrets, $"keeper://{RecordUid}/custom_field/phone[0][number]");
- Assert.AreEqual("555-5555555", value);
+ Assert.That("555-5555555", Is.EqualTo(value));
value = Notation.GetValue(secrets, $"keeper://{RecordUid}/custom_field/phone[1][number]");
- Assert.AreEqual("777-7777777", value);
+ Assert.That("777-7777777", Is.EqualTo(value));
value = Notation.GetValue(secrets, $"keeper://{RecordUid}/custom_field/phone[2]");
- Assert.AreEqual("{\"number\":\"888-8888888\",\"ext\":\"\",\"type\":\"Home\"}", value);
+ Assert.That("{\"number\":\"888-8888888\",\"ext\":\"\",\"type\":\"Home\"}", Is.EqualTo(value));
value = Notation.GetValue(secrets, $"keeper://{RecordUid}/custom_field/name[first]");
- Assert.AreEqual("Jenny", value);
+ Assert.That("Jenny", Is.EqualTo(value));
value = Notation.GetValue(secrets, $"keeper://{RecordUid}/custom_field/name[last]");
- Assert.AreEqual("Smith", value);
+ Assert.That("Smith", Is.EqualTo(value));
}
[Test]
@@ -86,35 +86,35 @@ public void NotationParserWork()
catch (Exception) { }
var res = Notation.ParseNotation("/type");
- Assert.AreEqual("type", res[2].Text.Item1);
+ Assert.That("type", Is.EqualTo(res[2].Text.Item1));
res = Notation.ParseNotation("/title");
- Assert.AreEqual("title", res[2].Text.Item1);
+ Assert.That("title", Is.EqualTo(res[2].Text.Item1));
res = Notation.ParseNotation("/notes");
- Assert.AreEqual("notes", res[2].Text.Item1);
+ Assert.That("notes", Is.EqualTo(res[2].Text.Item1));
res = Notation.ParseNotation("/file/filename.ext");
- Assert.AreEqual("file", res[2].Text.Item1);
- Assert.AreEqual("filename.ext", res[2].Parameter.Item1);
+ Assert.That("file", Is.EqualTo(res[2].Text.Item1));
+ Assert.That("filename.ext", Is.EqualTo(res[2].Parameter.Item1));
res = Notation.ParseNotation("/field/text");
- Assert.AreEqual("field", res[2].Text.Item1);
- Assert.AreEqual("text", res[2].Parameter.Item1);
+ Assert.That("field", Is.EqualTo(res[2].Text.Item1));
+ Assert.That("text", Is.EqualTo(res[2].Parameter.Item1));
res = Notation.ParseNotation(@"/custom_field/label with \[[0][middle]");
- Assert.AreEqual("", res[1].Text.Item1); // empty title
- Assert.AreEqual("custom_field", res[2].Text.Item1);
- Assert.AreEqual(@"label with [", res[2].Parameter.Item1);
- Assert.AreEqual("0", res[2].Index1.Item1);
- Assert.AreEqual("middle", res[2].Index2.Item1);
+ Assert.That("", Is.EqualTo(res[1].Text.Item1)); // empty title
+ Assert.That("custom_field", Is.EqualTo(res[2].Text.Item1));
+ Assert.That(@"label with [", Is.EqualTo(res[2].Parameter.Item1));
+ Assert.That("0", Is.EqualTo(res[2].Index1.Item1));
+ Assert.That("middle", Is.EqualTo(res[2].Index2.Item1));
res = Notation.ParseNotation(@"title with \[\]\//custom_field/label with \[[0][middle]");
- Assert.AreEqual(@"title with []/", res[1].Text.Item1);
- Assert.AreEqual("custom_field", res[2].Text.Item1);
- Assert.AreEqual(@"label with [", res[2].Parameter.Item1);
- Assert.AreEqual("0", res[2].Index1.Item1);
- Assert.AreEqual("middle", res[2].Index2.Item1);
+ Assert.That(@"title with []/", Is.EqualTo(res[1].Text.Item1));
+ Assert.That("custom_field", Is.EqualTo(res[2].Text.Item1));
+ Assert.That(@"label with [", Is.EqualTo(res[2].Parameter.Item1));
+ Assert.That("0", Is.EqualTo(res[2].Index1.Item1));
+ Assert.That("middle", Is.EqualTo(res[2].Index2.Item1));
}
}
}
diff --git a/sdk/dotNet/SecretsManager.Test.Core/SecretsManager.Test.Core.csproj b/sdk/dotNet/SecretsManager.Test.Core/SecretsManager.Test.Core.csproj
index b70d7b7b..20aa10c6 100644
--- a/sdk/dotNet/SecretsManager.Test.Core/SecretsManager.Test.Core.csproj
+++ b/sdk/dotNet/SecretsManager.Test.Core/SecretsManager.Test.Core.csproj
@@ -9,10 +9,13 @@
-
-
-
-
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/sdk/dotNet/SecretsManager.Test.Core/SecretsManagerClient.Test.cs b/sdk/dotNet/SecretsManager.Test.Core/SecretsManagerClient.Test.cs
index c8c6cbc4..b16d43fa 100644
--- a/sdk/dotNet/SecretsManager.Test.Core/SecretsManagerClient.Test.cs
+++ b/sdk/dotNet/SecretsManager.Test.Core/SecretsManagerClient.Test.cs
@@ -36,14 +36,14 @@ Task TestPostFunction(string s, TransmissionKey transmission
var storage = new LocalConfigStorage();
var fakeOneTimeCode = "YyIhK5wXFHj36wGBAOmBsxI3v5rIruINrC8KXjyM58c";
-
+
SecretsManagerClient.InitializeStorage(storage, fakeOneTimeCode, "fake.keepersecurity.com");
-
+
var options = new SecretsManagerOptions(storage, TestPostFunction);
var secrets = await SecretsManagerClient.GetSecrets(options);
var password = secrets.Records[1].FieldValue("password").ToString();
// ReSharper disable once StringLiteralTypo
- Assert.AreEqual("Lex1S++Wx6g^,LC.(Vp<", password);
+ Assert.That("Lex1S++Wx6g^,LC.(Vp<", Is.EqualTo(password));
}
[Test]
@@ -51,35 +51,35 @@ public void TestStoragePrefixes()
{
var storage = new InMemoryStorage();
SecretsManagerClient.InitializeStorage(storage, "US:ONE_TIME_TOKEN");
- Assert.AreEqual("keepersecurity.com", storage.GetString("hostname"));
+ Assert.That("keepersecurity.com", Is.EqualTo(storage.GetString("hostname")));
storage = new InMemoryStorage();
SecretsManagerClient.InitializeStorage(storage, "EU:ONE_TIME_TOKEN");
- Assert.AreEqual("keepersecurity.eu", storage.GetString("hostname"));
+ Assert.That("keepersecurity.eu", Is.EqualTo(storage.GetString("hostname")));
storage = new InMemoryStorage();
SecretsManagerClient.InitializeStorage(storage, "AU:ONE_TIME_TOKEN");
- Assert.AreEqual("keepersecurity.com.au", storage.GetString("hostname"));
+ Assert.That("keepersecurity.com.au", Is.EqualTo(storage.GetString("hostname")));
storage = new InMemoryStorage();
SecretsManagerClient.InitializeStorage(storage, "GOV:ONE_TIME_TOKEN");
- Assert.AreEqual("govcloud.keepersecurity.us", storage.GetString("hostname"));
+ Assert.That("govcloud.keepersecurity.us", Is.EqualTo(storage.GetString("hostname")));
storage = new InMemoryStorage();
SecretsManagerClient.InitializeStorage(storage, "JP:ONE_TIME_TOKEN");
- Assert.AreEqual("keepersecurity.jp", storage.GetString("hostname"));
+ Assert.That("keepersecurity.jp", Is.EqualTo(storage.GetString("hostname")));
storage = new InMemoryStorage();
SecretsManagerClient.InitializeStorage(storage, "CA:ONE_TIME_TOKEN");
- Assert.AreEqual("keepersecurity.ca", storage.GetString("hostname"));
+ Assert.That("keepersecurity.ca", Is.EqualTo(storage.GetString("hostname")));
storage = new InMemoryStorage();
SecretsManagerClient.InitializeStorage(storage, "eu:ONE_TIME_TOKEN");
- Assert.AreEqual("keepersecurity.eu", storage.GetString("hostname"));
+ Assert.That("keepersecurity.eu", Is.EqualTo(storage.GetString("hostname")));
storage = new InMemoryStorage();
SecretsManagerClient.InitializeStorage(storage, "fake.keepersecurity.com:ONE_TIME_TOKEN");
- Assert.AreEqual("fake.keepersecurity.com", storage.GetString("hostname"));
+ Assert.That("fake.keepersecurity.com", Is.EqualTo(storage.GetString("hostname")));
}
[Test]
@@ -88,9 +88,9 @@ public void TestStorageBase64Config()
string fakeBase64Config = "eyJhcHBLZXkiOiAiRkFLRV9BUFBfS0VZIiwgICAgICJjbGllbnRJZCI6ICJGQUtFX0NMSUVOVF9LRV" +
"kiLCAgICAgImhvc3RuYW1lIjogImZha2Uua2VlcGVyc2VjdXJpdHkuY29tIiwgICAgICJwcml2YXRl" +
"S2V5IjogIkZBS0VfUFJJVkFURV9LRVkiLCAgICAKInNlcnZlclB1YmxpY0tleUlkIjogIjEwIiB9";
-
+
var storage = new InMemoryStorage(fakeBase64Config);
- Assert.AreEqual("fake.keepersecurity.com", storage.GetString("hostname"));
+ Assert.That("fake.keepersecurity.com", Is.EqualTo(storage.GetString("hostname")));
}
}
-}
\ No newline at end of file
+}
diff --git a/sdk/dotNet/SecretsManager.sln b/sdk/dotNet/SecretsManager.sln
index e39c3e33..8839449f 100644
--- a/sdk/dotNet/SecretsManager.sln
+++ b/sdk/dotNet/SecretsManager.sln
@@ -11,6 +11,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SecretsManager.Test.Core",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SecretManagement.Keeper", "SecretManagement.Keeper\SecretManagement.Keeper.csproj", "{2BFFD8C3-29BD-4349-A183-7C01F9C34ED0}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{07124028-B5D7-4920-B2F3-76DF03ED030B}"
+ ProjectSection(SolutionItems) = preProject
+ .editorconfig = .editorconfig
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
diff --git a/sdk/dotNet/SecretsManager/CryptoUtils.cs b/sdk/dotNet/SecretsManager/CryptoUtils.cs
index fbf5784c..84f73137 100644
--- a/sdk/dotNet/SecretsManager/CryptoUtils.cs
+++ b/sdk/dotNet/SecretsManager/CryptoUtils.cs
@@ -5,7 +5,6 @@
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;
-using Org.BouncyCastle.Math;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Crypto.Paddings;
@@ -107,10 +106,10 @@ private static ECPublicKeyParameters ImportPublicKey(byte[] key)
}
private static ECPrivateKeyParameters ImportPrivateKey(byte[] privateKeyDer)
- {
- var privateKeyInfo = PrivateKeyInfo.GetInstance(privateKeyDer);
- var privateKeyStructure = ECPrivateKeyStructure.GetInstance(privateKeyInfo.ParsePrivateKey());
- var privateKeyValue = privateKeyStructure.GetKey();
+ {
+ var privateKeyInfo = PrivateKeyInfo.GetInstance(privateKeyDer);
+ var privateKeyStructure = ECPrivateKeyStructure.GetInstance(privateKeyInfo.ParsePrivateKey());
+ var privateKeyValue = privateKeyStructure.GetKey();
return new ECPrivateKeyParameters(privateKeyValue, ECParameters);
}
diff --git a/sdk/dotNet/SecretsManager/JsonUtils.cs b/sdk/dotNet/SecretsManager/JsonUtils.cs
index 30c874a8..e70a5ab7 100644
--- a/sdk/dotNet/SecretsManager/JsonUtils.cs
+++ b/sdk/dotNet/SecretsManager/JsonUtils.cs
@@ -22,4 +22,4 @@ public static byte[] SerializeJson(T obj)
return CryptoUtils.StringToBytes(JsonSerializer.Serialize(obj, Options));
}
}
-}
\ No newline at end of file
+}
diff --git a/sdk/dotNet/SecretsManager/LocalConfigStorage.cs b/sdk/dotNet/SecretsManager/LocalConfigStorage.cs
index 871fef50..e93e290a 100644
--- a/sdk/dotNet/SecretsManager/LocalConfigStorage.cs
+++ b/sdk/dotNet/SecretsManager/LocalConfigStorage.cs
@@ -195,4 +195,4 @@ public static byte[] GetCachedValue()
return File.ReadAllBytes("cache.dat");
}
}
-}
\ No newline at end of file
+}
diff --git a/sdk/dotNet/SecretsManager/RecordData.cs b/sdk/dotNet/SecretsManager/RecordData.cs
index ad8482cc..2c60db46 100644
--- a/sdk/dotNet/SecretsManager/RecordData.cs
+++ b/sdk/dotNet/SecretsManager/RecordData.cs
@@ -567,8 +567,8 @@ public class Script
public string fileRef { get; set; }
public string command { get; set; }
public List recordRef { get; set; }
- }
-
+ }
+
[SuppressMessage("ReSharper", "InconsistentNaming")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
diff --git a/sdk/dotNet/SecretsManager/SecretsManager.csproj b/sdk/dotNet/SecretsManager/SecretsManager.csproj
index 563789cf..0a662ddc 100644
--- a/sdk/dotNet/SecretsManager/SecretsManager.csproj
+++ b/sdk/dotNet/SecretsManager/SecretsManager.csproj
@@ -23,7 +23,7 @@
-
+
From 06598203be665f9017c3572dc4e47c290ddf10c4 Mon Sep 17 00:00:00 2001
From: idimov-keeper <78815270+idimov-keeper@users.noreply.github.com>
Date: Tue, 23 Jul 2024 16:37:06 -0500
Subject: [PATCH 6/9] target netstandard2.0 (#626)
update to ModuleVersion 16.6.6
---
.../SecretManagement.Keeper.Extension.psd1 | 2 +-
.../SecretManagement.Keeper/SecretManagement.Keeper.csproj | 6 +++---
.../SecretManagement.Keeper/SecretManagement.Keeper.psd1 | 2 +-
sdk/dotNet/SecretManagement.Keeper/build.ps1 | 6 +++---
4 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.Extension/SecretManagement.Keeper.Extension.psd1 b/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.Extension/SecretManagement.Keeper.Extension.psd1
index 46074a60..3ab03258 100644
--- a/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.Extension/SecretManagement.Keeper.Extension.psd1
+++ b/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.Extension/SecretManagement.Keeper.Extension.psd1
@@ -1,5 +1,5 @@
@{
- ModuleVersion = '16.6.4'
+ ModuleVersion = '16.6.6'
RootModule = 'SecretManagement.Keeper.Extension.psm1'
RequiredAssemblies = '../SecretManagement.Keeper.dll'
CompatiblePSEditions = @('Core')
diff --git a/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.csproj b/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.csproj
index b1d1dcee..090d492d 100644
--- a/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.csproj
+++ b/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.csproj
@@ -1,11 +1,11 @@

- netstandard2.1
+ netstandard2.0
SecretManagement.Keeper
true
- 16.6.4
- 16.6.4
+ 16.6.6
+ 16.6.6
diff --git a/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.psd1 b/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.psd1
index 15685c93..02255fda 100644
--- a/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.psd1
+++ b/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.psd1
@@ -1,5 +1,5 @@
@{
- ModuleVersion = '16.6.4'
+ ModuleVersion = '16.6.6'
CompatiblePSEditions = @('Core')
GUID = '20ab89cb-f0dd-4e8e-b276-f3a7708c1eb2'
Author = 'Sergey Aldoukhov'
diff --git a/sdk/dotNet/SecretManagement.Keeper/build.ps1 b/sdk/dotNet/SecretManagement.Keeper/build.ps1
index f8b94d3e..3595c5bb 100644
--- a/sdk/dotNet/SecretManagement.Keeper/build.ps1
+++ b/sdk/dotNet/SecretManagement.Keeper/build.ps1
@@ -28,9 +28,9 @@ if ($Package) {
}
@(
- './bin/Release/netstandard2.1/SecretManagement.Keeper.dll'
- './bin/Release/netstandard2.1/SecretsManager.dll'
- './bin/Release/netstandard2.1/BouncyCastle.Cryptography.dll'
+ './bin/Release/netstandard2.0/SecretManagement.Keeper.dll'
+ './bin/Release/netstandard2.0/SecretsManager.dll'
+ './bin/Release/netstandard2.0/BouncyCastle.Cryptography.dll'
) | ForEach-Object {
Copy-Item -Path $_ -Destination $outDir -Force
}
From 924fb89c949376d0098d609c82f4d0d4c0e188fc Mon Sep 17 00:00:00 2001
From: idimov-keeper <78815270+idimov-keeper@users.noreply.github.com>
Date: Tue, 30 Jul 2024 13:34:55 -0500
Subject: [PATCH 7/9] KSM-542 added escape char '\' for dots in title for
Powershell (#630)
---
.../SecretManagement.Keeper.Extension.psm1 | 7 +--
sdk/dotNet/SecretManagement.Keeper/build.ps1 | 14 ++++-
.../src/SecretManagement.cs | 53 +++++++++++++++++--
3 files changed, 65 insertions(+), 9 deletions(-)
diff --git a/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.Extension/SecretManagement.Keeper.Extension.psm1 b/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.Extension/SecretManagement.Keeper.Extension.psm1
index e76368a3..54dd4d56 100644
--- a/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.Extension/SecretManagement.Keeper.Extension.psm1
+++ b/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.Extension/SecretManagement.Keeper.Extension.psm1
@@ -3,14 +3,15 @@ function Get-Config {
[string] $LocalVaultName
)
$vaults = Microsoft.Powershell.SecretManagement\Get-SecretVault
- $localVault = $vaults.Where( { $_.Name -eq $LocalVaultName } )
+ $localVault = $vaults.Where( { $_.Name -eq $LocalVaultName } ) # SecretStore/LocalStore
if (!$localVault) {
return $null
}
+
$moduleInstance = Import-Module -Name $localVault.ModuleName -PassThru
- $configSecretName = 'KeeperVault.' + $VaultName
+ $configSecretName = 'KeeperVault.' + $VaultName # passed by SecretStore while enumerating registered vaults
$config = & $moduleInstance Get-Secret -Name $configSecretName -VaultName $localVault.Name
- if ($config -isnot [Hashtable]) {
+ if ($config -isnot [Hashtable]) {
$config = $config[0] # SecretStore returns a List
}
return $config
diff --git a/sdk/dotNet/SecretManagement.Keeper/build.ps1 b/sdk/dotNet/SecretManagement.Keeper/build.ps1
index 3595c5bb..b986b263 100644
--- a/sdk/dotNet/SecretManagement.Keeper/build.ps1
+++ b/sdk/dotNet/SecretManagement.Keeper/build.ps1
@@ -28,9 +28,19 @@ if ($Package) {
}
@(
- './bin/Release/netstandard2.0/SecretManagement.Keeper.dll'
'./bin/Release/netstandard2.0/SecretsManager.dll'
+ './bin/Release/netstandard2.0/SecretManagement.Keeper.dll'
+ './bin/Release/netstandard2.0/SecretManagement.Keeper.deps.json'
'./bin/Release/netstandard2.0/BouncyCastle.Cryptography.dll'
+ './bin/Release/netstandard2.0/Microsoft.Bcl.AsyncInterfaces.dll'
+ './bin/Release/netstandard2.0/System.Buffers.dll'
+ './bin/Release/netstandard2.0/System.Management.Automation.dll'
+ './bin/Release/netstandard2.0/System.Memory.dll'
+ './bin/Release/netstandard2.0/System.Numerics.Vectors.dll'
+ './bin/Release/netstandard2.0/System.Runtime.CompilerServices.Unsafe.dll'
+ './bin/Release/netstandard2.0/System.Text.Encodings.Web.dll'
+ './bin/Release/netstandard2.0/System.Text.Json.dll'
+ './bin/Release/netstandard2.0/System.Threading.Tasks.Extensions.dll'
) | ForEach-Object {
Copy-Item -Path $_ -Destination $outDir -Force
}
@@ -45,4 +55,4 @@ if ($Publish) {
Publish-Module -Path ./out/SecretManagement.Keeper -NuGetApiKey $APIKey -Verbose
}
-Pop-Location
\ No newline at end of file
+Pop-Location
diff --git a/sdk/dotNet/SecretManagement.Keeper/src/SecretManagement.cs b/sdk/dotNet/SecretManagement.Keeper/src/SecretManagement.cs
index 345f5970..73780dbf 100644
--- a/sdk/dotNet/SecretManagement.Keeper/src/SecretManagement.cs
+++ b/sdk/dotNet/SecretManagement.Keeper/src/SecretManagement.cs
@@ -4,8 +4,10 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
+using System.IO;
using System.Linq;
using System.Management.Automation;
+using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
@@ -19,7 +21,7 @@ public static async Task GetVaultConfigFromToken(string oneTimeTok
SecretsManagerClient.InitializeStorage(storage, oneTimeToken);
try
{
- await SecretsManagerClient.GetSecrets(new SecretsManagerOptions(storage));
+ await SecretsManagerClient.GetSecrets(new SecretsManagerOptions(storage), new string[] { new string('A', 22) });
}
catch (Exception e)
{
@@ -34,7 +36,7 @@ public static async Task GetVaultConfigFromConfigString(string con
var storage = new InMemoryStorage(config);
try
{
- await SecretsManagerClient.GetSecrets(new SecretsManagerOptions(storage));
+ await SecretsManagerClient.GetSecrets(new SecretsManagerOptions(storage), new string[] { new string('A', 22) });
}
catch (Exception e)
{
@@ -75,9 +77,52 @@ public static KeeperResult Error(string errorMsg)
}
}
+ private const char EscapeChar = '\\';
+ private static readonly char[] EscapedChars = { '\\', '.' };
+ private static string[] ParseQuery(string query)
+ {
+ // escape char to be used only in title and only when title has dot(s)
+ if (string.IsNullOrEmpty(query)) return new string[] { "" };
+ if (!query.Contains('.') || !query.Contains(EscapeChar)) return query.Split(new[] { '.' }, 2);
+
+ int pos = 0;
+ bool inEscSequence = query[pos].Equals(EscapeChar);
+ StringBuilder title = inEscSequence ? new StringBuilder() : new StringBuilder(query[pos].ToString());
+ while (++pos <= query.Length)
+ {
+ if (pos >= query.Length)
+ {
+ // unfinished escape sequence - treat last EscapeChar as regular character
+ if (inEscSequence) title.Append(EscapeChar);
+ break;
+ }
+ if (inEscSequence)
+ {
+ // \N (where N not in EscapedChars) is bad esc sequence but treat \ as single non esc char
+ if (!EscapedChars.Contains(query[pos]))
+ title.Append(EscapeChar);
+ title.Append(query[pos]);
+ inEscSequence = false;
+ }
+ else
+ {
+ inEscSequence = query[pos].Equals(EscapeChar);
+ if (!inEscSequence)
+ {
+ if ('.'.Equals(query[pos])) break;
+ title.Append(query[pos]);
+ }
+ }
+ }
+
+ // return cleaned up title with escape chars removed
+ if (pos >= query.Length) return new string[] { title.ToString() };
+ return new string[] { title.ToString(), query.Substring(pos + 1) };
+ }
+
public static async Task