v0.16 Further fixups to work with Bitnami Discourse 3.2.5
This commit is contained in:
parent
9edabc2a3a
commit
7226bf0b9a
BIN
.plugin.rb.bck.swp
Normal file
BIN
.plugin.rb.bck.swp
Normal file
Binary file not shown.
338
plugin.rb
338
plugin.rb
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
# name: discourse-md5_authentication
|
# name: discourse-md5_authentication
|
||||||
# about: A plugin to authenticate users with MD5 passwords from legacy systems
|
# about: A plugin to authenticate users with MD5 passwords from legacy systems
|
||||||
# version: 0.14
|
# version: 0.16
|
||||||
# authors: saint@federated.computer
|
# authors: saint@federated.computer
|
||||||
# url: https://gitea.federated.computer/saint/discourse-md5_authentication.git
|
# url: https://gitea.federated.computer/saint/discourse-md5_authentication.git
|
||||||
|
|
||||||
@ -13,200 +13,208 @@
|
|||||||
# enabled_site_setting :legacymd5password_auth_enabled
|
# enabled_site_setting :legacymd5password_auth_enabled
|
||||||
|
|
||||||
after_initialize do
|
after_initialize do
|
||||||
# Extend the SessionController class to include our custom authentication logic
|
# Define a module to hold the legacy MD5 authentication logic
|
||||||
class ::SessionController
|
module LegacyMd5Authentication
|
||||||
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)
|
|
||||||
|
|
||||||
# Validate the length of the password
|
# Constants
|
||||||
return invalid_credentials if params[:password].length > User.max_password_length
|
ITOA64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||||
|
|
||||||
# Find the user by username or email
|
# Override the create method to add our custom authentication checks
|
||||||
user = User.find_by_username_or_email(normalized_login_param)
|
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"
|
# Validate the length of the password
|
||||||
# Check if site is in staff writes-only mode and ensure user is staff if true
|
return invalid_credentials if params[:password].length > User.max_password_length
|
||||||
raise Discourse::ReadOnly if staff_writes_only_mode? && !user&.staff?
|
|
||||||
|
|
||||||
# Apply rate limiting for second factor authentication
|
# Find the user by username or email
|
||||||
rate_limit_second_factor!(user)
|
user = User.find_by_username_or_email(normalized_login_param)
|
||||||
|
|
||||||
if user.present?
|
Rails.logger.warn "MD5 -- BB -- second"
|
||||||
Rails.logger.warn "MD5 -- CC -- user.present"
|
# Check if site is in staff writes-only mode and ensure user is staff if true
|
||||||
# Retrieve the provided password and custom MD5 password hash from user custom fields
|
raise Discourse::ReadOnly if staff_writes_only_mode? && !user&.staff?
|
||||||
password = params[:password]
|
|
||||||
custom_password_md5 = user.custom_fields['custom_password_md5']
|
|
||||||
|
|
||||||
# Log the presence of custom MD5 hash for debugging
|
# Apply rate limiting for second factor authentication
|
||||||
Rails.logger.warn "MD5 -- Check for MD5 password in custom field"
|
rate_limit_second_factor!(user)
|
||||||
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 user.present?
|
||||||
if verify_gossamer_password(password, custom_password_md5)
|
Rails.logger.warn "MD5 -- CC -- user.present"
|
||||||
# If MD5 hash matches, update the user's password and other attributes
|
# Retrieve the provided password and custom MD5 password hash from user custom fields
|
||||||
Rails.logger.warn "MD5 -- MD5 matches"
|
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
|
# Log the presence of custom MD5 hash for debugging
|
||||||
user.password = password
|
Rails.logger.warn "MD5 -- Check for MD5 password in custom field"
|
||||||
user.active = true
|
if custom_password_md5.present?
|
||||||
user.approved = true
|
Rails.logger.warn "MD5 -- MD5 password is present custom_password_md5: #{custom_password_md5} password: #{password}"
|
||||||
user.approved_at = Time.now
|
|
||||||
user.approved_by_id = 1
|
# Verify the provided password against the stored MD5 hash
|
||||||
user.custom_fields['custom_password_md5'] = nil # Clear the custom MD5 field
|
if verify_gossamer_password(password, custom_password_md5)
|
||||||
user.save!
|
# 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"
|
Rails.logger.warn "MD5 -- DD -- user.present"
|
||||||
|
|
||||||
# Generate a new token and hash it
|
# Generate a new token and hash it
|
||||||
token = SecureRandom.hex(20)
|
token = SecureRandom.hex(20)
|
||||||
token_hash = EmailToken.hash_token(token)
|
token_hash = EmailToken.hash_token(token)
|
||||||
|
|
||||||
# Create a confirmed email token for the user
|
# Create a confirmed email token for the user
|
||||||
EmailToken.create!(
|
EmailToken.create!(
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
token_hash: token_hash,
|
token_hash: token_hash,
|
||||||
confirmed: true
|
confirmed: true
|
||||||
)
|
)
|
||||||
Rails.logger.warn("MD5 -- Generated token for user #{user.username}: #{token}")
|
Rails.logger.warn("MD5 -- Generated token for user #{user.username}: #{token}")
|
||||||
|
|
||||||
Rails.logger.warn "MD5 -- Updated user: #{user.id}"
|
Rails.logger.warn "MD5 -- Updated user: #{user.id}"
|
||||||
else
|
else
|
||||||
# If MD5 hash does not match, log the failed login attempt
|
# If MD5 hash does not match, log the failed login attempt
|
||||||
Rails.logger.warn "MD5 -- MD5 Password (hash) incorrect for user: #{user.id}"
|
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}"
|
|
||||||
invalid_credentials
|
invalid_credentials
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
# If the site requires user approval and the user is not yet approved
|
elsif !user.confirm_password?(password)
|
||||||
if login_not_approved_for?(user)
|
# If no MD5 hash is present and the provided password is incorrect
|
||||||
render json: login_not_approved
|
Rails.logger.warn "MD5 -- Password incorrect for user: #{user.id}"
|
||||||
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
|
|
||||||
invalid_credentials
|
invalid_credentials
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
# Check for any login errors
|
# If the site requires user approval and the user is not yet approved
|
||||||
if payload = login_error_check(user)
|
if login_not_approved_for?(user)
|
||||||
return render json: payload
|
render json: login_not_approved
|
||||||
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
# Authenticate second factor if required
|
# Invalidate any invite link for the user
|
||||||
second_factor_auth_result = authenticate_second_factor(user)
|
Invite.invalidate_for_email(user.email)
|
||||||
return render(json: @second_factor_failure_payload) unless second_factor_auth_result.ok
|
|
||||||
|
|
||||||
# If user is active and email is confirmed, proceed with login
|
# Check if the user's password has expired
|
||||||
if user.active && user.email_confirmed?
|
if user.password_expired?(password)
|
||||||
login(user, second_factor_auth_result)
|
render json: { error: "expired", reason: "expired" }
|
||||||
else
|
return
|
||||||
not_activated(user)
|
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
# If no user is found with the provided credentials
|
||||||
|
invalid_credentials
|
||||||
|
return
|
||||||
end
|
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)
|
# Authenticate second factor if required
|
||||||
# Convert a value to a base64-like representation
|
second_factor_auth_result = authenticate_second_factor(user)
|
||||||
result = String.new
|
return render(json: @second_factor_failure_payload) unless second_factor_auth_result.ok
|
||||||
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)
|
# If user is active and email is confirmed, proceed with login
|
||||||
# Extract the salt from the legacy hash
|
if user.active && user.email_confirmed?
|
||||||
parts = legacy_hash.split('$')
|
login(user, second_factor_auth_result)
|
||||||
salt = parts[2]
|
else
|
||||||
|
not_activated(user)
|
||||||
# Limit the salt to 8 characters
|
end
|
||||||
salt = salt[0, 8]
|
end
|
||||||
|
|
||||||
magic = "$GT$"
|
# Helper methods to handle MD5 password verification
|
||||||
Rails.logger.warn "MD5 magic: #{magic}"
|
|
||||||
|
def to64(value, length)
|
||||||
ctx = Digest::MD5.new
|
# Convert a value to a base64-like representation
|
||||||
ctx.update(password)
|
result = String.new
|
||||||
ctx.update(magic)
|
length.times do
|
||||||
ctx.update(salt)
|
result << ITOA64[value & 0x3f]
|
||||||
|
value >>= 6
|
||||||
final = Digest::MD5.new
|
Rails.logger.warn "to64 result: #{result}"
|
||||||
final.update(password)
|
end
|
||||||
final.update(salt)
|
result
|
||||||
final.update(password)
|
end
|
||||||
final_digest = final.digest
|
|
||||||
|
def gossamer_md5_crypt(password, legacy_hash)
|
||||||
password_length = password.length
|
# Extract the salt from the legacy hash
|
||||||
|
parts = legacy_hash.split('$')
|
||||||
# Process the password with various hashing operations
|
salt = parts[2]
|
||||||
while password_length > 0
|
|
||||||
ctx.update(final_digest[0, [password_length, 16].min])
|
# Limit the salt to 8 characters
|
||||||
password_length -= 16
|
salt = salt[0, 8]
|
||||||
end
|
|
||||||
|
magic = "$GT$"
|
||||||
password_length = password.length
|
Rails.logger.warn "MD5 magic: #{magic}"
|
||||||
while password_length > 0
|
|
||||||
if password_length & 1 != 0
|
ctx = Digest::MD5.new
|
||||||
ctx.update("\x00")
|
ctx.update(password)
|
||||||
else
|
ctx.update(magic)
|
||||||
ctx.update(password[0, 1])
|
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
|
end
|
||||||
password_length >>= 1
|
|
||||||
end
|
end
|
||||||
|
|
||||||
final_digest = ctx.digest
|
# Extend the SessionController class to include the LegacyMd5Authentication module
|
||||||
Rails.logger.warn "MD6 final_digest: #{final_digest}"
|
class ::SessionController
|
||||||
|
prepend LegacyMd5Authentication
|
||||||
result = String.new
|
end
|
||||||
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
|
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
|
|
||||||
|
Loading…
Reference in New Issue
Block a user