using System; using System.Collections.Generic; using Crestron.SimplSharp; namespace PepperDash.Core.PasswordManagement { /// /// Allows passwords to be stored and managed /// public class PasswordManager { /// /// Public dictionary of known passwords /// public static Dictionary Passwords = new Dictionary(); /// /// Indicates whether the manager has been initialized /// public static bool IsInitialized { get; private set; } /// /// Tracks keys changed during the debounce window for notification /// private readonly List _pendingChanges = new List(); /// /// Timer used to wait until password changes have stopped before updating the dictionary /// CTimer _passwordTimer; /// /// Timer length /// public long PasswordTimerElapsedMs = 5000; /// /// Boolean event /// public event EventHandler BoolChange; /// /// Ushort event /// public event EventHandler UshrtChange; /// /// String event /// public event EventHandler StringChange; /// /// Event to notify clients of an updated password at the specified index (uint) /// public static event EventHandler PasswordChange; /// /// Event to notify clients when the manager has been initialized /// public static event EventHandler Initialized; /// /// Constructor (empty constructor required by S+) /// public PasswordManager() { } /// /// Initialize password manager /// public void Initialize() { if (Passwords == null) { Passwords = new Dictionary(); } OnBoolChange(true, 0, PasswordConstants.Initialized); IsInitialized = true; var handler = Initialized; if (handler != null) handler(this, new BoolChangeEventArgs(true, PasswordConstants.Initialized)); } /// /// Updates password stored in the dictonary /// /// /// public void UpdatePassword(string key, string password) { // validate the parameters if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(password)) { Debug.Console(1, string.Format("PasswordManager.UpdatePassword: key [{0}] or password are not valid", key, password)); return; } try { Passwords[key] = password; if (!_pendingChanges.Contains(key)) _pendingChanges.Add(key); Debug.Console(1, string.Format("PasswordManager.UpdatePassword: Passwords[{0}] = {1}", key, Passwords[key])); if (_passwordTimer == null) { _passwordTimer = new CTimer((o) => PasswordTimerElapsed(), PasswordTimerElapsedMs); Debug.Console(1, string.Format("PasswordManager.UpdatePassword: CTimer Started")); OnBoolChange(true, 0, PasswordConstants.UpdateBusy); } else { _passwordTimer.Reset(PasswordTimerElapsedMs); Debug.Console(1, string.Format("PasswordManager.UpdatePassword: CTimer Reset")); } } catch (Exception e) { var msg = string.Format("PasswordManager.UpdatePassword key-value[{0}, {1}] failed:\r{2}", key, password, e); Debug.Console(1, msg); } } /// /// Helper method for debugging to see what passwords are in the lists /// public void ListEntries() { Debug.Console(0, "PasswordManager.ListEntries:\r"); foreach (var pw in Passwords) { var maskedValue = string.IsNullOrEmpty(pw.Value) ? string.Empty : new string('*', pw.Value.Length); Debug.Console(0, "Passwords[{0}]: {1}\r", pw.Key, maskedValue); } } /// /// Validates the username against the known entries /// /// public static PasswordValidationResult ValidateUsername(string username) { if (Passwords == null) return PasswordValidationResult.Failure("No entires defined, verify list is initilaized"); if (string.IsNullOrEmpty(username)) return PasswordValidationResult.Failure("Username or password is empty"); string storedPassword; return !Passwords.TryGetValue(username, out storedPassword) ? PasswordValidationResult.Failure(string.Format("Username '{0}' not found", username)) : PasswordValidationResult.Success("Valid username", storedPassword); } /// /// Validates the username password against the known entries /// /// /// public static PasswordValidationResult ValidateUsernameAndPassword(string username, string password) { if (Passwords == null) return PasswordValidationResult.Failure("No entires defined, verify list is initilaized"); if (string.IsNullOrEmpty(username)) return PasswordValidationResult.Failure("Username is empty or null"); if(string.IsNullOrEmpty(password)) return PasswordValidationResult.Failure("Password is empty or null"); string storedPassword; if (!Passwords.TryGetValue(username, out storedPassword)) return PasswordValidationResult.Failure(string.Format("Username '{0}' not found", username)); return !string.Equals(storedPassword, password, StringComparison.Ordinal) ? PasswordValidationResult.Failure("Invalid password, verify password and try again") : PasswordValidationResult.Success("Valid credentials, login was successfull", storedPassword); } /// /// CTimer callback function /// private void PasswordTimerElapsed() { try { _passwordTimer.Stop(); Debug.Console(1, string.Format("PasswordManager.PasswordTimerElapsed: CTimer Stopped")); OnBoolChange(false, 0, PasswordConstants.UpdateBusy); foreach (var key in _pendingChanges) { string value; if (!Passwords.TryGetValue(key, out value)) continue; Debug.Console(1, string.Format("PasswordManager.PasswordTimerElapsed: Notifying change for [{0}]", key)); OnPasswordChange(key, 0, PasswordConstants.PasswordUpdated); } _pendingChanges.Clear(); OnUshrtChange((ushort)Passwords.Count, 0, PasswordConstants.Count); } catch (Exception e) { var msg = string.Format("PasswordManager.PasswordTimerElapsed failed:\r{0}", e); Debug.Console(1, msg); } } /// /// Method to change the default timer value, (default 5000ms/5s) /// /// public void PasswordTimerMs(ushort time) { PasswordTimerElapsedMs = Convert.ToInt64(time); } #region event handlers /// /// Protected boolean change event handler /// /// /// /// protected void OnBoolChange(bool state, ushort index, ushort type) { var handler = BoolChange; if (handler == null) return; var args = new BoolChangeEventArgs(state, type) {Index = index}; BoolChange(this, args); } /// /// Protected ushort change event handler /// /// /// /// protected void OnUshrtChange(ushort value, ushort index, ushort type) { var handler = UshrtChange; if (handler == null) return; var args = new UshrtChangeEventArgs(value, type) {Index = index}; UshrtChange(this, args); } /// /// Protected string change event handler /// /// /// /// protected void OnStringChange(string value, ushort index, ushort type) { var handler = StringChange; if (handler == null) return; var args = new StringChangeEventArgs(value, type) {Index = index}; StringChange(this, args); } /// /// Protected password change event handler /// /// /// /// protected void OnPasswordChange(string value, ushort index, ushort type) { var handler = PasswordChange; if (handler == null) return; var args = new StringChangeEventArgs(value, type) {Index = index}; PasswordChange(this, args); } #endregion } }