diff options
author | Peter Eisentraut | 2017-09-12 13:46:14 +0000 |
---|---|---|
committer | Peter Eisentraut | 2017-09-12 13:49:04 +0000 |
commit | 83aaac41c66959a3ebaec7daadc4885b5f98f561 (patch) | |
tree | 2e96d2fe57254ebdf90aad0ceee2ee5b95321aa4 | |
parent | 35e15688269a2af13f4cddff0c13536a9a42115d (diff) |
Allow custom search filters to be configured for LDAP auth
Before, only filters of the form "(<ldapsearchattribute>=<user>)"
could be used to search an LDAP server. Introduce ldapsearchfilter
so that more general filters can be configured using patterns, like
"(|(uid=$username)(mail=$username))" and "(&(uid=$username)
(objectClass=posixAccount))". Also allow search filters to be included
in an LDAP URL.
Author: Thomas Munro
Reviewed-By: Peter Eisentraut, Mark Cave-Ayland, Magnus Hagander
Discussion: https://postgr.es/m/CAEepm=0XTkYvMci0WRubZcf_1am8=gP=7oJErpsUfRYcKF2gwg@mail.gmail.com
-rw-r--r-- | doc/src/sgml/client-auth.sgml | 43 | ||||
-rw-r--r-- | src/backend/libpq/auth.c | 44 | ||||
-rw-r--r-- | src/backend/libpq/hba.c | 47 | ||||
-rw-r--r-- | src/include/libpq/hba.h | 1 |
4 files changed, 110 insertions, 25 deletions
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index 1b568683a4..405bf26832 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -1508,19 +1508,33 @@ omicron bryanh guest1 </listitem> </varlistentry> <varlistentry> + <term><literal>ldapsearchfilter</literal></term> + <listitem> + <para> + The search filter to use when doing search+bind authentication. + Occurrences of <literal>$username</literal> will be replaced with the + user name. This allows for more flexible search filters than + <literal>ldapsearchattribute</literal>. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><literal>ldapurl</literal></term> <listitem> <para> An RFC 4516 LDAP URL. This is an alternative way to write some of the other LDAP options in a more compact and standard form. The format is <synopsis> -ldap://<replaceable>host</replaceable>[:<replaceable>port</replaceable>]/<replaceable>basedn</replaceable>[?[<replaceable>attribute</replaceable>][?[<replaceable>scope</replaceable>]]] +ldap://<replaceable>host</replaceable>[:<replaceable>port</replaceable>]/<replaceable>basedn</replaceable>[?[<replaceable>attribute</replaceable>][?[<replaceable>scope</replaceable>][?[<replaceable>filter</replaceable>]]]] </synopsis> <replaceable>scope</replaceable> must be one of <literal>base</literal>, <literal>one</literal>, <literal>sub</literal>, - typically the latter. Only one attribute is used, and some other - components of standard LDAP URLs such as filters and extensions are - not supported. + typically the last. <replaceable>attribute</replaceable> can + nominate a single attribute, in which case it is used as a value for + <literal>ldapsearchattribute</literal>. If + <replaceable>attribute</replaceable> is empty then + <replaceable>filter</replaceable> can be used as a value for + <literal>ldapsearchfilter</literal>. </para> <para> @@ -1550,6 +1564,17 @@ ldap://<replaceable>host</replaceable>[:<replaceable>port</replaceable>]/<replac </para> <para> + When using search+bind mode, the search can be performed using a single + attribute specified with <literal>ldapsearchattribute</literal>, or using + a custom search filter specified with + <literal>ldapsearchfilter</literal>. + Specifying <literal>ldapsearchattribute=foo</literal> is equivalent to + specifying <literal>ldapsearchfilter="(foo=$username)"</literal>. If neither + option is specified the default is + <literal>ldapsearchattribute=uid</literal>. + </para> + + <para> Here is an example for a simple-bind LDAP configuration: <programlisting> host ... ldap ldapserver=ldap.example.net ldapprefix="cn=" ldapsuffix=", dc=example, dc=net" @@ -1584,6 +1609,16 @@ host ... ldap ldapurl="ldap://ldap.example.net/dc=example,dc=net?uid?sub" same URL format, so it will be easier to share the configuration. </para> + <para> + Here is an example for a search+bind configuration that uses + <literal>ldapsearchfilter</literal> instead of + <literal>ldapsearchattribute</literal> to allow authentication by + user ID or email address: +<programlisting> +host ... ldap ldapserver=ldap.example.net ldapbasedn="dc=example, dc=net" ldapsearchfilter="(|(uid=$username)(mail=$username))" +</programlisting> + </para> + <tip> <para> Since LDAP often uses commas and spaces to separate the different diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index cb30fc7b71..62ff624dbd 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -2394,6 +2394,34 @@ InitializeLDAPConnection(Port *port, LDAP **ldap) return STATUS_OK; } +/* Placeholders recognized by FormatSearchFilter. For now just one. */ +#define LPH_USERNAME "$username" +#define LPH_USERNAME_LEN (sizeof(LPH_USERNAME) - 1) + +/* + * Return a newly allocated C string copied from "pattern" with all + * occurrences of the placeholder "$username" replaced with "user_name". + */ +static char * +FormatSearchFilter(const char *pattern, const char *user_name) +{ + StringInfoData output; + + initStringInfo(&output); + while (*pattern != '\0') + { + if (strncmp(pattern, LPH_USERNAME, LPH_USERNAME_LEN) == 0) + { + appendStringInfoString(&output, user_name); + pattern += LPH_USERNAME_LEN; + } + else + appendStringInfoChar(&output, *pattern++); + } + + return output.data; +} + /* * Perform LDAP authentication */ @@ -2437,7 +2465,7 @@ CheckLDAPAuth(Port *port) char *filter; LDAPMessage *search_message; LDAPMessage *entry; - char *attributes[2]; + char *attributes[] = { LDAP_NO_ATTRS, NULL }; char *dn; char *c; int count; @@ -2479,13 +2507,13 @@ CheckLDAPAuth(Port *port) return STATUS_ERROR; } - /* Fetch just one attribute, else *all* attributes are returned */ - attributes[0] = port->hba->ldapsearchattribute ? port->hba->ldapsearchattribute : "uid"; - attributes[1] = NULL; - - filter = psprintf("(%s=%s)", - attributes[0], - port->user_name); + /* Build a custom filter or a single attribute filter? */ + if (port->hba->ldapsearchfilter) + filter = FormatSearchFilter(port->hba->ldapsearchfilter, port->user_name); + else if (port->hba->ldapsearchattribute) + filter = psprintf("(%s=%s)", port->hba->ldapsearchattribute, port->user_name); + else + filter = psprintf("(uid=%s)", port->user_name); r = ldap_search_s(ldap, port->hba->ldapbasedn, diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index ba011b6d61..b2c487a8e8 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -1505,22 +1505,24 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) /* * LDAP can operate in two modes: either with a direct bind, using * ldapprefix and ldapsuffix, or using a search+bind, using - * ldapbasedn, ldapbinddn, ldapbindpasswd and ldapsearchattribute. - * Disallow mixing these parameters. + * ldapbasedn, ldapbinddn, ldapbindpasswd and one of + * ldapsearchattribute or ldapsearchfilter. Disallow mixing these + * parameters. */ if (parsedline->ldapprefix || parsedline->ldapsuffix) { if (parsedline->ldapbasedn || parsedline->ldapbinddn || parsedline->ldapbindpasswd || - parsedline->ldapsearchattribute) + parsedline->ldapsearchattribute || + parsedline->ldapsearchfilter) { ereport(elevel, (errcode(ERRCODE_CONFIG_FILE_ERROR), - errmsg("cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, or ldapurl together with ldapprefix"), + errmsg("cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, ldapsearchfilter or ldapurl together with ldapprefix"), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); - *err_msg = "cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, or ldapurl together with ldapprefix"; + *err_msg = "cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, ldapsearchfilter or ldapurl together with ldapprefix"; return NULL; } } @@ -1534,6 +1536,22 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) *err_msg = "authentication method \"ldap\" requires argument \"ldapbasedn\", \"ldapprefix\", or \"ldapsuffix\" to be set"; return NULL; } + + /* + * When using search+bind, you can either use a simple attribute + * (defaulting to "uid") or a fully custom search filter. You can't + * do both. + */ + if (parsedline->ldapsearchattribute && parsedline->ldapsearchfilter) + { + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("cannot use ldapsearchattribute together with ldapsearchfilter"), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "cannot use ldapsearchattribute together with ldapsearchfilter"; + return NULL; + } } if (parsedline->auth_method == uaRADIUS) @@ -1729,14 +1747,7 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, hbaline->ldapsearchattribute = pstrdup(urldata->lud_attrs[0]); /* only use first one */ hbaline->ldapscope = urldata->lud_scope; if (urldata->lud_filter) - { - ereport(elevel, - (errcode(ERRCODE_CONFIG_FILE_ERROR), - errmsg("filters not supported in LDAP URLs"))); - *err_msg = "filters not supported in LDAP URLs"; - ldap_free_urldesc(urldata); - return false; - } + hbaline->ldapsearchfilter = pstrdup(urldata->lud_filter); ldap_free_urldesc(urldata); #else /* not OpenLDAP */ ereport(elevel, @@ -1788,6 +1799,11 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, REQUIRE_AUTH_OPTION(uaLDAP, "ldapsearchattribute", "ldap"); hbaline->ldapsearchattribute = pstrdup(val); } + else if (strcmp(name, "ldapsearchfilter") == 0) + { + REQUIRE_AUTH_OPTION(uaLDAP, "ldapsearchfilter", "ldap"); + hbaline->ldapsearchfilter = pstrdup(val); + } else if (strcmp(name, "ldapbasedn") == 0) { REQUIRE_AUTH_OPTION(uaLDAP, "ldapbasedn", "ldap"); @@ -2266,6 +2282,11 @@ gethba_options(HbaLine *hba) CStringGetTextDatum(psprintf("ldapsearchattribute=%s", hba->ldapsearchattribute)); + if (hba->ldapsearchfilter) + options[noptions++] = + CStringGetTextDatum(psprintf("ldapsearchfilter=%s", + hba->ldapsearchfilter)); + if (hba->ldapscope) options[noptions++] = CStringGetTextDatum(psprintf("ldapscope=%d", hba->ldapscope)); diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index 07d92d4f9f..e711bee8bf 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -80,6 +80,7 @@ typedef struct HbaLine char *ldapbinddn; char *ldapbindpasswd; char *ldapsearchattribute; + char *ldapsearchfilter; char *ldapbasedn; int ldapscope; char *ldapprefix; |