From 6d3cabcd6c72522b08e3164d1df9c9d8aad909a1 Mon Sep 17 00:00:00 2001 From: Mariana Rios Flores Date: Fri, 7 Jun 2019 16:14:14 -0700 Subject: [PATCH] Keys Sync implementation (#6490) * sync implementation * changes from other PRs * PR feedback --- .../src/KeyClient.cs | 96 ++++++++++++++----- .../src/KeyClient_private.cs | 69 +++++++++++++ 2 files changed, 142 insertions(+), 23 deletions(-) diff --git a/sdk/keyvault/Azure.Security.KeyVault.Keys/src/KeyClient.cs b/sdk/keyvault/Azure.Security.KeyVault.Keys/src/KeyClient.cs index c71ff745018b..32826baaf74f 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Keys/src/KeyClient.cs +++ b/sdk/keyvault/Azure.Security.KeyVault.Keys/src/KeyClient.cs @@ -43,7 +43,12 @@ public KeyClient(Uri vaultUri, TokenCredential credential, KeyClientOptions opti public virtual Response CreateKey(string name, KeyType keyType, KeyCreateOptions keyOptions = default, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + if (string.IsNullOrEmpty(name)) throw new ArgumentException($"{nameof(name)} can't be empty or null"); + if (keyType == default) throw new ArgumentNullException(nameof(keyType)); + + var parameters = new KeyRequestParameters(keyType, keyOptions); + + return SendRequest(HttpPipelineMethod.Put, parameters, () => new Key(name), cancellationToken, KeysPath, name, "create"); } public virtual async Task> CreateKeyAsync(string name, KeyType keyType, KeyCreateOptions keyOptions = default, CancellationToken cancellationToken = default) @@ -53,12 +58,16 @@ public virtual async Task> CreateKeyAsync(string name, KeyType key var parameters = new KeyRequestParameters(keyType, keyOptions); - return await SendRequestAsync(HttpPipelineMethod.Put, parameters, () => new Key(name), cancellationToken, KeysPath, name); + return await SendRequestAsync(HttpPipelineMethod.Put, parameters, () => new Key(name), cancellationToken, KeysPath, name, "create"); } public virtual Response CreateEcKey(EcKeyCreateOptions ecKey, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + if (ecKey == default) throw new ArgumentNullException(nameof(ecKey)); + + var parameters = new KeyRequestParameters(ecKey); + + return SendRequest(HttpPipelineMethod.Put, parameters, () => new Key(ecKey.Name), cancellationToken, KeysPath, ecKey.Name, "create"); } public virtual async Task> CreateEcKeyAsync(EcKeyCreateOptions ecKey, CancellationToken cancellationToken = default) @@ -67,12 +76,16 @@ public virtual async Task> CreateEcKeyAsync(EcKeyCreateOptions ecK var parameters = new KeyRequestParameters(ecKey); - return await SendRequestAsync(HttpPipelineMethod.Put, parameters, () => new Key(ecKey.Name), cancellationToken, KeysPath, ecKey.Name); + return await SendRequestAsync(HttpPipelineMethod.Put, parameters, () => new Key(ecKey.Name), cancellationToken, KeysPath, ecKey.Name, "create"); } public virtual Response CreateRsaKey(RsaKeyCreateOptions rsaKey, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + if (rsaKey == default) throw new ArgumentNullException(nameof(rsaKey)); + + var parameters = new KeyRequestParameters(rsaKey); + + return SendRequest(HttpPipelineMethod.Put, parameters, () => new Key(rsaKey.Name), cancellationToken, KeysPath, rsaKey.Name, "create"); } public virtual async Task> CreateRsaKeyAsync(RsaKeyCreateOptions rsaKey, CancellationToken cancellationToken = default) @@ -81,12 +94,17 @@ public virtual async Task> CreateRsaKeyAsync(RsaKeyCreateOptions r var parameters = new KeyRequestParameters(rsaKey); - return await SendRequestAsync(HttpPipelineMethod.Put, parameters, () => new Key(rsaKey.Name), cancellationToken, KeysPath, rsaKey.Name); + return await SendRequestAsync(HttpPipelineMethod.Put, parameters, () => new Key(rsaKey.Name), cancellationToken, KeysPath, rsaKey.Name, "create"); } public virtual Response UpdateKey(KeyBase key, IEnumerable keyOperations, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + if (string.IsNullOrEmpty(key?.Name)) throw new ArgumentException($"{nameof(key.Name)} can't be empty or null"); + if (string.IsNullOrEmpty(key?.Version)) throw new ArgumentException($"{nameof(key.Version)} can't be empty or null"); + + var parameters = new KeyRequestParameters(key, keyOperations); + + return SendRequest(HttpPipelineMethod.Patch, parameters, () => new Key(key.Name), cancellationToken, KeysPath, key.Name, key.Version); } public virtual async Task> UpdateKeyAsync(KeyBase key, IEnumerable keyOperations, CancellationToken cancellationToken = default) @@ -101,7 +119,9 @@ public virtual async Task> UpdateKeyAsync(KeyBase key, IEnumerable public virtual Response GetKey(string name, string version = null, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + if (string.IsNullOrEmpty(name)) throw new ArgumentException($"{nameof(name)} can't be empty or null"); + + return SendRequest(HttpPipelineMethod.Get, () => new Key(name), cancellationToken, KeysPath, name, version); } public virtual async Task> GetKeyAsync(string name, string version = null, CancellationToken cancellationToken = default) @@ -113,7 +133,9 @@ public virtual async Task> GetKeyAsync(string name, string version public virtual IEnumerable> GetKeys(CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + Uri firstPageUri = CreateFirstPageUri(KeysPath); + + return PageResponseEnumerator.CreateEnumerable(nextLink => GetPage(firstPageUri, nextLink, () => new KeyBase(), cancellationToken)); } public virtual IAsyncEnumerable> GetKeysAsync(CancellationToken cancellationToken = default) @@ -125,33 +147,41 @@ public virtual IAsyncEnumerable> GetKeysAsync(CancellationToke public virtual IEnumerable> GetKeyVersions(string name, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + if (string.IsNullOrEmpty(name)) throw new ArgumentException($"{nameof(name)} can't be empty or null"); + + Uri firstPageUri = CreateFirstPageUri($"{KeysPath}{name}/versions"); + + return PageResponseEnumerator.CreateEnumerable(nextLink => GetPage(firstPageUri, nextLink, () => new KeyBase(), cancellationToken)); } public virtual IAsyncEnumerable> GetKeyVersionsAsync(string name, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(name)) throw new ArgumentException($"{nameof(name)} can't be empty or null"); - Uri firstPageUri = CreateFirstPageUri(KeysPath); + Uri firstPageUri = CreateFirstPageUri($"{KeysPath}{name}/versions"); return PageResponseEnumerator.CreateAsyncEnumerable(nextLink => GetPageAsync(firstPageUri, nextLink, () => new KeyBase(), cancellationToken)); } public virtual Response GetDeletedKey(string name, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + if (string.IsNullOrEmpty(name)) throw new ArgumentException($"{nameof(name)} can't be empty or null"); + + return SendRequest(HttpPipelineMethod.Get, () => new DeletedKey(name), cancellationToken, DeletedKeysPath, name); } public virtual async Task> GetDeletedKeyAsync(string name, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(name)) throw new ArgumentException($"{nameof(name)} can't be empty or null"); - return await SendRequestAsync(HttpPipelineMethod.Get, () => new DeletedKey(name), cancellationToken, KeysPath, name); + return await SendRequestAsync(HttpPipelineMethod.Get, () => new DeletedKey(name), cancellationToken, DeletedKeysPath, name); } public virtual Response DeleteKey(string name, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + if (string.IsNullOrEmpty(name)) throw new ArgumentException($"{nameof(name)} can't be empty or null"); + + return SendRequest(HttpPipelineMethod.Delete, () => new DeletedKey(name), cancellationToken, KeysPath, name); } public virtual async Task> DeleteKeyAsync(string name, CancellationToken cancellationToken = default) @@ -163,7 +193,9 @@ public virtual async Task> DeleteKeyAsync(string name, Canc public virtual IEnumerable> GetDeletedKeys(CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + Uri firstPageUri = CreateFirstPageUri(DeletedKeysPath); + + return PageResponseEnumerator.CreateEnumerable(nextLink => GetPage(firstPageUri, nextLink, () => new DeletedKey(), cancellationToken)); } public virtual IAsyncEnumerable> GetDeletedKeysAsync(CancellationToken cancellationToken = default) @@ -175,7 +207,9 @@ public virtual IAsyncEnumerable> GetDeletedKeysAsync(Cancel public virtual Response PurgeDeletedKey(string name, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + if (string.IsNullOrEmpty(name)) throw new ArgumentException($"{nameof(name)} can't be empty or null"); + + return SendRequest(HttpPipelineMethod.Delete, cancellationToken, DeletedKeysPath, name); } public virtual async Task PurgeDeletedKeyAsync(string name, CancellationToken cancellationToken = default) @@ -187,18 +221,25 @@ public virtual async Task PurgeDeletedKeyAsync(string name, Cancellati public virtual Response RecoverDeletedKey(string name, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + if (string.IsNullOrEmpty(name)) throw new ArgumentException($"{nameof(name)} can't be empty or null"); + + return SendRequest(HttpPipelineMethod.Post, () => new Key(name), cancellationToken, DeletedKeysPath, name, "recover"); } public virtual async Task> RecoverDeletedKeyAsync(string name, CancellationToken cancellationToken = default) { - await Task.CompletedTask; - throw new NotImplementedException(); + if (string.IsNullOrEmpty(name)) throw new ArgumentException($"{nameof(name)} can't be empty or null"); + + return await SendRequestAsync(HttpPipelineMethod.Post, () => new Key(name), cancellationToken, DeletedKeysPath, name, "recover"); } public virtual Response BackupKey(string name, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + if (string.IsNullOrEmpty(name)) throw new ArgumentException($"{nameof(name)} can't be empty or null"); + + var backup = SendRequest(HttpPipelineMethod.Post, () => new KeyBackup(), cancellationToken, KeysPath, name, "backup"); + + return new Response(backup.GetRawResponse(), backup.Value.Value); } public virtual async Task> BackupKeyAsync(string name, CancellationToken cancellationToken = default) @@ -212,7 +253,9 @@ public virtual async Task> BackupKeyAsync(string name, Cancella public virtual Response RestoreKey(byte[] backup, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + if (backup == null) throw new ArgumentNullException(nameof(backup)); + + return SendRequest(HttpPipelineMethod.Post, new KeyBackup { Value = backup }, () => new Key(), cancellationToken, KeysPath, "restore"); } public virtual async Task> RestoreKeyAsync(byte[] backup, CancellationToken cancellationToken = default) @@ -224,7 +267,12 @@ public virtual async Task> RestoreKeyAsync(byte[] backup, Cancella public virtual Response ImportKey(string name, JsonWebKey keyMaterial, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + if (string.IsNullOrEmpty(name)) throw new ArgumentException($"{nameof(name)} can't be empty or null"); + if (keyMaterial == default) throw new ArgumentNullException(nameof(keyMaterial)); + + var keyImportOptions = new KeyImportOptions(name, keyMaterial); + + return SendRequest(HttpPipelineMethod.Put, keyImportOptions, () => new Key(name), cancellationToken, KeysPath, name); } public virtual async Task> ImportKeyAsync(string name, JsonWebKey keyMaterial, CancellationToken cancellationToken = default) @@ -239,7 +287,9 @@ public virtual async Task> ImportKeyAsync(string name, JsonWebKey public virtual Response ImportKey(KeyImportOptions keyImportOptions, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + if (keyImportOptions == default) throw new ArgumentNullException(nameof(keyImportOptions)); + + return SendRequest(HttpPipelineMethod.Put, keyImportOptions, () => new Key(keyImportOptions.Name), cancellationToken, KeysPath, keyImportOptions.Name); } public virtual async Task> ImportKeyAsync(KeyImportOptions keyImportOptions, CancellationToken cancellationToken = default) diff --git a/sdk/keyvault/Azure.Security.KeyVault.Keys/src/KeyClient_private.cs b/sdk/keyvault/Azure.Security.KeyVault.Keys/src/KeyClient_private.cs index d7f6d74b8119..d38dfbf25199 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Keys/src/KeyClient_private.cs +++ b/sdk/keyvault/Azure.Security.KeyVault.Keys/src/KeyClient_private.cs @@ -59,6 +59,53 @@ private async Task SendRequestAsync(Request request, CancellationToken } } + private Response SendRequest(HttpPipelineMethod method, TContent content, Func resultFactory, CancellationToken cancellationToken, params string[] path) + where TContent : Model + where TResult : Model + { + using (Request request = CreateRequest(method, path)) + { + request.Content = HttpPipelineRequestContent.Create(content.Serialize()); + + Response response = SendRequest(request, cancellationToken); + + return CreateResponse(response, resultFactory()); + } + } + + private Response SendRequest(HttpPipelineMethod method, Func resultFactory, CancellationToken cancellationToken, params string[] path) + where TResult : Model + { + using (Request request = CreateRequest(method, path)) + { + Response response = SendRequest(request, cancellationToken); + + return CreateResponse(response, resultFactory()); + } + } + + private Response SendRequest(HttpPipelineMethod method, CancellationToken cancellationToken, params string[] path) + { + using (Request request = CreateRequest(method, path)) + { + return SendRequest(request, cancellationToken); + } + } + + private Response SendRequest(Request request, CancellationToken cancellationToken) + { + var response = _pipeline.SendRequest(request, cancellationToken); + + switch (response.Status) + { + case 200: + case 201: + return response; + default: + throw response.CreateRequestFailedException(); + } + } + private Request CreateRequest(HttpPipelineMethod method, params string[] path) { // duplicating the code from the overload which takes a URI here because there is currently a bug in @@ -136,5 +183,27 @@ private async Task> GetPageAsync(Uri firstPageUri, string nex return new PageResponse(responseAsPage.Items.ToArray(), response, responseAsPage.NextLink?.ToString()); } } + + private PageResponse GetPage(Uri firstPageUri, string nextLink, Func itemFactory, CancellationToken cancellationToken) + where T : Model + { + // if we don't have a nextLink specified, use firstPageUri + if (nextLink != null) + { + firstPageUri = new Uri(nextLink); + } + + using (Request request = CreateRequest(HttpPipelineMethod.Get, firstPageUri)) + { + Response response = SendRequest(request, cancellationToken); + + // read the respose + Page responseAsPage = new Page(itemFactory); + responseAsPage.Deserialize(response.ContentStream); + + // convert from the Page to PageResponse + return new PageResponse(responseAsPage.Items.ToArray(), response, responseAsPage.NextLink?.ToString()); + } + } } }