Skip to content

Commit

Permalink
Update JNDI adapter to follow referrals by default (#182)
Browse files Browse the repository at this point in the history
* Update JNDI adapter to follow referrals by default

I ran in to some issues while exploring OpenLDAP where
memberOf was not returning while using JNDI but was returning using
net-ldap and the ldapsearch command. I finally narrowed it down to JNDI
not following referrals by default.

Now this JNDI adapter will follow referrals by default like net-ldap
and the ruby ldap adapters do here. This can be changed with the
follow_referrals connection configurations option.

* Add test for reading memberOf attribute

* Add test_follow_referrals_option

* Update test_follow_referrals_option to test all three values

* Add dynamic groups test

This highlights the need for enabling the follow referrals configuration to load dynamic group
memberships.

* Update dockerfile and dockerignore

* Use dyngroup schema in slapd

* Move LDIF to enable dynamic groups to test/

* Remove needless GroupOfNames class

It's not related to this change.

* test: simplify

* Add support for follow_referrals per connect

* Enable no follow_referrals only for JRuby

Co-authored-by: Sutou Kouhei <kou@clear-code.com>
  • Loading branch information
HarlemSquirrel and kou committed Aug 15, 2020
1 parent 31576d5 commit ca535f8
Show file tree
Hide file tree
Showing 10 changed files with 159 additions and 14 deletions.
1 change: 1 addition & 0 deletions .dockerignore
Expand Up @@ -10,3 +10,4 @@
!/dockerfiles/openldap-prepare.sh
!/lib/active_ldap/version.rb
!/test/add-phonetic-attribute-options-to-slapd.ldif
!/test/enable-dynamic-groups.ldif
6 changes: 4 additions & 2 deletions dockerfiles/openldap-prepare.dockerfile
Expand Up @@ -11,15 +11,17 @@ RUN \
RUN \
apt update && \
apt install -y -V \
ldap-utils && \
ldap-utils \
slapd && \
apt clean && \
rm -rf /var/lib/apt/lists/*

RUN echo "TLS_REQCERT never" > /etc/ldap/ldap.conf

COPY test/add-phonetic-attribute-options-to-slapd.ldif /
COPY dockerfiles/add-test-example-org-dc.ldif /
COPY dockerfiles/olc-access-readable-by-all.ldif /
COPY dockerfiles/openldap-prepare.sh /
COPY test/add-phonetic-attribute-options-to-slapd.ldif /
COPY test/enable-dynamic-groups.ldif /

ENTRYPOINT ["/openldap-prepare.sh"]
16 changes: 16 additions & 0 deletions dockerfiles/openldap-prepare.sh
Expand Up @@ -37,3 +37,19 @@ retry \
-D cn=admin,cn=config \
-w config \
-f olc-access-readable-by-all.ldif

retry \
ldapadd \
-ZZ \
-H ldap://openldap \
-D cn=admin,cn=config \
-w config \
-f /etc/ldap/schema/dyngroup.ldif

retry \
ldapmodify \
-ZZ \
-H ldap://openldap \
-D cn=admin,cn=config \
-w config \
-f enable-dynamic-groups.ldif
20 changes: 17 additions & 3 deletions lib/active_ldap/adapter/jndi.rb
Expand Up @@ -22,9 +22,23 @@ def connect(options={})
super do |host, port, method|
uri = construct_uri(host, port, method == :ssl)
with_start_tls = method == :start_tls
info = {:uri => uri, :with_start_tls => with_start_tls}
[log("connect", info) {JndiConnection.new(host, port, method, @timeout)},
uri, with_start_tls]
follow_referrals = follow_referrals?(options)
info = {
:uri => uri,
:with_start_tls => with_start_tls,
:follow_referrals => follow_referrals,
}
[
log("connect", info) {
JndiConnection.new(host,
port,
method,
@timeout,
follow_referrals)
},
uri,
with_start_tls,
]
end
end

Expand Down
8 changes: 5 additions & 3 deletions lib/active_ldap/adapter/jndi_connection.rb
Expand Up @@ -75,13 +75,14 @@ def to_java_attribute
end
end

def initialize(host, port, method, timeout)
def initialize(host, port, method, timeout, follow_referrals)
@host = host
@port = port
@method = method
@timeout = timeout
@context = nil
@tls = nil
@follow_referrals = follow_referrals
end

def unbind
Expand Down Expand Up @@ -203,9 +204,10 @@ def setup_context(bind_dn, password, authentication)
Context::PROVIDER_URL => ldap_uri,
'com.sun.jndi.ldap.connect.timeout' => (@timeout * 1000).to_i.to_s,
'com.sun.jndi.ldap.read.timeout' => (@timeout * 1000).to_i.to_s,
'java.naming.ldap.derefAliases' => 'never',
'java.naming.referral' => @follow_referrals ? 'follow' : 'ignore',
}
environment = HashTable.new(environment)
context = InitialLdapContext.new(environment, nil)
context = InitialLdapContext.new(HashTable.new(environment), nil)
if @method == :start_tls
@tls = context.extended_operation(StartTlsRequest.new)
@tls.negotiate
Expand Down
8 changes: 5 additions & 3 deletions lib/active_ldap/adapter/ldap.rb
Expand Up @@ -209,15 +209,17 @@ def modify_rdn(dn, new_rdn, delete_old_rdn, new_superior, options={})
def prepare_connection(options={})
operation(options) do
@connection.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
unless follow_referrals?(options)
@connection.set_option(LDAP::LDAP_OPT_REFERRALS, 0)
end
@ldap_follow_referrals = follow_referrals?(options) ? 1 : 0
@connection.set_option(LDAP::LDAP_OPT_REFERRALS,
@ldap_follow_referrals)
end
end

def execute(method, info=nil, *args, &block)
begin
name = (info || {}).delete(:name) || method
@connection.set_option(LDAP::LDAP_OPT_REFERRALS,
@ldap_follow_referrals)
log(name, info) {@connection.send(method, *args, &block)}
rescue LDAP::ResultError
@connection.assert_error_code
Expand Down
42 changes: 41 additions & 1 deletion test/al-test-utils.rb
Expand Up @@ -182,6 +182,7 @@ def populate
populate_ou
populate_user_class
populate_group_class
populate_group_of_urls_class
populate_associations
end

Expand Down Expand Up @@ -215,7 +216,7 @@ def entry_class(prefix="")
end

def populate_ou
%w(Users Groups).each do |name|
%w(Users Groups GroupOfURLsSet).each do |name|
make_ou(name)
end
end
Expand Down Expand Up @@ -246,6 +247,14 @@ def populate_group_class
assign_class_name(@group_class, "Group")
end

def populate_group_of_urls_class
@group_of_urls_class = Class.new(ActiveLdap::Base)
@group_of_urls_class.ldap_mapping :prefix => "ou=GroupOfURLsSet",
:scope => :sub,
:classes => ["groupOfURLs"]
assign_class_name(@group_of_urls_class, "GroupOfURLs")
end

def populate_associations
@user_class.belongs_to :groups, :many => "memberUid"
@user_class.belongs_to :primary_group,
Expand Down Expand Up @@ -280,6 +289,7 @@ def setup
super
@user_index = 0
@group_index = 0
@group_of_urls_index = 0
@temporary_uids = []
end

Expand Down Expand Up @@ -366,6 +376,25 @@ def make_temporary_group(config={})
end
end

def make_temporary_group_of_urls(config={})
@group_of_urls_index += 1
cn = config[:cn] || "temp-group-of-urls-#{@group_of_urls_index}"
ensure_delete_group_of_urls(cn) do
_wrap_assertion do
assert(!@group_of_urls_class.exists?(cn))
assert_raise(ActiveLdap::EntryNotFound) do
@group_of_urls_class.find(cn)
end
group_of_urls = @group_of_urls_class.new(cn)
assert(group_of_urls.new_entry?)
group_of_urls.member_url = config[:member_url]
assert(group_of_urls.save!)
assert(!group_of_urls.new_entry?)
yield(@group_of_urls_class.find(group_of_urls.cn))
end
end
end

def ensure_delete_user(uid)
yield(uid)
ensure
Expand All @@ -379,6 +408,12 @@ def ensure_delete_group(cn)
@group_class.delete(cn) if @group_class.exists?(cn)
end

def ensure_delete_group_of_urls(cn)
yield(cn)
ensure
@group_of_urls_class.delete(cn) if @group_of_urls_class.exists?(cn)
end

def default_uid
"10000#{@user_index}"
end
Expand Down Expand Up @@ -467,6 +502,11 @@ def omit_if_jruby(message=nil)
omit(message || "This test is not for JRuby")
end

def omit_unless_jruby(message=nil)
return if RUBY_PLATFORM == "java"
omit(message || "This test is only for JRuby")
end

def omit_if_ldap(message=nil)
return if current_configuration[:adapter] == "ldap"
omit(message || "This test is not for ruby-ldap")
Expand Down
22 changes: 22 additions & 0 deletions test/enable-dynamic-groups.ldif
@@ -0,0 +1,22 @@
# Your LDAP server needs to support dynamic list for test.
# This is a LDIF file for OpenLDAP to do the configuration.
# You can use this file by the following command line on Debian GNU/Linux
# or Ubuntu:
#
# % sudo -H ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/ldap/schema/dyngroup.ldif
# % sudo -H ldapmodify -Y EXTERNAL -H ldapi:/// -f test/enable-dynamic-groups.ldif
version: 1

# Enable dynlist module
dn: cn=module{0},cn=config
changetype: modify
add: olcModuleLoad
olcModuleLoad: dynlist

# Set up dynlist overlay
dn: olcOverlay=dynlist,olcDatabase={1}mdb,cn=config
changetype: add
objectClass: olcOverlayConfig
objectClass: olcDynamicList
olcOverlay: dynlist
olcDlAttrSet: groupOfURLs memberURL member
49 changes: 47 additions & 2 deletions test/test_base.rb
Expand Up @@ -5,15 +5,59 @@
class TestBase < Test::Unit::TestCase
include AlTestUtils

sub_test_case("follow_referrals") do
def test_default
make_temporary_user do |user1,|
make_temporary_user do |user2,|
member_url = ["ldap:///#{user1.base.to_s}??one?(objectClass=person)"]
make_temporary_group_of_urls(member_url: member_url) do |group_of_urls|
assert_equal([user1.dn, user2.dn],
group_of_urls.attributes["member"])
end
end
end
end

def test_connection_false
omit_unless_jruby
@group_of_urls_class.setup_connection(
current_configuration.merge(follow_referrals: false)
)
make_temporary_user do |user1,|
make_temporary_user do |user2,|
member_url = ["ldap:///#{user1.base.to_s}??one?(objectClass=person)"]
make_temporary_group_of_urls(member_url: member_url) do |group_of_urls|
assert_nil(group_of_urls.attributes["member"])
end
end
end
end

def test_connect_false
omit_unless_jruby
connection = @group_of_urls_class.connection
connection.disconnect!
connection.connect(follow_referrals: false)
make_temporary_user do |user1,|
make_temporary_user do |user2,|
member_url = ["ldap:///#{user1.base.to_s}??one?(objectClass=person)"]
make_temporary_group_of_urls(member_url: member_url) do |group_of_urls|
assert_nil(group_of_urls.attributes["member"])
end
end
end
end
end

priority :must
priority :normal
def test_search_colon_value
make_temporary_group(:cn => "temp:group") do |group|
assert_equal("temp:group", group.cn)
assert_not_nil(@group_class.find("temp:group"))
end
end

priority :normal
def test_lower_case_object_class
fixture_file = fixture("lower_case_object_class_schema.rb")
schema_entries = eval(File.read(fixture_file))
Expand Down Expand Up @@ -958,7 +1002,8 @@ def test_ldap_mapping_symbol_dn_attribute
ou_class.ldap_mapping(:dn_attribute => :ou,
:prefix => "",
:classes => ["top", "organizationalUnit"])
assert_equal(["ou=Groups,#{current_configuration['base']}",
assert_equal(["ou=GroupOfURLsSet,#{current_configuration['base']}",
"ou=Groups,#{current_configuration['base']}",
"ou=Users,#{current_configuration['base']}"],
ou_class.find(:all).collect(&:dn).collect(&:to_s).sort)
end
Expand Down
1 change: 1 addition & 0 deletions test/test_entry.rb
Expand Up @@ -11,6 +11,7 @@ def test_all
all_entries = [ActiveLdap::Base.base]
all_entries += [user.dn, user.base]
all_entries += [group.dn, group.base]
all_entries += [@group_of_urls_class.base]
assert_equal(all_entries.sort,
ActiveLdap::Entry.all.collect(&:dn).sort)
end
Expand Down

0 comments on commit ca535f8

Please sign in to comment.