// /************************************************************************ // * @file RecipeLockPanel.xaml.cs // * @author Su Liang // * @date 2023/03/08 // * // * @copyright © Sicentury Inc. // * // * @brief // * // * @details // * // * // * *****************************************************************************/ using System; using System.Diagnostics; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Media.Effects; using Aitex.Core.Util; using MECF.Framework.Common.DataCenter; using MECF.Framework.Common.OperationCenter; using Panel = System.Windows.Controls.Panel; namespace MECF.Framework.UI.Client.Ctrlib.Controls { public partial class PanelLocker { /// /// 锁定时背景模糊半径 /// private const double BLUR_RADIUS = 30; /// /// 默认密码admin的SHA256 /// private const string DEF_PASSWORD = "8C6976E5B5410415BDE908BD4DEE15DFB167A9C873FC4BB8A81F6F2AB448A918"; // 阻止密码攻击定时器 private readonly DeviceTimer _timPassAttackDetector; // 密码连续重试次数 private int _retryTimes; // 自动锁定计时器 private readonly Stopwatch _swAutoLock; private PeriodicJob _pjobAutoLock; private int _timeoutAutoLock = int.MaxValue; public PanelLocker() { InitializeComponent(); _timPassAttackDetector = new DeviceTimer(); _retryTimes = 0; _pjobAutoLock = new PeriodicJob(int.MaxValue, () => { Dispatcher.Invoke(Lock); return true; }, "AutoLockRecipeEditor"); // Lock面板加载后,或重新激活后,将焦点自动设置到PasswordBox Loaded += (sender, args) => { txtPass.Focus(); }; } #region Dps public static readonly DependencyProperty LockTargetProperty = DependencyProperty.Register( nameof(LockTarget), typeof(Panel), typeof(PanelLocker), new PropertyMetadata(default(Panel), (sender, args) => { // 初次拿到LockTarget对象时,锁定对象 if (sender is PanelLocker pl && args.NewValue is Panel) { pl.Lock(); } })); /// /// 设置或返回待锁定的界面 /// public Panel LockTarget { get => (Panel)GetValue(LockTargetProperty); set => SetValue(LockTargetProperty, value); } public static readonly DependencyProperty ScPathPasswordProperty = DependencyProperty.Register( nameof(ScPathPassword), typeof(string), typeof(PanelLocker), new PropertyMetadata("System.Recipe.EditorPassword")); /// /// 设置或返回SC中存放密码的节点。 /// public string ScPathPassword { get => (string)GetValue(ScPathPasswordProperty); set => SetValue(ScPathPasswordProperty, value); } public static readonly DependencyProperty ScPathIsResetPasswordProperty = DependencyProperty.Register( nameof(ScPathIsResetPassword), typeof(string), typeof(PanelLocker), new PropertyMetadata("System.Recipe.ResetEditorPassword")); /// /// 设置或返回SC中复位密码的节点。 /// public string ScPathIsResetPassword { get => (string)GetValue(ScPathIsResetPasswordProperty); set => SetValue(ScPathIsResetPasswordProperty, value); } public static readonly DependencyProperty OpPathSetNewPasswordProperty = DependencyProperty.Register( nameof(OpPathSetNewPassword), typeof(string), typeof(PanelLocker), new PropertyMetadata("System.SetNewRecipeEditorPassword")); /// /// 设置或返回RT中注册的用于设置新密码的OP。 /// public string OpPathSetNewPassword { get => (string)GetValue(OpPathSetNewPasswordProperty); set => SetValue(OpPathSetNewPasswordProperty, value); } public static readonly DependencyProperty OpPathResetPasswordToDefaultProperty = DependencyProperty.Register( nameof(OpPathResetPasswordToDefault), typeof(string), typeof(PanelLocker), new PropertyMetadata("System.ResetRecipeEditorPassword")); public string OpPathResetPasswordToDefault { get => (string)GetValue(OpPathResetPasswordToDefaultProperty); set => SetValue(OpPathResetPasswordToDefaultProperty, value); } #endregion #region Methods protected override void OnRender(DrawingContext drawingContext) { base.OnRender(drawingContext); txtPass.Focus(); } public void Lock() { txtPass.Password = ""; txtInfo.Text = ""; LockTarget.Effect = new BlurEffect(); Visibility = Visibility.Visible; var aniBlurFadeOut = new DoubleAnimation(0, BLUR_RADIUS, new Duration(TimeSpan.FromMilliseconds(300))); LockTarget.Effect.BeginAnimation(BlurEffect.RadiusProperty, aniBlurFadeOut); // 停止自动锁定计时器 //_swAutoLock.Stop(); } /// /// 重置自动锁定超时计时器 /// public void ResetAutoLockTimer() { _pjobAutoLock.Stop(); _pjobAutoLock.Start(); } /// /// 校验密码 /// /// private bool CheckPassword() { // 是否请求重置密码 var reqResetPass = (bool)QueryDataClient.Instance.Service.GetConfig(ScPathIsResetPassword); if (reqResetPass) { // 密码重置为admin InvokeClient.Instance.Service.DoOperation(OpPathResetPasswordToDefault, DEF_PASSWORD); } // 读取系统配置中的Password,如果读取失败,使用默认密码admin var passInSc = QueryDataClient.Instance.Service.GetConfig(ScPathPassword).ToString(); if (string.IsNullOrEmpty(passInSc)) passInSc = DEF_PASSWORD; using (var sha = SHA256.Create()) { var bytesHash = sha.ComputeHash(Encoding.UTF8.GetBytes(txtPass.Password)); var passInput = BitConverter.ToString(bytesHash).Replace("-", ""); if (passInput == passInSc) { txtPass.Password = ""; return true; } } // 检测是否5s内重试密码5次以上。 if (_timPassAttackDetector.IsIdle() || _timPassAttackDetector.IsTimeout()) { _timPassAttackDetector.Start(5000); _retryTimes = 0; } else { _retryTimes++; if (_retryTimes > 5) { btnUnlock.IsEnabled = false; txtPass.IsEnabled = false; // 如果5s内重试的次数大于5次,禁用解锁窗口5秒钟 Task.Run(() => { var deniedSec = 5; while (deniedSec-- > 0) { Dispatcher.Invoke(() => { txtInfo.Text = $"You have retried too many times, try again in {deniedSec} seconds"; }); Thread.Sleep(1000); } Dispatcher.Invoke(() => { txtInfo.Text = ""; btnUnlock.IsEnabled = true; txtPass.IsEnabled = true; }); }); } } txtInfo.Text = "Incorrect Password"; txtPass.Password = ""; return false; } #endregion #region Events private void BtnUnlock_OnClick(object sender, RoutedEventArgs e) { if (!CheckPassword()) { txtPass.Focus(); return; } if (LockTarget.Effect is BlurEffect eff) { var aniBlurFadeOut = new DoubleAnimation(BLUR_RADIUS, 0, new Duration(TimeSpan.FromMilliseconds(300))); eff.BeginAnimation(BlurEffect.RadiusProperty, aniBlurFadeOut); } LockTarget.Effect = null; this.Visibility = Visibility.Collapsed; // 启动自动锁定计时器 /*var autoLockTimeout = (int)QueryDataClient.Instance.Service.GetConfig("System.RecipeEditorAutoLockTimeout"); if (autoLockTimeout <= 0) autoLockTimeout = 60; _swAutoLock.Interval = autoLockTimeout; _swAutoLock.Start();*/ } private void TxtPass_OnPreviewKeyDown(object sender, KeyEventArgs e) { if (e.Key == Key.Enter) BtnUnlock_OnClick(this, new RoutedEventArgs()); } #endregion } }