2011-07-28 3 views
3

J'ai commencé à travailler sur un projet qui avait déjà beaucoup de code en place. C'est une application Ruby on Rail qui utilise Devise pour l'authentification des utilisateurs. L'une des exigences de l'application est que lorsqu'un utilisateur change son mot de passe, il n'est pas autorisé à utiliser le même mot de passe que les trois derniers mots de passe précédemment utilisés. Pour accomplir ceci, il y a une table qui contient un historique des mots de passe pour un utilisateur donné. Ces mots de passe sont des copies des mots de passe cryptés qui existaient avant tout changement de mot de passe sur l'utilisateur.Cryptage des mots de passe pour correspondre aux mots de passe cryptés stockés.

Voici où le problème entre en jeu. Nous avons un formulaire de changement de mot de passe qui recueille le nouveau mot de passe pour un utilisateur donné. Je dois être en mesure de prendre le nouveau mot de passe et de le crypter afin que je puisse faire correspondre la valeur cryptée du nouveau mot de passe avec les valeurs cryptées des anciens mots de passe dans l'histoire.

trucs techniques
Rails Version 3.0.9
version 1.3.4
Devise Utilisation Bcrypt standard avec. Devise bcrypt_ruby version 2.1.4

Pour cela, nous sommes en train de surcharger la méthode reset_password supportée par Devise. Cela nous permet d'introduire notre propre méthode, has_repeated_password dans le contrôleur de l'utilisateur.

La version de has_repeated_password j'ai commencé avec est ci-dessous:

def has_repeated_password? 
    return false if self.new_record? || self.version == 1 
    histories = self.versions.find(:all, :order => 'version DESC', :limit => 3) 

    histories.detect do |history| 
     history.encrypted_password == self.class.encryptor_class.digest(self.password, self.class.stretches, history.password_salt, self.class.pepper) 
    end 
    end 

Le problème ici est que la classe chiffreur est jamais définie, ce qui provoque une erreur à chaque fois que cette routine se exécute. Même s'il existe de nombreux exemples qui prétendent que cela fonctionne, je ne peux pas le faire fonctionner lorsque Devise utilise le cryptage par défaut.

Une deuxième tentative de c'est le code suivant:

def has_repeated_password?<br> 
    return false if self.new_record? || self.version == 1 
    histories = self.versions.find(:all, :order => 'version DESC', :limit => 3) 

    histories.detect do |history| 
     pwd = self.password_digest(self.password) 
     history.encrypted_password == pwd 
    end 
    end 

Dans ce cas, je ne reçois jamais un mot de passe qui correspond à l'un des mots de passe stockés, même si je l'ai vérifié que le mot de passe dans la base de données ce que j'attends.

J'ai essayé de creuser le code Devise pour voir ce que je peux y trouver. Je sais que l'autentication doit le faire d'une manière ou d'une autre quand elle fait correspondre les mots de passe collectés des utilisateurs avec le mot de passe stocké.

Toute aide serait appréciée.

Répondre

2

Je pense avoir trouvé une solution à mon problème. Le principal problème était que j'essayais d'obtenir un mot de passe chiffré qui ne faisait plus partie du modèle d'utilisateur (plus) lié à Devise. Cette solution suppose que Devise utilisera Bcrypt comme outil de cryptage standard (ne peut pas se rappeler quelle version de Devise a fait le déplacement). Bcrypt/Devise enterre réellement le sel pour le mot de passe dans le mot de passe crypté. Si vous avez le sel et le poivre, vous pouvez obtenir le même mot de passe pour générer la même valeur cryptée.

Voici donc le code mis à jour pour la routine refernced ci-dessus:

def has_repeated_password? 
    return false if self.new_record? || self.version == 1 

    histories = self.versions.find(:all, :order => 'version DESC', :limit => 3) 
    histories.detect do |history| 
     bcrypt = ::BCrypt::Password.new(history.encrypted_password) 
     password = ::BCrypt::Engine.hash_secret("#{self.password}#{self.class.pepper}", bcrypt.salt) 
     password == history.encrypted_password 
    end 
    end 

La clé ici est que l'objet Bcyrpt doit être créé avec un mot de passe chiffré existant en utilisant le même sel qui a généré le mot de passe d'origine. Ceci est accompli en lui donnant mon mot de passe crypté historique stocké (history.encrypted_password). L'un des autres éléments clés est que les deux mots de passe de l'historique et le nouveau mot de passe proposé utilisent le même poivre, qui est géré par Devise. Donc en utilisant l'Engne.has_secret appel avec le nouveau mot de passe prévu, il peut être comparé avec le mot de passe de l'histoire.

J'ai dû déplacer le code bcrypt ici parce que toutes les méthodes de mot de passe supportées par Devise supposent que vous voulez agir sur le mot de passe utilisateur de l'objet utilisateur courant.

+0

Il est plus correct d'utiliser 'p = BCrypt :: Password.new (history.encrypted_password)' et ensuite 'p == password'. La classe 'BCrypt :: Password' a l'opérateur' == 'défini. –