public static async Task PopulateUserPublicKeys(this EnterpriseData enterprise, IDictionary <string, byte[]> publicKeys, Action <string> warnings = null)
        {
            var toLoad = new HashSet <string>(StringComparer.InvariantCultureIgnoreCase);

            toLoad.UnionWith(publicKeys.Keys);
            toLoad.ExceptWith(enterprise.UserPublicKeyCache.Keys);
            if (toLoad.Count > 0)
            {
                var publicKeyRq = new PublicKeysCommand
                {
                    keyOwners = toLoad.ToArray()
                };
                var publicKeyRs = await enterprise.Auth.ExecuteAuthCommand <PublicKeysCommand, PublicKeysResponse>(publicKeyRq);

                if (publicKeyRs.publicKeys != null)
                {
                    foreach (var key in publicKeyRs.publicKeys)
                    {
                        if (!string.IsNullOrEmpty(key.publicKey))
                        {
                            enterprise.UserPublicKeyCache[key.keyOwner] = key.publicKey.Base64UrlDecode();
                        }
                        else
                        {
                            warnings?.Invoke($"User \'{key.keyOwner}\': Public key error ({key.resultCode}): {key.message}");
                            enterprise.UserPublicKeyCache[key.keyOwner] = null;
                        }
                    }
                }
            }

            foreach (var email in publicKeys.Keys.ToArray())
            {
                if (enterprise.UserPublicKeyCache.TryGetValue(email, out var pk))
                {
                    publicKeys[email] = pk;
                }
            }
        }
        /// <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));
        }