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
This commit is contained in:
parent
35e1568826
commit
83aaac41c6
@ -1507,6 +1507,17 @@ omicron bryanh guest1
|
|||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</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>
|
<varlistentry>
|
||||||
<term><literal>ldapurl</literal></term>
|
<term><literal>ldapurl</literal></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
@ -1514,13 +1525,16 @@ omicron bryanh guest1
|
|||||||
An RFC 4516 LDAP URL. This is an alternative way to write some of the
|
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
|
other LDAP options in a more compact and standard form. The format is
|
||||||
<synopsis>
|
<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>
|
</synopsis>
|
||||||
<replaceable>scope</replaceable> must be one
|
<replaceable>scope</replaceable> must be one
|
||||||
of <literal>base</literal>, <literal>one</literal>, <literal>sub</literal>,
|
of <literal>base</literal>, <literal>one</literal>, <literal>sub</literal>,
|
||||||
typically the latter. Only one attribute is used, and some other
|
typically the last. <replaceable>attribute</replaceable> can
|
||||||
components of standard LDAP URLs such as filters and extensions are
|
nominate a single attribute, in which case it is used as a value for
|
||||||
not supported.
|
<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>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
@ -1549,6 +1563,17 @@ ldap://<replaceable>host</replaceable>[:<replaceable>port</replaceable>]/<replac
|
|||||||
for search+bind.
|
for search+bind.
|
||||||
</para>
|
</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>
|
<para>
|
||||||
Here is an example for a simple-bind LDAP configuration:
|
Here is an example for a simple-bind LDAP configuration:
|
||||||
<programlisting>
|
<programlisting>
|
||||||
@ -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.
|
same URL format, so it will be easier to share the configuration.
|
||||||
</para>
|
</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>
|
<tip>
|
||||||
<para>
|
<para>
|
||||||
Since LDAP often uses commas and spaces to separate the different
|
Since LDAP often uses commas and spaces to separate the different
|
||||||
|
@ -2394,6 +2394,34 @@ InitializeLDAPConnection(Port *port, LDAP **ldap)
|
|||||||
return STATUS_OK;
|
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
|
* Perform LDAP authentication
|
||||||
*/
|
*/
|
||||||
@ -2437,7 +2465,7 @@ CheckLDAPAuth(Port *port)
|
|||||||
char *filter;
|
char *filter;
|
||||||
LDAPMessage *search_message;
|
LDAPMessage *search_message;
|
||||||
LDAPMessage *entry;
|
LDAPMessage *entry;
|
||||||
char *attributes[2];
|
char *attributes[] = { LDAP_NO_ATTRS, NULL };
|
||||||
char *dn;
|
char *dn;
|
||||||
char *c;
|
char *c;
|
||||||
int count;
|
int count;
|
||||||
@ -2479,13 +2507,13 @@ CheckLDAPAuth(Port *port)
|
|||||||
return STATUS_ERROR;
|
return STATUS_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fetch just one attribute, else *all* attributes are returned */
|
/* Build a custom filter or a single attribute filter? */
|
||||||
attributes[0] = port->hba->ldapsearchattribute ? port->hba->ldapsearchattribute : "uid";
|
if (port->hba->ldapsearchfilter)
|
||||||
attributes[1] = NULL;
|
filter = FormatSearchFilter(port->hba->ldapsearchfilter, port->user_name);
|
||||||
|
else if (port->hba->ldapsearchattribute)
|
||||||
filter = psprintf("(%s=%s)",
|
filter = psprintf("(%s=%s)", port->hba->ldapsearchattribute, port->user_name);
|
||||||
attributes[0],
|
else
|
||||||
port->user_name);
|
filter = psprintf("(uid=%s)", port->user_name);
|
||||||
|
|
||||||
r = ldap_search_s(ldap,
|
r = ldap_search_s(ldap,
|
||||||
port->hba->ldapbasedn,
|
port->hba->ldapbasedn,
|
||||||
|
@ -1505,22 +1505,24 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
|
|||||||
/*
|
/*
|
||||||
* LDAP can operate in two modes: either with a direct bind, using
|
* LDAP can operate in two modes: either with a direct bind, using
|
||||||
* ldapprefix and ldapsuffix, or using a search+bind, using
|
* ldapprefix and ldapsuffix, or using a search+bind, using
|
||||||
* ldapbasedn, ldapbinddn, ldapbindpasswd and ldapsearchattribute.
|
* ldapbasedn, ldapbinddn, ldapbindpasswd and one of
|
||||||
* Disallow mixing these parameters.
|
* ldapsearchattribute or ldapsearchfilter. Disallow mixing these
|
||||||
|
* parameters.
|
||||||
*/
|
*/
|
||||||
if (parsedline->ldapprefix || parsedline->ldapsuffix)
|
if (parsedline->ldapprefix || parsedline->ldapsuffix)
|
||||||
{
|
{
|
||||||
if (parsedline->ldapbasedn ||
|
if (parsedline->ldapbasedn ||
|
||||||
parsedline->ldapbinddn ||
|
parsedline->ldapbinddn ||
|
||||||
parsedline->ldapbindpasswd ||
|
parsedline->ldapbindpasswd ||
|
||||||
parsedline->ldapsearchattribute)
|
parsedline->ldapsearchattribute ||
|
||||||
|
parsedline->ldapsearchfilter)
|
||||||
{
|
{
|
||||||
ereport(elevel,
|
ereport(elevel,
|
||||||
(errcode(ERRCODE_CONFIG_FILE_ERROR),
|
(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\"",
|
errcontext("line %d of configuration file \"%s\"",
|
||||||
line_num, HbaFileName)));
|
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;
|
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";
|
*err_msg = "authentication method \"ldap\" requires argument \"ldapbasedn\", \"ldapprefix\", or \"ldapsuffix\" to be set";
|
||||||
return NULL;
|
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)
|
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->ldapsearchattribute = pstrdup(urldata->lud_attrs[0]); /* only use first one */
|
||||||
hbaline->ldapscope = urldata->lud_scope;
|
hbaline->ldapscope = urldata->lud_scope;
|
||||||
if (urldata->lud_filter)
|
if (urldata->lud_filter)
|
||||||
{
|
hbaline->ldapsearchfilter = pstrdup(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;
|
|
||||||
}
|
|
||||||
ldap_free_urldesc(urldata);
|
ldap_free_urldesc(urldata);
|
||||||
#else /* not OpenLDAP */
|
#else /* not OpenLDAP */
|
||||||
ereport(elevel,
|
ereport(elevel,
|
||||||
@ -1788,6 +1799,11 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
|
|||||||
REQUIRE_AUTH_OPTION(uaLDAP, "ldapsearchattribute", "ldap");
|
REQUIRE_AUTH_OPTION(uaLDAP, "ldapsearchattribute", "ldap");
|
||||||
hbaline->ldapsearchattribute = pstrdup(val);
|
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)
|
else if (strcmp(name, "ldapbasedn") == 0)
|
||||||
{
|
{
|
||||||
REQUIRE_AUTH_OPTION(uaLDAP, "ldapbasedn", "ldap");
|
REQUIRE_AUTH_OPTION(uaLDAP, "ldapbasedn", "ldap");
|
||||||
@ -2266,6 +2282,11 @@ gethba_options(HbaLine *hba)
|
|||||||
CStringGetTextDatum(psprintf("ldapsearchattribute=%s",
|
CStringGetTextDatum(psprintf("ldapsearchattribute=%s",
|
||||||
hba->ldapsearchattribute));
|
hba->ldapsearchattribute));
|
||||||
|
|
||||||
|
if (hba->ldapsearchfilter)
|
||||||
|
options[noptions++] =
|
||||||
|
CStringGetTextDatum(psprintf("ldapsearchfilter=%s",
|
||||||
|
hba->ldapsearchfilter));
|
||||||
|
|
||||||
if (hba->ldapscope)
|
if (hba->ldapscope)
|
||||||
options[noptions++] =
|
options[noptions++] =
|
||||||
CStringGetTextDatum(psprintf("ldapscope=%d", hba->ldapscope));
|
CStringGetTextDatum(psprintf("ldapscope=%d", hba->ldapscope));
|
||||||
|
@ -80,6 +80,7 @@ typedef struct HbaLine
|
|||||||
char *ldapbinddn;
|
char *ldapbinddn;
|
||||||
char *ldapbindpasswd;
|
char *ldapbindpasswd;
|
||||||
char *ldapsearchattribute;
|
char *ldapsearchattribute;
|
||||||
|
char *ldapsearchfilter;
|
||||||
char *ldapbasedn;
|
char *ldapbasedn;
|
||||||
int ldapscope;
|
int ldapscope;
|
||||||
char *ldapprefix;
|
char *ldapprefix;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user