public static async Task PutNonSharedData <T>(this VaultOnline vault, string recordUid, T nonSharedData) where T : RecordNonSharedData, new() { var existingData = vault.LoadNonSharedData <T>(recordUid) ?? new T(); nonSharedData.ExtensionData = existingData.ExtensionData; var data = JsonUtils.DumpJson(nonSharedData); var existingRecord = vault.Storage.Records.GetEntity(recordUid); var updateRecord = new RecordUpdateRecord { RecordUid = recordUid, Revision = existingRecord?.Revision ?? 0, NonSharedData = CryptoUtils.EncryptAesV1(data, vault.Auth.AuthContext.DataKey).Base64UrlEncode() }; var command = new RecordUpdateCommand { deviceId = vault.Auth.Endpoint.DeviceName, UpdateRecords = new[] { updateRecord } }; await vault.Auth.ExecuteAuthCommand <RecordUpdateCommand, RecordUpdateResponse>(command); await vault.ScheduleSyncDown(TimeSpan.FromSeconds(0)); }
/// <summary> /// Incrementally downloads vault data. /// </summary> /// <param name="vault">Vault connected to Keeper.</param> /// <returns></returns> internal static async Task RunSyncDown(this VaultOnline vault) { var storage = vault.Storage; var context = vault.Auth.AuthContext; var clientKey = vault.ClientKey; var command = new SyncDownCommand { revision = storage.Revision, include = new[] { "sfheaders", "sfrecords", "sfusers", "teams", "folders" }, deviceName = vault.Auth.Endpoint.DeviceName, deviceId = vault.Auth.Endpoint.DeviceName }; var rs = await vault.Auth.ExecuteAuthCommand <SyncDownCommand, SyncDownResponse>(command); Debug.WriteLine("Sync Down: Enter"); var isFullSync = rs.fullSync; if (isFullSync) { storage.Clear(); } var result = new RebuildTask(isFullSync); if (rs.removedRecords != null) { storage.RecordKeys.DeleteLinks( rs.removedRecords .Select(recordUid => UidLink.Create(recordUid, storage.PersonalScopeUid))); var folderRecords = new List <IUidLink>(); foreach (var recordUid in rs.removedRecords) { result.AddRecord(recordUid); var links = storage.FolderRecords.GetLinksForObject(recordUid).ToArray(); foreach (var link in links) { var folderUid = link.FolderUid; if (string.IsNullOrEmpty(folderUid) && folderUid == storage.PersonalScopeUid) { folderRecords.Add(link); } else { var folder = storage.Folders.GetEntity(folderUid); if (folder?.FolderType == "user_folder") { folderRecords.Add(link); } } } } storage.FolderRecords.DeleteLinks(folderRecords); } if (rs.removedTeams != null) { foreach (var teamUid in rs.removedTeams) { var sfLinks = storage.SharedFolderKeys.GetLinksForObject(teamUid).ToArray(); foreach (var sfLink in sfLinks) { result.AddSharedFolder(sfLink.SharedFolderUid); } storage.SharedFolderKeys.DeleteLinks(sfLinks); } storage.Teams.DeleteUids(rs.removedTeams); } if (rs.removedSharedFolders != null) { foreach (var sharedFolderUid in rs.removedSharedFolders) { result.AddSharedFolder(sharedFolderUid); var links = storage.RecordKeys.GetLinksForObject(sharedFolderUid).ToArray(); foreach (var recLink in links) { result.AddRecord(recLink.RecordUid); } } storage.SharedFolderKeys.DeleteLinks( rs.removedSharedFolders .Select(x => UidLink.Create(x, storage.PersonalScopeUid))); } if (rs.userFoldersRemoved != null) { storage.FolderRecords.DeleteLinksForSubjects(rs.userFoldersRemoved.Select(x => x.folderUid)); storage.Folders.DeleteUids(rs.userFoldersRemoved.Select(x => x.folderUid)); } if (rs.sharedFolderFolderRemoved != null) { var folderUids = rs.sharedFolderFolderRemoved .Select(x => x.FolderUid ?? x.SharedFolderUid).ToArray(); storage.FolderRecords.DeleteLinksForSubjects(folderUids); storage.Folders.DeleteUids(folderUids); } if (rs.userFolderSharedFoldersRemoved != null) { storage.FolderRecords.DeleteLinksForSubjects(rs.userFolderSharedFoldersRemoved .Select(x => x.SharedFolderUid)); storage.Folders.DeleteUids(rs.userFolderSharedFoldersRemoved .Select(x => x.SharedFolderUid)); } if (rs.userFoldersRemovedRecords != null) { var links = rs.userFoldersRemovedRecords .Select(x => UidLink.Create(x.folderUid ?? storage.PersonalScopeUid, x.RecordUid)) .ToArray(); storage.FolderRecords.DeleteLinks(links); } if (rs.sharedFolderFolderRecordsRemoved != null) { var links = rs.sharedFolderFolderRecordsRemoved .Select(x => UidLink.Create(x.folderUid ?? x.sharedFolderUid, x.recordUid)) .ToArray(); storage.FolderRecords.DeleteLinks(links); } if (rs.sharedFolders != null) { // full sync shared folders var fullSyncSharedFolders = rs.sharedFolders .Where(x => x.fullSync == true) .Select(x => x.SharedFolderUid) .ToArray(); storage.RecordKeys.DeleteLinksForObjects(fullSyncSharedFolders); storage.SharedFolderKeys.DeleteLinksForSubjects(fullSyncSharedFolders); storage.SharedFolderPermissions.DeleteLinksForSubjects(fullSyncSharedFolders); // records var affectedLinks = rs.sharedFolders .Where(x => !x.fullSync.HasValue || !x.fullSync.Value) .Where(x => x.recordsRemoved != null) .SelectMany(x => x.recordsRemoved, (x, recordUid) => UidLink.Create(recordUid, x.SharedFolderUid)) .Cast <IUidLink>() .ToArray(); if (affectedLinks.Any()) { storage.RecordKeys.DeleteLinks(affectedLinks); foreach (var x in affectedLinks) { result.AddRecord(x.SubjectUid); } } // teams var affectedTeams = rs.sharedFolders .Where(x => !x.fullSync.HasValue || !x.fullSync.Value) .Where(x => x.teamsRemoved != null) .SelectMany(x => x.teamsRemoved, (x, teamUid) => UidLink.Create(x.SharedFolderUid, teamUid)) .Cast <IUidLink>() .ToArray(); if (affectedTeams.Any()) { storage.SharedFolderKeys.DeleteLinks(affectedTeams); } //users var affectedUsers = rs.sharedFolders .Where(x => !x.fullSync.HasValue || !x.fullSync.Value) .Where(x => x.usersRemoved != null) .SelectMany(x => x.usersRemoved, (x, username) => UidLink.Create(x.SharedFolderUid, username)) .Cast <IUidLink>() .ToArray(); if (affectedTeams.Any() || affectedLinks.Any()) { storage.SharedFolderPermissions.DeleteLinks(affectedTeams.Concat(affectedUsers)); } } if (rs.nonSharedData != null) { storage.NonSharedData.PutEntities(rs.nonSharedData .Where(x => !string.IsNullOrEmpty(x.data)) .Select(x => { try { var data = x.data.Base64UrlDecode(); data = CryptoUtils.DecryptAesV1(data, context.DataKey); data = CryptoUtils.EncryptAesV1(data, clientKey); x.data = data.Base64UrlEncode(); return(x); } catch (Exception e) { Trace.TraceError(e.Message); return(null); } }) .Where(x => x != null)); } var recordOwnership = new Dictionary <string, bool>(); if (rs.recordMetaData != null) { foreach (var rmd in rs.recordMetaData) { recordOwnership[rmd.RecordUid] = rmd.Owner; } } if (rs.records != null) { result.AddRecords(rs.records.Select(x => x.RecordUid)); storage.Records.PutEntities(rs.records .Select(x => { x.AdjustUdata(); if (!recordOwnership.ContainsKey(x.RecordUid)) { return(x); } x.Owner = recordOwnership[x.RecordUid]; recordOwnership.Remove(x.RecordUid); return(x); })); } if (rs.recordMetaData != null) { result.AddRecords(rs.recordMetaData.Select(x => x.RecordUid)); var toUpdate = rs.recordMetaData .Where(x => recordOwnership.ContainsKey(x.RecordUid)) .Select(x => { var sr = storage.Records.GetEntity(x.RecordUid); if (sr == null) { return(null); } if (sr.Owner == x.Owner) { return(null); } sr.Owner = x.Owner; return(sr); }) .Where(x => x != null) .ToArray(); if (toUpdate.Any()) { storage.Records.PutEntities(toUpdate); } var rmds = rs.recordMetaData .Select(rmd => { try { byte[] key; switch (rmd.RecordKeyType) { case 0: key = context.DataKey; break; case 1: key = CryptoUtils.DecryptAesV1(rmd.RecordKey.Base64UrlDecode(), context.DataKey); break; case 2: key = CryptoUtils.DecryptRsa(rmd.RecordKey.Base64UrlDecode(), context.PrivateKey); break; default: throw new Exception( $"Record metadata UID {rmd.RecordUid}: unsupported key type {rmd.RecordKeyType}"); } if (key != null) { rmd.RecordKey = CryptoUtils.EncryptAesV1(key, context.ClientKey).Base64UrlEncode(); rmd.RecordKeyType = (int)KeyType.DataKey; rmd.SharedFolderUid = storage.PersonalScopeUid; return(rmd); } } catch (Exception e) { Trace.TraceError(e.Message); } return(null); }) .ToArray(); storage.RecordKeys.PutLinks(rmds); } if (rs.teams != null) { var removedSharedFolderLinks = rs.teams .Where(x => x.removedSharedFolders != null) .SelectMany(x => x.removedSharedFolders, (team, sharedFolderUid) => UidLink.Create(sharedFolderUid, team.TeamUid)) .Cast <IUidLink>() .ToArray(); if (removedSharedFolderLinks.Any()) { result.AddSharedFolders(removedSharedFolderLinks.Select(x => x.SubjectUid)); storage.SharedFolderKeys.DeleteLinks(removedSharedFolderLinks); } var teams = rs.teams .Select(x => { try { byte[] teamKey; switch (x.KeyType) { case (int)KeyType.DataKey: teamKey = CryptoUtils.DecryptAesV1(x.TeamKey.Base64UrlDecode(), context.DataKey); break; case (int)KeyType.PrivateKey: teamKey = CryptoUtils.DecryptRsa(x.TeamKey.Base64UrlDecode(), context.PrivateKey); break; default: throw new Exception($"Team UID {x.TeamUid}: unsupported key type {x.KeyType}"); } x.TeamKey = CryptoUtils.EncryptAesV1(teamKey, clientKey).Base64UrlEncode(); x.KeyType = (int)KeyType.DataKey; return(x); } catch (Exception e) { Trace.TraceError(e.Message); return(null); } }) .Where(x => x != null) .ToArray(); storage.Teams.PutEntities(teams); var sharedFolderKeys = rs.teams .Where(x => x.sharedFolderKeys != null) .SelectMany(x => x.sharedFolderKeys, (team, sharedFolderKey) => { sharedFolderKey.TeamUid = team.TeamUid; sharedFolderKey.KeyType = sharedFolderKey.KeyType == 2 ? (int)KeyType.TeamPrivateKey : (int)KeyType.TeamKey; return(sharedFolderKey); }) .ToArray(); storage.SharedFolderKeys.PutLinks(sharedFolderKeys); } if (rs.sharedFolders != null) { result.AddSharedFolders(rs.sharedFolders.Select(x => x.SharedFolderUid)); // shared folders storage.SharedFolders.PutEntities(rs.sharedFolders); // shared folder keys var sharedFolderKeys = rs.sharedFolders .Where(x => !string.IsNullOrEmpty(x.SharedFolderKey)) .Select(x => { try { var sharedFolderKey = x.SharedFolderKey.Base64UrlDecode(); switch (x.KeyType) { case 1: sharedFolderKey = CryptoUtils.DecryptAesV1(sharedFolderKey, context.DataKey); break; case 2: sharedFolderKey = CryptoUtils.DecryptRsa(sharedFolderKey, context.PrivateKey); break; default: throw new Exception( $"Shared Folder UID {x.SharedFolderUid}: unsupported key type {x.KeyType}"); } return(new SyncDownSharedFolderKey { SharedFolderUid = x.SharedFolderUid, TeamUid = storage.PersonalScopeUid, SharedFolderKey = CryptoUtils.EncryptAesV1(sharedFolderKey, clientKey) .Base64UrlEncode(), KeyType = (int)KeyType.DataKey }); } catch (Exception e) { Trace.TraceError(e.Message); return(null); } }) .ToArray(); if (sharedFolderKeys.Any()) { storage.SharedFolderKeys.PutLinks(sharedFolderKeys); } result.AddRecords(rs.sharedFolders .Where(x => x.records != null) .SelectMany(x => x.records, (sf, r) => r.RecordUid)); // Records var sharedFolderRecords = rs.sharedFolders .Where(x => x.records != null) .SelectMany(x => x.records, (sf, sfr) => new SyncDownRecordMetaData { SharedFolderUid = sf.SharedFolderUid, RecordUid = sfr.RecordUid, RecordKey = sfr.RecordKey, RecordKeyType = (int)KeyType.SharedFolderKey, CanEdit = sfr.CanEdit, CanShare = sfr.CanShare }) .ToArray(); if (sharedFolderRecords.Any()) { storage.RecordKeys.PutLinks(sharedFolderRecords); } // Teams var teams = rs.sharedFolders .Where(x => x.teams != null) .SelectMany(x => x.teams, (sf, sft) => { sft.SharedFolderUid = sf.SharedFolderUid; return(sft); }) .Cast <ISharedFolderPermission>() .ToArray(); // Users var users = rs.sharedFolders .Where(x => x.users != null) .SelectMany(x => x.users, (sf, sfu) => { sfu.SharedFolderUid = sf.SharedFolderUid; return(sfu); }) .Cast <ISharedFolderPermission>() .ToArray(); if (teams.Any() || users.Any()) { storage.SharedFolderPermissions.PutLinks(teams.Concat(users)); } } if (rs.userFolders != null) { var userFolders = rs.userFolders .Select(uf => { try { var folderKey = uf.FolderKey.Base64UrlDecode(); switch (uf.keyType) { case (int)KeyType.DataKey: folderKey = CryptoUtils.DecryptAesV1(folderKey, context.DataKey); break; case (int)KeyType.PrivateKey: folderKey = CryptoUtils.DecryptRsa(folderKey, context.PrivateKey); break; default: throw new Exception($"User Folder UID {uf.FolderUid}: unsupported key type {uf.keyType}"); } uf.FolderKey = CryptoUtils.EncryptAesV1(folderKey, clientKey).Base64UrlEncode(); uf.keyType = (int)KeyType.DataKey; return(uf); } catch (Exception e) { Trace.TraceError(e.Message); return(null); } }) .ToArray(); storage.Folders.PutEntities(userFolders); } if (rs.sharedFolderFolders != null) { storage.Folders.PutEntities(rs.sharedFolderFolders); } if (rs.userFolderSharedFolders != null) { storage.Folders.PutEntities(rs.userFolderSharedFolders); } if (rs.userFolderRecords != null) { storage.FolderRecords.PutLinks(rs.userFolderRecords .Select(ufr => { ufr.FolderUid = string.IsNullOrEmpty(ufr.FolderUid) ? storage.PersonalScopeUid : ufr.FolderUid; return(ufr); })); } if (rs.sharedFolderFolderRecords != null) { storage.FolderRecords.PutLinks(rs.sharedFolderFolderRecords); } storage.Revision = rs.revision; Debug.WriteLine("Sync Down: Leave"); Debug.WriteLine("Rebuild Data: Enter"); vault.RebuildData(result); Debug.WriteLine("Rebuild Data: Leave"); }
public static async Task <PasswordRecord> AddRecordToFolder(this VaultOnline vault, PasswordRecord record, string folderUid = null) { FolderNode node = null; if (!string.IsNullOrEmpty(folderUid)) { vault.TryGetFolder(folderUid, out node); } record.Uid = CryptoUtils.GenerateUid(); record.RecordKey = CryptoUtils.GenerateEncryptionKey(); var recordAdd = new RecordAddCommand { RecordUid = record.Uid, RecordKey = CryptoUtils.EncryptAesV1(record.RecordKey, vault.Auth.AuthContext.DataKey).Base64UrlEncode(), RecordType = "password" }; if (node == null) { recordAdd.FolderType = "user_folder"; } else { switch (node.FolderType) { case FolderType.UserFolder: recordAdd.FolderType = "user_folder"; recordAdd.FolderUid = node.FolderUid; break; case FolderType.SharedFolder: case FolderType.SharedFolderFolder: recordAdd.FolderUid = node.FolderUid; recordAdd.FolderType = node.FolderType == FolderType.SharedFolder ? "shared_folder" : "shared_folder_folder"; if (vault.TryGetSharedFolder(node.SharedFolderUid, out var sf)) { recordAdd.FolderKey = CryptoUtils.EncryptAesV1(record.RecordKey, sf.SharedFolderKey) .Base64UrlEncode(); } if (string.IsNullOrEmpty(recordAdd.FolderKey)) { throw new Exception($"Cannot resolve shared folder for folder UID: {folderUid}"); } break; } } var dataSerializer = new DataContractJsonSerializer(typeof(RecordData), JsonUtils.JsonSettings); var data = record.ExtractRecordData(); using (var ms = new MemoryStream()) { dataSerializer.WriteObject(ms, data); recordAdd.Data = CryptoUtils.EncryptAesV1(ms.ToArray(), record.RecordKey).Base64UrlEncode(); } await vault.Auth.ExecuteAuthCommand(recordAdd); await vault.ScheduleSyncDown(TimeSpan.FromSeconds(0)); return(vault.TryGetRecord(record.Uid, out var r) ? r : record); }
public static async Task DeleteVaultObjects(this VaultOnline vault, IEnumerable <RecordPath> objectsToDelete, bool forceDelete = false) { var sharedFoldersToDelete = new Dictionary <string, Tuple <SharedFolder, string> >(); var preDeleteObjects = new Dictionary <string, PreDeleteObject>(); foreach (var toDelete in objectsToDelete) { var folder = vault.GetFolder(toDelete.FolderUid); string teamUid = null; if (folder.FolderType != FolderType.UserFolder) { var sharedFolderUid = folder.FolderType == FolderType.SharedFolder ? folder.FolderUid : folder.SharedFolderUid; var perm = vault.ResolveSharedFolderAccessPath(vault.Auth.Username, sharedFolderUid, true, true); if (perm == null) { throw new VaultException($"You don't have delete permissions in shared folder \"{folder.Name}\" ({sharedFolderUid})"); } if (perm.UserType == UserType.Team) { teamUid = perm.UserId; } } if (!string.IsNullOrEmpty(toDelete.RecordUid)) // delete record { if (folder.Records.Any(x => x == toDelete.RecordUid)) { preDeleteObjects[folder.FolderUid] = new PreDeleteObject { fromUid = string.IsNullOrEmpty(folder.FolderUid) ? null : folder.FolderUid, fromType = folder.FolderType == FolderType.UserFolder ? FolderType.UserFolder.GetFolderTypeText() : FolderType.SharedFolderFolder.GetFolderTypeText(), objectUid = toDelete.RecordUid, objectType = "record", deleteResolution = "unlink", }; } else { throw new VaultException($"Record UID ({toDelete.RecordUid}) does not exist in folder \"{folder.Name}\""); } } else { if (string.IsNullOrEmpty(folder.FolderUid)) { throw new VaultException("Cannot delete root folder."); } if (folder.FolderType == FolderType.SharedFolder) { sharedFoldersToDelete[folder.FolderUid] = Tuple.Create(vault.GetSharedFolder(folder.FolderUid), teamUid); } else { var parent = vault.GetFolder(folder.ParentUid); preDeleteObjects[folder.FolderUid] = new PreDeleteObject { fromUid = string.IsNullOrEmpty(parent.FolderUid) ? null : parent.FolderUid, fromType = parent.FolderType == FolderType.UserFolder ? FolderType.UserFolder.GetFolderTypeText() : FolderType.SharedFolderFolder.GetFolderTypeText(), objectUid = folder.FolderUid, objectType = folder.FolderType.GetFolderTypeText(), deleteResolution = "unlink", }; } } } if (sharedFoldersToDelete.Count > 0) { var requests = new List <SharedFolderUpdateCommand>(); var recordCount = 0; foreach (var tuple in sharedFoldersToDelete.Values) { var sharedFolder = tuple.Item1; var teamUid = tuple.Item2; recordCount += sharedFolder.RecordPermissions?.Count ?? 0; requests.Add(new SharedFolderUpdateCommand { pt = vault.Auth.AuthContext.SessionToken.Base64UrlEncode(), operation = "delete", shared_folder_uid = tuple.Item1.Uid, from_team_uid = tuple.Item2, }); } var ok = forceDelete || vault.VaultUi == null; if (!ok) { var confirmation = $"Your request will result in the deletion of:\n{sharedFoldersToDelete.Count} Shared Folder(s)"; if (recordCount > 0) { confirmation += $"{recordCount} Record(s)"; } ok = await vault.VaultUi.Confirmation(confirmation); if (ok) { foreach (var rq in requests) { _ = vault.Auth.ExecuteAuthCommand <SharedFolderUpdateCommand, SharedFolderUpdateResponse>(rq); } } } } else { if (preDeleteObjects.Count > 0) { var preRequest = new PreDeleteCommand { objects = preDeleteObjects.Values.ToArray(), }; var preResponse = await vault.Auth.ExecuteAuthCommand <PreDeleteCommand, PreDeleteResponse>(preRequest); var ok = forceDelete || vault.VaultUi == null; if (!ok) { ok = await vault.VaultUi.Confirmation(string.Join("\n", preResponse.preDeleteResponse.wouldDelete.deletionSummary)); } if (ok) { await vault.Auth.ExecuteAuthCommand(new DeleteCommand { preDeleteToken = preResponse.preDeleteResponse.preDeleteToken, }); await vault.ScheduleSyncDown(TimeSpan.FromSeconds(0)); } } } await vault.ScheduleSyncDown(TimeSpan.FromSeconds(0)); }
public static async Task <FolderNode> FolderUpdate(this VaultOnline vault, string folderUid, string folderName, SharedFolderOptions sharedFolderOptions = null) { if (string.IsNullOrEmpty(folderName) && sharedFolderOptions == null) { throw new VaultException("Folder name cannot be empty"); } var folder = vault.GetFolder(folderUid); if (string.IsNullOrEmpty(folderName)) { folderName = folder.Name; } var parent = vault.RootFolder; if (!string.IsNullOrEmpty(folder.ParentUid)) { vault.TryGetFolder(folder.ParentUid, out parent); } var nameExists = parent.Subfolders .Select(x => vault.TryGetFolder(x, out var v) ? v : null) .Any(x => x != null && x.FolderUid != folderUid && string.Compare(x.Name, folderName, StringComparison.InvariantCultureIgnoreCase) == 0); if (nameExists) { throw new VaultException($"Folder with name {folderName} already exists in {parent.Name}"); } var request = new FolderUpdateCommand { FolderUid = folder.FolderUid, FolderType = folder.FolderType.GetFolderTypeText(), ParentUid = string.IsNullOrEmpty(folder.ParentUid) ? null : folder.ParentUid, SharedFolderUid = string.IsNullOrEmpty(folder.SharedFolderUid) ? null : folder.SharedFolderUid, }; var existingRecord = vault.Storage.Folders.GetEntity(folderUid); var data = string.IsNullOrEmpty(existingRecord?.Data) ? new FolderData() : JsonUtils.ParseJson <FolderData>(existingRecord.Data.Base64UrlDecode()); data.name = folderName; var dataBytes = JsonUtils.DumpJson(data); var encryptionKey = vault.Auth.AuthContext.DataKey; if (folder.FolderType == FolderType.SharedFolderFolder) { encryptionKey = vault.GetSharedFolder(folder.SharedFolderUid).SharedFolderKey; } request.Data = CryptoUtils.EncryptAesV1(dataBytes, encryptionKey).Base64UrlEncode(); if (folder.FolderType != FolderType.UserFolder) { var sharedFolderUid = folder.FolderType == FolderType.UserFolder ? folder.FolderUid : folder.SharedFolderUid; var perm = vault.ResolveSharedFolderAccessPath(vault.Auth.Username, sharedFolderUid, true, true); if (perm != null) { if (perm.UserType == UserType.Team) { request.TeamUid = perm.UserId; } } else { throw new VaultException($"You don't have permissions to modify shared folder ({sharedFolderUid})"); } } if (sharedFolderOptions != null && folder.FolderType == FolderType.SharedFolder) { if (!vault.TryGetSharedFolder(folder.FolderUid, out var sharedFolder)) { request.Name = CryptoUtils.EncryptAesV1(Encoding.UTF8.GetBytes(folderName), sharedFolder.SharedFolderKey).Base64UrlEncode(); } request.ManageUsers = sharedFolderOptions.ManageUsers; request.ManageRecords = sharedFolderOptions.ManageRecords; request.CanEdit = sharedFolderOptions.CanEdit; request.CanShare = sharedFolderOptions.CanShare; } await vault.Auth.ExecuteAuthCommand(request); await vault.ScheduleSyncDown(TimeSpan.FromSeconds(0)); return(vault.TryGetFolder(request.FolderUid, out var f) ? f : null); }
public static async Task <FolderNode> AddFolder <T>(this VaultOnline vault, string folderName, string parentFolderUid = null, T sharedFolderOptions = null) where T : class, ISharedFolderUserOptions, ISharedFolderRecordOptions { var parent = vault.GetFolder(parentFolderUid); FolderType folderType; if (sharedFolderOptions != null) { if (parent.FolderType != FolderType.UserFolder) { throw new VaultException($"Shared folder cannot be created"); } folderType = FolderType.SharedFolder; } else { folderType = parent.FolderType == FolderType.UserFolder ? FolderType.UserFolder : FolderType.SharedFolderFolder; } var encryptionKey = vault.Auth.AuthContext.DataKey; SharedFolder sharedFolder = null; if (folderType == FolderType.SharedFolderFolder) { var sharedFolderUid = parent.FolderType == FolderType.SharedFolder ? parent.FolderUid : parent.SharedFolderUid; sharedFolder = vault.GetSharedFolder(sharedFolderUid); encryptionKey = sharedFolder.SharedFolderKey; } var data = new FolderData { name = folderName ?? "", }; var dataBytes = JsonUtils.DumpJson(data); var folderKey = CryptoUtils.GenerateEncryptionKey(); var request = new FolderAddCommand { FolderUid = CryptoUtils.GenerateUid(), FolderType = folderType.GetFolderTypeText(), Key = CryptoUtils.EncryptAesV1(folderKey, encryptionKey).Base64UrlEncode(), Data = CryptoUtils.EncryptAesV1(dataBytes, folderKey).Base64UrlEncode(), ParentUid = string.IsNullOrEmpty(parent.FolderUid) || parent.FolderType == FolderType.SharedFolder ? null : parent.FolderUid, SharedFolderUid = sharedFolder?.Uid, }; if (sharedFolderOptions != null) { request.Name = CryptoUtils.EncryptAesV1(Encoding.UTF8.GetBytes(folderName), folderKey).Base64UrlEncode(); request.ManageUsers = sharedFolderOptions.ManageUsers ?? false; request.ManageRecords = sharedFolderOptions.ManageRecords ?? false; request.CanEdit = sharedFolderOptions.CanEdit ?? false; request.CanShare = sharedFolderOptions.CanShare ?? false; } _ = await vault.Auth.ExecuteAuthCommand <FolderAddCommand, AddFolderResponse>(request); await vault.ScheduleSyncDown(TimeSpan.FromSeconds(0)); return(vault.TryGetFolder(request.FolderUid, out var f) ? f : null); }
public static async Task <PasswordRecord> PutRecord(this VaultOnline vault, PasswordRecord record, bool skipData = false, bool skipExtra = true) { IPasswordRecord existingRecord = null; if (!string.IsNullOrEmpty(record.Uid)) { existingRecord = vault.Storage.Records.GetEntity(record.Uid); } if (existingRecord == null) { return(await vault.AddRecordToFolder(record)); } var updateRecord = new RecordUpdateRecord { RecordUid = existingRecord.RecordUid }; var rmd = vault.ResolveRecordAccessPath(updateRecord, true); if (rmd != null) { if (rmd.RecordKeyType == (int)KeyType.NoKey || rmd.RecordKeyType == (int)KeyType.PrivateKey) { updateRecord.RecordKey = CryptoUtils.EncryptAesV1(record.RecordKey, vault.Auth.AuthContext.DataKey) .Base64UrlEncode(); } } updateRecord.Revision = existingRecord.Revision; if (!skipData) { var dataSerializer = new DataContractJsonSerializer(typeof(RecordData), JsonUtils.JsonSettings); RecordData existingData = null; try { var unencryptedData = CryptoUtils.DecryptAesV1(existingRecord.Data.Base64UrlDecode(), record.RecordKey); using (var ms = new MemoryStream(unencryptedData)) { existingData = (RecordData)dataSerializer.ReadObject(ms); } } catch (Exception e) { Trace.TraceError("Decrypt Record: UID: {0}, {1}: \"{2}\"", existingRecord.RecordUid, e.GetType().Name, e.Message); } var data = record.ExtractRecordData(existingData); using (var ms = new MemoryStream()) { dataSerializer.WriteObject(ms, data); updateRecord.Data = CryptoUtils.EncryptAesV1(ms.ToArray(), record.RecordKey).Base64UrlEncode(); } } if (!skipExtra) { var extraSerializer = new DataContractJsonSerializer(typeof(RecordExtra), JsonUtils.JsonSettings); RecordExtra existingExtra = null; try { var unencryptedExtra = CryptoUtils.DecryptAesV1(existingRecord.Extra.Base64UrlDecode(), record.RecordKey); using (var ms = new MemoryStream(unencryptedExtra)) { existingExtra = (RecordExtra)extraSerializer.ReadObject(ms); } } catch (Exception e) { Trace.TraceError("Decrypt Record: UID: {0}, {1}: \"{2}\"", existingRecord.RecordUid, e.GetType().Name, e.Message); } var extra = record.ExtractRecordExtra(existingExtra); using (var ms = new MemoryStream()) { extraSerializer.WriteObject(ms, extra); updateRecord.Extra = CryptoUtils.EncryptAesV1(ms.ToArray(), record.RecordKey).Base64UrlEncode(); } var udata = new RecordUpdateUData(); var ids = new HashSet <string>(); if (record.Attachments != null) { foreach (var atta in record.Attachments) { ids.Add(atta.Id); if (atta.Thumbnails != null) { foreach (var thumb in atta.Thumbnails) { ids.Add(thumb.Id); } } } } udata.FileIds = ids.ToArray(); updateRecord.Udata = udata; } var command = new RecordUpdateCommand { deviceId = vault.Auth.Endpoint.DeviceName, UpdateRecords = new[] { updateRecord } }; await vault.Auth.ExecuteAuthCommand <RecordUpdateCommand, RecordUpdateResponse>(command); await vault.ScheduleSyncDown(TimeSpan.FromSeconds(0)); return(vault.TryGetRecord(record.Uid, out var r) ? r : record); }
public static async Task MoveToFolder(this VaultOnline vault, IEnumerable <RecordPath> objects, string toFolderUid, bool link = false) { var destinationFolder = vault.GetFolder(toFolderUid); var destinationFolderScope = destinationFolder.FolderType != FolderType.UserFolder ? destinationFolder.FolderType == FolderType.SharedFolderFolder ? destinationFolder.SharedFolderUid : destinationFolder.FolderUid : ""; var encryptionKey = vault.Auth.AuthContext.DataKey; if (!string.IsNullOrEmpty(destinationFolderScope)) { if (!vault.TryGetSharedFolder(destinationFolderScope, out var sf)) { throw new VaultException($"Cannot find destination shared folder"); } encryptionKey = sf.SharedFolderKey; } var moveObjects = new List <MoveObject>(); var keyObjects = new Dictionary <string, TransitionKey>(); void TraverseFolderForRecords(FolderNode folder) { if (folder.FolderType == FolderType.SharedFolder && destinationFolder.FolderType != FolderType.UserFolder) { throw new VaultException($"Cannot move shared folder \"{folder.Name}\" to another shared folder"); } var scope = folder.FolderType != FolderType.UserFolder ? folder.FolderType == FolderType.SharedFolderFolder ? folder.SharedFolderUid : destinationFolder.FolderUid : ""; if (scope != destinationFolderScope) { foreach (var recordUid in folder.Records) { if (keyObjects.ContainsKey(recordUid)) { continue; } if (!vault.TryGetRecord(recordUid, out var record)) { keyObjects.Add(recordUid, new TransitionKey { uid = recordUid, key = CryptoUtils.EncryptAesV1(record.RecordKey, encryptionKey).Base64UrlEncode(), }); } } } foreach (var fUid in folder.Subfolders) { TraverseFolderForRecords(vault.GetFolder(fUid)); } } foreach (var mo in objects) { var sourceFolder = vault.GetFolder(mo.FolderUid); if (string.IsNullOrEmpty(mo.RecordUid)) // move folder { if (string.IsNullOrEmpty(sourceFolder.ParentUid)) { throw new VaultException("Cannot move root folder"); } var f = destinationFolder; while (!string.IsNullOrEmpty(f.ParentUid)) { if (f.FolderUid == sourceFolder.FolderUid) { throw new VaultException($"Cannot move the folder into its subfolder."); } f = vault.GetFolder(f.ParentUid); } TraverseFolderForRecords(sourceFolder); var parentFolder = vault.GetFolder(sourceFolder.ParentUid); moveObjects.Add(new MoveObject { fromUid = string.IsNullOrEmpty(sourceFolder.FolderUid) ? null : sourceFolder.FolderUid, fromType = parentFolder.FolderType.GetFolderTypeText(), uid = mo.RecordUid, type = sourceFolder.FolderType.GetFolderTypeText(), cascade = true, }); } else { if (!vault.TryGetRecord(mo.RecordUid, out var record)) { throw new VaultException(""); } var scope = sourceFolder.FolderType != FolderType.UserFolder ? sourceFolder.FolderType == FolderType.SharedFolderFolder ? sourceFolder.SharedFolderUid : sourceFolder.FolderUid : ""; if (scope != destinationFolderScope && !keyObjects.ContainsKey(mo.RecordUid)) { keyObjects.Add(mo.RecordUid, new TransitionKey { uid = mo.RecordUid, key = CryptoUtils.EncryptAesV1(record.RecordKey, encryptionKey).Base64UrlEncode(), }); } moveObjects.Add(new MoveObject { fromUid = string.IsNullOrEmpty(sourceFolder.FolderUid) ? null : sourceFolder.FolderUid, fromType = sourceFolder.FolderType.GetFolderTypeText(), uid = mo.RecordUid, type = "record", cascade = false }); } } var request = new MoveCommand { toUid = destinationFolder.FolderUid, toType = destinationFolder.FolderType.GetFolderTypeText(), isLink = link, moveObjects = moveObjects.ToArray(), transitionKeys = keyObjects.Count == 0 ? null : keyObjects.Values.ToArray(), }; await vault.Auth.ExecuteAuthCommand(request); }