diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aa3bdf3..a01be4c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -42,7 +42,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-dotnet@v4 with: - dotnet-version: 6.0.x + dotnet-version: '8.0.x' - uses: microsoft/setup-msbuild@v2 - uses: nuget/setup-nuget@v2 diff --git a/Commander/App.config b/Commander/App.config index 931b343..6f06adb 100644 --- a/Commander/App.config +++ b/Commander/App.config @@ -21,6 +21,14 @@ + + + + + + + + \ No newline at end of file diff --git a/Commander/Commander.csproj b/Commander/Commander.csproj index 1aae3ba..67e69c2 100644 --- a/Commander/Commander.csproj +++ b/Commander/Commander.csproj @@ -69,15 +69,15 @@ - - ..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll + + ..\packages\System.Memory.4.5.3\lib\netstandard2.0\System.Memory.dll ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll - - ..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll diff --git a/Commander/packages.config b/Commander/packages.config index cc90140..b658a16 100644 --- a/Commander/packages.config +++ b/Commander/packages.config @@ -4,7 +4,7 @@ - + - + \ No newline at end of file diff --git a/KeeperSdk/KeeperSdk.csproj b/KeeperSdk/KeeperSdk.csproj index 3a9edd1..27d3907 100644 --- a/KeeperSdk/KeeperSdk.csproj +++ b/KeeperSdk/KeeperSdk.csproj @@ -50,6 +50,13 @@ + + + + + + 4.5.2 + diff --git a/KeeperSdk/vault/FileAttachment.cs b/KeeperSdk/vault/FileAttachment.cs index 57ff190..aa6b011 100644 --- a/KeeperSdk/vault/FileAttachment.cs +++ b/KeeperSdk/vault/FileAttachment.cs @@ -11,15 +11,17 @@ using System.Text; using System.Threading.Tasks; using Google.Protobuf; +using Org.BouncyCastle.Utilities.Zlib; +using System.Net.Http; +using System.Runtime.CompilerServices; +using System.Net.Http.Headers; -namespace KeeperSecurity.Vault -{ +namespace KeeperSecurity.Vault { /// /// Creates an attachment upload task. /// - public class AttachmentUploadTask : IAttachmentUploadTask - { + public class AttachmentUploadTask : IAttachmentUploadTask { /// /// Initializes a new instance of class. /// @@ -50,8 +52,7 @@ public AttachmentUploadTask(Stream attachmentStream, IThumbnailUploadTask thumbn /// /// Creates a file attachment upload task. /// - public class FileAttachmentUploadTask : AttachmentUploadTask, IDisposable - { + public class FileAttachmentUploadTask : AttachmentUploadTask, IDisposable { /// /// Initializes a new instance of class. /// @@ -60,25 +61,16 @@ public class FileAttachmentUploadTask : AttachmentUploadTask, IDisposable public FileAttachmentUploadTask(string fileName, IThumbnailUploadTask thumbnail = null) : base(null, thumbnail) { - if (File.Exists(fileName)) - { - Name = Path.GetFileName(fileName); - Title = Name; - try - { - MimeType = MimeTypes.MimeTypeMap.GetMimeType(Path.GetExtension(fileName)); - } - catch - { - // ignored - } - - Stream = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); - } - else - { - Trace.TraceError("FileAttachmentUploadTask: fileName: \"{0}\" not found.", fileName); + if (!File.Exists(fileName)) { + throw new Exception($"Cannot open file \"{fileName}\""); } + Name = Path.GetFileName(fileName); + Title = Name; + try { + MimeType = MimeTypes.MimeTypeMap.GetMimeType(Path.GetExtension(fileName)); + } catch {/*ignored*/} + + Stream = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); } public void Dispose() @@ -87,47 +79,39 @@ public void Dispose() } } - public partial class VaultOnline : IVaultFileAttachment - { + public partial class VaultOnline : IVaultFileAttachment { /// public IEnumerable RecordAttachments(KeeperRecord record) { - switch (record) - { - case PasswordRecord password: - if (password.Attachments != null) - { - foreach (var atta in password.Attachments) - { - yield return atta; - } + switch (record) { + case PasswordRecord password: + if (password.Attachments != null) { + foreach (var atta in password.Attachments) { + yield return atta; } + } - break; - - case TypedRecord typed: - var fileRef = typed.Fields - .Where(x => x.FieldName == "fileRef") - .OfType>().FirstOrDefault(); - if (fileRef != null) - { - foreach (var fileUid in fileRef.Values) - { - if (TryGetKeeperRecord(fileUid, out var kr)) - { - if (kr is FileRecord fr) - { - yield return fr; - } + break; + + case TypedRecord typed: + var fileRef = typed.Fields + .Where(x => x.FieldName == "fileRef") + .OfType>().FirstOrDefault(); + if (fileRef != null) { + foreach (var fileUid in fileRef.Values) { + if (TryGetKeeperRecord(fileUid, out var kr)) { + if (kr is FileRecord fr) { + yield return fr; } } } + } - break; + break; - case FileRecord file: - yield return file; - break; + case FileRecord file: + yield return file; + break; } } @@ -136,15 +120,12 @@ public IEnumerable RecordAttachments(KeeperRecord record) public async Task DownloadAttachment(KeeperRecord record, string attachment, Stream destination) { var atta = RecordAttachments(record) - .Where(x => - { - if (string.IsNullOrEmpty(attachment)) - { + .Where(x => { + if (string.IsNullOrEmpty(attachment)) { return true; } - if (attachment == x.Id || attachment == x.Name || attachment == x.Title) - { + if (attachment == x.Id || attachment == x.Name || attachment == x.Title) { return true; } @@ -153,23 +134,21 @@ public async Task DownloadAttachment(KeeperRecord record, string attachment, Str }) .FirstOrDefault(); - if (atta == null) - { - throw new KeeperInvalidParameter("Vault::DownloadAttachment", "attachment", attachment, "not found"); + if (atta == null) { + throw new KeeperInvalidParameter("Vault::DownloadAttachment", "attachment", attachment, "not found"); } - switch (atta) - { - case AttachmentFile attachmentFile: - await DownloadAttachmentFile(record.Uid, attachmentFile, destination); - break; + switch (atta) { + case AttachmentFile attachmentFile: + await DownloadAttachmentFile(record.Uid, attachmentFile, destination); + break; - case FileRecord fileRecord: - await DownloadFile(fileRecord, destination); - break; + case FileRecord fileRecord: + await DownloadFile(fileRecord, destination); + break; - default: - throw new KeeperInvalidParameter("Vault::DownloadAttachment", "attachment", atta.GetType().Name, "attachment type is not supported"); + default: + throw new KeeperInvalidParameter("Vault::DownloadAttachment", "attachment", atta.GetType().Name, "attachment type is not supported"); } } @@ -178,17 +157,16 @@ public async Task DownloadAttachment(KeeperRecord record, string attachment, Str /// public async Task UploadAttachment(KeeperRecord record, IAttachmentUploadTask uploadTask) { - switch (record) - { - case PasswordRecord password: - await UploadPasswordAttachment(password, uploadTask); - break; - - case TypedRecord typed: - await UploadTypedAttachment(typed, uploadTask); - break; - default: - throw new KeeperInvalidParameter("Vault::UploadAttachment", "record", record.GetType().Name, "unsupported record type"); + switch (record) { + case PasswordRecord password: + await UploadPasswordAttachment(password, uploadTask); + break; + + case TypedRecord typed: + await UploadTypedAttachment(typed, uploadTask); + break; + default: + throw new KeeperInvalidParameter("Vault::UploadAttachment", "record", record.GetType().Name, "unsupported record type"); } } @@ -196,33 +174,26 @@ public async Task UploadAttachment(KeeperRecord record, IAttachmentUploadTask up public async Task DeleteAttachment(KeeperRecord record, string attachmentId) { var deleted = false; - switch (record) - { - case PasswordRecord password: - if (password.Attachments != null) - { - var atta = password.Attachments.FirstOrDefault(x => x.Id == attachmentId); - if (atta != null) - { - deleted = password.Attachments.Remove(atta); - } - } - - break; - case TypedRecord typed: - var fileRef = typed.Fields - .Where(x => x.FieldName == "fileRef") - .OfType>().FirstOrDefault(); - if (fileRef != null) - { - deleted = fileRef.Values.Remove(attachmentId); + switch (record) { + case PasswordRecord password: + if (password.Attachments != null) { + var atta = password.Attachments.FirstOrDefault(x => x.Id == attachmentId); + if (atta != null) { + deleted = password.Attachments.Remove(atta); } - - break; + } + break; + case TypedRecord typed: + var fileRef = typed.Fields + .Where(x => x.FieldName == "fileRef") + .OfType>().FirstOrDefault(); + if (fileRef != null) { + deleted = fileRef.Values.Remove(attachmentId); + } + break; } - if (deleted) - { + if (deleted) { await UpdateRecord(record, false); } @@ -234,19 +205,16 @@ public async Task DeleteAttachment(KeeperRecord record, string attachmentI /// public async Task DownloadFile(FileRecord fileRecord, Stream destination) { - var rq = new Records.FilesGetRequest - { + var rq = new Records.FilesGetRequest { ForThumbnails = false }; rq.RecordUids.Add(ByteString.CopyFrom(fileRecord.Uid.Base64UrlDecode())); var rs = await Auth.ExecuteAuthRest( "vault/files_download", rq); var fileResult = rs.Files[0]; - if (fileResult.Status != Records.FileGetResult.FgSuccess) - { + if (fileResult.Status != Records.FileGetResult.FgSuccess) { var status = fileResult.Status.ToString().ToSnakeCase(); - if (status.StartsWith("fg_")) - { + if (status.StartsWith("fg_")) { status = status.Substring(3); } @@ -255,15 +223,11 @@ public async Task DownloadFile(FileRecord fileRecord, Stream destination) var request = WebRequest.Create(new Uri(fileResult.Url)); - using (var response = (HttpWebResponse) await request.GetResponseAsync()) - { - using (var stream = response.GetResponseStream()) - { + using (var response = (HttpWebResponse) await request.GetResponseAsync()) { + using (var stream = response.GetResponseStream()) { var transform = new DecryptAesV2Transform(fileRecord.RecordKey); - using (var decodeStream = new CryptoStream(stream, transform, CryptoStreamMode.Read)) - { - if (destination != null) - { + using (var decodeStream = new CryptoStream(stream, transform, CryptoStreamMode.Read)) { + if (destination != null) { await decodeStream.CopyToAsync(destination); } } @@ -274,10 +238,9 @@ public async Task DownloadFile(FileRecord fileRecord, Stream destination) /// public async Task DownloadAttachmentFile(string recordUid, AttachmentFile attachment, Stream destination) { - var command = new RequestDownloadCommand - { + var command = new RequestDownloadCommand { RecordUid = recordUid, - FileIDs = new[] { attachment .Id} + FileIDs = new[] { attachment.Id } }; this.ResolveRecordAccessPath(command); var rs = await this.Auth.ExecuteAuthCommand(command); @@ -285,111 +248,60 @@ public async Task DownloadAttachmentFile(string recordUid, AttachmentFile attach var download = rs.Downloads[0]; var request = WebRequest.Create(new Uri(download.Url)); using (var response = (HttpWebResponse) await request.GetResponseAsync()) - using (var stream = response.GetResponseStream()) - { + using (var stream = response.GetResponseStream()) { var transform = new DecryptAesV1Transform(attachment.Key.Base64UrlDecode()); - using (var decodeStream = new CryptoStream(stream, transform, CryptoStreamMode.Read)) - { - if (destination != null) - { + using (var decodeStream = new CryptoStream(stream, transform, CryptoStreamMode.Read)) { + if (destination != null) { await decodeStream.CopyToAsync(destination); } } } } - internal static async Task UploadSingleFile(UploadParameters upload, Stream source) + internal static async Task UploadSingleFile(UploadParameters upload, Stream inputStream, IWebProxy proxy = null) { - var boundary = "----------" + DateTime.Now.Ticks.ToString("x"); - var boundaryBytes = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary); - - var request = (HttpWebRequest) WebRequest.Create(new Uri(upload.Url)); - request.Method = "POST"; - request.ContentType = "multipart/form-data; boundary=" + boundary; - - using (var requestStream = await Task.Factory.FromAsync(request.BeginGetRequestStream, request.EndGetRequestStream, null)) - { - const string parameterTemplate = "\r\nContent-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}"; - if (upload.Parameters != null) - { - foreach (var pair in upload.Parameters) - { - await requestStream.WriteAsync(boundaryBytes, 0, boundaryBytes.Length); - var formItem = string.Format(parameterTemplate, pair.Key, pair.Value); - var formItemBytes = Encoding.UTF8.GetBytes(formItem); - await requestStream.WriteAsync(formItemBytes, 0, formItemBytes.Length); - } - } - - await requestStream.WriteAsync(boundaryBytes, 0, boundaryBytes.Length); - const string fileTemplate = "\r\nContent-Disposition: form-data; name=\"{0}\"\r\nContent-Type: application/octet-stream\r\n\r\n"; - var fileItem = string.Format(fileTemplate, upload.FileParameter); - var fileBytes = Encoding.UTF8.GetBytes(fileItem); - await requestStream.WriteAsync(fileBytes, 0, fileBytes.Length); - - await source.CopyToAsync(requestStream); - - await requestStream.WriteAsync(boundaryBytes, 0, boundaryBytes.Length); - var trailer = Encoding.ASCII.GetBytes("--\r\n"); - await requestStream.WriteAsync(trailer, 0, trailer.Length); - } - - HttpWebResponse response; - try - { - response = (HttpWebResponse) await Task.Factory.FromAsync(request.BeginGetResponse, request.EndGetResponse, null); - if ((int) response.StatusCode != upload.SuccessStatusCode) - { - throw new KeeperInvalidParameter("Vault::UploadSingleFile", "StatusCode", response.StatusCode.ToString(), "not success"); - } + var content = new MultipartFormDataContent(); + foreach (var pair in upload.Parameters) content.Add(new StringContent(pair.Value), pair.Key); + var fileContent = new StreamContent(inputStream); + fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); + content.Add(fileContent, upload.FileParameter); + var httpMessageHandler = new HttpClientHandler(); + if (proxy != null) { + httpMessageHandler.Proxy = proxy; } - catch (WebException e) - { - response = (HttpWebResponse) e.Response; - if (response == null || response.ContentType != "application/xml") throw; - using (var stream = new MemoryStream()) - { - var srcStream = response.GetResponseStream(); - if (srcStream == null) throw; - await srcStream.CopyToAsync(stream); - Trace.TraceError(Encoding.UTF8.GetString(stream.ToArray())); - } - - throw; + using (var httpClient = new HttpClient(httpMessageHandler, true)) { + var rs = await httpClient.PostAsync(upload.Url, content); + if ((int) rs.StatusCode != upload.SuccessStatusCode) + throw new Exception($"File upload HTTP error: {rs.StatusCode}"); } } private async Task UploadPasswordAttachment(PasswordRecord record, IAttachmentUploadTask uploadTask) { var fileStream = uploadTask.Stream; - if (fileStream == null) - { + if (fileStream == null) { throw new KeeperInvalidParameter("Vault::UploadAttachment", "uploadTask", "GetStream()", "null"); } var thumbStream = uploadTask.Thumbnail?.Stream; - var command = new RequestUploadCommand - { + var command = new RequestUploadCommand { FileCount = 1, ThumbnailCount = thumbStream != null ? 1 : 0 }; var rs = await Auth.ExecuteAuthCommand(command); - if (rs.FileUploads == null || rs.FileUploads.Length < 1) - { + if (rs.FileUploads == null || rs.FileUploads.Length < 1) { throw new KeeperInvalidParameter("Vault::UploadAttachment", "request_upload", "file_uploads", "empty"); } var fileUpload = rs.FileUploads[0]; UploadParameters thumbUpload = null; - if (rs.ThumbnailUploads != null && rs.ThumbnailUploads.Length > 0) - { + if (rs.ThumbnailUploads != null && rs.ThumbnailUploads.Length > 0) { thumbUpload = rs.ThumbnailUploads[0]; } var key = CryptoUtils.GenerateEncryptionKey(); - var atta = new AttachmentFile - { + var atta = new AttachmentFile { Id = fileUpload.FileId, Name = uploadTask.Name, Title = uploadTask.Title, @@ -398,33 +310,26 @@ private async Task UploadPasswordAttachment(PasswordRecord record, IAttachmentUp LastModified = DateTimeOffset.Now, }; var transform = new EncryptAesV1Transform(key); - using (var cryptoStream = new CryptoStream(fileStream, transform, CryptoStreamMode.Read)) - { + using (var cryptoStream = new CryptoStream(fileStream, transform, CryptoStreamMode.Read)) { await UploadSingleFile(fileUpload, cryptoStream); atta.Size = transform.EncryptedBytes; } - if (thumbUpload != null && thumbStream != null) - { - try - { + if (thumbUpload != null && thumbStream != null) { + try { transform = new EncryptAesV1Transform(key); - using (var cryptoStream = new CryptoStream(thumbStream, transform, CryptoStreamMode.Read)) - { + using (var cryptoStream = new CryptoStream(thumbStream, transform, CryptoStreamMode.Read)) { await UploadSingleFile(thumbUpload, cryptoStream); } - var thumbnail = new AttachmentFileThumb - { + var thumbnail = new AttachmentFileThumb { Id = thumbUpload.FileId, Type = uploadTask.Thumbnail.MimeType, Size = uploadTask.Thumbnail.Size }; - var ts = new[] {thumbnail}; + var ts = new[] { thumbnail }; atta.Thumbnails = atta.Thumbnails == null ? ts : atta.Thumbnails.Concat(ts).ToArray(); - } - catch (Exception e) - { + } catch (Exception e) { Trace.TraceError("Upload Thumbnail: {0}: \"{1}\"", e.GetType().Name, e.Message); } } @@ -437,13 +342,11 @@ private async Task UploadPasswordAttachment(PasswordRecord record, IAttachmentUp private async Task UploadTypedAttachment(TypedRecord record, IAttachmentUploadTask uploadTask) { var fileStream = uploadTask.Stream; - if (fileStream == null) - { + if (fileStream == null) { throw new KeeperInvalidParameter("Vault::UploadAttachment", "uploadTask", "GetStream()", "null"); } - var fileData = new RecordFileData - { + var fileData = new RecordFileData { Type = uploadTask.MimeType, Name = uploadTask.Name, Title = uploadTask.Title, @@ -454,10 +357,8 @@ private async Task UploadTypedAttachment(TypedRecord record, IAttachmentUploadTa }; var fileKey = CryptoUtils.GenerateEncryptionKey(); byte[] encryptedThumb = null; - if (uploadTask.Thumbnail != null) - { - using (var ts = new MemoryStream()) - { + if (uploadTask.Thumbnail != null) { + using (var ts = new MemoryStream()) { await uploadTask.Stream.CopyToAsync(ts); await ts.FlushAsync(); var thumbBytes = ts.ToArray(); @@ -469,86 +370,67 @@ private async Task UploadTypedAttachment(TypedRecord record, IAttachmentUploadTa var tempFile = Path.GetTempFileName(); var transform = new EncryptAesV2Transform(fileKey); using (var encryptedFile = File.OpenWrite(tempFile)) - using (var cryptoStream = new CryptoStream(uploadTask.Stream, transform, CryptoStreamMode.Read)) - { + using (var cryptoStream = new CryptoStream(uploadTask.Stream, transform, CryptoStreamMode.Read)) { await cryptoStream.CopyToAsync(encryptedFile); fileData.Size = transform.EncryptedBytes; } var fileInfo = new FileInfo(tempFile); var fileUid = CryptoUtils.GenerateUid(); - var fileRq = new Records.File - { + var fileRq = new Records.File { RecordUid = ByteString.CopyFrom(fileUid.Base64UrlDecode()), RecordKey = ByteString.CopyFrom(CryptoUtils.EncryptAesV2(fileKey, Auth.AuthContext.DataKey)), Data = ByteString.CopyFrom(CryptoUtils.EncryptAesV2(JsonUtils.DumpJson(fileData), fileKey)), FileSize = fileInfo.Length, ThumbSize = encryptedThumb?.Length ?? 0, }; - var rq = new Records.FilesAddRequest - { + var rq = new Records.FilesAddRequest { ClientTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() }; rq.Files.Add(fileRq); var fileRs = await Auth.ExecuteAuthRest("vault/files_add", rq); var uploadRs = fileRs.Files[0]; - var fileUpload = new UploadParameters - { + var fileUpload = new UploadParameters { Url = uploadRs.Url, FileParameter = "file", SuccessStatusCode = uploadRs.SuccessStatusCode, - Parameters = JsonUtils.ParseJson>(Encoding.UTF8.GetBytes(uploadRs.Parameters)) + Parameters = JsonUtils.ParseJson>(Encoding.UTF8.GetBytes(uploadRs.Parameters)) }; - if (record.LinkedKeys == null) - { + if (record.LinkedKeys == null) { record.LinkedKeys = new Dictionary(); } record.LinkedKeys[fileUid] = fileKey; - try - { - using (var cryptoStream = File.OpenRead(tempFile)) - { + try { + using (var cryptoStream = File.OpenRead(tempFile)) { await UploadSingleFile(fileUpload, cryptoStream); } - } - catch (Exception e) - { + } catch (Exception e) { Trace.TraceError("Upload Thumbnail: {0}: \"{1}\"", e.GetType().Name, e.Message); } - if (encryptedThumb != null && !string.IsNullOrEmpty(uploadRs.ThumbnailParameters)) - { - var thumbUpload = new UploadParameters - { + if (encryptedThumb != null && !string.IsNullOrEmpty(uploadRs.ThumbnailParameters)) { + var thumbUpload = new UploadParameters { Url = uploadRs.Url, FileParameter = "thumb", SuccessStatusCode = uploadRs.SuccessStatusCode, - Parameters = JsonUtils.ParseJson>(Encoding.UTF8.GetBytes(uploadRs.ThumbnailParameters)) + Parameters = JsonUtils.ParseJson>(Encoding.UTF8.GetBytes(uploadRs.ThumbnailParameters)) }; - try - { - using (var cryptoStream = new MemoryStream(encryptedThumb)) - { + try { + using (var cryptoStream = new MemoryStream(encryptedThumb)) { await UploadSingleFile(thumbUpload, cryptoStream); } - } - catch (Exception e) - { + } catch (Exception e) { Trace.TraceError("Upload Thumbnail: {0}: \"{1}\"", e.GetType().Name, e.Message); } } var facade = new TypedRecordFacade(record); - if (facade.Fields.FileRef != null) - { + if (facade.Fields.FileRef != null) { var uids = facade.Fields.FileRef.Values; - if (uids.Count > 0 && string.IsNullOrEmpty(uids[0])) - { + if (uids.Count > 0 && string.IsNullOrEmpty(uids[0])) { uids[0] = fileUid; - } - else - { + } else { uids.Add(fileUid); } } diff --git a/KeeperSdk/vault/VaultCommands.cs b/KeeperSdk/vault/VaultCommands.cs index b892dcf..094e2eb 100644 --- a/KeeperSdk/vault/VaultCommands.cs +++ b/KeeperSdk/vault/VaultCommands.cs @@ -588,7 +588,7 @@ internal class UploadParameters public string FileParameter; [DataMember(Name = "parameters")] - public IDictionary Parameters; + public IDictionary Parameters; } diff --git a/PowerCommander/AttachmentCommands.ps1 b/PowerCommander/AttachmentCommands.ps1 index f858725..2d0df5c 100644 --- a/PowerCommander/AttachmentCommands.ps1 +++ b/PowerCommander/AttachmentCommands.ps1 @@ -9,7 +9,7 @@ function Copy-KeeperFileAttachment { .Folder Keeper Folder - .Record + .Record Keeper Record .Parameter Path @@ -32,7 +32,7 @@ function Copy-KeeperFileAttachment { $Path = '.' } $records = $null - if ($Record) { + if ($Record) { $r = Get-KeeperRecord $record if ($r) { $records = @() @@ -87,7 +87,7 @@ function Copy-KeeperFileAttachment { $fileStream = $newFile.OpenWrite() try { $vault.DownloadAttachment($keeperRecord, $atta.Id, $fileStream).GetAwaiter().GetResult() | Out-Null - } + } finally { $fileStream.Dispose() } @@ -102,7 +102,7 @@ function Copy-KeeperFileAttachmentToStream { .Synopsis Get Attachment as stream - .Record + .Record Keeper Record Uid .AttachmentName @@ -126,3 +126,33 @@ function Copy-KeeperFileAttachmentToStream { [KeeperSecurity.Vault.VaultOnline]$vault = getVault $vault.DownloadAttachment($keeperRecord, $AttachmentName, $Stream).GetAwaiter().GetResult() | Out-Null } + +function Copy-FileToKeeperRecord { + <# + .Synopsis + Upload file attachment to a record + + .Record + Keeper Record Uid + + .Filename + File path + #> + + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $true)][string] $Record, + [Parameter(Position = 0, Mandatory = $true)][string] $Filename + ) + + $keeperRecord = Get-KeeperRecord $Record + if ($keeperRecord.Length -ne 1) { + Write-Error "Record `"$Record`" was not found" -ErrorAction Stop + } + [KeeperSecurity.Vault.VaultOnline]$vault = getVault + + $path = Resolve-Path $Filename -ErrorAction Stop + $uploadTask = New-Object -TypeName KeeperSecurity.Vault.FileAttachmentUploadTask -ArgumentList $path.Path, $null + + $vault.UploadAttachment($keeperRecord, $uploadTask).GetAwaiter().GetResult() | Out-Null +} diff --git a/PowerCommander/Google.Protobuf.dll b/PowerCommander/Google.Protobuf.dll index 2c139d8..d49c513 100644 Binary files a/PowerCommander/Google.Protobuf.dll and b/PowerCommander/Google.Protobuf.dll differ diff --git a/PowerCommander/KeeperSdk.dll b/PowerCommander/KeeperSdk.dll index a627396..dd25d0f 100644 Binary files a/PowerCommander/KeeperSdk.dll and b/PowerCommander/KeeperSdk.dll differ diff --git a/PowerCommander/PowerCommander.psd1 b/PowerCommander/PowerCommander.psd1 index 626bf49..caa722b 100644 --- a/PowerCommander/PowerCommander.psd1 +++ b/PowerCommander/PowerCommander.psd1 @@ -11,7 +11,7 @@ RootModule = 'PowerCommander.psm1' # Version number of this module. - ModuleVersion = '0.9.10' + ModuleVersion = '0.9.11' # Supported PSEditions CompatiblePSEditions = @('Desktop') @@ -84,7 +84,7 @@ 'Revoke-KeeperSharedFolderAccess', 'Get-KeeperAvailableTeam', 'Move-KeeperRecordOwnership', 'Get-KeeperSecretManagerApp', 'Add-KeeperSecretManagerApp', 'Grant-KeeperSecretManagerFolderAccess', 'Revoke-KeeperSecretManagerFolderAccess', 'Add-KeeperSecretManagerClient', 'Remove-KeeperSecretManagerClient', 'New-KeeperOneTimeShare', 'Get-KeeperOneTimeShare', - 'Remove-KeeperOneTimeShare', 'Copy-KeeperFileAttachment', 'Copy-KeeperFileAttachmentToStream' + 'Remove-KeeperOneTimeShare', 'Copy-KeeperFileAttachment', 'Copy-KeeperFileAttachmentToStream', 'Copy-FileToKeeperRecord' ) # Cmdlets to export from this module @@ -112,7 +112,7 @@ LicenseUri = 'https://github.com/Keeper-Security/keeper-sdk-dotnet/blob/master/LICENSE' ProjectUri = 'https://github.com/Keeper-Security/keeper-sdk-dotnet' IconUri = 'https://keeper-email-images.s3.amazonaws.com/common/powershell.png' - ReleaseNotes = "Add-KeeperRecord cmdlet help" + ReleaseNotes = "Copy-FileToKeeperRecord cmdlet help" } } diff --git a/PowerCommander/PowerCommander.psm1 b/PowerCommander/PowerCommander.psm1 index ba462fd..72e5e69 100644 --- a/PowerCommander/PowerCommander.psm1 +++ b/PowerCommander/PowerCommander.psm1 @@ -55,7 +55,7 @@ Export-ModuleMember -Function Get-KeeperSecretManagerApp, Add-KeeperSecretManage Revoke-KeeperSecretManagerFolderAccess, Add-KeeperSecretManagerClient, Remove-KeeperSecretManagerClient Export-ModuleMember -Alias ksm, ksm-create, ksm-share, ksm-unshare, ksm-addclient, ksm-rmclient -Export-ModuleMember -Function Copy-KeeperFileAttachment, Copy-KeeperFileAttachmentToStream +Export-ModuleMember -Function Copy-KeeperFileAttachment, Copy-KeeperFileAttachmentToStream, Copy-FileToKeeperRecord Export-ModuleMember -Alias kda # function Test-Keeper { diff --git a/PowerCommander/README.md b/PowerCommander/README.md index 1854fba..263ff66 100644 --- a/PowerCommander/README.md +++ b/PowerCommander/README.md @@ -30,6 +30,7 @@ To run the PowerCommander module from the source copy PowerCommander\ directory | Show-TwoFactorCode | 2fa | Display Two Factor Code | Copy-KeeperFileAttachment | kda | Download file attachments | Copy-KeeperFileAttachmentToStream | | Download file attachement to stream +| Copy-FileToKeeperRecord | | Upload file attachment to a record ### Sharing Cmdlets | Cmdlet name | Alias | Description diff --git a/PowerCommander/System.Buffers.dll b/PowerCommander/System.Buffers.dll index fb2911d..f2d83c5 100644 Binary files a/PowerCommander/System.Buffers.dll and b/PowerCommander/System.Buffers.dll differ diff --git a/PowerCommander/System.Memory.dll b/PowerCommander/System.Memory.dll index 3423680..bdfc501 100644 Binary files a/PowerCommander/System.Memory.dll and b/PowerCommander/System.Memory.dll differ diff --git a/PowerCommander/System.Runtime.CompilerServices.Unsafe.dll b/PowerCommander/System.Runtime.CompilerServices.Unsafe.dll index d99e9f9..3156239 100644 Binary files a/PowerCommander/System.Runtime.CompilerServices.Unsafe.dll and b/PowerCommander/System.Runtime.CompilerServices.Unsafe.dll differ