/// <summary>
        /// Adds (if needed) user or team to the shared folder and set user access permissions.
        /// </summary>
        /// <param name="sharedFolderUid">Shared Folder UID.</param>
        /// <param name="userId">User email or Team UID.</param>
        /// <param name="userType">Type of <see cref="userId"/> parameter.</param>
        /// <param name="options">Shared Folder User Permissions.</param>
        /// <returns>Awaitable task.</returns>
        /// <remarks>
        /// If <seealso cref="options"/> parameter is <c>null</c> then user gets default user permissions when added./>
        /// </remarks>
        /// <exception cref="Authentication.KeeperApiException"></exception>
        /// <seealso cref="IVaultSharedFolder.PutUserToSharedFolder"/>
        public async Task PutUserToSharedFolder(string sharedFolderUid,
                                                string userId,
                                                UserType userType,
                                                ISharedFolderUserOptions options)
        {
            var sharedFolder = this.GetSharedFolder(sharedFolderUid);
            var perm         = this.ResolveSharedFolderAccessPath(Auth.Username, sharedFolderUid, true);

            if (perm == null)
            {
                throw new VaultException("You don't have permission to manage users.");
            }

            var request = new SharedFolderUpdateCommand
            {
                pt                = Auth.AuthContext.SessionToken.Base64UrlEncode(),
                operation         = "update",
                shared_folder_uid = sharedFolder.Uid,
                from_team_uid     = perm.UserType == UserType.Team ? perm.UserId : null,
                name              = CryptoUtils.EncryptAesV1(Encoding.UTF8.GetBytes(sharedFolder.Name), sharedFolder.SharedFolderKey).Base64UrlEncode(),
                forceUpdate       = true,
            };

            if (userType == UserType.User)
            {
                if (sharedFolder.UsersPermissions.Any(x => x.UserType == UserType.User && x.UserId == userId))
                {
                    request.updateUsers = new[]
                    {
                        new SharedFolderUpdateUser
                        {
                            Username      = userId,
                            ManageUsers   = options?.ManageUsers,
                            ManageRecords = options?.ManageRecords,
                        }
                    };
                }
                else
                {
                    var pkRq = new PublicKeysCommand
                    {
                        keyOwners = new[] { userId },
                    };
                    var pkRs = await Auth.ExecuteAuthCommand <PublicKeysCommand, PublicKeysResponse>(pkRq);

                    if (pkRs.publicKeys == null || pkRs.publicKeys.Length == 0)
                    {
                        throw new VaultException($"Cannot get public key of user: {userId}");
                    }

                    var pk = pkRs.publicKeys[0];
                    if (!string.IsNullOrEmpty(pk.resultCode))
                    {
                        throw new KeeperApiException(pk.resultCode, pk.message);
                    }

                    var publicKey = CryptoUtils.LoadPublicKey(pk.publicKey.Base64UrlDecode());
                    request.addUsers = new[]
                    {
                        new SharedFolderUpdateUser
                        {
                            Username        = userId,
                            ManageUsers     = options?.ManageUsers,
                            ManageRecords   = options?.ManageRecords,
                            SharedFolderKey = CryptoUtils.EncryptRsa(sharedFolder.SharedFolderKey, publicKey).Base64UrlEncode(),
                        }
                    };
                }
            }
            else
            {
                if (sharedFolder.UsersPermissions.Any(x => x.UserType == UserType.Team && x.UserId == userId))
                {
                    request.updateTeams = new[]
                    {
                        new SharedFolderUpdateTeam
                        {
                            TeamUid       = userId,
                            ManageUsers   = options?.ManageUsers,
                            ManageRecords = options?.ManageRecords,
                        }
                    };
                }
                else
                {
                    string encryptedSharedFolderKey;
                    if (TryGetTeam(userId, out var team))
                    {
                        encryptedSharedFolderKey = CryptoUtils.EncryptAesV1(sharedFolder.SharedFolderKey, team.TeamKey).Base64UrlEncode();
                    }
                    else
                    {
                        var tkRq = new TeamGetKeysCommand
                        {
                            teams = new[] { userId },
                        };
                        var tkRs = await Auth.ExecuteAuthCommand <TeamGetKeysCommand, TeamGetKeysResponse>(tkRq);

                        if (tkRs.keys == null || tkRs.keys.Length == 0)
                        {
                            throw new VaultException($"Cannot get public key of team: {userId}");
                        }

                        var tk = tkRs.keys[0];
                        if (!string.IsNullOrEmpty(tk.resultCode))
                        {
                            throw new KeeperApiException(tk.resultCode, tk.message);
                        }

                        var tpk = CryptoUtils.LoadPublicKey(tk.key.Base64UrlDecode());
                        encryptedSharedFolderKey = CryptoUtils.EncryptRsa(sharedFolder.SharedFolderKey, tpk).Base64UrlEncode();
                    }

                    request.addTeams = new[]
                    {
                        new SharedFolderUpdateTeam
                        {
                            TeamUid         = userId,
                            ManageUsers     = options?.ManageUsers,
                            ManageRecords   = options?.ManageRecords,
                            SharedFolderKey = encryptedSharedFolderKey,
                        }
                    };
                }
            }


            var response = await Auth.ExecuteAuthCommand <SharedFolderUpdateCommand, SharedFolderUpdateResponse>(request);

            foreach (var arr in (new[] { response.addUsers, response.updateUsers }))
            {
                var failed = arr?.FirstOrDefault(x => x.Status != "success");
                if (failed != null)
                {
                    throw new VaultException($"Put \"{failed.Username}\" to Shared Folder \"{sharedFolder.Name}\" error: {failed.Status}");
                }
            }

            foreach (var arr in (new[] { response.addTeams, response.updateTeams }))
            {
                var failed = arr?.FirstOrDefault(x => x.Status != "success");
                if (failed != null)
                {
                    throw new VaultException($"Put Team Uid \"{failed.TeamUid}\" to Shared Folder \"{sharedFolder.Name}\" error: {failed.Status}");
                }
            }

            await ScheduleSyncDown(TimeSpan.FromSeconds(0));
        }
        public static async Task PopulateTeamKeys(this EnterpriseData enterprise, IDictionary <string, byte[]> teamKeys, Action <string> warnings = null)
        {
            var toLoad = new HashSet <string>();

            foreach (var teamUid in teamKeys.Keys.ToArray())
            {
                if (enterprise.TryGetTeam(teamUid, out var team))
                {
                    if (team.TeamKey != null)
                    {
                        teamKeys[teamUid] = team.TeamKey;
                    }
                    else
                    {
                        toLoad.Add(teamUid);
                    }
                }
            }

            if (toLoad.Count > 0)
            {
                var teamKeyRq = new TeamGetKeysCommand
                {
                    teams = toLoad.ToArray()
                };
                var teamKeyRs = await enterprise.Auth.ExecuteAuthCommand <TeamGetKeysCommand, TeamGetKeysResponse>(teamKeyRq);

                if (teamKeyRs.keys != null)
                {
                    foreach (var tk in teamKeyRs.keys)
                    {
                        byte[] key = null;
                        if (!string.IsNullOrEmpty(tk.key))
                        {
                            try
                            {
                                switch (tk.keyType)
                                {
                                case 1:
                                    key = CryptoUtils.DecryptAesV1(tk.key.Base64UrlDecode(), enterprise.Auth.AuthContext.DataKey);
                                    break;

                                case 2:
                                    key = CryptoUtils.DecryptRsa(tk.key.Base64UrlDecode(), enterprise.Auth.AuthContext.PrivateKey);
                                    break;

                                default:
                                    warnings?.Invoke($"Team \'{tk.teamUid}\' unsupported key type: {tk.keyType}");
                                    break;
                                }
                            }
                            catch (Exception e)
                            {
                                warnings?.Invoke(e.Message);
                            }
                        }

                        if (key == null)
                        {
                            continue;
                        }

                        if (enterprise.TryGetTeam(tk.teamUid, out var team))
                        {
                            team.TeamKey = key;
                        }

                        teamKeys[tk.teamUid] = key;
                    }
                }
            }
        }