Comment se lier à une boîte de passe dans MVVM

j'ai rencontré un problème de reliure à une boîte de dialogue. Il semble que c'est un risque de sécurité mais j'utilise le modèle MVVM donc je souhaite contourner cela. J'ai trouvé un code intéressant ici (quelqu'un a-t-il utilisé ceci ou quelque chose de similaire?)

http://www.wpftutorial.net/PasswordBox.html

cela semble techniquement génial, mais je ne sais pas comment récupérer le mot de passe.

j'ai essentiellement propriétés dans mon LoginViewModel pour Username et Password . Username est très bien et fonctionne comme un TextBox .

j'ai utilisé le code ci-dessus comme indiqué et entré ce

<PasswordBox ff:PasswordHelper.Attach="True"
    ff:PasswordHelper.Password="{Binding Path=Password}" Width="130"/>

quand j'ai eu le PasswordBox comme un TextBox et Binding Path=Password alors la propriété dans mon LoginViewModel a été mise à jour.

mon code est très simple, fondamentalement j'ai un Command pour mon Button . Quand je le presse CanLogin est appelé et s'il retourne true il appelle Login .

Vous pouvez voir que je vérifie ma propriété pour Username ici qui fonctionne très bien.

Dans Login j'envoie à mon service un 151940920" et Password , Username contient des données à partir de mon View mais Password est Null|Empty

private DelegateCommand loginCommand;

    public string Username { get; set; }
    public string Password { get; set; }


    public ICommand LoginCommand
    {
        get
        {
            if (loginCommand == null)
            {
                loginCommand = new DelegateCommand(
                    Login, CanLogin );
            }
            return loginCommand;
        }
    }

    private bool CanLogin()
    {
        return !string.IsNullOrEmpty(Username);
    }

    private void Login()
    {
        bool result = securityService.IsValidLogin(Username, Password);

        if (result) { }
        else { }
    }

C'est ce que je fais

<TextBox Text="{Binding Path=Username, UpdateSourceTrigger=PropertyChanged}"
         MinWidth="180" />

<PasswordBox ff:PasswordHelper.Attach="True" 
             ff:PasswordHelper.Password="{Binding Path=Password}" Width="130"/>

j'ai mon TextBox , ce n'est pas un problème, mais dans mon ViewModel le Password est vide.

est-ce que je fais quelque chose de mal ou je manque une étape?

j'ai mis un point de rupture et assez sûr le code entre dans la classe de helper statique, mais il ne met jamais à jour mon Password dans mon ViewModel .

217
demandé sur wonea 2009-09-27 20:34:25

30 réponses

désolé, mais tu le fais mal.

les gens devraient avoir les directives de sécurité suivantes tatouées à l'intérieur de leurs paupières:

ne gardez jamais de mots de passe en clair en mémoire.

la raison pour laquelle la WPF/Silverlight PasswordBox n'expose pas un DP pour la propriété Password est liée à la sécurité.

Si WPF / Silverlight devait conserver un DP pour mot de passe, il faudrait cadre de garder le mot de passe non crypté dans la mémoire. Ce qui est considéré comme un vecteur d'attaque de sécurité assez gênant. La boîte de dialogue utilise une mémoire cryptée (de toutes sortes) et la seule façon d'accéder au mot de passe est par la propriété CLR.

je suggère que lors de l'accès à la boîte de passe.Mot de passe propriété CLR vous éviteriez de le placer dans une variable ou comme une valeur pour une propriété.

Conserver votre mot de passe en texte clair sur le client machine RAM est une sécurité non-non.

Alors débarrasse-toi de ce" mot de passe public string { get; set;} " que tu as là-haut.

en accédant à PasswordBox.Le mot de passe, il suffit de le sortir et de l'envoyer au serveur dès que possible. Ne gardez pas la valeur du mot de passe autour et ne le traitez pas comme vous le feriez n'importe quel autre texte machine client. Ne gardez pas de mots de passe clairs en mémoire.

je sais que cela brise le modèle MVVM, mais vous ne devriez jamais lier à PasswordBox.DP attaché au mot de passe, stockez votre mot de passe dans le ViewModel ou tout autre shenanigans similaire.

si vous cherchez une solution sur-architecturée, en voici une:

1. Créer L'interface IHavePassword avec une méthode qui renvoie le mot de passe texte clair.

2. Demandez à votre UserControl d'implémenter une interface IHavePassword.

3. Enregistrez L'instance UserControl auprès de votre CIO en mise en œuvre de L'interface IHavePassword.

4. Quand une demande de serveur nécessitant votre mot de passe a lieu, appelez votre CIO pour la mise en œuvre de IHavePassword et seulement obtenir le mot de passe tant convoité.

C'est mon avis.

-- Justin

144
répondu JustinAngel 2017-12-09 22:09:58

mes 2 cents:

j'ai développé une fois un dialogue de connexion typique (boîtes d'utilisateur et de mot de passe, plus bouton" Ok") en utilisant WPF et MVVM. J'ai résolu le problème de la fixation du mot de passe en passant simplement la commande PasswordBox comme paramètre à la commande attachée au bouton "Ok". Ainsi, dans la vue que j'avais:

<PasswordBox Name="txtPassword" VerticalAlignment="Top" Width="120" />
<Button Content="Ok" Command="{Binding Path=OkCommand}"
   CommandParameter="{Binding ElementName=txtPassword}"/>

et dans le modèle de vue, la méthode Execute de la commande ci-jointe était la suivante:

void Execute(object parameter)
{
    var passwordBox = parameter as PasswordBox;
    var password = passwordBox.Password;
    //Now go ahead and check the user name and password
}

This viole légèrement le modèle MVVM depuis maintenant le modèle de vue sait quelque chose sur la façon dont la vue est mise en œuvre, mais dans ce projet particulier je pourrais me le permettre. J'espère que c'est aussi utile pour quelqu'un.

173
répondu Konamiman 2011-01-10 17:57:31

peut-être que je manque quelque chose, mais il semble que la plupart de ces solutions compliquent les choses et suppriment les pratiques sécuritaires.

cette méthode ne viole pas le modèle MVVM et maintient une sécurité complète. Oui, techniquement c'est un code derrière, mais ce n'est rien de plus qu'un "cas spécial" contraignant. Le ViewModel n'a toujours aucune connaissance de L'implémentation de la vue, ce qui à mon avis est le cas si vous essayez de passer la boîte de dialogue Password au ViewModel.

Code Derrière != Violation automatique du MVVM. Tout dépend de ce que vous faites avec lui. Dans ce cas, nous ne faisons que coder manuellement une liaison, donc tout est considéré comme faisant partie de L'implémentation de L'interface utilisateur et est donc correct.

Dans le ViewModel, juste une simple propriété. Je l'ai fait "écrire seulement" car il ne devrait pas y avoir un besoin de le récupérer à l'extérieur du modèle de vue pour une raison quelconque, mais il ne doit pas l'être. Notez qu'il S'agit d'une sécurisation, pas seulement d'une chaîne.

public SecureString SecurePassword { private get; set; }

dans le xaml, vous mettez en place un gestionnaire D'événements modifié par mot de passe.

<PasswordBox PasswordChanged="PasswordBox_PasswordChanged"/>

dans le code derrière:

private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
    if (this.DataContext != null)
    { ((dynamic)this.DataContext).SecurePassword = ((PasswordBox)sender).SecurePassword; }
}

avec cette méthode, Votre mot de passe reste en sécurité en tout temps et offre donc un maximum de sécurité. Si vous ne vous souciez vraiment pas de la sécurité ou vous avez besoin du mot de passe texte clair pour une méthode en aval qui l'exige (note: la plupart des méthodes .NET qui exigent un mot de passe prennent également en charge un Option SecureString, de sorte que vous ne pouvez pas vraiment besoin d'un mot de passe texte clair, même si vous pensez que vous le faites), vous pouvez juste utiliser la propriété Password à la place. Comme ceci:

(bien ViewModel)

public string Password { private get; set; }

(Code derrière)

private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
    if (this.DataContext != null)
    { ((dynamic)this.DataContext).Password = ((PasswordBox)sender).Password; }
}

si vous voulez garder les choses fortement dactylographiées, vous pouvez remplacer la fonte (dynamique) avec l'interface de votre modèle de vue. Mais en réalité, les reliures de données "normales" ne sont pas fortement tapées non plus, pas un gros problème.

private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
    if (this.DataContext != null)
    { ((IMyViewModel)this.DataContext).Password = ((PasswordBox)sender).Password; }
}

donc le meilleur de tous les mondes - votre mot de passe est sécurisé, votre ViewModel a juste une propriété comme toute autre propriété, et votre vue est autonome sans références externes nécessaires.

141
répondu Steve In CO 2014-07-28 17:45:19

vous pouvez utiliser ce XAML:

<PasswordBox Name="PasswordBox">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="PasswordChanged">
            <i:InvokeCommandAction Command="{Binding PasswordChangedCommand}" CommandParameter="{Binding ElementName=PasswordBox}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</PasswordBox>

et cette commande exécute la méthode:

private void ExecutePasswordChangedCommand(PasswordBox obj)
{ 
   if (obj != null)
     Password = obj.Password;
}
18
répondu Sergey 2017-05-02 13:52:44

ça me va très bien.

<Button Command="{Binding Connect}" 
        CommandParameter="{Binding ElementName=MyPasswordBox}"/>
12
répondu Vladislav Borovikov 2017-05-02 14:12:10

une solution simple sans violer le modèle MVVM est d'introduire un événement (ou un délégué) dans le modèle de vue qui récolte le mot de passe.

dans le ViewModel :

public event EventHandler<HarvestPasswordEventArgs> HarvestPassword;

avec ces EventArgs:

class HarvestPasswordEventArgs : EventArgs
{
    public string Password;
}

dans la View , abonnez-vous à l'événement lors de la création du ViewModel et saisissez la valeur du mot de passe.

_viewModel.HarvestPassword += (sender, args) => 
    args.Password = passwordBox1.Password;

dans le ViewModel , quand vous avez besoin du mot de passe, vous pouvez tirer l'événement et récolter le mot de passe à partir de là:

if (HarvestPassword == null)
  //bah 
  return;

var pwargs = new HarvestPasswordEventArgs();
HarvestPassword(this, pwargs);

LoginHelpers.Login(Username, pwargs.Password);
9
répondu Jan Willem B 2012-10-17 09:29:19

j'ai posté un GIST ici qui est une boîte de mot de passe bindable.

using System.Windows;
using System.Windows.Controls;

namespace CustomControl
{
    public class BindablePasswordBox : Decorator
    {
        /// <summary>
        /// The password dependency property.
        /// </summary>
        public static readonly DependencyProperty PasswordProperty;

        private bool isPreventCallback;
        private RoutedEventHandler savedCallback;

        /// <summary>
        /// Static constructor to initialize the dependency properties.
        /// </summary>
        static BindablePasswordBox()
        {
            PasswordProperty = DependencyProperty.Register(
                "Password",
                typeof(string),
                typeof(BindablePasswordBox),
                new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(OnPasswordPropertyChanged))
            );
        }

        /// <summary>
        /// Saves the password changed callback and sets the child element to the password box.
        /// </summary>
        public BindablePasswordBox()
        {
            savedCallback = HandlePasswordChanged;

            PasswordBox passwordBox = new PasswordBox();
            passwordBox.PasswordChanged += savedCallback;
            Child = passwordBox;
        }

        /// <summary>
        /// The password dependency property.
        /// </summary>
        public string Password
        {
            get { return GetValue(PasswordProperty) as string; }
            set { SetValue(PasswordProperty, value); }
        }

        /// <summary>
        /// Handles changes to the password dependency property.
        /// </summary>
        /// <param name="d">the dependency object</param>
        /// <param name="eventArgs">the event args</param>
        private static void OnPasswordPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs eventArgs)
        {
            BindablePasswordBox bindablePasswordBox = (BindablePasswordBox) d;
            PasswordBox passwordBox = (PasswordBox) bindablePasswordBox.Child;

            if (bindablePasswordBox.isPreventCallback)
            {
                return;
            }

            passwordBox.PasswordChanged -= bindablePasswordBox.savedCallback;
            passwordBox.Password = (eventArgs.NewValue != null) ? eventArgs.NewValue.ToString() : "";
            passwordBox.PasswordChanged += bindablePasswordBox.savedCallback;
        }

        /// <summary>
        /// Handles the password changed event.
        /// </summary>
        /// <param name="sender">the sender</param>
        /// <param name="eventArgs">the event args</param>
        private void HandlePasswordChanged(object sender, RoutedEventArgs eventArgs)
        {
            PasswordBox passwordBox = (PasswordBox) sender;

            isPreventCallback = true;
            Password = passwordBox.Password;
            isPreventCallback = false;
        }
    }
}
8
répondu Taylor Leese 2015-06-23 14:18:27

cette implémentation est légèrement différente. Vous passez un passwordbox à la vue par liaison D'une propriété dans ViewModel, il n'utilise pas de params de commande. Le modèle de vue reste Ignorant de la vue. J'ai un projet VB vs 2010 qui peut être téléchargé à partir de SkyDrive. WPF MVVM PassWordBox Exemple.zip https://skydrive.live.com/redir.aspx?cid=e95997d33a9f8d73&resid=E95997D33A9F8D73-oui.511

la façon dont J'utilise PasswordBox dans un MvvM Wpf L'Application est assez simpliste et fonctionne bien pour Moi. Cela ne signifie pas que je pense que c'est la manière correcte ou le meilleur moyen. Il s'agit simplement d'une implémentation de L'utilisation de PasswordBox et du modèle MvvM.

de manière basique, vous créez une propriété publique en lecture seule à laquelle la vue peut se rattacher en tant que PasswordBox (le contrôle réel) exemple:

Private _thePassWordBox As PasswordBox
Public ReadOnly Property ThePassWordBox As PasswordBox
    Get
        If IsNothing(_thePassWordBox) Then _thePassWordBox = New PasswordBox
        Return _thePassWordBox
    End Get
End Property

j'utilise un champ de soutien juste pour faire l'Auto-initialisation de la propriété.

Puis De Xaml vous liez le contenu d'un ContentControl ou D'un conteneur de contrôle exemple:

 <ContentControl Grid.Column="1" Grid.Row="1" Height="23" Width="120" Content="{Binding Path=ThePassWordBox}" HorizontalAlignment="Center" VerticalAlignment="Center" />

de là, vous avez le contrôle total de la passwordbox j'utilise également un PasswordAccessor (juste une fonction de chaîne de caractères) pour retourner la valeur du mot de passe lors de la connexion ou ce que vous voulez le mot de passe pour. Dans l'exemple, j'ai une propriété publique dans un modèle générique D'objet utilisateur. Exemple:

Public Property PasswordAccessor() As Func(Of String)

dans L'objet utilisateur la propriété chaîne de mots de passe est en lecture seule, sans aucune sauvegarde, il renvoie simplement le mot de passe de la boîte de dialogue. Exemple:

Public ReadOnly Property PassWord As String
    Get
        Return If((PasswordAccessor Is Nothing), String.Empty, PasswordAccessor.Invoke())
    End Get
End Property

puis dans le ViewModel Je m'assure que L'accesseur est créé et réglé sur la boîte de dialogue.Propriété de mot de passe' Exemple:

Public Sub New()
    'Sets the Accessor for the Password Property
    SetPasswordAccessor(Function() ThePassWordBox.Password)
End Sub

Friend Sub SetPasswordAccessor(ByVal accessor As Func(Of String))
    If Not IsNothing(VMUser) Then VMUser.PasswordAccessor = accessor
End Sub

quand j'ai besoin de la chaîne de mots de passe dire pour la connexion je viens d'obtenir la propriété des mots de passe D'objets utilisateur qui invoque vraiment la fonction pour saisir le mot de passe et le retourner, alors le mot de passe réel n'est pas stocké par l'Utilisateur de l'Objet. Exemple: serait dans le modèle de vue

Private Function LogIn() as Boolean
    'Make call to your Authentication methods and or functions. I usally place that code in the Model
    Return AuthenticationManager.Login(New UserIdentity(User.UserName, User.Password)
End Function

ça devrait le faire. Le ViewModel n'a pas besoin de connaître les commandes de la vue. La vue se lie simplement à la propriété dans le modèle de vue, pas différent de la vue liant à une Image ou autre ressource. Dans ce cas, cette ressource(propriété) se trouve être un contrôle d'utilisateur. Il permet de tester que le ViewModel crée et possède la Propriété et la Propriété est indépendante de la Vue. Comme pour SÉCURITÉ Je ne sais pas si cette implémentation est bonne. Mais en utilisant une fonction la valeur n'est pas stockée dans la propriété elle-même à laquelle vient d'Accéder la propriété.

6
répondu William Rawson 2011-08-31 06:36:36

pour résoudre le problème OP sans casser le MVVM, j'utiliserais un convertisseur de valeur personnalisé et un wrapper pour la valeur (le mot de passe) qui doit être récupéré à partir de la boîte de mot de passe.

public interface IWrappedParameter<T>
{
    T Value { get; }
}

public class PasswordBoxWrapper : IWrappedParameter<string>
{
    private readonly PasswordBox _source;

    public string Value
    {
        get { return _source != null ? _source.Password : string.Empty; }
    }

    public PasswordBoxWrapper(PasswordBox source)
    {
        _source = source;
    }
}

public class PasswordBoxConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        // Implement type and value check here...
        return new PasswordBoxWrapper((PasswordBox)value);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new InvalidOperationException("No conversion.");
    }
}

dans le modèle view:

public string Username { get; set; }

public ICommand LoginCommand
{
    get
    {
        return new RelayCommand<IWrappedParameter<string>>(password => { Login(Username, password); });
    }
}

private void Login(string username, string password)
{
    // Perform login here...
}

parce que le modèle view utilise IWrappedParameter<T> , il n'a pas besoin d'avoir des connaissances sur PasswordBoxWrapper ni PasswordBoxConverter . De cette façon, vous pouvez isoler l'objet PasswordBox du modèle de vue et ne pas casser le pattern MVVM.

Dans la vue:

<Window.Resources>
    <h:PasswordBoxConverter x:Key="PwdConverter" />
</Window.Resources>
...
<PasswordBox Name="PwdBox" />
<Button Content="Login" Command="{Binding LoginCommand}"
        CommandParameter="{Binding ElementName=PwdBox, Converter={StaticResource PwdConverter}}" />
6
répondu Aoi Karasu 2014-03-12 14:56:41

bien que je convienne qu'il est important d'éviter de stocker le mot de passe n'importe où, j'ai encore besoin de la capacité d'instancier le modèle de vue sans une vue et d'exécuter mes tests contre elle.

la solution qui a fonctionné pour moi était d'enregistrer la boîte de dialogue.Mot de passe Fonction avec le modèle de vue, et avoir le modèle de vue l'invoquer lors de l'exécution du code de connexion.

Ce ne s'entend d'une ligne de code dans la vue de code-behind.

donc, dans mon Login.xaml j'ai

<PasswordBox x:Name="PasswordBox"/>

et dans Login.XAML.cs j'ai

LoginViewModel.PasswordHandler = () => PasswordBox.Password;

puis dans LoginViewModel.cs j'ai le PasswordHandler défini

public Func<string> PasswordHandler { get; set; }

et quand le login doit se produire le code invoque le gestionnaire pour obtenir le mot de passe de la vue...

bool loginResult = Login(Username, PasswordHandler());

de cette façon, quand je veux tester le viewmodel je peux simplement mettre PasswordHandler à un méthode anonyme qui me permet de délivrer le mot de passe que je veux utiliser dans le test.

5
répondu mike mckechnie 2014-10-16 13:07:57

j'ai passé beaucoup de temps à étudier diverses solutions. Je n'ai pas aimé l'idée des décorateurs, les comportements perturbent l'interface de validation, le code derrière... vraiment?

le meilleur encore est de s'en tenir à une propriété personnalisée attachée et de se lier à votre propriété SecureString dans votre modèle de vue. Garder là pour aussi longtemps que vous le pouvez. Chaque fois que vous aurez besoin d'un accès rapide au mot de passe simple, convertissez-le temporairement en une chaîne non sécurisée en utilisant le code ci-dessous:

namespace Namespace.Extensions
{
    using System;
    using System.Runtime.InteropServices;
    using System.Security;

    /// <summary>
    /// Provides unsafe temporary operations on secured strings.
    /// </summary>
    [SuppressUnmanagedCodeSecurity]
    public static class SecureStringExtensions
    {
        /// <summary>
        /// Converts a secured string to an unsecured string.
        /// </summary>
        public static string ToUnsecuredString(this SecureString secureString)
        {
            // copy&paste from the internal System.Net.UnsafeNclNativeMethods
            IntPtr bstrPtr = IntPtr.Zero;
            if (secureString != null)
            {
                if (secureString.Length != 0)
                {
                    try
                    {
                        bstrPtr = Marshal.SecureStringToBSTR(secureString);
                        return Marshal.PtrToStringBSTR(bstrPtr);
                    }
                    finally
                    {
                        if (bstrPtr != IntPtr.Zero)
                            Marshal.ZeroFreeBSTR(bstrPtr);
                    }
                }
            }
            return string.Empty;
        }

        /// <summary>
        /// Copies the existing instance of a secure string into the destination, clearing the destination beforehand.
        /// </summary>
        public static void CopyInto(this SecureString source, SecureString destination)
        {
            destination.Clear();
            foreach (var chr in source.ToUnsecuredString())
            {
                destination.AppendChar(chr);
            }
        }

        /// <summary>
        /// Converts an unsecured string to a secured string.
        /// </summary>
        public static SecureString ToSecuredString(this string plainString)
        {
            if (string.IsNullOrEmpty(plainString))
            {
                return new SecureString();
            }

            SecureString secure = new SecureString();
            foreach (char c in plainString)
            {
                secure.AppendChar(c);
            }
            return secure;
        }
    }
}

assurez-vous que vous permettez au GC de recueillir votre élément UI, donc résistez à l'envie d'utiliser un gestionnaire d'événements statique pour l'événement PasswordChanged sur le PasswordBox . J'ai aussi découvert une anomalie où la commande ne mettait pas à jour L'interface en utilisant la propriété SecurePassword pour la configurer, raison pour laquelle je copie le mot de passe dans Password à la place.

namespace Namespace.Controls
{
    using System.Security;
    using System.Windows;
    using System.Windows.Controls;
    using Namespace.Extensions;

    /// <summary>
    /// Creates a bindable attached property for the <see cref="PasswordBox.SecurePassword"/> property.
    /// </summary>
    public static class PasswordBoxHelper
    {
        // an attached behavior won't work due to view model validation not picking up the right control to adorn
        public static readonly DependencyProperty SecurePasswordBindingProperty = DependencyProperty.RegisterAttached(
            "SecurePassword",
            typeof(SecureString),
            typeof(PasswordBoxHelper),
            new FrameworkPropertyMetadata(new SecureString(),FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, AttachedPropertyValueChanged)
        );

        private static readonly DependencyProperty _passwordBindingMarshallerProperty = DependencyProperty.RegisterAttached(
            "PasswordBindingMarshaller",
            typeof(PasswordBindingMarshaller),
            typeof(PasswordBoxHelper),
            new PropertyMetadata()
        );

        public static void SetSecurePassword(PasswordBox element, SecureString secureString)
        {
            element.SetValue(SecurePasswordBindingProperty, secureString);
        }

        public static SecureString GetSecurePassword(PasswordBox element)
        {
            return element.GetValue(SecurePasswordBindingProperty) as SecureString;
        }

        private static void AttachedPropertyValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            // we'll need to hook up to one of the element's events
            // in order to allow the GC to collect the control, we'll wrap the event handler inside an object living in an attached property
            // don't be tempted to use the Unloaded event as that will be fired  even when the control is still alive and well (e.g. switching tabs in a tab control) 
            var passwordBox = (PasswordBox)d;
            var bindingMarshaller = passwordBox.GetValue(_passwordBindingMarshallerProperty) as PasswordBindingMarshaller;
            if (bindingMarshaller == null)
            {
                bindingMarshaller = new PasswordBindingMarshaller(passwordBox);
                passwordBox.SetValue(_passwordBindingMarshallerProperty, bindingMarshaller);
            }

            bindingMarshaller.UpdatePasswordBox(e.NewValue as SecureString);
        }

        /// <summary>
        /// Encapsulated event logic
        /// </summary>
        private class PasswordBindingMarshaller
        {
            private readonly PasswordBox _passwordBox;
            private bool _isMarshalling;

            public PasswordBindingMarshaller(PasswordBox passwordBox)
            {
                _passwordBox = passwordBox;
                _passwordBox.PasswordChanged += this.PasswordBoxPasswordChanged;
            }

            public void UpdatePasswordBox(SecureString newPassword)
            {
                if (_isMarshalling)
                {
                    return;
                }

                _isMarshalling = true;
                try
                {
                    // setting up the SecuredPassword won't trigger a visual update so we'll have to use the Password property
                    _passwordBox.Password = newPassword.ToUnsecuredString();

                    // you may try the statement below, however the benefits are minimal security wise (you still have to extract the unsecured password for copying)
                    //newPassword.CopyInto(_passwordBox.SecurePassword);
                }
                finally
                {
                    _isMarshalling = false;
                }
            }

            private void PasswordBoxPasswordChanged(object sender, RoutedEventArgs e)
            {
                // copy the password into the attached property
                if (_isMarshalling)
                {
                    return;
                }

                _isMarshalling = true;
                try
                {
                    SetSecurePassword(_passwordBox, _passwordBox.SecurePassword.Copy());
                }
                finally
                {
                    _isMarshalling = false;
                }
            }
        }
    }
}

et l'usage XAML:

<PasswordBox controls:PasswordBoxHelper.SecurePassword="{Binding LogonPassword, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}">

ma propriété dans le modèle de vue ressemblait à ceci:

[RequiredSecureString]
public SecureString LogonPassword
{
   get
   {
       return _logonPassword;
   }
   set
   {
       _logonPassword = value;
       NotifyPropertyChanged(nameof(LogonPassword));
   }
}

le RequiredSecureString est juste un validateur personnalisé simple qui a la logique suivante:

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]    
public class RequiredSecureStringAttribute:ValidationAttribute
{
    public RequiredSecureStringAttribute()
        :base("Field is required")
    {            
    }

    public override bool IsValid(object value)
    {
        return (value as SecureString)?.Length > 0;
    }
}

voilà. Une solution complète et testée de MVVM pure.

4
répondu MoonStom 2016-02-18 06:37:21

j'ai utilisé cette méthode et j'ai passé la boîte de mot de passe, bien que cela ne viole pas le MVVM il était essentiel pour moi parce que j'utilisais un contrôle de contenu avec le modèle de données pour mon login dans mon shell qui est un environnement de shell complexe. Donc accéder au code derrière le shell aurait été nul.

passer la boîte de transmission, je pense que c'est comme accéder au contrôle depuis le code derrière aussi loin que je sache. Je suis d'accord des mots de passe, ne pas garder en mémoire, etc Dans ce la mise en œuvre je n'ai pas de propriété pour le mot de passe dans le modèle de vue.

Commande Bouton

Command="{Binding Path=DataContext.LoginCommand, ElementName=MyShell}" CommandParameter="{Binding ElementName=PasswordBox}"

ViewModel

private void Login(object parameter)
{
    System.Windows.Controls.PasswordBox p = (System.Windows.Controls.PasswordBox)parameter;
    MessageBox.Show(p.Password);
}
3
répondu Legz 2011-07-30 10:52:16

j'ai pensé jeter ma solution dans le mix, puisque c'est une question si commune... et d'avoir beaucoup d'options est toujours une bonne chose.

j'ai simplement enveloppé un PasswordBox dans un UserControl et mis en place un DependencyProperty pour pouvoir le lier. Je fais tout ce que je peux pour éviter de stocker n'importe quel texte clair dans la mémoire, donc tout est fait par un SecureString et le PasswordBox.Password propriété. Pendant la boucle foreach , chaque personnage obtient exposé, mais elle est très brève. Honnêtement, si vous craignez que votre application WPF soit compromise par cette brève exposition, vous avez de plus gros problèmes de sécurité qui devraient être traités.

la beauté de ceci est que vous n'enfreignez aucune règle MVVM, même les" puristes", puisque c'est un UserControl , donc il est permis d'avoir code-behind. Lorsque vous l'utilisez, vous pouvez avoir une communication pure entre View et ViewModel sans votre VideModel connaître toute partie de View ou la source du mot de passe. Assurez-vous que vous êtes lié à SecureString dans votre ViewModel .

BindablePasswordBox.xaml

<UserControl x:Class="BK.WPF.CustomControls.BindanblePasswordBox"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" d:DesignHeight="22" d:DesignWidth="150">
    <PasswordBox x:Name="PswdBox"/>
</UserControl>

BindablePasswordBox.XAML.cs (Version 1 - Pas de deux-chemin en charge de la liaison.)

using System.ComponentModel;
using System.Security;
using System.Windows;
using System.Windows.Controls;

namespace BK.WPF.CustomControls
{
    public partial class BindanblePasswordBox : UserControl
    {
        public static readonly DependencyProperty PasswordProperty =
            DependencyProperty.Register("Password", typeof(SecureString), typeof(BindanblePasswordBox));

        public SecureString Password
        {
            get { return (SecureString)GetValue(PasswordProperty); }
            set { SetValue(PasswordProperty, value); }
        }

        public BindanblePasswordBox()
        {
            InitializeComponent();
            PswdBox.PasswordChanged += PswdBox_PasswordChanged;
        }

        private void PswdBox_PasswordChanged(object sender, RoutedEventArgs e)
        {
            var secure = new SecureString();
            foreach (var c in PswdBox.Password)
            {
                secure.AppendChar(c);
            }
            Password = secure;
        }
    }
}

utilisation de la Version 1:

<local:BindanblePasswordBox Width="150" HorizontalAlignment="Center"
                            VerticalAlignment="Center"
                            Password="{Binding Password, Mode=OneWayToSource}"/>

BindablePasswordBox.XAML.cs (Version 2 - a le soutien de liaison bidirectionnel.)

public partial class BindablePasswordBox : UserControl
{
    public static readonly DependencyProperty PasswordProperty =
        DependencyProperty.Register("Password", typeof(SecureString), typeof(BindablePasswordBox),
        new PropertyMetadata(PasswordChanged));

    public SecureString Password
    {
        get { return (SecureString)GetValue(PasswordProperty); }
        set { SetValue(PasswordProperty, value); }
    }

    public BindablePasswordBox()
    {
        InitializeComponent();
        PswdBox.PasswordChanged += PswdBox_PasswordChanged;
    }

    private void PswdBox_PasswordChanged(object sender, RoutedEventArgs e)
    {
        var secure = new SecureString();
        foreach (var c in PswdBox.Password)
        {
            secure.AppendChar(c);
        }
        if (Password != secure)
        {
            Password = secure;
        }
    }

    private static void PasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var pswdBox = d as BindablePasswordBox;
        if (pswdBox != null && e.NewValue != e.OldValue)
        {
            var newValue = e.NewValue as SecureString;
            if (newValue == null)
            {
                return;
            }

            var unmanagedString = IntPtr.Zero;
            string newString;
            try
            {
                unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(newValue);
                newString = Marshal.PtrToStringUni(unmanagedString);
            }
            finally
            {
                Marshal.ZeroFreeGlobalAllocUnicode(unmanagedString);
            }

            var currentValue = pswdBox.PswdBox.Password;
            if (currentValue != newString)
            {
                pswdBox.PswdBox.Password = newString;
            }
        }
    }
}

Usage de la Version 2:

<local:BindanblePasswordBox Width="150" HorizontalAlignment="Center"
                            VerticalAlignment="Center"
                            Password="{Binding Password, Mode=TwoWay}"/>
3
répondu B.K. 2015-09-27 22:13:52

vous pouvez le faire avec la propriété attachée, le voir.. PasswordBox avec MVVM

2
répondu Rangel 2017-05-23 12:34:47

comme vous pouvez le voir, je suis lié par un mot de passe, mais peut-être qu'il le lie à la classe statique..

C'est un attaché à la propriété . Ce type de propriété peut être appliqué à n'importe quelle sorte de DependencyObject , pas seulement le type dans lequel il est déclaré. Donc, même si elle est déclarée dans la classe statique PasswordHelper , elle est appliquée à la PasswordBox sur laquelle vous l'utilisez.

Pour utiliser ce joint propriété, vous avez juste besoin de le lier à la propriété Password dans votre ViewModel:

<PasswordBox w:PasswordHelper.Attach="True" 
         w:PasswordHelper.Password="{Binding Password}"/>
1
répondu Thomas Levesque 2009-09-27 16:45:54

comme mentionné ci-dessus VM ne devrait pas être au courant de la vue, mais passer la boîte de passe entière ressemble à l'approche la plus simple. Donc peut-être qu'au lieu de passer le paramètre passé à PasswordBox, utilisez la réflexion pour en extraire la propriété Password. Dans ce cas VM s'attend à une sorte de conteneur de mot de passe avec le mot de passe de propriété (je suis Ussing RelayCommands de Mvmm Light-Toolkit):

public RelayCommand<object> SignIn
{
    get
    {
        if (this.signIn == null)
        {
            this.signIn = new RelayCommand<object>((passwordContainer) => 
                {
                    var password = passwordContainer.GetType().GetProperty("Password").GetValue(passwordContainer) as string;
                    this.authenticationService.Authenticate(this.Login, password);
                });
        }

        return this.signIn;
    }
}

Il peut être facile de tester la classe anonyme:

var passwordContainer = new
    {
        Password = "password"
    };
1
répondu mokula 2013-11-03 09:09:12

pour moi, ces deux choses se sentent mal:

  • la mise en Œuvre de mot de passe en clair "propriétés de la 151990920"
  • envoyant le PasswordBox comme paramètre de commande au ViewModel

transférer le mot-clé SecurePassword (SecureString instance) tel que décrit par Steve in CO semble acceptable. Je préfère Behaviors pour coder derrière, et j'ai également eu l'exigence supplémentaire d'être en mesure de réinitialiser le mot de passe à partir de ce dernier.

Xaml ( Password est la propriété ViewModel):

<PasswordBox>
    <i:Interaction.Behaviors>
        <behaviors:PasswordBinding BoundPassword="{Binding Password, Mode=TwoWay}" />
    </i:Interaction.Behaviors>
</PasswordBox>

Comportement:

using System.Security;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;

namespace Evidence.OutlookIntegration.AddinLogic.Behaviors
{
    /// <summary>
    /// Intermediate class that handles password box binding (which is not possible directly).
    /// </summary>
    public class PasswordBoxBindingBehavior : Behavior<PasswordBox>
    {
        // BoundPassword
        public SecureString BoundPassword { get { return (SecureString)GetValue(BoundPasswordProperty); } set { SetValue(BoundPasswordProperty, value); } }
        public static readonly DependencyProperty BoundPasswordProperty = DependencyProperty.Register("BoundPassword", typeof(SecureString), typeof(PasswordBoxBindingBehavior), new FrameworkPropertyMetadata(OnBoundPasswordChanged));

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

        /// <summary>
        /// Link up the intermediate SecureString (BoundPassword) to the UI instance
        /// </summary>
        private void AssociatedObjectOnPasswordChanged(object s, RoutedEventArgs e)
        {
            this.BoundPassword = this.AssociatedObject.SecurePassword;
        }

        /// <summary>
        /// Reacts to password reset on viewmodel (ViewModel.Password = new SecureString())
        /// </summary>
        private static void OnBoundPasswordChanged(object s, DependencyPropertyChangedEventArgs e)
        {
            var box = ((PasswordBoxBindingBehavior)s).AssociatedObject;
            if (box != null)
            {
                if (((SecureString)e.NewValue).Length == 0)
                    box.Password = string.Empty;
            }
        }

    }
}
1
répondu Mike Fuchs 2017-05-23 12:02:46

dans windows universal app

vous pouvez utiliser ce code avec la propriété "Password" et lier avec le modelView

 <PasswordBox x:Uid="PasswordBox" Password="{Binding Waiter.Password, Mode=TwoWay}" Name="txtPassword" HorizontalAlignment="Stretch" Margin="50,200,50,0" VerticalAlignment="Top"/>
1
répondu Baz08 2016-04-12 09:09:34

C'est très simple . Créez une autre propriété pour le mot de passe et liez-la avec la boîte de texte

mais toutes les opérations d'entrée se produisent avec la propriété de mot de passe réelle

private string _Password;

    public string PasswordChar
    {
        get
        {
            string szChar = "";

            foreach(char szCahr in _Password)
            {
                szChar = szChar + "*";
            }

            return szChar;
        }

        set
        {
            _PasswordChar = value; NotifyPropertyChanged();
        }
    }

public string Mot de passe { obtenir { retour _Password; }

        set
        {
            _Password = value; NotifyPropertyChanged();
            PasswordChar = _Password;
        }
    }

1
répondu Niji 2016-09-26 13:01:09

pour quiconque est conscient des risques que cette implémentation impose, pour avoir le mot de passe synchronisé sur votre modèle de vue, il suffit d'ajouter Mode=OneWayToSource .

XAML

<PasswordBox
    ff:PasswordHelper.Attach="True"
    ff:PasswordHelper.Password="{Binding Path=Password, Mode=OneWayToSource}" />
1
répondu Kevin 2017-07-06 06:50:55

vous trouvez une solution pour le PasswordBox dans L'application D'échantillon de ViewModel du projet WPF Application Framework (WAF) .

cependant, Justin a raison. Ne passez pas le mot de passe en texte simple entre View et ViewModel. Utilisez SecureString à la place (voir MSDN PasswordBox).

0
répondu jbe 2009-10-02 17:58:38

j'ai fait comme:

XAML:

<PasswordBox x:Name="NewPassword" PasswordChanged="NewPassword_PasswordChanged"/>
<!--change tablenameViewSource: yours!-->
<Grid DataContext="{StaticResource tablenameViewSource}" Visibility="Hidden">
        <TextBox x:Name="Password" Text="{Binding password, Mode=TwoWay}"/>
</Grid>

c#:

private void NewPassword_PasswordChanged(object sender, RoutedEventArgs e)
    {
        try
        {
           //change tablenameDataTable: yours! and tablenameViewSource: yours!
           tablenameDataTable.Rows[tablenameViewSource.View.CurrentPosition]["password"] = NewPassword.Password;
        }
        catch
        {
            this.Password.Text = this.NewPassword.Password;
        }
    }

ça me va!

0
répondu José Roberto Cuello Alcaraz 2013-10-15 20:23:10

j'ai utilisé une vérification d'authentification suivie d'un sub appelé par une classe mediator à la vue (qui implémente également une vérification d'authentification) pour écrire le mot de passe à la classe data.

ce n'est pas une solution parfaite; cependant, il a remédié à mon problème de ne pas être en mesure de déplacer le mot de passe.

0
répondu Miles 2014-05-22 15:12:38

j'utilise une solution MVVM-friendly succincte qui n'a pas encore été mentionnée. Tout d'abord, je nomme la boîte de passe dans XAML:

<PasswordBox x:Name="Password" />

puis j'ajoute un appel de méthode simple dans le constructeur de vue:

public LoginWindow()
{
    InitializeComponent();
    ExposeControl<LoginViewModel>.Expose(this, view => view.Password,
        (model, box) => model.SetPasswordBox(box));
}

Et c'est tout. Le modèle de vue recevra une notification quand il est attaché à une vue via DataContext et une autre notification quand il est détaché. Le contenu de cette notification est configurable via les lambdas, juste un setter ou une méthode font appel au modèle de vue, en passant le contrôle problématique comme paramètre.

il peut être rendu MVVM-friendly très facilement en ayant l'interface View exposure au lieu de contrôles enfants.

le code ci-dessus s'appuie sur helper class publié sur mon blog.

0
répondu Robert Važan 2014-07-16 17:54:22

j'ai passé des années à essayer de faire marcher ça. Finalement, j'ai abandonné et j'ai utilisé le PasswordBoxEdit de DevExpress.

c'est la solution la plus simple de tous les temps, car elle permet de lier sans tirer des trucs horribles.

Solution sur le site de DevExpress

pour info, Je ne suis pas affilié à DevExpress.

0
répondu Contango 2014-10-28 16:10:54

<UserControl x:Class="Elections.Server.Handler.Views.LoginView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
             xmlns:cal="http://www.caliburnproject.org"
             mc:Ignorable="d" 
             Height="531" Width="1096">
    <ContentControl>
        <ContentControl.Background>
            <ImageBrush/>
        </ContentControl.Background>
        <Grid >
            <Border BorderBrush="#FFABADB3" BorderThickness="1" HorizontalAlignment="Left" Height="23" Margin="900,100,0,0" VerticalAlignment="Top" Width="160">
                <TextBox TextWrapping="Wrap"/>
            </Border>
            <Border BorderBrush="#FFABADB3" BorderThickness="1" HorizontalAlignment="Left" Height="23" Margin="900,150,0,0" VerticalAlignment="Top" Width="160">
                <PasswordBox x:Name="PasswordBox"/>
            </Border>
            <Button Content="Login" HorizontalAlignment="Left" Margin="985,200,0,0" VerticalAlignment="Top" Width="75">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <cal:ActionMessage MethodName="Login">
                            <cal:Parameter Value="{Binding ElementName=PasswordBox}" />
                        </cal:ActionMessage>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </Button>

        </Grid>
    </ContentControl>
</UserControl>

using System;
using System.Windows;
using System.Windows.Controls;
using Caliburn.Micro;

namespace Elections.Server.Handler.ViewModels
{
    public class LoginViewModel : PropertyChangedBase
    {
        MainViewModel _mainViewModel;
        public void SetMain(MainViewModel mainViewModel)
        {
            _mainViewModel = mainViewModel;
        }

        public void Login(Object password)
        {
            var pass = (PasswordBox) password;
            MessageBox.Show(pass.Password);

            //_mainViewModel.ScreenView = _mainViewModel.ControlPanelView;
            //_mainViewModel.TitleWindow = "Panel de Control";
            //HandlerBootstrapper.Title(_mainViewModel.TitleWindow);
        }
    }
}

;) facile!

0
répondu Hector Lobo 2016-07-28 19:49:03

Eh bien ma réponse est plus simple juste dans le modèle pour le MVVM

dans la classe viewmodel

public string password;

PasswordChangedCommand = new DelegateCommand<RoutedEventArgs>(PasswordChanged);

Private void PasswordChanged(RoutedEventArgs obj)

{

    var e = (WatermarkPasswordBox)obj.OriginalSource;

    //or depending or what are you using

    var e = (PasswordBox)obj.OriginalSource;

    password =e.Password;

}

la propriété password du PasswordBox que win fournit ou WatermarkPasswordBox que XCeedtoolkit fournit génère un RoutedEventArgs de sorte que vous pouvez le lier.

maintenant, en xmal vue

<Xceed:WatermarkPasswordBox Watermark="Input your Password" Grid.Column="1" Grid.ColumnSpan="3" Grid.Row="7" PasswordChar="*" >

        <i:Interaction.Triggers>

            <i:EventTrigger EventName="PasswordChanged">

                <prism:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path= DataContext.PasswordChangedCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path= Password}"/>

            </i:EventTrigger>

        </i:Interaction.Triggers>

    </Xceed:WatermarkPasswordBox>

ou

<PasswordBox Grid.Column="1" Grid.ColumnSpan="3" Grid.Row="7" PasswordChar="*" >

        <i:Interaction.Triggers>

            <i:EventTrigger EventName="PasswordChanged">

                <prism:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path= DataContext.PasswordChangedCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path= Password}"/>

            </i:EventTrigger>

        </i:Interaction.Triggers>

    </PasswordBox>
0
répondu carlos rodriguez 2017-07-31 18:17:58

Si vous voulez, il a combiné le tout dans un seul contrôle et une commande

<PasswordBox Name="PasswordBoxPin" PasswordChar="*">
    <PasswordBox.InputBindings>
        <KeyBinding Key="Return" Command="{Binding AuthentifyEmpCommand}" CommandParameter="{Binding ElementName=PasswordBoxPin}"/>
    </PasswordBox.InputBindings>
</PasswordBox>

et sur votre Vm (comme Konamiman a montré)

public void AuthentifyEmp(object obj)
{
    var passwordBox = obj as PasswordBox;
    var password = passwordBox.Password;
}
private RelayCommand _authentifyEmpCommand;
public RelayCommand AuthentifyEmpCommand => _authentifyEmpCommand ?? (_authentifyEmpCommand = new RelayCommand(AuthentifyEmp, null));
0
répondu Cristian G 2018-01-25 16:47:40

Voici mon point de vue:

  1. L'utilisation d'une propriété jointe pour lier le mot de passe va à l'encontre de l'objectif de sécurisation du mot de passe. La propriété mot de passe d'une boîte de mot de passe n'est pas liable pour une raison.

  2. en passant la boîte de mots de passe comme paramètre de commande, le ViewModel sera conscient de la commande. Cela ne fonctionnera pas si vous prévoyez de rendre votre plateforme de visualisation réutilisable. Ne pas informez votre VM de votre vue ou de tout autre contrôle.

  3. Je ne pense pas que l'introduction d'une nouvelle propriété, d'une interface, de l'abonnement à des événements modifiés par mot de passe ou d'autres choses compliquées est nécessaire pour une simple tâche de fournir le mot de passe.

XAML

<PasswordBox x:Name="pbPassword" />
<Button Content="Login" Command="{Binding LoginCommand}" x:Name="btnLogin"/>

code derrière - utiliser le code derrière ne viole pas nécessairement MVVM. Tant que vous ne mettez pas toute la logique métier.

btnLogin.CommandParameter = new Func<string>(()=>pbPassword.Password); 

ViewModel

LoginCommand = new RelayCommand<Func<string>>(getpwd=> { service.Login(username, getpwd()); });
0
répondu Lance 2018-08-22 14:22:02

pour les débutants complets comme moi, voici un échantillon complet de ce que Konamiman suggéré ci-dessus. Merci Konamiman .

XAML

    <PasswordBox x:Name="textBoxPassword"/>
    <Button x:Name="buttonLogin" Content="Login"
            Command="{Binding PasswordCommand}"
            CommandParameter="{Binding ElementName=textBoxPassword}"/> 

ViewModel

public class YourViewModel : ViewModelBase
{
    private ICommand _passwordCommand;
    public ICommand PasswordCommand
    {
        get {
            if (_passwordCommand == null) {
                _passwordCommand = new RelayCommand<object>(PasswordClick);
            }
            return _passwordCommand;
        }
    }

    public YourViewModel()
    {
    }

    private void PasswordClick(object p)
    {
        var password = p as PasswordBox;
        Console.WriteLine("Password is: {0}", password.Password);
    }
}
0
répondu fs_tigre 2018-09-07 10:59:42