commit 83cbf095987ae58dfc7573cf673ff161c693b65f Author: DiscourseHosting Date: Sun Sep 7 20:04:01 2014 +0000 First release diff --git a/plugin.rb b/plugin.rb new file mode 100644 index 0000000..00af02a --- /dev/null +++ b/plugin.rb @@ -0,0 +1,129 @@ +# name: discourse-migratepassword +# about: enable alternative password hashes +# version: 0.3 +# authors: Jens Maier and Michael@discoursehosting.com + +# uses phpass-ruby https://github.com/uu59/phpass-ruby + +# Usage: +# When migrating, store a custom field with the user containing the crypted password + +# for vBulletin this should be #{password}:#{salt} md5(md5(pass) + salt) +# for Phorum #{password} md5(pass) +# for Wordpress #{password} phpass(8).crypt(pass) + + +require 'digest' + + class WordpressHash + def initialize(stretch=8) + @itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' + stretch = 8 unless (8..30).include?(stretch) + @stretch = stretch + @random_state = '%s%s' % [Time.now.to_f, $$] + end + + def hash(pw) + rnd = '' + rnd = Phpass.random_bytes(6) + crypt(pw, gensalt(rnd)) + end + + def check(pw, hash) + crypt(pw, hash) == hash + end + + private + + def gensalt(input) + out = '$P$' + out << @itoa64[[@stretch + 5, 30].min] + out << encode64(input, 6) + out + end + + def crypt(pw, setting) + out = '*0' + out = '*1' if setting.start_with?(out) + iter = @itoa64.index(setting[3]) + return out unless (8..30).include?(iter) + count = 1 << iter + salt = setting[4...12] + return out if salt.length != 8 + hash = Digest::MD5.digest(salt + pw) + while count > 0 + hash = Digest::MD5.digest(hash + pw) + count -= 1 + end + setting[0,12] + encode64(hash, 16) + end + + def encode64(input, count) + out = '' + cur = 0 + while cur < count + value = input[cur].ord + cur += 1 + out << @itoa64[value & 0x3f] + if cur < count + value |= input[cur].ord << 8 + end + out << @itoa64[(value >> 6) & 0x3f] + break if cur >= count + cur += 1 + + if cur < count + value |= input[cur].ord << 16 + end + out << @itoa64[(value >> 12) & 0x3f] + break if cur >= count + cur += 1 + out << @itoa64[(value >> 18) & 0x3f] + end + out + end + end + + +after_initialize do + + module ::AlternativePassword + def confirm_password?(password) + return true if super + return false unless self.custom_fields.has_key?('import_pass') + + if AlternativePassword::check_all(password, self.custom_fields['import_pass']) + self.password = password + self.custom_fields.delete('import_pass') + return save + end + false + end + + def self.check_all(password, crypted_pass) + AlternativePassword::check_vbulletin(password, crypted_pass) || + AlternativePassword::check_md5(password, crypted_pass) || + AlternativePassword::check_wordpress(password, crypted_pass) + end + + def self.check_vbulletin(password, crypted_pass) + hash, salt = crypted_pass.split(':', 2) + !salt.nil? && hash == Digest::MD5.hexdigest(Digest::MD5.hexdigest(password) + salt) + end + + def self.check_md5(password, crypted_pass) + crypted_pass == Digest::MD5.hexdigest(password) + end + + def self.check_wordpress(password, crypted_pass) + hasher = WordpressHash.new(8) + hasher.check(password, crypted_pass) + end + end + + class ::User + prepend AlternativePassword + end + +end +