Comment se lier à un PasswordBox dans MVVM

251

Je suis venu à travers un problème avec la liaison à un P asswordBox. Il semble que ce soit un risque pour la 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

Techniquement, il a fière allure, mais je ne sais pas comment récupérer le mot de passe.

J'ai essentiellement des propriétés dans mon LoginViewModelpour Usernameet Password. Usernameest bien et fonctionne comme c'est 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 je l' ai eu PasswordBoxcomme TextBoxet Binding Path=Passwordpuis la propriété dans mon LoginViewModelété mis à jour.

Mon code est très simple, en gros j'ai un Commandpour moi Button. Lorsque j'appuie, il CanLoginest appelé et s'il renvoie vrai, il appelle Login.
Vous pouvez voir que je vérifie ma propriété Usernameici, ce qui fonctionne très bien.

Dans Loginj'envoie à mon service un Usernameet Password, Usernamecontient des données de mon Viewmais PasswordestNull|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 ViewModelle Passwordest vide.

Suis-je en train de faire quelque chose de mal ou de manquer une étape?

J'ai mis un point d'arrêt et bien sûr le code entre dans la classe d'assistance statique mais il ne met jamais à jour mon Passworddans mon ViewModel.

Mark Smith
la source
3
Eh bien, il s'avère que le code n'a pas fonctionné, mais j'ai essayé un code alternatif ici et cela fonctionne parfaitement. blog.functionalfun.net/2008/06/…
mark smith
5
Le fait de passer tout le contrôle de la boîte de mots de passe ne va-t-il pas à l'encontre de la séparation de la vue du modèle de vue?

Réponses:

164

Désolé, mais vous vous trompez.

Les gens devraient se faire tatouer les lignes directrices de sécurité suivantes à l'intérieur de leurs paupières:
Ne conservez jamais de mots de passe en texte brut en mémoire.

La raison pour laquelle le WPF / Silverlight PasswordBoxn'expose pas de DP pour la Passwordpropriété est liée à la sécurité.
Si WPF / Silverlight devait conserver un DP car Passwordil faudrait que le framework garde le mot de passe lui-même non chiffré en mémoire. Ce qui est considéré comme un vecteur d'attaque de sécurité assez gênant. Le PasswordBoxutilise la mémoire cryptée (en quelque sorte) et la seule façon d'accéder au mot de passe est via la propriété CLR.

Je suggérerais que lors de l'accès à la PasswordBox.Passwordpropriété CLR, vous vous absteniez de la placer dans une variable ou en tant que valeur pour une propriété.
Garder votre mot de passe en texte brut sur la RAM de l'ordinateur client est un non-non de sécurité.
Alors débarrassez- public string Password { get; set; }vous de ce que vous avez là-haut.

Lors de l'accès PasswordBox.Password, sortez-le et expédiez-le au serveur dès que possible. Ne conservez pas la valeur du mot de passe et ne le traitez pas comme vous le feriez pour tout autre texte de machine client. Ne conservez pas de mots de passe en texte clair en mémoire.

Je sais que cela rompt le modèle MVVM, mais vous ne devriez jamais vous lier à PasswordBox.PasswordAttached DP, stocker votre mot de passe dans le ViewModel ou tout autre manigance similaire.

Si vous recherchez une solution sur-architecturée, en voici une:
1. Créez l' IHavePasswordinterface avec une méthode qui renvoie le texte clair du mot de passe.
2. Demandez à votre UserControlimplémenter une IHavePasswordinterface.
3. Enregistrez l' UserControlinstance auprès de votre IoC comme implémentant leIHavePassword interface.
4. Lorsqu'une demande de serveur nécessitant votre mot de passe est en cours, appelez votre IoC pour la IHavePasswordmise en œuvre et obtenez uniquement le mot de passe tant convoité.

Juste mon point de vue.

-- Justin

JustinAngel
la source
19
N'avez-vous pas pu utiliser SecureString dans la machine virtuelle pour WPF pour résoudre ce problème? Il ne semble pas qu'il y ait quelque chose pour Silverlight.
Bryant
35
Je suis d'accord avec votre intention et le message que vous véhiculez, mais votre réponse implique que la chaîne de mot de passe n'est jamais en mémoire si vous suivez cette approche. La valeur du mot de passe sera en mémoire à partir du moment où l'utilisateur le saisit. L'élimination de la propriété contenant votre phrase secrète est une bonne idée et limitera les copies de votre mot de passe qui restent à la traîne pour le ramasse-miettes ou qui pourraient peut-être être trouvées par un autre code géré et non géré exécuté dans le cadre de votre programme, mais pas le cacher complètement.
IanNorton
182
Dans la plupart des cas, vous n'avez pas besoin de ce niveau de sécurité. Quel est l'intérêt de rendre cette chose difficile quand il y a tant d'autres façons de voler des mots de passe? Au moins WPF aurait dû autoriser l'utilisation de SecureString comme @Bryant l'a dit.
chakrit
336
Si les méchants ont accès à la RAM de votre machine, vous avez de plus gros problèmes qu'ils volent votre mot de passe.
Cameron MacFarland
13
Depuis des années, j'utilise un contrôle utilisateur personnalisé qui se comporte exactement comme PasswordBox, mais ne renvoie que la valeur du texte en tant que SecureString. Oui, cela empêche Snoop d'afficher le mot de passe en texte brut. Cependant, la valeur en texte brut de SecureString peut toujours être extraite assez facilement et ne dissuade que les hacks novices. Si votre système risque d'utiliser secrètement des enregistreurs de frappe et des renifleurs comme Snoop, vous devez réévaluer la sécurité de votre système.
Mike Christian
199

Mes 2 cents:

J'ai développé une fois une boîte de dialogue de connexion typique (zones utilisateur et mot de passe, plus le bouton "Ok") en utilisant WPF et MVVM. J'ai résolu le problème de liaison de mot de passe en passant simplement le contrôle PasswordBox lui-même comme paramètre à la commande attachée au bouton "Ok". Donc, selon moi, j'avais:

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

Et dans le ViewModel, la Executeméthode de la commande 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
}

Cela viole légèrement le modèle MVVM puisque maintenant le ViewModel sait quelque chose sur la façon dont la vue est implémentée, mais dans ce projet particulier, je pouvais me le permettre. J'espère que c'est utile pour quelqu'un aussi.

Konamiman
la source
Bonjour Konamiman, lorsque la méthode Execute est appelée.Dans mon viewmodel, j'ai une classe User (login, pass) et une commande authenticate.Comment puis-je utiliser Execute dans ce contexte?
3
très utile, merci. pour info, quelqu'un pourrait être habitué à voir quelque chose comme _loginCommand = new RelayCommand (param => Login (UserName, (PasswordBox) param), param => CanLogIn);
Chuck Rostance
5
ceci est une solution correcte mais échoue pour quelque chose comme un combo mot de passe + confirmation de mot de passe
Julien
Bonjour Konamiman, j'utilise votre solution mais elle ne fonctionne pas sur l'application Windows 8.1 Store. J'ai posé cette question: stackoverflow.com/questions/26221594/…
VansFannel
2
Merci pour cela! Cela a résolu un énorme problème que j'avais avec le déplacement des données du thread d'interface utilisateur vers le thread principal du programme. Assurez-vous de mettre en œuvre l'approche SecureString et ~ supprimez le mot de passe dès que possible ~. Jette le. Jetez-le. Efface ça. Faites ce que vous devez faire. Assurez-vous également d'implémenter IDisposable.
Steven C. Britton
184

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

Cette méthode ne viole pas le modèle MVVM et maintient une sécurité complète. Oui, techniquement, c'est du code, mais ce n'est rien de plus qu'une liaison "cas spécial". Le ViewModel n'a toujours aucune connaissance de l'implémentation de View, ce qui, à mon avis, le fait si vous essayez de transmettre le PasswordBox au ViewModel.

Code Behind! = Violation automatique de MVVM. Tout dépend de ce que vous en faites. Dans ce cas, nous codons juste manuellement une liaison, donc tout cela fait partie de l'implémentation de l'interface utilisateur et est donc correct.

Dans le ViewModel, juste une propriété simple. Je l'ai fait "écrire seulement" car il ne devrait pas être nécessaire de le récupérer de l'extérieur du ViewModel pour une raison quelconque, mais ce n'est pas obligatoire. Notez qu'il s'agit d'une SecureString, pas seulement d'une chaîne.

public SecureString SecurePassword { private get; set; }

Dans le xaml, vous configurez un gestionnaire d'événements PasswordChanged.

<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 à tout moment dans une SecureString et offre donc une sécurité maximale. Si vous ne vous souciez vraiment pas de la sécurité ou si vous avez besoin du mot de passe en texte clair pour une méthode en aval qui l'exige (remarque: la plupart des méthodes .NET qui nécessitent un mot de passe prennent également en charge une option SecureString, donc vous n'aurez peut-être pas vraiment besoin d'un mot de passe en texte clair même si vous pensez le faire), vous pouvez simplement utiliser la propriété Password à la place. Comme ça:

(Propriété 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 vouliez garder les choses fortement tapées, vous pouvez remplacer la distribution (dynamique) par l'interface de votre ViewModel. Mais vraiment, les liaisons de données "normales" ne sont pas non plus fortement typées, donc ce n'est pas si grave.

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 View est autonome sans aucune référence externe requise.

Steve In CO
la source
1
Celui-ci me semble bien! Si vous vouliez être super strict du côté de la sécurité, je ne suis pas sûr que cela le couperait, mais pour moi, c'est un parfait compromis. Merci!
jrich523
3
Merci pour l'aspect pratique du dogme rigide sur MVVM et la paranoïa. Fonctionne très bien, merci.
Bruce Pierson
2
L'exemple SecureString serait parfait avec cette extension blogs.msdn.com/b/fpintos/archive/2009/06/12/…
Ayman
1
Bien en effet. Je souhaite que MS vient d'ajouter un mot de passe DP de type SecureString à ce contrôle.
Keith Hill du
1
C'est la réponse parfaite, car elle conserve la sécurité et MVVM.
LoRdPMN
20

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 méthode d'exécution de commande:

private void ExecutePasswordChangedCommand(PasswordBox obj)
{ 
   if (obj != null)
     Password = obj.Password;
}
Sergey
la source
3
FYIxmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
XAMlMAX
Sans exiger de nommer le PasswordBox: CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=PasswordBox}}"(remarque: non RelativeSource Self ).
wondra
Cette solution viole le modèle MVVM.
BionicCode
13

Cela fonctionne très bien pour moi.

<Button Command="{Binding Connect}" 
        CommandParameter="{Binding ElementName=MyPasswordBox}"/>
Vladislav Borovikov
la source
3
Qu'en est-il de CommandParameter = "{Binding ElementName = MyPasswordBox, Path = SecurePassword"}?
LukeN
2
LukeN, cela ne fonctionne pas (du moins pour moi). Probablement pour la même raison - SecurePassword n'est pas une propriété de dépendance.
vkrzv
En supposant que le ICommandsoit implémenté dans le modèle de vue, cette solution violerait le modèle MVVM.
BionicCode
9

Une solution simple sans violer le modèle MVVM consiste à introduire un événement (ou délégué) dans le ViewModel 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 vue , 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 , lorsque vous avez besoin du mot de passe, vous pouvez déclencher l'événement et récupérer le mot de passe à partir de là:

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

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

LoginHelpers.Login(Username, pwargs.Password);
Jan Willem B
la source
La seule chose qui vous manque, c'est que lorsque vous abonnez une vue à un événement de modèle de vue, vous devez utiliser un WeakEventManager<TEventSource, TEventArgs>pour éviter les fuites de mémoire. Souvent, la vue n'a pas la même durée de vie que le modèle de vue. WeakEventManager<IViewModel, EventArgs>.AddHandler(iViewModelInstance, nameof(IViewModel.Event), eventHandlerMethod);
Todd A. Stedel
Je préfère cette solution, car elle est simple, ne viole pas MVVM, a un code minimal derrière, permet une bonne utilisation de passwordbox (si vous utilisez ´SecurePassword´ à la place). De plus, il est maintenant simple d'implémenter d'autres méthodes HarvestPassword (comme la SmartCard ....)
Matt
8

J'ai passé beaucoup de temps à étudier différentes solutions. Je n'aimais pas l'idée des décorateurs, les comportements gâchent l'interface utilisateur de validation, le code derrière ... vraiment?

La meilleure solution consiste à vous en tenir à une propriété attachée personnalisée et à la lier à votre SecureStringpropriété dans votre modèle de vue. Gardez-le aussi longtemps que vous le pouvez. Chaque fois que vous aurez besoin d'un accès rapide au mot de passe ordinaire, convertissez-le temporairement en une chaîne non sécurisée à l'aide du 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 d'autoriser le GC à collecter votre élément d'interface utilisateur, afin de résister à l'envie d'utiliser un gestionnaire d'événements statique pour l' PasswordChangedévénement sur le PasswordBox. J'ai également découvert une anomalie où le contrôle ne SecurePasswordmettait pas à jour l'interface utilisateur lors de l'utilisation de la propriété pour la configurer, raison pour laquelle je copie le mot de passe à la Passwordplace.

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'utilisation de 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));
   }
}

Il RequiredSecureStrings'agit simplement d'un simple validateur personnalisé 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;
    }
}

Ici vous l'avez. Une solution MVVM pure complète et testée.

MoonStom
la source
7

J'ai posté un GIST ici qui est une boîte de mot de passe pouvant être liée.

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;
        }
    }
}
Taylor Leese
la source
1
bien que ce ne soit pas mauvais, vous perdez la possibilité de définir des attributs simples comme le rembourrage et tabindex
Julien
1
Taylor, j'ai inséré l'essentiel pour qu'il soit disponible dans la réponse. (Cela ressemblait à une réponse de lien uniquement sinon ... essayant simplement d'éviter que cela ne soit supprimé en tant que tel.) N'hésitez pas à jouer avec le contenu intégré.
Lynn Crumbling
@Julien mais vous pouvez résoudre ce problème avec des styles. Je résous ce problème d'une manière similaire, mais j'utilise un, ContentControlvous pouvez alors simplement utiliser un PasswordBox en tant que contenu et style en XAML comme vous le souhaitez. Le but de ContentControlest simplement de s'abonner à l' PasswordChangedévénement et d'exposer une propriété pouvant être liée dans deux directions. Dans l'ensemble, c'est 65 lignes de code et à peu près ce que fait cette classe de décoration. Voir ici pour mon résumé des gist.github.com/leidegre/c7343b8c720000fe3132
John Leidegren
6

Cette implémentation est légèrement différente. Vous passez une boîte de mot de passe à la liaison View via une propriété dans ViewModel, elle n'utilise aucun paramètre de commande. Le ViewModel reste ignorant de la vue. J'ai un projet VB vs 2010 qui peut être téléchargé depuis SkyDrive. Wpf MvvM PassWordBox Example.zip https://skydrive.live.com/redir.aspx?cid=e95997d33a9f8d73&resid=E95997D33A9F8D73!511

La façon dont j'utilise PasswordBox dans une application Wpf MvvM est assez simpliste et fonctionne bien pour moi. Cela ne signifie pas que je pense que c'est la bonne ou la meilleure façon. Il s'agit simplement d'une implémentation de l'utilisation de PasswordBox et du modèle MvvM.

Fondamentalement, vous créez une propriété publique en lecture seule à laquelle la vue peut se lier 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 support juste pour faire l'auto initialisation de la propriété.

Ensuite, à partir de Xaml, vous liez le contenu d'un ContentControl ou d'un exemple de conteneur de contrôle:

 <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 boîte de mot de passe. J'utilise également un PasswordAccessor (juste une fonction de chaîne) pour renvoyer la valeur du mot de passe lorsque vous vous connectez ou tout ce que vous voulez pour le mot de passe. Dans l'exemple, j'ai une propriété publique dans un modèle d'objet utilisateur générique. Exemple:

Public Property PasswordAccessor() As Func(Of String)

Dans l'objet utilisateur, la propriété de chaîne de mot de passe est en lecture seule sans aucun magasin de sauvegarde, elle renvoie simplement le mot de passe à partir de PasswordBox. Exemple:

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

Ensuite, dans le ViewModel, je m'assure que l'accesseur est créé et défini sur la propriété PasswordBox.Password '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

Lorsque j'ai besoin que la chaîne de mot de passe dise pour la connexion, j'obtiens simplement la propriété Mot de passe des objets utilisateur qui invoque vraiment la fonction pour récupérer le mot de passe et le renvoyer, puis le mot de passe réel n'est pas stocké par l'objet utilisateur. Exemple: serait dans le ViewModel

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 besoin d'aucune connaissance des contrôles de la vue. La vue se lie simplement à la propriété du ViewModel, pas différente de la liaison de la vue à une image ou à une autre ressource. Dans ce cas, cette ressource (propriété) se trouve être un contrôle utilisateur. Il permet de tester lorsque le ViewModel crée et possède la propriété et que la propriété est indépendante de la vue. Quant à la sécurité, je ne sais pas à quel point 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 elle accède.

William Rawson
la source
6

Pour résoudre le problème OP sans casser la MVVM, j'utiliserais un convertisseur de valeur personnalisé et un wrapper pour la valeur (le mot de passe) qui doit être récupérée dans 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 de vue:

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...
}

Comme le modèle de vue utilise IWrappedParameter<T>, il n'a pas besoin de connaître PasswordBoxWrapperni PasswordBoxConverter. De cette façon, vous pouvez isoler lePasswordBox objet du modèle de vue et ne pas casser le modèle 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}}" />
Aoi Karasu
la source
solution très élégante imo. j'ai basé le mien sur cela. la seule différence: je passe SecureString SecurePassword à la fonction de connexion au lieu du mot de passe de chaîne. afin qu'il n'y ait pas de chaînes non cryptées avec un mot de passe volant autour de la mémoire.
appelez-moi la carotte le
Cela fait un moment mais je n'arrive pas à faire fonctionner cela à cause de ma commande de relais. Pourriez-vous ajouter le vôtre?
Ecnerwal
5

Bien que je convienne qu'il est important d'éviter de stocker le mot de passe n'importe où, j'ai toujours besoin de pouvoir instancier le modèle de vue sans vue et exécuter mes tests par rapport à celui-ci.

La solution qui a fonctionné pour moi a été d'enregistrer la fonction PasswordBox.Password avec le modèle de vue et de l'invoquer lors de l'exécution du code de connexion.

Cela ne signifie une ligne de code dans le code - behind de la vue.

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 lorsque la connexion 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, lorsque je veux tester le viewmodel, je peux simplement définir PasswordHandler sur une méthode anonyme qui me permet de fournir le mot de passe que je veux utiliser dans le test.

mike mckechnie
la source
4

J'ai pensé que je jetterais ma solution dans le mélange, car c'est un problème si courant ... et avoir beaucoup d'options est toujours une bonne chose.

J'ai simplement enveloppé un PasswordBoxdans un UserControlet mis en œuvre un DependencyPropertypour pouvoir lier. Je fais tout ce que je peux pour éviter de stocker du texte clair dans la mémoire, donc tout se fait via a SecureStringet la PasswordBox.Passwordpropriété. Pendant la foreachboucle, chaque personnage est exposé, mais c'est très bref. Honnêtement, si vous craignez que votre application WPF soit compromise par cette brève exposition, vous avez des problèmes de sécurité plus importants qui doivent être traités.

La beauté de ceci est que vous ne violez aucune règle MVVM, même celles "puristes", car c'est un UserControl, donc il est permis d'avoir du code-behind. Lorsque vous l'utilisez, vous pouvez avoir une communication pure entre Viewet ViewModelsans que vous soyez au VideModelcourant d'une partie Viewou de la source du mot de passe. Assurez-vous simplement que vous vous liez à SecureStringvotre 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 prise en charge de la liaison bidirectionnelle.)

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 - Prend en charge la liaison bidirectionnelle.)

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;
            }
        }
    }
}

Utilisation de la version 2:

<local:BindanblePasswordBox Width="150" HorizontalAlignment="Center"
                            VerticalAlignment="Center"
                            Password="{Binding Password, Mode=TwoWay}"/>
BK
la source
J'ai essayé de l'implémenter, mais vous obtenez une boucle infinie lorsque vous mettez à jour le mot de passe sur l'interface utilisateur; car if (Password != secure)sera toujours faux car SecureString ne remplace pas égal. Des pensées?
simonalexander2005
2

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

Passer la boîte de mots de passe, je pense que c'est la même chose que d'accéder au contrôle du code derrière autant que je sache. J'accepte les mots de passe, ne les garde pas en mémoire, etc. Dans cette implémentation, je n'ai pas de propriété pour le mot de passe dans le modèle d'affichage.

Commande de 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);
}
Legz
la source
Il s'agit clairement d'une violation du modèle MVVM. Le motif ne permet pas de gérer les contrôles dans le modèle de vue.
BionicCode
2

Pour moi, ces deux choses semblent fausses:

  • Implémentation de propriétés de mot de passe en texte clair
  • Envoi en PasswordBoxtant que paramètre de commande au ViewModel

Le transfert du SecurePassword (instance SecureString) comme décrit par Steve dans CO semble acceptable. je préfèreBehaviors coder derrière, et j'avais également l'exigence supplémentaire de pouvoir réinitialiser le mot de passe à partir du viewmodel.

Xaml ( Passwordest 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;
            }
        }

    }
}
Mike Fuchs
la source
2

Pour les débutants comme moi, voici un échantillon de travail complet de ce qui est Konamimansuggé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);
    }
}
fs_tigre
la source
Il s'agit clairement d'une violation du modèle MVVM. Le motif ne permet pas de gérer les contrôles dans le modèle de vue.
BionicCode
1

Comme vous pouvez le voir, je lie à Password, mais peut-être que cela le lie à la classe statique.

C'est une propriété attenante . Ce type de propriété peut être appliqué à tout type de DependencyObject, et pas seulement au type dans lequel il est déclaré. Ainsi, même s'il est déclaré dans la PasswordHelperclasse statique, il est appliqué à celui PasswordBoxsur lequel vous l'utilisez.

Pour utiliser cette propriété jointe, il vous suffit de la lier à la Passwordpropriété de votre ViewModel:

<PasswordBox w:PasswordHelper.Attach="True" 
         w:PasswordHelper.Password="{Binding Password}"/>
Thomas Levesque
la source
1

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 marche pour moi!

José Roberto Cuello Alcaraz
la source
Tu me donnes une bonne idée. :)
Andre Mendonca
1

Comme mentionné précédemment, VM ne devrait pas être au courant de la vue, mais passer toute PasswordBox ressemble à l'approche la plus simple. Alors peut-être qu'au lieu de transtyper le paramètre passé dans PasswordBox, utilisez Reflection pour en extraire la propriété Password. Dans ce cas, VM attend une sorte de conteneur de mots de passe avec la propriété Password (j'utilise les 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 facilement testé avec une classe anonyme:

var passwordContainer = new
    {
        Password = "password"
    };
mokula
la source
Les commentaires ne sont pas pour une discussion approfondie; cette conversation a été déplacée vers le chat .
Samuel Liew
1

Dans l'application universelle Windows

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

 <PasswordBox x:Uid="PasswordBox" Password="{Binding Waiter.Password, Mode=TwoWay}" Name="txtPassword" HorizontalAlignment="Stretch" Margin="50,200,50,0" VerticalAlignment="Top"/>

Baz08
la source
1

Pour toute personne consciente des risques que cette implémentation comporte, pour que le mot de passe se synchronise avec votre ViewModel, ajoutez simplement Mode = OneWayToSource .

XAML

<PasswordBox
    ff:PasswordHelper.Attach="True"
    ff:PasswordHelper.Password="{Binding Path=Password, Mode=OneWayToSource}" />
Kevin
la source
Pourquoi ne pas simplement le faire OneWayToSource?
BK
@BK Modifié ma réponse. Merci.
Kevin
1
le mode ne devrait-il pas être à l'intérieur des accolades de reliure?
Mat
@Mat Yap. Merci.
Kevin
1

Voici mon point de vue:

  1. L'utilisation d'une propriété attachée pour lier le mot de passe va à l'encontre de l'objectif de sécurisation du mot de passe. La propriété Password d'une zone de mot de passe n'est pas lisible pour une raison.

  2. La transmission de la zone de mot de passe comme paramètre de commande rendra le ViewModel conscient du contrôle. Cela ne fonctionnera pas si vous envisagez de rendre votre plateforme multiplateforme ViewModel réutilisable. Ne faites pas connaître à votre machine virtuelle votre vue ou 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 de toute autre chose compliquée ne soit pas nécessaire pour une simple tâche de fourniture du mot de passe.

XAML

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

Code derrière - l'utilisation du code derrière ne viole pas nécessairement MVVM. Tant que vous n'y mettez aucune logique métier.

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

ViewModel

LoginCommand = new RelayCommand<Func<string>>(getpwd=> { service.Login(username, getpwd()); });
Lance
la source
0

Vous trouverez une solution pour PasswordBox dans l'exemple d'application ViewModel de WPF Application Framework (WAF) .

Cependant, Justin a raison. Ne passez pas le mot de passe en texte brut entre View et ViewModel. Utilisez plutôt SecureString (voir MSDN PasswordBox).

jbe
la source
2
La manière qui est utilisée dans Pop3SettingsView de WAF est drôle. PasswordBox passwordBox = expéditeur (PasswordBox); if (ViewModel! = null) {ViewModel.Pop3Password = passwordBox.Password; } Pop3Password de ViewModel est la propriété de chaîne. donc, ce n'est pas aussi sécurisé .. mieux vaut utiliser la propriété jointe
Michael Sync
0

J'ai utilisé une vérification d'authentification suivie d'un sous-appel appelé par une classe de médiateur à la vue (qui implémente également une vérification d'authentification) pour écrire le mot de passe dans la classe de données.

Ce n'est pas une solution parfaite; cependant, cela a résolu mon problème de ne pas pouvoir déplacer le mot de passe.

Miles
la source
0

J'utilise une solution conviviale MVVM succincte qui n'a pas encore été mentionnée. Tout d'abord, je nomme le PasswordBox en XAML:

<PasswordBox x:Name="Password" />

Ensuite, j'ajoute un seul appel de méthode 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 lorsqu'il est attaché à une vue via DataContext et une autre notification lorsqu'il est détaché. Le contenu de cette notification est configurable via les lambdas, mais il s'agit généralement d'un appel de setter ou de méthode sur le modèle de vue, en passant le contrôle problématique en paramètre.

Il peut être rendu très convivial pour MVVM en ayant l'interface d'affichage de vue au lieu des contrôles enfants.

Le code ci-dessus repose sur une classe d'assistance publiée sur mon blog.

Robert Važan
la source
0

J'ai passé des heures à essayer de faire fonctionner ça. À la fin, j'ai abandonné et j'ai juste utilisé le PasswordBoxEdit de DevExpress.

C'est la solution la plus simple de tous les temps, car elle permet de relier sans tirer d'horribles trucs.

Solution sur le site Web DevExpress

Pour mémoire, je ne suis en aucun cas affilié à DevExpress.

Contango
la source
0

<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!

Hector Lobo
la source
0

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

Mais toutes les opérations d'entrée s'effectuent avec la propriété de mot de passe réelle

chaîne privée _Password;

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

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

            return szChar;
        }

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

chaîne publique Mot de passe {get {return _Password; }

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

Niji
la source
La raison pour laquelle la zone de mot de passe n'est pas contraignable est que nous ne voulons pas stocker le mot de passe dans une chaîne claire. La chaîne est immuable et nous ne savons pas combien de temps elle restera en mémoire.
Lance
0

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

en 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é de mot de passe de PasswordBox fournie par win ou de WatermarkPasswordBox fournie par XCeedtoolkit génère un RoutedEventArgs afin que vous puissiez le lier.

maintenant en vue xmal

<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>
Carlos rodriguez
la source
0

Envoyer un SecureString au modèle de vue à l'aide d'un comportement attaché etICommand

Il n'y a rien de mal avec le code-behind lors de l'implémentation de MVVM. MVVM est un modèle architectural qui vise à séparer la vue du modèle / logique métier. MVVM décrit comment atteindre cet objectif de manière reproductible (le modèle). Il ne se soucie pas des détails d'implémentation, comme comment structurer ou implémenter la vue. Il trace simplement les limites et définit ce qu'est la vue, le modèle de vue et ce que le modèle en termes de terminologie de ce modèle.

MVVM ne se soucie pas du langage (XAML ou C #) ou du compilateur ( partialclasses). Être indépendant de la langue est une caractéristique obligatoire d'un modèle de conception - il doit être indépendant de la langue.

Cependant, le code-behind présente certains inconvénients, comme rendre votre logique d'interface utilisateur plus difficile à comprendre, lorsqu'elle est largement distribuée entre XAML et C #. Mais la plus importante implémentation de la logique de l'interface utilisateur ou des objets comme les modèles, les styles, les déclencheurs, les animations, etc. en C # est très complexe et laide / moins lisible que l'utilisation de XAML. XAML est un langage de balisage qui utilise des balises et une imbrication pour visualiser la hiérarchie des objets. La création d'interface utilisateur à l'aide de XAML est très pratique. Bien qu'il existe des situations où vous pouvez choisir d'implémenter la logique de l'interface utilisateur en C # (ou code-behind). Gérer lePasswordBox est un exemple.

Pour cette raison, le traitement du PasswordBoxcode-behind en manipulant lePasswordBox.PasswordChanged , n'est pas une violation du modèle MVVM.

Une violation claire serait de passer un contrôle (le PasswordBox) au modèle de vue. De nombreuses solutions le recommandent, par exemple, la baie passant l'instance de l' PasswordBoxasICommand.CommandParameter au modèle de vue. De toute évidence, une recommandation très mauvaise et inutile.

Si vous ne vous souciez pas de l'utilisation de C #, mais que vous souhaitez simplement garder votre fichier code-behind propre ou simplement encapsuler une logique de comportement / d'interface utilisateur, vous pouvez toujours utiliser les propriétés attachées et implémenter un comportement attaché.

Opposé à la tristement célèbre aide à large diffusion qui permet de se lier au mot de passe en texte brut (très mauvais anti-modèle et risque de sécurité), ce comportement utilise un ICommandpour envoyer le mot de passe quant SecureStringau modèle d'affichage, chaque fois que PasswordBoxlePasswordBox.PasswordChanged événement événement.

MainWindow.xaml

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

  <PasswordBox PasswordBox.Command="{Binding VerifyPasswordCommand}" />
</Window>

ViewModel.cs

public class ViewModel : INotifyPropertyChanged
{
  public ICommand VerifyPasswordCommand => new RelayCommand(VerifyPassword);

  public void VerifyPassword(object commadParameter)
  {
    if (commandParameter is SecureString secureString)
    {
      IntPtr valuePtr = IntPtr.Zero;
      try
      {
        valuePtr = Marshal.SecureStringToGlobalAllocUnicode(value);
        string plainTextPassword = Marshal.PtrToStringUni(valuePtr);

        // Handle plain text password. 
        // It's recommended to convert the SecureString to plain text in the model, when really needed.
      } 
      finally 
      {
        Marshal.ZeroFreeGlobalAllocUnicode(valuePtr);
      }
    }
  }
}

PasswordBox.cs

// Attached behavior
class PasswordBox : DependencyObject
{
  #region Command attached property

  public static readonly DependencyProperty CommandProperty =
    DependencyProperty.RegisterAttached(
      "Command",
      typeof(ICommand),
      typeof(PasswordBox),
      new PropertyMetadata(default(ICommand), PasswordBox.OnSendPasswordCommandChanged));

  public static void SetCommand(DependencyObject attachingElement, ICommand value) =>
    attachingElement.SetValue(PasswordBox.CommandProperty, value);

  public static ICommand GetCommand(DependencyObject attachingElement) =>
    (ICommand) attachingElement.GetValue(PasswordBox.CommandProperty);

  #endregion

  private static void OnSendPasswordCommandChanged(
    DependencyObject attachingElement,
    DependencyPropertyChangedEventArgs e)
  {
    if (!(attachingElement is System.Windows.Controls.PasswordBox passwordBox))
    {
      throw new ArgumentException("Attaching element must be of type 'PasswordBox'");
    }

    if (e.OldValue != null)
    {
      return;
    }

    WeakEventManager<object, RoutedEventArgs>.AddHandler(
      passwordBox,
      nameof(System.Windows.Controls.PasswordBox.PasswordChanged),
      SendPassword_OnPasswordChanged);
  }

  private static void SendPassword_OnPasswordChanged(object sender, RoutedEventArgs e)
  {
    var attachedElement = sender as System.Windows.Controls.PasswordBox;
    SecureString commandParameter = attachedElement?.SecurePassword;
    if (commandParameter == null || commandParameter.Length < 1)
    {
      return;
    }

    ICommand sendCommand = GetCommand(attachedElement);
    sendCommand?.Execute(commandParameter);
  }
}
BionicCode
la source