PasswordBox PasswordHelper Multibinding - PullRequest
       65

PasswordBox PasswordHelper Multibinding

0 голосов
/ 14 февраля 2020

В WPF свойство Password для PasswordBox не является DependencyProperty, поэтому я не могу связываться с ним напрямую. В качестве обходного пути я использую это PasswordHelper из https://www.wpftutorial.net/PasswordBox.html, которое присоединяет PasswordHelper.Password к PasswordBox, чтобы я мог привязаться к нему.

Чтобы предотвратить сохранение пароля в DataContext в виде обычного текста я хотел бы использовать конвертер, который генерирует затравленный га sh пароля перед сохранением его в DataContext. Поскольку мне нужно сохранить и соль, и соленую га sh на DataContext, я использую MultiBinding и IMultiValueConverter конвертер StringToSaltedHashConverter.

Моя проблема в том, что Password и PasswordSalt свойства моих DataContext не обновляются, когда я заполняю PasswordBox. Я проверил с Sn oop и оба свойства Password и PasswordHelper.Password PasswordBox меняются в соответствии с тем, что я печатаю. Также вызывается мой StringToSaltedHashConverter, и я возвращаю правильные значения.

У меня есть другие привязки в этой форме (имя пользователя, имя, фамилия, пол ...), и все они работают нормально. Это единственный, который не обновляется.

¿Почему мой DataContext не обновляется?

XAML:

<PasswordBox x:Name="Password"
             Style="{DynamicResource PasswordBoxStyle1}"
             local:PasswordHelper.Attach="True">
    <local:PasswordHelper.Password>
        <MultiBinding Converter="{StaticResource StringToSaltedHashConverter}"
                      Mode="OneWayToSource">
            <Binding Path="Password" />
            <Binding Path="PasswordSalt" />
        </MultiBinding>
    </local:PasswordHelper.Password>
</PasswordBox>

Код позади:

public class StringToSaltedHashConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        string str = value as string;
        string salt = Hash.CreateSalt();
        string hash = Hash.CreateHash(str, salt);
        object[] vs = { hash, salt };
        return vs;
    }
}

Ответы [ 3 ]

1 голос
/ 14 февраля 2020

Это не путь к go. PasswordHelper класс и их лайки должны быть забанены в inte rnet. Свойство PasswordBox.Password не может быть привязано по очень веской причине, которая хорошо документирована. Доступ к этому свойству создаст текстовое представление string, которое можно легко получить с помощью бесплатных инструментов, например, Microsoft Process Explorer.

Когда вы получаете значение свойства Password, вы выставляете пароль в виде простого текста в памяти. Чтобы избежать этой потенциальной угрозы безопасности, используйте свойство SecurePassword, чтобы получить пароль в качестве SecureString.

Если для этого свойства задано значение NULL, для базового пароля будет установлено значение Пусто.

You даже хранить значение простой соли в памяти - я действительно надеюсь, что это не коммерческое и не публичное c приложение. У вас нет абсолютно никакого контроля, когда сборщик мусора удалит любое значение из памяти. Поскольку String является неизменяемым типом, весьма вероятно, что несколько копий пароля пользователя останутся в памяти c.

Рекомендуемая аутентификация в среде Windows заключается в использовании Windows API аутентификации пользователя.

По крайней мере, необходимо очистить свойство PasswordBox.Password, установив его null.

Шифрование не должно выполняться в представлении.

И, пожалуйста, напишите ответственный код! Данные пользователя не ваши данные!

MainWindow.xaml

<Window>
  <Window.DataContext>
    <ViewModel />
  </Window.DataContext>

  <PasswordBox x:Name="PasswordBox" />
</Window>

MainWindow.xaml.cs

public partial class MainWindow : Window
{
  public MainWindow()
  {
    InitializeComponent();

    this.PasswordBox.PasswordChanged += OnPasswordChanged;
  }

  private void OnPasswordChanged(object sender, RoutedEventArgs e)
  {
    (this.DataContext as ViewModel).Password = this.PasswordBox.SecurePassword;
  }
}

ViewModel.cs

pucblic class ViewModel
{
  private Model Model { get; }

  private SecureString password;
  public SecureString Password
  {
    private get => this.password;
    public set
    {
      this.password = value;
      OnPasswordChanged();
    }
  }

  private void OnPasswordChanged()
  {
    // Hash password in the model e.g. to compare it to a hashed database value
    this.Model.TryLogin(this.Password);
  }
}

Model.cs

public class Model
{
  public bool TryLogin(SecureString password)
  {  
    IntPtr unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(password);
    string hashedPassword = HashPassword(Marshal.PtrToStringUni(unmanagedString));

    return PasswordIsValid(hashedPassword);
  }

  private string HashPassword(string unsecurePassword)
  {
    // Hash algorithm
  }
}
0 голосов
/ 26 февраля 2020

Я просто хотел поблагодарить всех за ваши ответы и глубокие комментарии. Вы заставили меня осознать, что я не должен использовать PasswordHelper и что я вообще не должен пытаться связываться с паролем.

На тот случай, если у кого-то возникнет аналогичная проблема с MultiBinding, неправильно обновляющим DataContext, это можно исправить, добавив режим OneWayToSource к каждой из привязок внутри MultiBinding.

<MultiBinding Converter="{StaticResource StringToSaltedHashConverter}" 
              Mode="OneWayToSource">
    <Binding Path="Password"
             Mode="OneWayToSource" />
    <Binding Path="PasswordSalt"
             Mode="OneWayToSource" />
</MultiBinding>
0 голосов
/ 14 февраля 2020

Прежде чем мне понадобится пароль от PasswordBox, пользователь скажет мне, что он закончил ввод, нажав кнопку.

Для меня лучше всего использовать CommandParameter из Button с LoginCommand:

<StackPanel
        Width="300"
        HorizontalAlignment="Center"
        VerticalAlignment="Center">
    <Label Content="Username:" />
    <TextBox Text="{Binding Username}" />
    <Label Content="Password:" />
    <PasswordBox PasswordChanged="PasswordChanged" />
    <StackPanel
            Orientation="Horizontal">
        <Button
                Name="LoginButton"
                Command="{Binding LoginCommand}"
                Content="Login" />
    </StackPanel>
</StackPanel>

Просмотреть код

private void PasswordChanged( object sender, RoutedEventArgs e )
{
    LoginButton.CommandParameter = ( sender as PasswordBox ).SecurePassword;
}

Команда ViewModel

Login = ReactiveCommand.CreateFromTask( async ( SecureString password, CancellationToken cancellationToken ) =>
{
    var loggedIn = await AuthenticationService.LoginAsync( Username, password, cancellationToken );
    ...
} );
...