diff --git a/plugin.rb b/plugin.rb index 9574d88..ef6345f 100644 --- a/plugin.rb +++ b/plugin.rb @@ -4,12 +4,100 @@ # name: discourse-md5_authentication # about: A plugin to authenticate users with MD5 passwords from legacy systems -# version: 0.7 +# version: 0.9.3 # authors: saint # url: https://gitea.federated.computer/saint/discourse-md5_authentication.git +# require 'digest' + after_initialize do class ::SessionController < ApplicationController + ITOA64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + + def to64(value, length) + result = "" + length.times do + result << ITOA64[value & 0x3f] + value >>= 6 + end + result + end + + def gossamer_md5_crypt(password, legacy_hash) + # Extract the salt from the legacy hash + parts = legacy_hash.split('$') + salt = parts[2] + + # Limit the salt to 8 characters + salt = salt[0, 8] + + magic = "$GT$" + Rails.logger.debug "MD5 magic: #{magic}" + + ctx = Digest::MD5.new + ctx.update(password) + ctx.update(magic) + ctx.update(salt) + + final = Digest::MD5.new + final.update(password) + final.update(salt) + final.update(password) + final_digest = final.digest + + password_length = password.length + + while password_length > 0 + ctx.update(final_digest[0, [password_length, 16].min]) + password_length -= 16 + end + + password_length = password.length + while password_length > 0 + if password_length & 1 != 0 + ctx.update("\x00") + else + ctx.update(password[0, 1]) + end + password_length >>= 1 + end + + final_digest = ctx.digest + Rails.logger.debug "MD5 final_digest: #{final_digest}" + + 1000.times do |i| + ctx1 = Digest::MD5.new + if i & 1 != 0 + ctx1.update(password) + else + ctx1.update(final_digest) + end + ctx1.update(salt) if i % 3 != 0 + ctx1.update(password) if i % 7 != 0 + if i & 1 != 0 + ctx1.update(final_digest) + else + ctx1.update(password) + end + final_digest = ctx1.digest + end + + result = '' + result << to64((final_digest[0].ord << 16) | (final_digest[6].ord << 8) | final_digest[12].ord, 4) + result << to64((final_digest[1].ord << 16) | (final_digest[7].ord << 8) | final_digest[13].ord, 4) + result << to64((final_digest[2].ord << 16) | (final_digest[8].ord << 8) | final_digest[14].ord, 4) + result << to64((final_digest[3].ord << 16) | (final_digest[9].ord << 8) | final_digest[15].ord, 4) + result << to64((final_digest[4].ord << 16) | (final_digest[10].ord << 8) | final_digest[5].ord, 4) + result << to64(final_digest[11].ord, 2) + + "#{magic}#{salt}$#{result}" + end + + def verify_gossamer_password(password, legacy_hash) + generated_hash = gossamer_md5_crypt(password, legacy_hash) + generated_hash == legacy_hash + end + def create params.require(:login) params.require(:password) @@ -27,28 +115,26 @@ after_initialize do custom_password_md5 = user.custom_fields['custom_password_md5'] # Check for MD5 password in custom field + Rails.logger.debug "Check for MD5 password in custom field" if custom_password_md5.present? - # MD5 password is present - if Digest::MD5.hexdigest(password) == custom_password_md5 + Rails.logger.debug "MD5 password is present custom_password_md5: #{custom_password_md5} password: #{password}" + if verify_gossamer_password(password, custom_password_md5) # MD5 matches, so update the user's password to the new one and remove the custom field + Rails.logger.debug "MD5 matches" user.password = password user.custom_fields['custom_password_md5'] = nil user.save! Rails.logger.debug "Updated MD5 password for user: #{user.id}" - else - # MD5 doesn't match, so we have a failed login attempt. - Rails.logger.debug "Password incorrect for user: #{user.id}" + Rails.logger.debug "MD5 Password incorrect for user: #{user.id}" invalid_credentials return end - # If their password is incorrect elsif !user.confirm_password?(password) - # There is no MD5 password and the password was incorrect. Rails.logger.debug "Password incorrect for user: #{user.id}" invalid_credentials @@ -90,3 +176,4 @@ after_initialize do end end end +