Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save elvanja/4214666 to your computer and use it in GitHub Desktop.
Save elvanja/4214666 to your computer and use it in GitHub Desktop.
Gitlab bulk add permissions respecting ldap groups (without username, for Gitlab version <= 3.1.0)
require "net/ldap"
# if a project belongs to a ldap group
# add users from ldap group to the project
# remove all other users from the project
# else
# add all users as usual
desc "Add or removes users from projects (admin users are added as masters) respecting LDAP and project groups"
task :add_or_remove_users_from_project_teams_using_ldap_groups => :environment do |t, args|
gl = Gitlab.config
return unless gl.ldap_enabled?
return if already_running?(t)
start = Time.now
puts "=== Add or remove users from teams using ldap groups ==="
puts "started @ #{start}"
ldap = Net::LDAP.new(
host: gl.ldap['host'],
port: gl.ldap['port'],
auth: {
method: :simple,
username: gl.ldap['bind_dn'],
password: gl.ldap['password']
}
)
users = User.where(:admin => false).select("id, email")
admins = User.where(:admin => true).select("id, email")
all_user_ids = users.map(&:id)
all_admins_ids = admins.map(&:id)
ldap_groups = {}
Project.find_each do |project|
user_ids = all_user_ids
admin_ids = all_admins_ids
to_reject_ids = []
project.group.try(:name).tap do |group_name|
if ldap_groups[group_name]
ldap_groups[group_name].tap do |ids|
user_ids = ids[:user_ids]
admin_ids = ids[:admin_ids]
to_reject_ids = ids[:rejected_ids]
end
else
get_ldap_group_dn(gl, ldap, group_name) do |group_dn|
ldap_users = get_ldap_users_for_group_dn(gl, ldap, group_dn)
user_ids = users.select {|user| ldap_users.include?(user)}.map(&:id)
admin_ids = admins.select {|user| ldap_users.include?(user)}.map(&:id)
to_reject_ids = (all_user_ids - user_ids) + (all_admins_ids - admin_ids)
ldap_groups[group_name] = {user_ids: user_ids, admin_ids: admin_ids, rejected_ids: to_reject_ids}
end
end
end
existing_user_ids = UsersProject.where(project_id: project.id).pluck(:user_id)
user_ids -= existing_user_ids
admin_ids -= existing_user_ids
to_reject_ids &= existing_user_ids
if user_ids.size > 0
puts "Importing #{user_ids.size} users into #{project.code}"
UsersProject.bulk_import(project, user_ids, UsersProject::DEVELOPER)
end
if admin_ids.size > 0
puts "Importing #{admin_ids.size} admins into #{project.code}"
UsersProject.bulk_import(project, admin_ids, UsersProject::MASTER)
end
if to_reject_ids.size > 0
puts "Removing #{to_reject_ids.size} users from #{project.code}"
UsersProject.bulk_delete(project, to_reject_ids)
end
end
finish = Time.now
puts "finished @ #{finish}"
puts "took #{(finish - start).to_i} seconds"
puts "=== /Add or remove users from teams using ldap groups ==="
end
class LdapUser < Struct.new(:username, :email)
include Comparable
def <=>(other)
email <=> other.email
end
end
def get_ldap_users_for_group_dn(gl, ldap, group_dn)
return [] unless ldap && group_dn
group_filter = Net::LDAP::Filter.eq("memberof", group_dn)
record_type_filter = Net::LDAP::Filter.eq("objectClass", "user")
filter = Net::LDAP::Filter.join(group_filter, record_type_filter)
ldap.search(:base => gl.ldap['base'], :filter => filter, :attributes => [gl.ldap['uid'], 'mail'], :return_result => true).map { |user|
email = user.mail.first rescue ""
LdapUser.new(user.send(gl.ldap['uid']).first, email)
}
end
def get_ldap_group_dn(gl, ldap, group_name)
return nil unless ldap && group_name
name_filter = Net::LDAP::Filter.eq("cn", group_name)
record_type_filter = Net::LDAP::Filter.eq("objectClass", "group")
filter = Net::LDAP::Filter.join(name_filter, record_type_filter)
group_dns = ldap.search(:base => gl.ldap['base'], :filter => filter, :attributes => [], :return_result => true).map(&:dn)
return unless group_dns.size == 1
yield group_dns.first if block_given?
group_dns.first
end
def already_running?(t)
pid_file = "tmp/pids/#{t.name}.pid"
if File.exists?(pid_file)
# check if old process is running
pid = File.read(pid_file).to_i
running = false
begin
Process.kill(0, pid)
puts "#{t.name} is already running with pid #{pid}, checked @ #{Time.now}"
running = true
rescue Errno::EPERM
puts "no permission to query #{t.name} process with pid #{pid}, checked @ #{Time.now}"
running = true
rescue Errno::ESRCH
puts "#{t.name} is not running with pid #{pid}, removing old pid file and continuing, checked @ #{Time.now}"
# no need to delete id now, writing current pid will do the job
rescue
puts "unable to determine status for #{t.name} process with pid #{pid}, checked @ #{Time.now}, error: #{$!}"
running = true
end
return true if running
end
# write current process pid to pid_file
# use file lock to prevent parallel execution
pid = Process.pid
File.open(pid_file, File::RDWR|File::CREAT, 0644) {|f|
f.flock(File::LOCK_EX)
f.write(pid)
}
# no other process is already running
false
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment