Last active
October 13, 2015 15:08
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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