Skip to content

Instantly share code, notes, and snippets.

@joshbeitelspacher
Created December 9, 2011 06:48
Show Gist options
  • Save joshbeitelspacher/1450528 to your computer and use it in GitHub Desktop.
Save joshbeitelspacher/1450528 to your computer and use it in GitHub Desktop.
A script to copy Redmine users and groups from a Postgres DB to LDAP. Requires a minor modification to Redmine to use SSH passwords.
#!/usr/bin/ruby1.8 -rubygems
require "base64"
require "net/ldap"
require "pg"
require "set"
class User
attr_reader :login
attr_reader :firstname
attr_reader :lastname
attr_reader :mail
attr_reader :password
def initialize(login, firstname, lastname, mail, password)
@login = login
@firstname = firstname
@lastname = lastname
@mail = mail
@password = password
end
end
class Group
attr_reader :name
attr_reader :members
def initialize(name, members)
@name = name
@members = members
end
end
conn = PGconn.open(:dbname => 'redmine')
user_results = conn.exec('SELECT login, firstname, lastname, mail, admin, hashed_password, salt FROM users WHERE status = 1 AND type = \'User\'')
users = {}
admins = Set.new
user_results.values.each do |user|
password = "{SSHA}#{Base64.encode64(user[5].to_a.pack('H*') + user[6]).gsub("\n", '')}"
users[user[0]] = User.new(user[0], user[1], user[2], user[3], password)
if user[4] == 't'
admins << user[0]
end
end
user_results.clear()
group_results = conn.exec('SELECT id, lastname FROM users WHERE status = 1 AND type = \'Group\'')
groups = {"Administrators" => Group.new("Administrators", admins)}
group_results.values.each do |group|
member_results = conn.exec("SELECT login FROM groups_users, users WHERE user_id = id AND group_id = #{group[0]}")
members = Set.new
member_results.values.each do |member|
members << member[0]
end
member_results.clear()
groups[group[1]] = Group.new(group[1], members)
end
group_results.clear()
conn.close()
conn = Net::LDAP.new({
:host => 'localhost',
:port => 389,
:auth => {
:method => :simple,
:username => 'cn=admin,dc=netbeetle,dc=com',
:password => 'secret'
}
})
base = "ou=people,dc=netbeetle,dc=com"
filter = Net::LDAP::Filter.eq("objectClass", "person")
conn.search(:base => base, :filter => filter, :attributes=> ['uid']) do |entry|
user = users[entry.uid.to_s]
if user
puts "Updating #{entry.dn}"
ops = [
[:replace, :uid, user.login],
[:replace, :sn, user.lastname],
[:replace, :cn, "#{user.firstname} #{user.lastname}"],
[:replace, :displayName, "#{user.firstname} #{user.lastname}"],
[:replace, :mail, user.mail],
[:replace, :userPassword, user.password]
]
conn.modify(:dn => entry.dn, :operations => ops)
users.delete(entry.uid.to_s)
else
puts "Removing #{entry.dn}"
conn.delete(:dn => entry.dn)
end
end
users.values.each do |user|
dn = "uid=#{user.login},ou=people,dc=netbeetle,dc=com"
puts "Adding #{dn}"
attr = {
:objectclass => ["inetOrgPerson", "organizationalPerson", "person", "top"],
:uid => user.login,
:sn => user.lastname,
:cn => "#{user.firstname} #{user.lastname}",
:displayName => "#{user.firstname} #{user.lastname}",
:mail => user.mail,
:userPassword => user.password
}
conn.add(:dn => dn, :attributes => attr)
end
base = "ou=groups,dc=netbeetle,dc=com"
filter = Net::LDAP::Filter.eq("objectClass", "groupOfNames")
conn.search(:base => base, :filter => filter, :attributes=> ['cn']) do |entry|
group = groups[entry.cn.to_s]
if group
puts "Updating #{entry.dn}"
member_dns = group.members.map do |member|
"uid=#{member},ou=people,dc=netbeetle,dc=com"
end
ops = [
[:replace, :cn, group.name],
[:replace, :member, member_dns]
]
conn.modify(:dn => entry.dn, :operations => ops)
groups.delete(entry.cn.to_s)
else
puts "Removing #{entry.dn}"
conn.delete(:dn => entry.dn)
end
end
groups.values.each do |group|
dn = "cn=#{group.name},ou=groups,dc=netbeetle,dc=com"
puts "Adding #{dn}"
member_dns = group.members.map do |member|
"uid=#{member},ou=people,dc=netbeetle,dc=com"
end
attr = {
:objectclass => ["groupOfNames", "top"],
:member => member_dns
}
conn.add(:dn => dn, :attributes => attr)
end
diff --git a/app/models/user.rb b/app/models/user.rb
index c06a907..83672e5 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -217,15 +217,15 @@ class User < Principal
if auth_source_id.present?
auth_source.authenticate(self.login, clear_password)
else
- User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
+ User.hash_password("#{clear_password}#{salt}") == hashed_password || User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
end
end
# Generates a random salt and computes hashed_password for +clear_password+
- # The hashed password is stored in the following form: SHA1(salt + SHA1(password))
+ # The hashed password is stored in the following form: SHA1(password + salt)
def salt_password(clear_password)
self.salt = User.generate_salt
- self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
+ self.hashed_password = User.hash_password("#{clear_password}#{salt}")
end
# Does the backend storage allow this user to change their password?
@@ -573,9 +573,9 @@ class User < Principal
Digest::SHA1.hexdigest(clear_password || "")
end
- # Returns a 128bits random salt as a hex string (32 chars long)
+ # Returns a 192bits random salt as a base64 string (32 chars long)
def self.generate_salt
- ActiveSupport::SecureRandom.hex(16)
+ ActiveSupport::SecureRandom.base64(24)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment