From 0a1a4f0a8ba6f12a89344169f228d681d9b4f1d0 Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Fri, 8 Jul 2016 14:19:28 -0500 Subject: [PATCH] Isolate EE LDAP library code in EE module (Part 1) --- CHANGELOG-EE | 1 + app/workers/ldap_group_sync_worker.rb | 2 +- lib/ee/gitlab/ldap/access_levels.rb | 19 + lib/ee/gitlab/ldap/adapter.rb | 44 ++ lib/ee/gitlab/ldap/group.rb | 80 ++++ lib/ee/gitlab/ldap/group_sync.rb | 393 ++++++++++++++++++ lib/gitlab/ldap/access_levels.rb | 17 - lib/gitlab/ldap/adapter.rb | 37 +- lib/gitlab/ldap/group.rb | 78 ---- lib/gitlab/ldap/group_sync.rb | 391 ----------------- .../gitlab/ldap/access_levels_spec.rb | 4 +- spec/lib/ee/gitlab/ldap/adapter_spec.rb | 9 + spec/lib/{ => ee}/gitlab/ldap/group_spec.rb | 2 +- .../{ => ee}/gitlab/ldap/group_sync_spec.rb | 145 ++++--- 14 files changed, 628 insertions(+), 594 deletions(-) create mode 100644 lib/ee/gitlab/ldap/access_levels.rb create mode 100644 lib/ee/gitlab/ldap/adapter.rb create mode 100644 lib/ee/gitlab/ldap/group.rb create mode 100644 lib/ee/gitlab/ldap/group_sync.rb delete mode 100644 lib/gitlab/ldap/access_levels.rb delete mode 100644 lib/gitlab/ldap/group.rb delete mode 100644 lib/gitlab/ldap/group_sync.rb rename spec/lib/{ => ee}/gitlab/ldap/access_levels_spec.rb (93%) create mode 100644 spec/lib/ee/gitlab/ldap/adapter_spec.rb rename spec/lib/{ => ee}/gitlab/ldap/group_spec.rb (96%) rename spec/lib/{ => ee}/gitlab/ldap/group_sync_spec.rb (84%) diff --git a/CHANGELOG-EE b/CHANGELOG-EE index f91e1856ba145e..60f4beccf15f68 100644 --- a/CHANGELOG-EE +++ b/CHANGELOG-EE @@ -3,6 +3,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.10.0 (unreleased) - Rename Git Hooks to Push Rules - Fix EE keys fingerprint add index migration if came from CE + - Isolate EE LDAP library code in EE module (Part 1) !511 v 8.9.5 - Fix of quoted text in lock tooltip. !518 diff --git a/app/workers/ldap_group_sync_worker.rb b/app/workers/ldap_group_sync_worker.rb index 4f1a715126910b..c00d6b7cf890e1 100644 --- a/app/workers/ldap_group_sync_worker.rb +++ b/app/workers/ldap_group_sync_worker.rb @@ -5,7 +5,7 @@ class LdapGroupSyncWorker def perform logger.info 'Started LDAP group sync' - Gitlab::LDAP::GroupSync.execute + EE::Gitlab::LDAP::GroupSync.execute logger.info 'Finished LDAP group sync' end end diff --git a/lib/ee/gitlab/ldap/access_levels.rb b/lib/ee/gitlab/ldap/access_levels.rb new file mode 100644 index 00000000000000..585a9bee197ea5 --- /dev/null +++ b/lib/ee/gitlab/ldap/access_levels.rb @@ -0,0 +1,19 @@ +module EE + module Gitlab + module LDAP + # Create a hash map of member DNs to access levels. The highest + # access level is retained in cases where `set` is called multiple times + # for the same DN. + class AccessLevels < Hash + def set(dns, to:) + dns.each do |dn| + current = self[dn] + + # Keep the higher of the access values. + self[dn] = to if current.nil? || to > current + end + end + end + end + end +end diff --git a/lib/ee/gitlab/ldap/adapter.rb b/lib/ee/gitlab/ldap/adapter.rb new file mode 100644 index 00000000000000..c78197dc1ec08f --- /dev/null +++ b/lib/ee/gitlab/ldap/adapter.rb @@ -0,0 +1,44 @@ +# LDAP connection adapter EE mixin +# +# This module is intended to encapsulate EE-specific adapter methods +# and be included in the `Gitlab::LDAP::Adapter` class. +module EE + module Gitlab + module LDAP + module Adapter + # Get LDAP groups from ou=Groups + # + # cn - filter groups by name + # + # Ex. + # groups("dev*") # return all groups start with 'dev' + # + def groups(cn = "*", size = nil) + options = { + base: config.group_base, + filter: Net::LDAP::Filter.eq("cn", cn) + } + + options.merge!(size: size) if size + + ldap_search(options).map do |entry| + Group.new(entry, self) + end + end + + def group(*args) + groups(*args).first + end + + def dn_matches_filter?(dn, filter) + ldap_search( + base: dn, + filter: filter, + scope: Net::LDAP::SearchScope_BaseObject, + attributes: %w{dn} + ).any? + end + end + end + end +end diff --git a/lib/ee/gitlab/ldap/group.rb b/lib/ee/gitlab/ldap/group.rb new file mode 100644 index 00000000000000..b088514e8c9996 --- /dev/null +++ b/lib/ee/gitlab/ldap/group.rb @@ -0,0 +1,80 @@ +module EE + module Gitlab + module LDAP + class Group + attr_accessor :adapter + + def self.find_by_cn(cn, adapter) + cn = Net::LDAP::Filter.escape(cn) + adapter.group(cn) + end + + def initialize(entry, adapter=nil) + Rails.logger.debug { "Instantiating #{self.class.name} with LDIF:\n#{entry.to_ldif}" } + @entry = entry + @adapter = adapter + end + + def active_directory? + adapter.config.active_directory + end + + def cn + entry.cn.first + end + + def name + cn + end + + def path + name.parameterize + end + + def memberuid? + entry.respond_to? :memberuid + end + + def member_uids + entry.memberuid + end + + def member_dns + dns = [] + + # There's an edge-case with AD where sometimes a recursive search + # doesn't return all users at the top-level. Concat recursive results + # with regular results to be safe. See gitlab-ee#484 + if active_directory? + dns = adapter.dns_for_filter(active_directory_recursive_memberof_filter) + end + + if (entry.respond_to? :member) && (entry.respond_to? :submember) + dns.concat(entry.member + entry.submember) + elsif entry.respond_to? :member + dns.concat(entry.member) + elsif entry.respond_to? :uniquemember + dns.concat(entry.uniquemember) + elsif entry.respond_to? :memberof + dns.concat(entry.memberof) + else + Rails.logger.warn("Could not find member DNs for LDAP group #{entry.inspect}") + end + dns.uniq + end + + private + + # We use the ActiveDirectory LDAP_MATCHING_RULE_IN_CHAIN matching rule; see + # http://msdn.microsoft.com/en-us/library/aa746475%28VS.85%29.aspx#code-snippet-5 + def active_directory_recursive_memberof_filter + Net::LDAP::Filter.ex("memberOf:1.2.840.113556.1.4.1941", entry.dn) + end + + def entry + @entry + end + end + end + end +end diff --git a/lib/ee/gitlab/ldap/group_sync.rb b/lib/ee/gitlab/ldap/group_sync.rb new file mode 100644 index 00000000000000..8fcff7a25d8934 --- /dev/null +++ b/lib/ee/gitlab/ldap/group_sync.rb @@ -0,0 +1,393 @@ +require 'net/ldap/dn' + +module EE + module Gitlab + module LDAP + class GroupSync + attr_reader :provider + + # Open a connection so we can run all queries through it. + # It's more efficient than the default of opening/closing per LDAP query. + def self.open(provider, &block) + ::Gitlab::LDAP::Adapter.open(provider) do |adapter| + block.call(self.new(provider, adapter)) + end + end + + def self.execute + # Shuffle providers to prevent a scenario where sync fails after a time + # and only the first provider or two get synced. This shuffles the order + # so subsequent syncs should eventually get to all providers. Obviously + # we should avoid failure, but this is an additional safeguard. + ::Gitlab::LDAP::Config.providers.shuffle.each do |provider| + self.open(provider) do |group_sync| + group_sync.update_permissions + end + end + + true + end + + def initialize(provider, adapter = nil) + @adapter = adapter + @provider = provider + end + + def update_permissions + if group_base.present? + logger.debug { "Performing LDAP group sync for '#{provider}' provider" } + sync_groups + logger.debug { "Finished LDAP group sync for '#{provider}' provider" } + else + logger.debug { "No `group_base` configured for '#{provider}' provider. Skipping" } + end + + if admin_group.present? + logger.debug { "Syncing admin users for '#{provider}' provider" } + sync_admin_users + logger.debug { "Finished syncing admin users for '#{provider}' provider" } + else + logger.debug { "No `admin_group` configured for '#{provider}' provider. Skipping" } + end + + if external_groups.empty? + logger.debug { "No `external_groups` configured for '#{provider}' provider. Skipping" } + else + logger.debug { "Syncing external users for '#{provider}' provider" } + sync_external_users + logger.debug { "Finished syncing external users for '#{provider}' provider" } + end + + nil + end + + # Iterate of all GitLab groups with LDAP links. Build an access hash + # representing a user's highest access level among the LDAP links within + # the same GitLab group. + def sync_groups + # Order results by last_ldap_sync_at ASC so groups with older last + # sync time are handled first + groups_where_group_links_with_provider_ordered.each do |group| + lease = ::Gitlab::ExclusiveLease.new( + "ldap_group_sync:#{provider}:#{group.id}", + timeout: 3600 + ) + next unless lease.try_obtain + + logger.debug { "Syncing '#{group.name}' group" } + + access_levels = AccessLevels.new + # Only iterate over group links for the current provider + group.ldap_group_links.with_provider(provider).each do |group_link| + if member_dns = dns_for_group_cn(group_link.cn) + access_levels.set(member_dns, to: group_link.group_access) + logger.debug do + "Resolved '#{group.name}' group member access: #{access_levels.to_hash}" + end + end + end + + update_existing_group_membership(group, access_levels) + add_new_members(group, access_levels) + + group.update(last_ldap_sync_at: Time.now) + + logger.debug { "Finished syncing '#{group.name}' group" } + end + end + + # Update global administrators based on the specified admin group CN + def sync_admin_users + admin_group_member_dns = dns_for_group_cn(admin_group) + current_admin_users = ::User.admins.with_provider(provider) + verified_admin_users = [] + + # Verify existing admin users and add new ones. + admin_group_member_dns.each do |member_dn| + user = ::Gitlab::LDAP::User.find_by_uid_and_provider(member_dn, provider) + + if user.present? + user.admin = true + user.save + verified_admin_users << user + else + logger.debug do + <<-MSG.strip_heredoc.tr("\n", ' ') + #{self.class.name}: User with DN `#{member_dn}` should have admin + access but there is no user in GitLab with that identity. + Membership will be updated once the user signs in for the first time. + MSG + end + end + end + + # Revoke the unverified admins. + current_admin_users.each do |user| + unless verified_admin_users.include?(user) + user.admin = false + user.save + end + end + end + + # Update external users based on the specified external groups CN + def sync_external_users + current_external_users = ::User.external.with_provider(provider) + verified_external_users = [] + + external_groups.each do |group| + group_dns = dns_for_group_cn(group) + + group_dns.each do |member_dn| + user = ::Gitlab::LDAP::User.find_by_uid_and_provider(member_dn, provider) + + if user.present? + user.external = true + user.save + verified_external_users << user + else + logger.debug do + <<-MSG.strip_heredoc.tr("\n", ' ') + #{self.class.name}: User with DN `#{member_dn}` should be marked as + external but there is no user in GitLab with that identity. + Membership will be updated once the user signs in for the first time. + MSG + end + end + end + end + + update_external_permissions(current_external_users, verified_external_users) + end + + private + + # Cache LDAP group member DNs so we don't query LDAP groups more than once. + def dns_for_group_cn(group_cn) + @dns_for_group_cn ||= Hash.new { |h, k| h[k] = ldap_group_member_dns(k) } + @dns_for_group_cn[group_cn] + end + + # Cache user DN so we don't generate excess queries to map UID to DN + def dn_for_uid(uid) + @dn_for_uid ||= Hash.new { |h, k| h[k] = member_uid_to_dn(k) } + @dn_for_uid[uid] + end + + def adapter + @adapter ||= ::Gitlab::LDAP::Adapter.new(provider) + end + + def config + @config ||= ::Gitlab::LDAP::Config.new(provider) + end + + def group_base + config.group_base + end + + def admin_group + config.admin_group + end + + def external_groups + config.external_groups + end + + def ldap_group_member_dns(ldap_group_cn) + ldap_group = Group.find_by_cn(ldap_group_cn, adapter) + unless ldap_group.present? + logger.warn { "Cannot find LDAP group with CN '#{ldap_group_cn}'. Skipping" } + return [] + end + + member_dns = ldap_group.member_dns + if member_dns.empty? + # Group must be empty + return [] unless ldap_group.memberuid? + + members = ldap_group.member_uids + member_dns = members.map { |uid| dn_for_uid(uid) } + end + ensure_full_dns!(member_dns) + + logger.debug { "Members in '#{ldap_group.name}' LDAP group: #{member_dns}" } + + # Various lookups in this method could return `nil` values. + # Compact the array to remove those entries + member_dns.compact + end + + # At least one customer reported that their LDAP `member` values contain + # only `uid=username` and not the full DN. This method allows us to + # account for that. See gitlab-ee#442 + def ensure_full_dns!(dns) + dns.map! do |dn| + begin + parsed_dn = Net::LDAP::DN.new(dn).to_a + rescue RuntimeError => e + # Net::LDAP raises a generic RuntimeError. Bad library! Bad! + logger.error { "Found malformed DN: '#{dn}'. Skipping. #{e.message}" } + next + end + + # If there is more than one key/value set we must have a full DN, + # or at least the probability is higher. + if parsed_dn.count > 2 + dn + elsif parsed_dn[0] == 'uid' + dn_for_uid(parsed_dn[1]) + else + logger.warn { "Found potentially malformed/incomplete DN: '#{dn}'" } + dn + end + end + + # Remove `nil` values generated by the rescue above. + dns.compact! + end + + def member_uid_to_dn(uid) + identity = Identity.find_by(provider: provider, secondary_extern_uid: uid) + + if identity.present? + # Use the DN on record in GitLab when it's available + identity.extern_uid + else + ldap_user = ::Gitlab::LDAP::Person.find_by_uid(uid, adapter) + + # Can't find a matching user for group entry + return nil unless ldap_user.present? + + # Update user identity so we don't have to go through this again + update_identity(ldap_user.dn, uid) + + ldap_user.dn + end + end + + def update_identity(dn, uid) + identity = + Identity.find_by(provider: provider, extern_uid: dn) + + # User may not exist in GitLab yet. Skip. + return unless identity.present? + + identity.secondary_extern_uid = uid + identity.save + end + + def update_existing_group_membership(group, access_levels) + logger.debug { "Updating existing membership for '#{group.name}' group" } + + select_and_preload_group_members(group).each do |member| + user = member.user + identity = user.identities.select(:id, :extern_uid) + .with_provider(provider).first + member_dn = identity.extern_uid + + # Skip if this is not an LDAP user with a valid `extern_uid`. + next unless member_dn.present? + + # Prevent shifting group membership, in case where user is a member + # of two LDAP groups from different providers linked to the same + # GitLab group. This is not ideal, but preserves existing behavior. + if user.ldap_identity.id != identity.id + access_levels.delete(member_dn) + next + end + + desired_access = access_levels[member_dn] + + # Don't do anything if the user already has the desired access level + if member.access_level == desired_access + access_levels.delete(member_dn) + next + end + + # Check and update the access level. If `desired_access` is `nil` + # we need to delete the user from the group. + if desired_access.present? + add_or_update_user_membership(user, group, desired_access) + + # Delete this entry from the hash now that we've acted on it + access_levels.delete(member_dn) + elsif group.last_owner?(user) + warn_cannot_remove_last_owner(user, group) + else + group.users.delete(user) + end + end + end + + def update_external_permissions(users, verified) + # Restore normal access to users no longer found in the external groups + users.each do |user| + unless verified.include?(user) + user.external = false + user.save + end + end + end + + def add_new_members(group, access_levels) + logger.debug { "Adding new members to '#{group.name}' group" } + + access_levels.each do |member_dn, access_level| + user = ::Gitlab::LDAP::User.find_by_uid_and_provider(member_dn, provider) + + if user.present? + add_or_update_user_membership(user, group, access_level) + else + logger.debug do + <<-MSG.strip_heredoc.tr("\n", ' ') + #{self.class.name}: User with DN `#{member_dn}` should have access + to '#{group.name}' group but there is no user in GitLab with that + identity. Membership will be updated once the user signs in for + the first time. + MSG + end + end + end + end + + def add_or_update_user_membership(user, group, access) + # Prevent the last owner of a group from being demoted + if access < ::Gitlab::Access::OWNER && group.last_owner?(user) + warn_cannot_remove_last_owner(user, group) + else + # If you pass the user object, instead of just user ID, + # it saves an extra user database query. + group.add_users([user], access, skip_notification: true) + end + end + + def warn_cannot_remove_last_owner(user, group) + logger.warn do + <<-MSG.strip_heredoc.tr("\n", ' ') + #{self.class.name}: LDAP group sync cannot remove #{user.name} + (#{user.id}) from group #{group.name} (#{group.id}) as this is + the group's last owner + MSG + end + end + + def select_and_preload_group_members(group) + group.members.select_access_level_and_user + .with_identity_provider(provider).preload(:user) + end + + def groups_where_group_links_with_provider_ordered + ::Group.where_group_links_with_provider(provider) + .preload(:ldap_group_links) + .reorder('last_ldap_sync_at ASC, namespaces.id ASC') + .distinct + end + + def logger + Rails.logger + end + end + end + end +end diff --git a/lib/gitlab/ldap/access_levels.rb b/lib/gitlab/ldap/access_levels.rb deleted file mode 100644 index 234c13bf46fec0..00000000000000 --- a/lib/gitlab/ldap/access_levels.rb +++ /dev/null @@ -1,17 +0,0 @@ -module Gitlab - module LDAP - # Create a hash map of member DNs to access levels. The highest - # access level is retained in cases where `set` is called multiple times - # for the same DN. - class AccessLevels < Hash - def set(dns, to:) - dns.each do |dn| - current = self[dn] - - # Keep the higher of the access values. - self[dn] = to if current.nil? || to > current - end - end - end - end -end diff --git a/lib/gitlab/ldap/adapter.rb b/lib/gitlab/ldap/adapter.rb index 79e360cd86c74e..19c01ae753d9b8 100644 --- a/lib/gitlab/ldap/adapter.rb +++ b/lib/gitlab/ldap/adapter.rb @@ -1,6 +1,12 @@ +# LDAP connection adapter +# +# Contains methods common to both GitLab CE and EE. +# All EE methods should be in `EE::Gitlab::LDAP::Adapter` only. module Gitlab module LDAP class Adapter + include EE::Gitlab::LDAP::Adapter + attr_reader :provider, :ldap def self.open(provider, &block) @@ -22,30 +28,6 @@ def config Gitlab::LDAP::Config.new(provider) end - # Get LDAP groups from ou=Groups - # - # cn - filter groups by name - # - # Ex. - # groups("dev*") # return all groups start with 'dev' - # - def groups(cn = "*", size = nil) - options = { - base: config.group_base, - filter: Net::LDAP::Filter.eq("cn", cn) - } - - options.merge!(size: size) if size - - ldap_search(options).map do |entry| - Gitlab::LDAP::Group.new(entry, self) - end - end - - def group(*args) - groups(*args).first - end - def users(field, value, limit = nil) if field.to_sym == :dn options = { @@ -86,13 +68,6 @@ def user(*args) users(*args).first end - def dn_matches_filter?(dn, filter) - ldap_search(base: dn, - filter: filter, - scope: Net::LDAP::SearchScope_BaseObject, - attributes: %w{dn}).any? - end - def dns_for_filter(filter) ldap_search( base: config.base, diff --git a/lib/gitlab/ldap/group.rb b/lib/gitlab/ldap/group.rb deleted file mode 100644 index c5b28782bfa1a9..00000000000000 --- a/lib/gitlab/ldap/group.rb +++ /dev/null @@ -1,78 +0,0 @@ -module Gitlab - module LDAP - class Group - attr_accessor :adapter - - def self.find_by_cn(cn, adapter) - cn = Net::LDAP::Filter.escape(cn) - adapter.group(cn) - end - - def initialize(entry, adapter=nil) - Rails.logger.debug { "Instantiating #{self.class.name} with LDIF:\n#{entry.to_ldif}" } - @entry = entry - @adapter = adapter - end - - def active_directory? - adapter.config.active_directory - end - - def cn - entry.cn.first - end - - def name - cn - end - - def path - name.parameterize - end - - def memberuid? - entry.respond_to? :memberuid - end - - def member_uids - entry.memberuid - end - - def member_dns - dns = [] - - # There's an edge-case with AD where sometimes a recursive search - # doesn't return all users at the top-level. Concat recursive results - # with regular results to be safe. See gitlab-ee#484 - if active_directory? - dns = adapter.dns_for_filter(active_directory_recursive_memberof_filter) - end - - if (entry.respond_to? :member) && (entry.respond_to? :submember) - dns.concat(entry.member + entry.submember) - elsif entry.respond_to? :member - dns.concat(entry.member) - elsif entry.respond_to? :uniquemember - dns.concat(entry.uniquemember) - elsif entry.respond_to? :memberof - dns.concat(entry.memberof) - else - Rails.logger.warn("Could not find member DNs for LDAP group #{entry.inspect}") - end - dns.uniq - end - - private - - # We use the ActiveDirectory LDAP_MATCHING_RULE_IN_CHAIN matching rule; see - # http://msdn.microsoft.com/en-us/library/aa746475%28VS.85%29.aspx#code-snippet-5 - def active_directory_recursive_memberof_filter - Net::LDAP::Filter.ex("memberOf:1.2.840.113556.1.4.1941", entry.dn) - end - - def entry - @entry - end - end - end -end diff --git a/lib/gitlab/ldap/group_sync.rb b/lib/gitlab/ldap/group_sync.rb deleted file mode 100644 index 88886759755d53..00000000000000 --- a/lib/gitlab/ldap/group_sync.rb +++ /dev/null @@ -1,391 +0,0 @@ -require 'net/ldap/dn' - -module Gitlab - module LDAP - class GroupSync - attr_reader :provider - - # Open a connection so we can run all queries through it. - # It's more efficient than the default of opening/closing per LDAP query. - def self.open(provider, &block) - Gitlab::LDAP::Adapter.open(provider) do |adapter| - block.call(self.new(provider, adapter)) - end - end - - def self.execute - # Shuffle providers to prevent a scenario where sync fails after a time - # and only the first provider or two get synced. This shuffles the order - # so subsequent syncs should eventually get to all providers. Obviously - # we should avoid failure, but this is an additional safeguard. - Gitlab::LDAP::Config.providers.shuffle.each do |provider| - self.open(provider) do |group_sync| - group_sync.update_permissions - end - end - - true - end - - def initialize(provider, adapter = nil) - @adapter = adapter - @provider = provider - end - - def update_permissions - if group_base.present? - logger.debug { "Performing LDAP group sync for '#{provider}' provider" } - sync_groups - logger.debug { "Finished LDAP group sync for '#{provider}' provider" } - else - logger.debug { "No `group_base` configured for '#{provider}' provider. Skipping" } - end - - if admin_group.present? - logger.debug { "Syncing admin users for '#{provider}' provider" } - sync_admin_users - logger.debug { "Finished syncing admin users for '#{provider}' provider" } - else - logger.debug { "No `admin_group` configured for '#{provider}' provider. Skipping" } - end - - if external_groups.empty? - logger.debug { "No `external_groups` configured for '#{provider}' provider. Skipping" } - else - logger.debug { "Syncing external users for '#{provider}' provider" } - sync_external_users - logger.debug { "Finished syncing external users for '#{provider}' provider" } - end - - nil - end - - # Iterate of all GitLab groups with LDAP links. Build an access hash - # representing a user's highest access level among the LDAP links within - # the same GitLab group. - def sync_groups - # Order results by last_ldap_sync_at ASC so groups with older last - # sync time are handled first - groups_where_group_links_with_provider_ordered.each do |group| - lease = Gitlab::ExclusiveLease.new( - "ldap_group_sync:#{provider}:#{group.id}", - timeout: 3600 - ) - next unless lease.try_obtain - - logger.debug { "Syncing '#{group.name}' group" } - - access_levels = Gitlab::LDAP::AccessLevels.new - # Only iterate over group links for the current provider - group.ldap_group_links.with_provider(provider).each do |group_link| - if member_dns = dns_for_group_cn(group_link.cn) - access_levels.set(member_dns, to: group_link.group_access) - logger.debug do - "Resolved '#{group.name}' group member access: #{access_levels.to_hash}" - end - end - end - - update_existing_group_membership(group, access_levels) - add_new_members(group, access_levels) - - group.update(last_ldap_sync_at: Time.now) - - logger.debug { "Finished syncing '#{group.name}' group" } - end - end - - # Update global administrators based on the specified admin group CN - def sync_admin_users - admin_group_member_dns = dns_for_group_cn(admin_group) - current_admin_users = ::User.admins.with_provider(provider) - verified_admin_users = [] - - # Verify existing admin users and add new ones. - admin_group_member_dns.each do |member_dn| - user = Gitlab::LDAP::User.find_by_uid_and_provider(member_dn, provider) - - if user.present? - user.admin = true - user.save - verified_admin_users << user - else - logger.debug do - <<-MSG.strip_heredoc.tr("\n", ' ') - #{self.class.name}: User with DN `#{member_dn}` should have admin - access but there is no user in GitLab with that identity. - Membership will be updated once the user signs in for the first time. - MSG - end - end - end - - # Revoke the unverified admins. - current_admin_users.each do |user| - unless verified_admin_users.include?(user) - user.admin = false - user.save - end - end - end - - # Update external users based on the specified external groups CN - def sync_external_users - current_external_users = ::User.external.with_provider(provider) - verified_external_users = [] - - external_groups.each do |group| - group_dns = dns_for_group_cn(group) - - group_dns.each do |member_dn| - user = Gitlab::LDAP::User.find_by_uid_and_provider(member_dn, provider) - - if user.present? - user.external = true - user.save - verified_external_users << user - else - logger.debug do - <<-MSG.strip_heredoc.tr("\n", ' ') - #{self.class.name}: User with DN `#{member_dn}` should be marked as - external but there is no user in GitLab with that identity. - Membership will be updated once the user signs in for the first time. - MSG - end - end - end - end - - update_external_permissions(current_external_users, verified_external_users) - end - - private - - # Cache LDAP group member DNs so we don't query LDAP groups more than once. - def dns_for_group_cn(group_cn) - @dns_for_group_cn ||= Hash.new { |h, k| h[k] = ldap_group_member_dns(k) } - @dns_for_group_cn[group_cn] - end - - # Cache user DN so we don't generate excess queries to map UID to DN - def dn_for_uid(uid) - @dn_for_uid ||= Hash.new { |h, k| h[k] = member_uid_to_dn(k) } - @dn_for_uid[uid] - end - - def adapter - @adapter ||= Gitlab::LDAP::Adapter.new(provider) - end - - def config - @config ||= Gitlab::LDAP::Config.new(provider) - end - - def group_base - config.group_base - end - - def admin_group - config.admin_group - end - - def external_groups - config.external_groups - end - - def ldap_group_member_dns(ldap_group_cn) - ldap_group = Gitlab::LDAP::Group.find_by_cn(ldap_group_cn, adapter) - unless ldap_group.present? - logger.warn { "Cannot find LDAP group with CN '#{ldap_group_cn}'. Skipping" } - return [] - end - - member_dns = ldap_group.member_dns - if member_dns.empty? - # Group must be empty - return [] unless ldap_group.memberuid? - - members = ldap_group.member_uids - member_dns = members.map { |uid| dn_for_uid(uid) } - end - ensure_full_dns!(member_dns) - - logger.debug { "Members in '#{ldap_group.name}' LDAP group: #{member_dns}" } - - # Various lookups in this method could return `nil` values. - # Compact the array to remove those entries - member_dns.compact - end - - # At least one customer reported that their LDAP `member` values contain - # only `uid=username` and not the full DN. This method allows us to - # account for that. See gitlab-ee#442 - def ensure_full_dns!(dns) - dns.map! do |dn| - begin - parsed_dn = Net::LDAP::DN.new(dn).to_a - rescue RuntimeError => e - # Net::LDAP raises a generic RuntimeError. Bad library! Bad! - logger.error { "Found malformed DN: '#{dn}'. Skipping. #{e.message}" } - next - end - - # If there is more than one key/value set we must have a full DN, - # or at least the probability is higher. - if parsed_dn.count > 2 - dn - elsif parsed_dn[0] == 'uid' - dn_for_uid(parsed_dn[1]) - else - logger.warn { "Found potentially malformed/incomplete DN: '#{dn}'" } - dn - end - end - - # Remove `nil` values generated by the rescue above. - dns.compact! - end - - def member_uid_to_dn(uid) - identity = Identity.find_by(provider: provider, secondary_extern_uid: uid) - - if identity.present? - # Use the DN on record in GitLab when it's available - identity.extern_uid - else - ldap_user = Gitlab::LDAP::Person.find_by_uid(uid, adapter) - - # Can't find a matching user for group entry - return nil unless ldap_user.present? - - # Update user identity so we don't have to go through this again - update_identity(ldap_user.dn, uid) - - ldap_user.dn - end - end - - def update_identity(dn, uid) - identity = - Identity.find_by(provider: provider, extern_uid: dn) - - # User may not exist in GitLab yet. Skip. - return unless identity.present? - - identity.secondary_extern_uid = uid - identity.save - end - - def update_existing_group_membership(group, access_levels) - logger.debug { "Updating existing membership for '#{group.name}' group" } - - select_and_preload_group_members(group).each do |member| - user = member.user - identity = user.identities.select(:id, :extern_uid) - .with_provider(provider).first - member_dn = identity.extern_uid - - # Skip if this is not an LDAP user with a valid `extern_uid`. - next unless member_dn.present? - - # Prevent shifting group membership, in case where user is a member - # of two LDAP groups from different providers linked to the same - # GitLab group. This is not ideal, but preserves existing behavior. - if user.ldap_identity.id != identity.id - access_levels.delete(member_dn) - next - end - - desired_access = access_levels[member_dn] - - # Don't do anything if the user already has the desired access level - if member.access_level == desired_access - access_levels.delete(member_dn) - next - end - - # Check and update the access level. If `desired_access` is `nil` - # we need to delete the user from the group. - if desired_access.present? - add_or_update_user_membership(user, group, desired_access) - - # Delete this entry from the hash now that we've acted on it - access_levels.delete(member_dn) - elsif group.last_owner?(user) - warn_cannot_remove_last_owner(user, group) - else - group.users.delete(user) - end - end - end - - def update_external_permissions(users, verified) - # Restore normal access to users no longer found in the external groups - users.each do |user| - unless verified.include?(user) - user.external = false - user.save - end - end - end - - def add_new_members(group, access_levels) - logger.debug { "Adding new members to '#{group.name}' group" } - - access_levels.each do |member_dn, access_level| - user = Gitlab::LDAP::User.find_by_uid_and_provider(member_dn, provider) - - if user.present? - add_or_update_user_membership(user, group, access_level) - else - logger.debug do - <<-MSG.strip_heredoc.tr("\n", ' ') - #{self.class.name}: User with DN `#{member_dn}` should have access - to '#{group.name}' group but there is no user in GitLab with that - identity. Membership will be updated once the user signs in for - the first time. - MSG - end - end - end - end - - def add_or_update_user_membership(user, group, access) - # Prevent the last owner of a group from being demoted - if access < Gitlab::Access::OWNER && group.last_owner?(user) - warn_cannot_remove_last_owner(user, group) - else - # If you pass the user object, instead of just user ID, - # it saves an extra user database query. - group.add_users([user], access, skip_notification: true) - end - end - - def warn_cannot_remove_last_owner(user, group) - logger.warn do - <<-MSG.strip_heredoc.tr("\n", ' ') - #{self.class.name}: LDAP group sync cannot remove #{user.name} - (#{user.id}) from group #{group.name} (#{group.id}) as this is - the group's last owner - MSG - end - end - - def select_and_preload_group_members(group) - group.members.select_access_level_and_user - .with_identity_provider(provider).preload(:user) - end - - def groups_where_group_links_with_provider_ordered - ::Group.where_group_links_with_provider(provider) - .preload(:ldap_group_links) - .reorder('last_ldap_sync_at ASC, namespaces.id ASC') - .distinct - end - - def logger - Rails.logger - end - end - end -end diff --git a/spec/lib/gitlab/ldap/access_levels_spec.rb b/spec/lib/ee/gitlab/ldap/access_levels_spec.rb similarity index 93% rename from spec/lib/gitlab/ldap/access_levels_spec.rb rename to spec/lib/ee/gitlab/ldap/access_levels_spec.rb index 5fea40a21aeeea..d874f7d1ae32bc 100644 --- a/spec/lib/gitlab/ldap/access_levels_spec.rb +++ b/spec/lib/ee/gitlab/ldap/access_levels_spec.rb @@ -1,8 +1,8 @@ require 'spec_helper' -describe Gitlab::LDAP::AccessLevels, lib: true do +describe EE::Gitlab::LDAP::AccessLevels, lib: true do describe '#set' do - let(:access_levels) { Gitlab::LDAP::AccessLevels.new } + let(:access_levels) { described_class.new } let(:dns) do %w( uid=johndoe,ou=users,dc=example,dc=com diff --git a/spec/lib/ee/gitlab/ldap/adapter_spec.rb b/spec/lib/ee/gitlab/ldap/adapter_spec.rb new file mode 100644 index 00000000000000..5e7db6f5c39a08 --- /dev/null +++ b/spec/lib/ee/gitlab/ldap/adapter_spec.rb @@ -0,0 +1,9 @@ +require 'spec_helper' + +# Test things specific to the EE mixin, but run the actual tests +# against the main adapter class to ensure it's properly included +describe Gitlab::LDAP::Adapter, lib: true do + subject { Gitlab::LDAP::Adapter.new 'ldapmain' } + + it { is_expected.to include_module(EE::Gitlab::LDAP::Adapter) } +end diff --git a/spec/lib/gitlab/ldap/group_spec.rb b/spec/lib/ee/gitlab/ldap/group_spec.rb similarity index 96% rename from spec/lib/gitlab/ldap/group_spec.rb rename to spec/lib/ee/gitlab/ldap/group_spec.rb index bff80e54f5993f..a3434456bb7f68 100644 --- a/spec/lib/gitlab/ldap/group_spec.rb +++ b/spec/lib/ee/gitlab/ldap/group_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::LDAP::Group, lib: true do +describe EE::Gitlab::LDAP::Group, lib: true do describe '#member_dns' do def ldif Net::LDAP::Entry.from_single_ldif_string( diff --git a/spec/lib/gitlab/ldap/group_sync_spec.rb b/spec/lib/ee/gitlab/ldap/group_sync_spec.rb similarity index 84% rename from spec/lib/gitlab/ldap/group_sync_spec.rb rename to spec/lib/ee/gitlab/ldap/group_sync_spec.rb index d692de89d413bb..6665ab932b1ff7 100644 --- a/spec/lib/gitlab/ldap/group_sync_spec.rb +++ b/spec/lib/ee/gitlab/ldap/group_sync_spec.rb @@ -1,13 +1,13 @@ require 'spec_helper' -describe Gitlab::LDAP::GroupSync, lib: true do - let(:group_sync) { Gitlab::LDAP::GroupSync.new('ldapmain') } +describe EE::Gitlab::LDAP::GroupSync, lib: true do + let(:group_sync) { described_class.new('ldapmain') } let(:config) { double(:config, active_directory: false) } let(:adapter) { double(:adapter, config: config) } subject { group_sync } before do - allow_any_instance_of(Gitlab::ExclusiveLease) + allow_any_instance_of(::Gitlab::ExclusiveLease) .to receive(:try_obtain).and_return(true) end @@ -59,7 +59,7 @@ context 'with all functionality against one LDAP group type' do before do - allow_any_instance_of(Gitlab::LDAP::Group) + allow_any_instance_of(EE::Gitlab::LDAP::Group) .to receive(:adapter).and_return(adapter) user1.identities.create( @@ -71,19 +71,19 @@ extern_uid: "uid=#{user2.username},ou=users,dc=example,dc=com" ) - allow(Gitlab::LDAP::Group) + allow(EE::Gitlab::LDAP::Group) .to receive(:find_by_cn) - .with('ldap_group1', kind_of(Gitlab::LDAP::Adapter)) - .and_return(Gitlab::LDAP::Group.new(ldap_group1)) + .with('ldap_group1', kind_of(::Gitlab::LDAP::Adapter)) + .and_return(EE::Gitlab::LDAP::Group.new(ldap_group1)) group1.ldap_group_links.create( cn: 'ldap_group1', - group_access: Gitlab::Access::DEVELOPER, + group_access: ::Gitlab::Access::DEVELOPER, provider: 'ldapmain' ) group2.ldap_group_links.create( cn: 'ldap_group1', - group_access: Gitlab::Access::OWNER, + group_access: ::Gitlab::Access::OWNER, provider: 'ldapmain' ) end @@ -92,9 +92,9 @@ before do # Pre-populate the group with some users group1.add_users([user1.id], - Gitlab::Access::MASTER, skip_notification: true) + ::Gitlab::Access::MASTER, skip_notification: true) group2.add_users([user2.id], - Gitlab::Access::DEVELOPER, skip_notification: true) + ::Gitlab::Access::DEVELOPER, skip_notification: true) end it 'adds new members' do @@ -108,7 +108,7 @@ .to change { group1.members.where( user_id: user1.id, - access_level: Gitlab::Access::DEVELOPER + access_level: ::Gitlab::Access::DEVELOPER ).any? }.from(false).to(true) end @@ -118,7 +118,7 @@ .to change { group2.members.where( user_id: user2.id, - access_level: Gitlab::Access::OWNER + access_level: ::Gitlab::Access::OWNER ).any? }.from(false).to(true) end @@ -136,9 +136,9 @@ .create(provider: group_sync.provider, extern_uid: "uid=johndoe,ou=users,dc=example,dc=com" ) group1.add_users([user_without_group.id], - Gitlab::Access::MASTER, skip_notification: true) + ::Gitlab::Access::MASTER, skip_notification: true) group2.add_users([user_without_group.id], - Gitlab::Access::OWNER, skip_notification: true) + ::Gitlab::Access::OWNER, skip_notification: true) end it 'removes the user from the group' do @@ -157,27 +157,26 @@ before do group1.ldap_group_links.create( cn: 'ldap_group1', - group_access: Gitlab::Access::DEVELOPER, + group_access: ::Gitlab::Access::DEVELOPER, provider: 'ldapmain' ) group1.add_users([user1.id, user2.id], - Gitlab::Access::OWNER, skip_notification: true) + ::Gitlab::Access::OWNER, skip_notification: true) end - # Check two users in a loop to uncover any stale group owner data it 'downgrades one user but not the other' do group_sync.sync_groups expect(group1.members.pluck(:access_level).sort) - .to eq([Gitlab::Access::DEVELOPER, Gitlab::Access::OWNER]) + .to eq([::Gitlab::Access::DEVELOPER, ::Gitlab::Access::OWNER]) end context 'when user is a member of two groups from different providers' do let(:config) { double(:config, active_directory: false, provider: 'ldapsecondary') } let(:adapter) { double(:adapter, config: config) } let(:secondary_group_sync) do - Gitlab::LDAP::GroupSync.new('ldapsecondary', adapter) + described_class.new('ldapsecondary', adapter) end let(:ldap_secondary_group1) do Net::LDAP::Entry.from_single_ldif_string(<<-EOS.strip_heredoc) @@ -194,14 +193,14 @@ let(:user_w_multiple_ids) { create(:user) } before do - allow(Gitlab::LDAP::Group) + allow(EE::Gitlab::LDAP::Group) .to receive(:find_by_cn) .with('ldap_group1', any_args) - .and_return(Gitlab::LDAP::Group.new(ldap_group1)) - allow(Gitlab::LDAP::Group) + .and_return(EE::Gitlab::LDAP::Group.new(ldap_group1)) + allow(EE::Gitlab::LDAP::Group) .to receive(:find_by_cn) .with('ldap_secondary_group1', any_args) - .and_return(Gitlab::LDAP::Group.new(ldap_secondary_group1)) + .and_return(EE::Gitlab::LDAP::Group.new(ldap_secondary_group1)) user_w_multiple_ids.identities.create( [ { @@ -216,16 +215,16 @@ ) group1.ldap_group_links.create( cn: 'ldap_group1', - group_access: Gitlab::Access::DEVELOPER, + group_access: ::Gitlab::Access::DEVELOPER, provider: 'ldapprimary' ) group1.ldap_group_links.create( cn: 'ldap_secondary_group1', - group_access: Gitlab::Access::OWNER, + group_access: ::Gitlab::Access::OWNER, provider: 'ldapsecondary' ) group1.add_users([user_w_multiple_ids.id], - Gitlab::Access::DEVELOPER, skip_notification: true) + ::Gitlab::Access::DEVELOPER, skip_notification: true) end it 'does not change user permissions for secondary group link' do @@ -233,7 +232,7 @@ .not_to change { group1.members.where( user_id: user_w_multiple_ids.id, - access_level: Gitlab::Access::OWNER + access_level: ::Gitlab::Access::OWNER ).any? } end @@ -252,7 +251,7 @@ objectclass: groupOfNames EOS - allow_any_instance_of(Gitlab::LDAP::Group) + allow_any_instance_of(EE::Gitlab::LDAP::Group) .to receive(:adapter).and_return(adapter) user1.identities.create( @@ -264,36 +263,36 @@ extern_uid: "uid=#{user2.username},ou=users,dc=example,dc=com" ) - allow(Gitlab::LDAP::Group) + allow(EE::Gitlab::LDAP::Group) .to receive(:find_by_cn) - .with('ldap_group1', kind_of(Gitlab::LDAP::Adapter)) - .and_return(Gitlab::LDAP::Group.new(ldap_group1)) - allow(Gitlab::LDAP::Group) + .with('ldap_group1', kind_of(::Gitlab::LDAP::Adapter)) + .and_return(EE::Gitlab::LDAP::Group.new(ldap_group1)) + allow(EE::Gitlab::LDAP::Group) .to receive(:find_by_cn) - .with('ldap_group2', kind_of(Gitlab::LDAP::Adapter)) - .and_return(Gitlab::LDAP::Group.new(ldap_group2)) + .with('ldap_group2', kind_of(::Gitlab::LDAP::Adapter)) + .and_return(EE::Gitlab::LDAP::Group.new(ldap_group2)) group1.members.destroy_all group1.ldap_group_links.destroy_all group1.ldap_group_links.create( cn: 'ldap_group1', - group_access: Gitlab::Access::DEVELOPER, + group_access: ::Gitlab::Access::DEVELOPER, provider: 'ldapmain' ) group2.members.destroy_all group2.ldap_group_links.destroy_all group2.ldap_group_links.create( cn: 'ldap_group2', - group_access: Gitlab::Access::MASTER, + group_access: ::Gitlab::Access::MASTER, provider: 'ldapmain' ) group_sync.sync_groups expect(group1.members.pluck(:user_id).sort).to eq([user1.id, user2.id].sort) - expect(group1.members.pluck(:access_level).uniq).to eq([Gitlab::Access::DEVELOPER]) + expect(group1.members.pluck(:access_level).uniq).to eq([::Gitlab::Access::DEVELOPER]) expect(group2.members.pluck(:user_id)).to eq([user2.id]) - expect(group2.members.pluck(:access_level).uniq).to eq([Gitlab::Access::MASTER]) + expect(group2.members.pluck(:access_level).uniq).to eq([::Gitlab::Access::MASTER]) end end end @@ -303,9 +302,9 @@ let(:secondary_extern_uid) { nil } before do - allow_any_instance_of(Gitlab::LDAP::Group) + allow_any_instance_of(EE::Gitlab::LDAP::Group) .to receive(:adapter).and_return(adapter) - allow(Gitlab::LDAP::Group) + allow(EE::Gitlab::LDAP::Group) .to receive(:find_by_cn) .with(ldap_group.cn, any_args) .and_return(ldap_group) @@ -316,7 +315,7 @@ ) group1.ldap_group_links.create( cn: ldap_group.cn, - group_access: Gitlab::Access::DEVELOPER, + group_access: ::Gitlab::Access::DEVELOPER, provider: 'ldapmain' ) end @@ -324,7 +323,7 @@ # GroupOfNames - OpenLDAP context 'with groupOfNames style LDAP group' do let(:ldap_group) do - Gitlab::LDAP::Group.new( + EE::Gitlab::LDAP::Group.new( Net::LDAP::Entry.from_single_ldif_string(<<-EOS.strip_heredoc) dn: cn=ldap_group1,ou=groups,dc=example,dc=com cn: ldap_group1 @@ -346,7 +345,7 @@ # posixGroup - Apple Open Directory context 'with posixGroup style LDAP group' do let(:ldap_group) do - Gitlab::LDAP::Group.new( + EE::Gitlab::LDAP::Group.new( Net::LDAP::Entry.from_single_ldif_string(<<-EOS.strip_heredoc) dn: cn=ldap_group1,ou=groups,dc=example,dc=com cn: ldap_group1 @@ -358,7 +357,7 @@ ) end let(:ldap_user) do - Gitlab::LDAP::Person.new( + ::Gitlab::LDAP::Person.new( Net::LDAP::Entry.from_single_ldif_string( "dn: uid=#{user1.username},ou=users,dc=example,dc=com" ), @@ -367,7 +366,7 @@ end before do - allow(Gitlab::LDAP::Person) + allow(::Gitlab::LDAP::Person) .to receive(:find_by_uid) .with(user1.username, any_args) .and_return(ldap_user) @@ -379,8 +378,8 @@ .from(false).to(true) end - it 'expects Gitlab::LDAP::Person to be called' do - expect(Gitlab::LDAP::Person).to receive(:find_by_uid) + it 'expects ::Gitlab::LDAP::Person to be called' do + expect(::Gitlab::LDAP::Person).to receive(:find_by_uid) group_sync.sync_groups end @@ -398,8 +397,8 @@ context 'when the uid is stored in the database' do let(:secondary_extern_uid) { user1.username } - it 'expects Gitlab::LDAP::Person will not be called' do - expect(Gitlab::LDAP::Person) + it 'expects ::Gitlab::LDAP::Person will not be called' do + expect(::Gitlab::LDAP::Person) .not_to receive(:find_by_uid) .with(user1.username, any_args) @@ -414,7 +413,7 @@ # Group 1 link was created above. Create another here. group2.ldap_group_links.create( cn: ldap_group.cn, - group_access: Gitlab::Access::DEVELOPER, + group_access: ::Gitlab::Access::DEVELOPER, provider: 'ldapmain' ) end @@ -429,8 +428,8 @@ group_sync.sync_groups end - it 'expects Gitlab::LDAP::Person will not be called' do - expect(Gitlab::LDAP::Person) + it 'expects ::Gitlab::LDAP::Person will not be called' do + expect(::Gitlab::LDAP::Person) .not_to receive(:find_by_uid) .with(user1.username, any_args) @@ -441,7 +440,7 @@ context 'with groupOfUniqueNames style LDAP group' do let(:ldap_group) do - Gitlab::LDAP::Group.new( + EE::Gitlab::LDAP::Group.new( Net::LDAP::Entry.from_single_ldif_string(<<-EOS.strip_heredoc) dn: cn=ldap_group1,ou=groups,dc=example,dc=com cn: ldap_group1 @@ -462,7 +461,7 @@ context 'with an empty LDAP group' do let(:ldap_group) do - Gitlab::LDAP::Group.new( + EE::Gitlab::LDAP::Group.new( Net::LDAP::Entry.from_single_ldif_string(<<-EOS.strip_heredoc) dn: cn=ldap_group1,ou=groups,dc=example,dc=com cn: ldap_group1 @@ -482,7 +481,7 @@ # See gitlab-ee#442 and comment in GroupSync#ensure_full_dns! context 'with uid=username member format' do let(:ldap_group) do - Gitlab::LDAP::Group.new( + EE::Gitlab::LDAP::Group.new( Net::LDAP::Entry.from_single_ldif_string(<<-EOS.strip_heredoc) dn: cn=ldap_group1,ou=groups,dc=example,dc=com cn: ldap_group1 @@ -494,7 +493,7 @@ ) end let(:ldap_user) do - Gitlab::LDAP::Person.new( + ::Gitlab::LDAP::Person.new( Net::LDAP::Entry.from_single_ldif_string( "dn: uid=#{user1.username},ou=users,dc=example,dc=com" ), @@ -503,7 +502,7 @@ end before do - allow(Gitlab::LDAP::Person) + allow(::Gitlab::LDAP::Person) .to receive(:find_by_uid) .with(user1.username, any_args) .and_return(ldap_user) @@ -515,8 +514,8 @@ .from(false).to(true) end - it 'expects Gitlab::LDAP::Person to be called' do - expect(Gitlab::LDAP::Person).to receive(:find_by_uid) + it 'expects ::Gitlab::LDAP::Person to be called' do + expect(::Gitlab::LDAP::Person).to receive(:find_by_uid) group_sync.sync_groups end @@ -534,7 +533,7 @@ context 'with invalid DNs in the LDAP group' do let(:ldap_group) do - Gitlab::LDAP::Group.new( + EE::Gitlab::LDAP::Group.new( Net::LDAP::Entry.from_single_ldif_string(<<-EOS.strip_heredoc) dn: cn=ldap_group1,ou=groups,dc=example,dc=com cn: ldap_group1 @@ -587,14 +586,14 @@ user1.update_attribute(:admin, true) user3.update_attribute(:admin, true) - allow_any_instance_of(Gitlab::LDAP::Group) + allow_any_instance_of(EE::Gitlab::LDAP::Group) .to receive(:adapter).and_return(adapter) - allow(Gitlab::LDAP::Group) + allow(EE::Gitlab::LDAP::Group) .to receive(:find_by_cn).with(admin_group.cn, any_args) - allow(Gitlab::LDAP::Group) + allow(EE::Gitlab::LDAP::Group) .to receive(:find_by_cn) - .with('admin_group', kind_of(Gitlab::LDAP::Adapter)) - .and_return(Gitlab::LDAP::Group.new(admin_group)) + .with('admin_group', kind_of(::Gitlab::LDAP::Adapter)) + .and_return(EE::Gitlab::LDAP::Group.new(admin_group)) user1.identities.create( provider: 'ldapmain', @@ -658,16 +657,16 @@ user3.update_attribute(:external, true) user4.update_attribute(:external, true) - allow_any_instance_of(Gitlab::LDAP::Group) + allow_any_instance_of(EE::Gitlab::LDAP::Group) .to receive(:adapter).and_return(adapter) - allow(Gitlab::LDAP::Group) + allow(EE::Gitlab::LDAP::Group) .to receive(:find_by_cn) - .with('external_group1', kind_of(Gitlab::LDAP::Adapter)) - .and_return(Gitlab::LDAP::Group.new(external_group1)) - allow(Gitlab::LDAP::Group) + .with('external_group1', kind_of(::Gitlab::LDAP::Adapter)) + .and_return(EE::Gitlab::LDAP::Group.new(external_group1)) + allow(EE::Gitlab::LDAP::Group) .to receive(:find_by_cn) - .with('external_group2', kind_of(Gitlab::LDAP::Adapter)) - .and_return(Gitlab::LDAP::Group.new(external_group2)) + .with('external_group2', kind_of(::Gitlab::LDAP::Adapter)) + .and_return(EE::Gitlab::LDAP::Group.new(external_group2)) user1.identities.create( provider: 'ldapmain', -- GitLab