From 7226bf0b9aa2dc8006ca0263700a648c43b0341f Mon Sep 17 00:00:00 2001 From: saint Date: Wed, 28 Aug 2024 21:48:07 +1000 Subject: [PATCH] v0.16 Further fixups to work with Bitnami Discourse 3.2.5 --- .plugin.rb.bck.swp | Bin 0 -> 16384 bytes plugin.rb | 338 +++++++++++++++++++++++---------------------- 2 files changed, 173 insertions(+), 165 deletions(-) create mode 100644 .plugin.rb.bck.swp diff --git a/.plugin.rb.bck.swp b/.plugin.rb.bck.swp new file mode 100644 index 0000000000000000000000000000000000000000..3fa27970b7b6adda92b503f2f2b8e3c883c0122a GIT binary patch literal 16384 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 literal 0 HcmV?d00001 diff --git a/plugin.rb b/plugin.rb index 4a25f66..a821878 100644 --- a/plugin.rb +++ b/plugin.rb @@ -4,7 +4,7 @@ # name: discourse-md5_authentication # about: A plugin to authenticate users with MD5 passwords from legacy systems -# version: 0.14 +# version: 0.16 # authors: saint@federated.computer # url: https://gitea.federated.computer/saint/discourse-md5_authentication.git @@ -13,200 +13,208 @@ # enabled_site_setting :legacymd5password_auth_enabled after_initialize do - # Extend the SessionController class to include our custom authentication logic - class ::SessionController - prepend Module.new { - # Override the create method to add our custom authentication checks - def create - Rails.logger.warn "MD5 -- AA -- start create" - # Ensure required parameters are present - params.require(:login) - params.require(:password) + # Define a module to hold the legacy MD5 authentication logic + module LegacyMd5Authentication - # Validate the length of the password - return invalid_credentials if params[:password].length > User.max_password_length + # Constants + ITOA64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" - # Find the user by username or email - user = User.find_by_username_or_email(normalized_login_param) + # Override the create method to add our custom authentication checks + def create + Rails.logger.warn "MD5 -- AA -- start create" + # Ensure required parameters are present + params.require(:login) + params.require(:password) - Rails.logger.warn "MD5 -- BB -- second" - # Check if site is in staff writes-only mode and ensure user is staff if true - raise Discourse::ReadOnly if staff_writes_only_mode? && !user&.staff? + # Validate the length of the password + return invalid_credentials if params[:password].length > User.max_password_length - # Apply rate limiting for second factor authentication - rate_limit_second_factor!(user) + # Find the user by username or email + user = User.find_by_username_or_email(normalized_login_param) - if user.present? - Rails.logger.warn "MD5 -- CC -- user.present" - # Retrieve the provided password and custom MD5 password hash from user custom fields - password = params[:password] - custom_password_md5 = user.custom_fields['custom_password_md5'] + Rails.logger.warn "MD5 -- BB -- second" + # Check if site is in staff writes-only mode and ensure user is staff if true + raise Discourse::ReadOnly if staff_writes_only_mode? && !user&.staff? - # Log the presence of custom MD5 hash for debugging - Rails.logger.warn "MD5 -- Check for MD5 password in custom field" - if custom_password_md5.present? - Rails.logger.warn "MD5 -- MD5 password is present custom_password_md5: #{custom_password_md5} password: #{password}" + # Apply rate limiting for second factor authentication + rate_limit_second_factor!(user) - # Verify the provided password against the stored MD5 hash - if verify_gossamer_password(password, custom_password_md5) - # If MD5 hash matches, update the user's password and other attributes - Rails.logger.warn "MD5 -- MD5 matches" + if user.present? + Rails.logger.warn "MD5 -- CC -- user.present" + # Retrieve the provided password and custom MD5 password hash from user custom fields + password = params[:password] + custom_password_md5 = user.custom_fields['custom_password_md5'] - # Set the user's password to the provided one and update other attributes - user.password = password - user.active = true - user.approved = true - user.approved_at = Time.now - user.approved_by_id = 1 - user.custom_fields['custom_password_md5'] = nil # Clear the custom MD5 field - user.save! + # Log the presence of custom MD5 hash for debugging + Rails.logger.warn "MD5 -- Check for MD5 password in custom field" + if custom_password_md5.present? + Rails.logger.warn "MD5 -- MD5 password is present custom_password_md5: #{custom_password_md5} password: #{password}" + + # Verify the provided password against the stored MD5 hash + if verify_gossamer_password(password, custom_password_md5) + # If MD5 hash matches, update the user's password and other attributes + Rails.logger.warn "MD5 -- MD5 matches" + + # Set the user's password to the provided one and update other attributes + user.password = password + user.active = true + user.approved = true + user.approved_at = Time.now + user.approved_by_id = 1 + user.custom_fields['custom_password_md5'] = nil # Clear the custom MD5 field + user.save! Rails.logger.warn "MD5 -- DD -- user.present" - # Generate a new token and hash it - token = SecureRandom.hex(20) - token_hash = EmailToken.hash_token(token) + # Generate a new token and hash it + token = SecureRandom.hex(20) + token_hash = EmailToken.hash_token(token) - # Create a confirmed email token for the user - EmailToken.create!( - user_id: user.id, - email: user.email, - token_hash: token_hash, - confirmed: true - ) - Rails.logger.warn("MD5 -- Generated token for user #{user.username}: #{token}") + # Create a confirmed email token for the user + EmailToken.create!( + user_id: user.id, + email: user.email, + token_hash: token_hash, + confirmed: true + ) + Rails.logger.warn("MD5 -- Generated token for user #{user.username}: #{token}") - Rails.logger.warn "MD5 -- Updated user: #{user.id}" - else - # If MD5 hash does not match, log the failed login attempt - Rails.logger.warn "MD5 -- MD5 Password (hash) incorrect for user: #{user.id}" - invalid_credentials - return - end - - elsif !user.confirm_password?(password) - # If no MD5 hash is present and the provided password is incorrect - Rails.logger.warn "MD5 -- Password incorrect for user: #{user.id}" + Rails.logger.warn "MD5 -- Updated user: #{user.id}" + else + # If MD5 hash does not match, log the failed login attempt + Rails.logger.warn "MD5 -- MD5 Password (hash) incorrect for user: #{user.id}" invalid_credentials return end - # If the site requires user approval and the user is not yet approved - if login_not_approved_for?(user) - render json: login_not_approved - return - end - - # Invalidate any invite link for the user - Invite.invalidate_for_email(user.email) - - # Check if the user's password has expired - if user.password_expired?(password) - render json: { error: "expired", reason: "expired" } - return - end - else - # If no user is found with the provided credentials + elsif !user.confirm_password?(password) + # If no MD5 hash is present and the provided password is incorrect + Rails.logger.warn "MD5 -- Password incorrect for user: #{user.id}" invalid_credentials return end - # Check for any login errors - if payload = login_error_check(user) - return render json: payload + # If the site requires user approval and the user is not yet approved + if login_not_approved_for?(user) + render json: login_not_approved + return end - # Authenticate second factor if required - second_factor_auth_result = authenticate_second_factor(user) - return render(json: @second_factor_failure_payload) unless second_factor_auth_result.ok + # Invalidate any invite link for the user + Invite.invalidate_for_email(user.email) - # If user is active and email is confirmed, proceed with login - if user.active && user.email_confirmed? - login(user, second_factor_auth_result) - else - not_activated(user) + # Check if the user's password has expired + if user.password_expired?(password) + render json: { error: "expired", reason: "expired" } + return end + else + # If no user is found with the provided credentials + invalid_credentials + return end - } - end -end -# Helper methods to handle MD5 password verification + # Check for any login errors + if payload = login_error_check(user) + return render json: payload + end -def to64(value, length) - # Convert a value to a base64-like representation - result = String.new - length.times do - result << ITOA64[value & 0x3f] - value >>= 6 - Rails.logger.warn "to64 result: #{result}" - end - result -end + # Authenticate second factor if required + second_factor_auth_result = authenticate_second_factor(user) + return render(json: @second_factor_failure_payload) unless second_factor_auth_result.ok -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.warn "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 - - # Process the password with various hashing operations - 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]) + # If user is active and email is confirmed, proceed with login + if user.active && user.email_confirmed? + login(user, second_factor_auth_result) + else + not_activated(user) + end + end + + # Helper methods to handle MD5 password verification + + def to64(value, length) + # Convert a value to a base64-like representation + result = String.new + length.times do + result << ITOA64[value & 0x3f] + value >>= 6 + Rails.logger.warn "to64 result: #{result}" + 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.warn "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 + + # Process the password with various hashing operations + 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.warn "MD6 final_digest: #{final_digest}" + + result = String.new + Rails.logger.warn "A result: #{result}" + result << to64((final_digest[0].ord << 16) | (final_digest[6].ord << 8) | final_digest[12].ord, 4) + Rails.logger.warn "B result: #{result}" + result << to64((final_digest[1].ord << 16) | (final_digest[7].ord << 8) | final_digest[13].ord, 4) + Rails.logger.warn "C result: #{result}" + result << to64((final_digest[2].ord << 16) | (final_digest[8].ord << 8) | final_digest[14].ord, 4) + Rails.logger.warn "D result: #{result}" + result << to64((final_digest[3].ord << 16) | (final_digest[9].ord << 8) | final_digest[15].ord, 4) + Rails.logger.warn "E result: #{result}" + result << to64((final_digest[4].ord << 16) | (final_digest[10].ord << 8) | final_digest[5].ord, 4) + Rails.logger.warn "F result: #{result}" + result << to64(final_digest[11].ord, 2) + Rails.logger.warn "G result: #{result}" + + Rails.logger.warn "magic salt result #{magic}#{salt}$#{result}" + "#{magic}#{salt}$#{result}" + end + + def verify_gossamer_password(password, legacy_hash) + # Verify the provided password against the stored MD5 hash + generated_hash = gossamer_md5_crypt(password, legacy_hash) + generated_hash == legacy_hash end - password_length >>= 1 end - final_digest = ctx.digest - Rails.logger.warn "MD6 final_digest: #{final_digest}" - - result = String.new - Rails.logger.warn "A result: #{result}" - result << to64((final_digest[0].ord << 16) | (final_digest[6].ord << 8) | final_digest[12].ord, 4) - Rails.logger.warn "B result: #{result}" - result << to64((final_digest[1].ord << 16) | (final_digest[7].ord << 8) | final_digest[13].ord, 4) - Rails.logger.warn "C result: #{result}" - result << to64((final_digest[2].ord << 16) | (final_digest[8].ord << 8) | final_digest[14].ord, 4) - Rails.logger.warn "D result: #{result}" - result << to64((final_digest[3].ord << 16) | (final_digest[9].ord << 8) | final_digest[15].ord, 4) - Rails.logger.warn "E result: #{result}" - result << to64((final_digest[4].ord << 16) | (final_digest[10].ord << 8) | final_digest[5].ord, 4) - Rails.logger.warn "F result: #{result}" - result << to64(final_digest[11].ord, 2) - Rails.logger.warn "G result: #{result}" - - Rails.logger.warn "magic salt result #{magic}#{salt}$#{result}" - "#{magic}#{salt}$#{result}" + # Extend the SessionController class to include the LegacyMd5Authentication module + class ::SessionController + prepend LegacyMd5Authentication + end end -def verify_gossamer_password(password, legacy_hash) - # Verify the provided password against the stored MD5 hash - generated_hash = gossamer_md5_crypt(password, legacy_hash) - generated_hash == legacy_hash -end