Привязать SecurePassword к ViewModel

Я пытаюсь связать SecurePassword свойство PasswordBox к моему ViewModel с обычаем Behavior, К сожалению, это не работает должным образом.

В основном я добавил свойство к Behavior который содержит целевое свойство моего ViewModel,

Есть идеи, почему это не работает?

PS: в настоящее время я возвращаюсь домой без моего ноутбука, я собираюсь обновить вопрос своим кодом через 15 минут. Но было бы хорошо, если бы кто-то отправил идеи или что-то в этом роде.

РЕДАКТИРОВАТЬ

Как я и обещал, вот код:)

Behavior первый:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Interactivity;
using System.Security;

namespace Knerd.Behaviors {
    public class PasswordChangedBehavior : Behavior<PasswordBox> {

        protected override void OnAttached() {
            AssociatedObject.PasswordChanged += AssociatedObject_PasswordChanged;
            base.OnAttached();
        }

        private void AssociatedObject_PasswordChanged(object sender, RoutedEventArgs e) {
            if (AssociatedObject.Password != null)
                TargetPassword = AssociatedObject.SecurePassword;
        }

        protected override void OnDetaching() {
            AssociatedObject.PasswordChanged -= AssociatedObject_PasswordChanged;
            base.OnDetaching();
        }

        public SecureString TargetPassword {
            get { return (SecureString)GetValue(TargetPasswordProperty); }
            set { SetValue(TargetPasswordProperty, value); }
        }

        // Using a DependencyProperty as the backing store for TargetPassword.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty TargetPasswordProperty = DependencyProperty.Register("TargetPassword", typeof(SecureString), typeof(PasswordChangedBehavior), new PropertyMetadata(default(SecureString)));
    }
}

PasswordBox:

<PasswordBox Grid.Column="1" Grid.Row="1" Margin="5" Width="300" MinWidth="200">
    <i:Interaction.Behaviors>
        <behaviors:PasswordChangedBehavior TargetPassword="{Binding Password}" />
    </i:Interaction.Behaviors>
</PasswordBox>

И наконец, часть моего ViewModel,

private SecureString password;

public SecureString Password {
    get { return password; }
    set {
        if (password != value) {
            password = value;
            OnPropertyChanged("Password");
        }
    }
}

Я надеюсь, что кто-нибудь может помочь, я использую версию codebehind, но я бы не стал.

РЕДАКТИРОВАТЬ 2

Что на самом деле не работает, так это то, что TargetPassword свойство не обновляет свойство моего ViewModel

2 ответа

Решение

Я думаю, что нашел странное решение. Пожалуйста, улучшайте, если есть что улучшить:)

Я просто изменил это так:

Behavior:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Interactivity;
using System.Security;

namespace Knerd.Behaviors {
    public class PasswordChangedBehavior : Behavior<PasswordBox> {

        protected override void OnAttached() {
            AssociatedObject.PasswordChanged += AssociatedObject_PasswordChanged;
            base.OnAttached();
        }

        private void AssociatedObject_PasswordChanged(object sender, RoutedEventArgs e) {
            if (AssociatedObject.SecurePassword != null)
                AssociatedObject.DataContext = AssociatedObject.SecurePassword.Copy();
        }

        protected override void OnDetaching() {
            AssociatedObject.PasswordChanged -= AssociatedObject_PasswordChanged;
            base.OnDetaching();
        }

        // Using a DependencyProperty as the backing store for TargetPassword.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty TargetPasswordProperty = DependencyProperty.Register("TargetPassword", typeof(SecureString), typeof(PasswordChangedBehavior), new PropertyMetadata(default(SecureString)));
    }
}

ViewModel ничего не изменилось, но вот мой View:

<PasswordBox Grid.Column="1" Grid.Row="1" Margin="5" Width="300" MinWidth="200" DataContext="{Binding Password, Mode=TwoWay}">
    <i:Interaction.Behaviors>
        <behaviors:PasswordChangedBehavior />
    </i:Interaction.Behaviors>
</PasswordBox>

Это работает просто отлично, без раскрытия незашифрованного пароля.

Создать свойство вложения

public static class PasswordBoxAssistant
{
 public static readonly DependencyProperty BoundPassword =
      DependencyProperty.RegisterAttached("BoundPassword", typeof(string), typeof(PasswordBoxAssistant), new PropertyMetadata(string.Empty, OnBoundPasswordChanged));

  public static readonly DependencyProperty BindPassword = DependencyProperty.RegisterAttached(
      "BindPassword", typeof (bool), typeof (PasswordBoxAssistant), new PropertyMetadata(false, OnBindPasswordChanged));


  private static readonly DependencyProperty UpdatingPassword =
      DependencyProperty.RegisterAttached("UpdatingPassword", typeof(bool), typeof(PasswordBoxAssistant), new PropertyMetadata(false));

  private static void OnBoundPasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  {
      PasswordBox box = d as PasswordBox;

      // only handle this event when the property is attached to a PasswordBox
      // and when the BindPassword attached property has been set to true
      if (d == null || !GetBindPassword(d))
      {
          return;
      }

      // avoid recursive updating by ignoring the box's changed event
      box.PasswordChanged -= HandlePasswordChanged;

      string newPassword = (string)e.NewValue;

      if (!GetUpdatingPassword(box))
      {
          box.Password = newPassword;
      }

      box.PasswordChanged += HandlePasswordChanged;
  }

  private static void OnBindPasswordChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
  {
      // when the BindPassword attached property is set on a PasswordBox,
      // start listening to its PasswordChanged event

      PasswordBox box = dp as PasswordBox;

      if (box == null)
      {
          return;
      }

      bool wasBound = (bool)(e.OldValue);
      bool needToBind = (bool)(e.NewValue);

      if (wasBound)
      {
          box.PasswordChanged -= HandlePasswordChanged;
      }

      if (needToBind)
      {
          box.PasswordChanged += HandlePasswordChanged;
      }
  }

  private static void HandlePasswordChanged(object sender, RoutedEventArgs e)
  {
      PasswordBox box = sender as PasswordBox;

      // set a flag to indicate that we're updating the password
      SetUpdatingPassword(box, true);
      // push the new password into the BoundPassword property
      SetBoundPassword(box, box.Password);
      SetUpdatingPassword(box, false);
  }

  public static void SetBindPassword(DependencyObject dp, bool value)
  {
      dp.SetValue(BindPassword, value);
  }

  public static bool GetBindPassword(DependencyObject dp)
  {
      return (bool)dp.GetValue(BindPassword);
  }

  public static string GetBoundPassword(DependencyObject dp)
  {
      return (string)dp.GetValue(BoundPassword);
  }

  public static void SetBoundPassword(DependencyObject dp, string value)
  {
      dp.SetValue(BoundPassword, value);
  }

  private static bool GetUpdatingPassword(DependencyObject dp)
  {
      return (bool)dp.GetValue(UpdatingPassword);
  }

  private static void SetUpdatingPassword(DependencyObject dp, bool value)
  {
      dp.SetValue(UpdatingPassword, value);
  }
}

И в твоем XAML

<Page xmlns:ff="clr-namespace:FunctionalFun.UI">
<!-- [Snip] -->
  <PasswordBox x:Name="PasswordBox"
      ff:PasswordBoxAssistant.BindPassword="true"  ff:PasswordBoxAssistant.BoundPassword="{Binding Path=Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">

</Page>

Вы, вероятно, не хотите делать это в любом случае, но если вы действительно хотите идти вперед.

Причина, по которой WPF/Silverlight PasswordBox не предоставляет DP для свойства Password, связана с безопасностью. Если бы WPF / Silverlight сохранял DP for Password, он потребовал бы от среды, чтобы сам пароль не был зашифрован в памяти. Что считается довольно неприятным вектором атаки безопасности. PasswordBox использует зашифрованную память (своего рода), и единственный способ получить доступ к паролю - через свойство CLR.

Я хотел бы предложить, чтобы при доступе к свойству CLR PasswordBox.Password вы воздерживались от размещения его в любой переменной или в качестве значения для любого свойства. Хранение пароля в виде обычного текста на оперативной памяти клиентского компьютера - это безопасность, нет-нет.

SecurePassword не может быть сделано с привязками.

Документация.NET объясняет, почему PasswordBox не был привязан в первую очередь.

Альтернативное решение - поставить PasswordBox в вашем ViewModelоткрытый класс LoginViewModel

public class LoginViewModel
{
   // other properties here

   public PasswordBox Password
   {
      get { return m_passwordBox; }
   }

   // Executed when the Login button is clicked.
   private void LoginExecute()
   {
      var password = Password.SecurePassword;

      // do more stuff...
   }
}

Да, вы нарушаете лучшие практики ViewModel здесь, но

  1. лучшие практики - это "рекомендации, которые в большинстве случаев работают хорошо", а не строгие правила и
  2. Написание простого, легко читаемого, поддерживаемого кода и избежание ненужной сложности также является одним из тех правил "наилучшей практики" (которые могут быть слегка нарушены обходным решением "присоединенного свойства").
Другие вопросы по тегам