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);
        }