For additional entertainment I have written a test suite for this LDAP
authentication functionality.  It's not quite robust enough to be run by
default, because it needs a full OpenLDAP installation, but it's been
very helpful for reviewing this patch.  Here it is.

-- 
Peter Eisentraut              http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
From 9457ef272d011dbb34b1a204cac9cbac08e4d652 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pete...@gmx.net>
Date: Fri, 8 Sep 2017 10:33:49 -0400
Subject: [PATCH 1/3] Add LDAP authentication test suite

---
 src/test/Makefile           |   2 +-
 src/test/ldap/.gitignore    |   2 +
 src/test/ldap/Makefile      |  20 +++++++
 src/test/ldap/README        |  16 +++++
 src/test/ldap/data.ldif     |  19 ++++++
 src/test/ldap/t/001_auth.pl | 139 ++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 197 insertions(+), 1 deletion(-)
 create mode 100644 src/test/ldap/.gitignore
 create mode 100644 src/test/ldap/Makefile
 create mode 100644 src/test/ldap/README
 create mode 100644 src/test/ldap/data.ldif
 create mode 100644 src/test/ldap/t/001_auth.pl

diff --git a/src/test/Makefile b/src/test/Makefile
index dbfa799a84..193b977bf3 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -17,7 +17,7 @@ SUBDIRS = perl regress isolation modules authentication 
recovery subscription
 # We don't build or execute examples/, locale/, or thread/ by default,
 # but we do want "make clean" etc to recurse into them.  Likewise for ssl/,
 # because the SSL test suite is not secure to run on a multi-user system.
-ALWAYS_SUBDIRS = examples locale thread ssl
+ALWAYS_SUBDIRS = examples ldap locale thread ssl
 
 # We want to recurse to all subdirs for all standard targets, except that
 # installcheck and install should not recurse into the subdirectory "modules".
diff --git a/src/test/ldap/.gitignore b/src/test/ldap/.gitignore
new file mode 100644
index 0000000000..871e943d50
--- /dev/null
+++ b/src/test/ldap/.gitignore
@@ -0,0 +1,2 @@
+# Generated by test suite
+/tmp_check/
diff --git a/src/test/ldap/Makefile b/src/test/ldap/Makefile
new file mode 100644
index 0000000000..9dd1bbeade
--- /dev/null
+++ b/src/test/ldap/Makefile
@@ -0,0 +1,20 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/test/ldap
+#
+# Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/test/ldap/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/test/ldap
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+check:
+       $(prove_check)
+
+clean distclean maintainer-clean:
+       rm -rf tmp_check
diff --git a/src/test/ldap/README b/src/test/ldap/README
new file mode 100644
index 0000000000..20a7a0b5de
--- /dev/null
+++ b/src/test/ldap/README
@@ -0,0 +1,16 @@
+src/test/ldap/README
+
+Tests for LDAP functionality
+============================
+
+This directory contains a test suite for LDAP functionality.  This
+requires a full OpenLDAP installation, including server and client
+tools, and is therefore kept separate and not run by default.
+
+
+Running the tests
+=================
+
+    make check
+
+NOTE: This requires the --enable-tap-tests argument to configure.
diff --git a/src/test/ldap/data.ldif b/src/test/ldap/data.ldif
new file mode 100644
index 0000000000..b30604e1f0
--- /dev/null
+++ b/src/test/ldap/data.ldif
@@ -0,0 +1,19 @@
+dn: dc=example,dc=net
+objectClass: top
+objectClass: dcObject
+objectClass: organization
+dc: example
+o: ExmapleCo
+
+dn: uid=test1,dc=example,dc=net
+objectClass: inetOrgPerson
+objectClass: posixAccount
+uid: test1
+sn: Lastname
+givenName: Firstname
+cn: First Test User
+displayName: First Test User
+uidNumber: 101
+gidNumber: 100
+homeDirectory: /home/test1
+mail: te...@example.net
diff --git a/src/test/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl
new file mode 100644
index 0000000000..af9e34d7cf
--- /dev/null
+++ b/src/test/ldap/t/001_auth.pl
@@ -0,0 +1,139 @@
+use strict;
+use warnings;
+use TestLib;
+use PostgresNode;
+use Test::More tests => 9;
+
+my ($slapd, $ldap_bin_dir, $ldap_schema_dir);
+
+$ldap_bin_dir = undef;                 # usually in PATH
+
+if ($^O eq 'darwin')
+{
+       $slapd = '/usr/local/opt/openldap/libexec/slapd';
+       $ldap_schema_dir = '/usr/local/etc/openldap/schema';
+}
+elsif ($^O eq 'linux')
+{
+       $slapd = '/usr/sbin/slapd';
+       $ldap_schema_dir = '/etc/ldap/schema' if -f '/etc/ldap/schema';
+       $ldap_schema_dir = '/etc/openldap/schema' if -f '/etc/openldap/schema';
+}
+
+# make your own edits here
+#$slapd = '';
+#$ldap_bin_dir = '';
+#$ldap_schema_dir = '';
+
+$ENV{PATH} = "$ldap_bin_dir:$ENV{PATH}" if $ldap_bin_dir;
+
+my $ldap_datadir = "${TestLib::tmp_check}/openldap-data";
+my $slapd_conf = "${TestLib::tmp_check}/slapd.conf";
+my $slapd_pidfile = "${TestLib::tmp_check}/slapd.pid";
+my $slapd_logfile = "${TestLib::tmp_check}/slapd.log";
+my $ldap_conf = "${TestLib::tmp_check}/ldap.conf";
+my $ldap_server = '127.0.0.1';
+my $ldap_port = int(rand() * 16384) + 49152;
+my $ldap_url = "ldap://$ldap_server:$ldap_port";;
+my $ldap_basedn = 'dc=example,dc=net';
+my $ldap_rootdn = 'cn=Manager,dc=example,dc=net';
+my $ldap_rootpw = 'secret';
+my $ldap_pwfile = "${TestLib::tmp_check}/ldappassword";
+
+note "setting up slapd";
+
+append_to_file($slapd_conf,
+qq{include $ldap_schema_dir/core.schema
+include $ldap_schema_dir/cosine.schema
+include $ldap_schema_dir/nis.schema
+include $ldap_schema_dir/inetorgperson.schema
+
+pidfile $slapd_pidfile
+logfile $slapd_logfile
+
+access to *
+        by * read
+        by anonymous auth
+
+database mdb
+dbnosync
+directory $ldap_datadir
+
+suffix "dc=example,dc=net"
+rootdn "$ldap_rootdn"
+rootpw $ldap_rootpw});
+
+mkdir $ldap_datadir or die;
+
+system_or_bail $slapd, '-f', $slapd_conf, '-h', $ldap_url;
+
+END
+{
+       kill 'INT', `cat $slapd_pidfile` if -f $slapd_pidfile;
+}
+
+append_to_file($ldap_pwfile, $ldap_rootpw);
+chmod 0600, $ldap_pwfile or die;
+
+$ENV{'LDAPURI'} = $ldap_url;
+$ENV{'LDAPBINDDN'} = $ldap_rootdn;
+
+note "loading LDAP data";
+
+system_or_bail 'ldapadd', '-x', '-y', $ldap_pwfile, '-f', 'data.ldif';
+system_or_bail 'ldappasswd', '-x', '-y', $ldap_pwfile, '-s', 'secret1', 
'uid=test1,dc=example,dc=net';
+
+note "setting up PostgreSQL instance";
+
+my $node = get_new_node('node');
+$node->init;
+$node->start;
+
+$node->safe_psql('postgres', 'CREATE USER test0;');
+$node->safe_psql('postgres', 'CREATE USER test1;');
+
+note "running tests";
+
+sub test_access
+{
+       my ($node, $role, $expected_res, $test_name) = @_;
+
+       my $res = $node->psql('postgres', 'SELECT 1', extra_params => [ '-U', 
$role ]);
+    is($res, $expected_res, $test_name);
+}
+
+note "simple bind";
+
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf', qq{local all all ldap 
ldapserver=$ldap_server ldapport=$ldap_port ldapprefix="uid=" 
ldapsuffix=",dc=example,dc=net"});
+$node->reload;
+
+$ENV{"PGPASSWORD"} = 'wrong';
+test_access($node, 'test0', 2, 'simple bind authentication fails if user not 
found in LDAP');
+test_access($node, 'test1', 2, 'simple bind authentication fails with wrong 
password');
+$ENV{"PGPASSWORD"} = 'secret1';
+test_access($node, 'test1', 0, 'simple bind authentication succeeds');
+
+note "search+bind";
+
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf', qq{local all all ldap 
ldapserver=$ldap_server ldapport=$ldap_port ldapbasedn="$ldap_basedn"});
+$node->reload;
+
+$ENV{"PGPASSWORD"} = 'wrong';
+test_access($node, 'test0', 2, 'search+bind authentication fails if user not 
found in LDAP');
+test_access($node, 'test1', 2, 'search+bind authentication fails with wrong 
password');
+$ENV{"PGPASSWORD"} = 'secret1';
+test_access($node, 'test1', 0, 'search+bind authentication succeeds');
+
+note "LDAP URLs";
+
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf', qq{local all all ldap 
ldapurl="$ldap_url/$ldap_basedn?uid?sub"});
+$node->reload;
+
+$ENV{"PGPASSWORD"} = 'wrong';
+test_access($node, 'test0', 2, 'search+bind with LDAP URL authentication fails 
if user not found in LDAP');
+test_access($node, 'test1', 2, 'search+bind with LDAP URL authentication fails 
with wrong password');
+$ENV{"PGPASSWORD"} = 'secret1';
+test_access($node, 'test1', 0, 'search+bind with LDAP URL authentication 
succeeds');
-- 
2.11.0 (Apple Git-81)

From 44aa04e26916bd8d16b680db21987ccb1cedbf34 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pete...@gmx.net>
Date: Fri, 8 Sep 2017 10:52:08 -0400
Subject: [PATCH 2/3] Add tests for ldapsearchfilter functionality

---
 src/test/ldap/data.ldif     | 13 +++++++++++++
 src/test/ldap/t/001_auth.pl | 36 +++++++++++++++++++++++++++++++++++-
 2 files changed, 48 insertions(+), 1 deletion(-)

diff --git a/src/test/ldap/data.ldif b/src/test/ldap/data.ldif
index b30604e1f0..8d82c284b1 100644
--- a/src/test/ldap/data.ldif
+++ b/src/test/ldap/data.ldif
@@ -17,3 +17,16 @@ uidNumber: 101
 gidNumber: 100
 homeDirectory: /home/test1
 mail: te...@example.net
+
+dn: uid=test2,dc=example,dc=net
+objectClass: inetOrgPerson
+objectClass: posixAccount
+uid: test2
+sn: Lastname
+givenName: Firstname
+cn: Second Test User
+displayName: Second Test User
+uidNumber: 102
+gidNumber: 100
+homeDirectory: /home/test2
+mail: te...@example.net
diff --git a/src/test/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl
index af9e34d7cf..eb72b16a0a 100644
--- a/src/test/ldap/t/001_auth.pl
+++ b/src/test/ldap/t/001_auth.pl
@@ -2,7 +2,7 @@
 use warnings;
 use TestLib;
 use PostgresNode;
-use Test::More tests => 9;
+use Test::More tests => 14;
 
 my ($slapd, $ldap_bin_dir, $ldap_schema_dir);
 
@@ -82,6 +82,7 @@ END
 
 system_or_bail 'ldapadd', '-x', '-y', $ldap_pwfile, '-f', 'data.ldif';
 system_or_bail 'ldappasswd', '-x', '-y', $ldap_pwfile, '-s', 'secret1', 
'uid=test1,dc=example,dc=net';
+system_or_bail 'ldappasswd', '-x', '-y', $ldap_pwfile, '-s', 'secret2', 
'uid=test2,dc=example,dc=net';
 
 note "setting up PostgreSQL instance";
 
@@ -91,6 +92,7 @@ END
 
 $node->safe_psql('postgres', 'CREATE USER test0;');
 $node->safe_psql('postgres', 'CREATE USER test1;');
+$node->safe_psql('postgres', 'CREATE USER "te...@example.net";');
 
 note "running tests";
 
@@ -137,3 +139,35 @@ sub test_access
 test_access($node, 'test1', 2, 'search+bind with LDAP URL authentication fails 
with wrong password');
 $ENV{"PGPASSWORD"} = 'secret1';
 test_access($node, 'test1', 0, 'search+bind with LDAP URL authentication 
succeeds');
+
+note "search filters";
+
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf', qq{local all all ldap 
ldapserver=$ldap_server ldapport=$ldap_port ldapbasedn="$ldap_basedn" 
ldapsearchfilter="(|(uid=%u)(mail=%u))"});
+$node->reload;
+
+$ENV{"PGPASSWORD"} = 'secret1';
+test_access($node, 'test1', 0, 'search filter finds by uid');
+$ENV{"PGPASSWORD"} = 'secret2';
+test_access($node, 'te...@example.net', 0, 'search filter finds by mail');
+
+note "search filters in LDAP URLs";
+
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf', qq{local all all ldap 
ldapurl="$ldap_url/$ldap_basedn??sub?(|(uid=%25u)(mail=%25u))"});
+$node->reload;
+
+$ENV{"PGPASSWORD"} = 'secret1';
+test_access($node, 'test1', 0, 'search filter finds by uid');
+$ENV{"PGPASSWORD"} = 'secret2';
+test_access($node, 'te...@example.net', 0, 'search filter finds by mail');
+
+# This is not documented: You can combine ldapurl and other ldap*
+# settings.  ldapurl is always parsed first, then the other settings
+# override.  It might be useful in a case like this.
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf', qq{local all all ldap 
ldapurl="$ldap_url/$ldap_basedn??sub" ldapsearchfilter="(|(uid=%u)(mail=%u))"});
+$node->reload;
+
+$ENV{"PGPASSWORD"} = 'secret1';
+test_access($node, 'test1', 0, 'combined LDAP URL and search filter');
-- 
2.11.0 (Apple Git-81)

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to