Commit 170bbb94 authored by Patricio Cano's avatar Patricio Cano

Allow LDAP to handle external users, based on users' group memberships.

parent 2331ec25
...@@ -351,6 +351,12 @@ production: &base ...@@ -351,6 +351,12 @@ production: &base
# #
admin_group: '' admin_group: ''
# LDAP group of users who should be marked as external users in GitLab
#
# Ex. ['Contractors', 'Interns']
#
external_groups: []
# Name of attribute which holds a ssh public key of the user object. # Name of attribute which holds a ssh public key of the user object.
# If false or nil, SSH key syncronisation will be disabled. # If false or nil, SSH key syncronisation will be disabled.
# #
......
...@@ -157,6 +157,7 @@ if Settings.ldap['enabled'] || Rails.env.test? ...@@ -157,6 +157,7 @@ if Settings.ldap['enabled'] || Rails.env.test?
server['attributes'] = {} if server['attributes'].nil? server['attributes'] = {} if server['attributes'].nil?
server['provider_name'] ||= "ldap#{key}".downcase server['provider_name'] ||= "ldap#{key}".downcase
server['provider_class'] = OmniAuth::Utils.camelize(server['provider_name']) server['provider_class'] = OmniAuth::Utils.camelize(server['provider_name'])
server['external_groups'] = [] if server['external_groups'].nil?
Settings.ldap['servers'][key] = server Settings.ldap['servers'][key] = server
end end
end end
......
...@@ -146,6 +146,14 @@ main: # 'main' is the GitLab 'provider ID' of this LDAP server ...@@ -146,6 +146,14 @@ main: # 'main' is the GitLab 'provider ID' of this LDAP server
# #
admin_group: '' admin_group: ''
# An array of CNs of groups containing users that should be considered external
#
# Ex. ['interns', 'contractors']
#
# Note: Not `cn=interns` or the full DN
#
external_groups: []
# The LDAP attribute containing a user's public SSH key # The LDAP attribute containing a user's public SSH key
# #
# Ex. ssh_public_key # Ex. ssh_public_key
...@@ -184,6 +192,28 @@ production: ...@@ -184,6 +192,28 @@ production:
# snip... # snip...
``` ```
### External Groups
>**Note:** External Groups configuration is only available in GitLab EE Version
8.9 and above.
Using the `external_groups` setting will allow you to mark all users belonging
to these groups as [external users](../../permissions/). Group membership is
checked periodically through the `LdapGroupSync` background task.
**Configuration**
```yaml
# An array of CNs of groups containing users that should be considered external
#
# Ex. ['interns', 'contractors']
#
# Note: Not `cn=interns` or the full DN
#
external_groups: []
```
## Using an LDAP filter to limit access to your GitLab server ## Using an LDAP filter to limit access to your GitLab server
If you want to limit all GitLab access to a subset of the LDAP users on your If you want to limit all GitLab access to a subset of the LDAP users on your
......
...@@ -101,6 +101,10 @@ module Gitlab ...@@ -101,6 +101,10 @@ module Gitlab
options['timeout'].to_i options['timeout'].to_i
end end
def external_groups
options['external_groups']
end
protected protected
def base_config def base_config
......
...@@ -49,6 +49,14 @@ module Gitlab ...@@ -49,6 +49,14 @@ module Gitlab
logger.debug { "No `admin_group` configured for '#{provider}' provider. Skipping" } logger.debug { "No `admin_group` configured for '#{provider}' provider. Skipping" }
end 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 nil
end end
...@@ -121,6 +129,42 @@ module Gitlab ...@@ -121,6 +129,42 @@ module Gitlab
end 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
# Restore normal access to users no longer found in the external groups
current_external_users.each do |user|
unless verified_external_users.include?(user)
user.external = false
user.save
end
end
end
private private
# Cache LDAP group member DNs so we don't query LDAP groups more than once. # Cache LDAP group member DNs so we don't query LDAP groups more than once.
...@@ -151,6 +195,10 @@ module Gitlab ...@@ -151,6 +195,10 @@ module Gitlab
config.admin_group config.admin_group
end end
def external_groups
config.external_groups
end
def ldap_group_member_dns(ldap_group_cn) def ldap_group_member_dns(ldap_group_cn)
ldap_group = Gitlab::LDAP::Group.find_by_cn(ldap_group_cn, adapter) ldap_group = Gitlab::LDAP::Group.find_by_cn(ldap_group_cn, adapter)
unless ldap_group.present? unless ldap_group.present?
......
...@@ -646,4 +646,97 @@ describe Gitlab::LDAP::GroupSync, lib: true do ...@@ -646,4 +646,97 @@ describe Gitlab::LDAP::GroupSync, lib: true do
.not_to change { User.admins.where(id: user3.id).any? } .not_to change { User.admins.where(id: user3.id).any? }
end end
end end
describe '#sync_external_users' do
let(:user1) { create(:user) }
let(:user2) { create(:user) }
let(:user3) { create(:user) }
let(:user4) { create(:user) }
let(:external_group1) do
Net::LDAP::Entry.from_single_ldif_string(
<<-EOS.strip_heredoc
dn: cn=external_group1,ou=groups,dc=example,dc=com
cn: external_group1
description: External Group 1
gidnumber: 42
uniqueMember: uid=#{user1.username},ou=users,dc=example,dc=com
objectclass: top
objectclass: groupOfNames
EOS
)
end
let(:external_group2) do
Net::LDAP::Entry.from_single_ldif_string(
<<-EOS.strip_heredoc
dn: cn=external_group2,ou=groups,dc=example,dc=com
cn: external_group2
description: External Group 2
gidnumber: 42
uniqueMember: uid=#{user2.username},ou=users,dc=example,dc=com
objectclass: top
objectclass: groupOfNames
EOS
)
end
before do
user3.external = true
user3.save
user4.external = true
user4.save
allow_any_instance_of(Gitlab::LDAP::Group)
.to receive(:adapter).and_return(adapter)
allow(Gitlab::LDAP::Group)
.to receive(:find_by_cn).with(external_group1.cn, any_args)
allow(Gitlab::LDAP::Group)
.to receive(:find_by_cn).with(external_group2.cn, any_args)
allow(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)
.to receive(:find_by_cn)
.with('external_group2', kind_of(Gitlab::LDAP::Adapter))
.and_return(Gitlab::LDAP::Group.new(external_group2))
user1.identities.create(
provider: 'ldapmain',
extern_uid: "uid=#{user1.username},ou=users,dc=example,dc=com"
)
user2.identities.create(
provider: 'ldapmain',
extern_uid: "uid=#{user2.username},ou=users,dc=example,dc=com"
)
user4.identities.create(
provider: 'ldapmain',
extern_uid: "uid=#{user4.username},ou=users,dc=example,dc=com"
)
allow(group_sync).to receive_messages(external_groups: %w(external_group1 external_group2))
end
it 'adds user1 as external user' do
expect { group_sync.sync_external_users }
.to change { User.external.where(id: user1.id).any? }.from(false).to(true)
end
it 'adds user2 as external user' do
expect { group_sync.sync_external_users }
.to change { User.external.where(id: user2.id).any? }.from(false).to(true)
end
it 'removes users that are not in the LDAP group' do
expect { group_sync.sync_external_users }
.to change { User.external.where(id: user4.id).any? }.from(true).to(false)
end
it 'leaves as external users that do not have the LDAP provider' do
expect { group_sync.sync_external_users }
.not_to change { User.external.where(id: user3.id).any? }
end
end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment