1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-24 06:20:56 +01:00

Support multiple LDAP filters in the Phabricator UI

Summary: Ref T3208. Not ready for prime time yet.

Test Plan: ldap T.T

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley, frgtn, aran

Maniphest Tasks: T3208

Differential Revision: https://secure.phabricator.com/D8160
This commit is contained in:
epriestley 2014-03-17 15:08:30 -07:00
parent 7167a729bf
commit ba8925a531

View file

@ -30,6 +30,10 @@ final class PhabricatorAuthProviderLDAP
$realname_attributes = array(); $realname_attributes = array();
} }
$search_attributes = $conf->getProperty(self::KEY_SEARCH_ATTRIBUTES);
$search_attributes = phutil_split_lines($search_attributes, false);
$search_attributes = array_filter($search_attributes);
$adapter = id(new PhutilAuthAdapterLDAP()) $adapter = id(new PhutilAuthAdapterLDAP())
->setHostname( ->setHostname(
$conf->getProperty(self::KEY_HOSTNAME)) $conf->getProperty(self::KEY_HOSTNAME))
@ -37,8 +41,7 @@ final class PhabricatorAuthProviderLDAP
$conf->getProperty(self::KEY_PORT)) $conf->getProperty(self::KEY_PORT))
->setBaseDistinguishedName( ->setBaseDistinguishedName(
$conf->getProperty(self::KEY_DISTINGUISHED_NAME)) $conf->getProperty(self::KEY_DISTINGUISHED_NAME))
->setSearchAttribute( ->setSearchAttributes($search_attributes)
$conf->getProperty(self::KEY_SEARCH_ATTRIBUTE))
->setUsernameAttribute( ->setUsernameAttribute(
$conf->getProperty(self::KEY_USERNAME_ATTRIBUTE)) $conf->getProperty(self::KEY_USERNAME_ATTRIBUTE))
->setRealNameAttributes($realname_attributes) ->setRealNameAttributes($realname_attributes)
@ -53,8 +56,6 @@ final class PhabricatorAuthProviderLDAP
->setAnonymousPassword( ->setAnonymousPassword(
new PhutilOpaqueEnvelope( new PhutilOpaqueEnvelope(
$conf->getProperty(self::KEY_ANONYMOUS_PASSWORD))) $conf->getProperty(self::KEY_ANONYMOUS_PASSWORD)))
->setSearchFirst(
$conf->getProperty(self::KEY_SEARCH_FIRST))
->setActiveDirectoryDomain( ->setActiveDirectoryDomain(
$conf->getProperty(self::KEY_ACTIVEDIRECTORY_DOMAIN)); $conf->getProperty(self::KEY_ACTIVEDIRECTORY_DOMAIN));
$this->adapter = $adapter; $this->adapter = $adapter;
@ -167,6 +168,11 @@ final class PhabricatorAuthProviderLDAP
} else { } else {
throw new Exception("Username and password are required!"); throw new Exception("Username and password are required!");
} }
} catch (PhutilAuthCredentialException $ex) {
$response = $controller->buildProviderPageResponse(
$this,
$this->renderLoginForm($request, 'login'));
return array($account, $response);
} catch (Exception $ex) { } catch (Exception $ex) {
// TODO: Make this cleaner. // TODO: Make this cleaner.
throw $ex; throw $ex;
@ -180,7 +186,7 @@ final class PhabricatorAuthProviderLDAP
const KEY_HOSTNAME = 'ldap:host'; const KEY_HOSTNAME = 'ldap:host';
const KEY_PORT = 'ldap:port'; const KEY_PORT = 'ldap:port';
const KEY_DISTINGUISHED_NAME = 'ldap:dn'; const KEY_DISTINGUISHED_NAME = 'ldap:dn';
const KEY_SEARCH_ATTRIBUTE = 'ldap:search-attribute'; const KEY_SEARCH_ATTRIBUTES = 'ldap:search-attribute';
const KEY_USERNAME_ATTRIBUTE = 'ldap:username-attribute'; const KEY_USERNAME_ATTRIBUTE = 'ldap:username-attribute';
const KEY_REALNAME_ATTRIBUTES = 'ldap:realname-attributes'; const KEY_REALNAME_ATTRIBUTES = 'ldap:realname-attributes';
const KEY_VERSION = 'ldap:version'; const KEY_VERSION = 'ldap:version';
@ -188,7 +194,6 @@ final class PhabricatorAuthProviderLDAP
const KEY_START_TLS = 'ldap:start-tls'; const KEY_START_TLS = 'ldap:start-tls';
const KEY_ANONYMOUS_USERNAME = 'ldap:anoynmous-username'; const KEY_ANONYMOUS_USERNAME = 'ldap:anoynmous-username';
const KEY_ANONYMOUS_PASSWORD = 'ldap:anonymous-password'; const KEY_ANONYMOUS_PASSWORD = 'ldap:anonymous-password';
const KEY_SEARCH_FIRST = 'ldap:search-first';
const KEY_ACTIVEDIRECTORY_DOMAIN = 'ldap:activedirectory-domain'; const KEY_ACTIVEDIRECTORY_DOMAIN = 'ldap:activedirectory-domain';
private function getPropertyKeys() { private function getPropertyKeys() {
@ -200,15 +205,14 @@ final class PhabricatorAuthProviderLDAP
self::KEY_HOSTNAME => pht('LDAP Hostname'), self::KEY_HOSTNAME => pht('LDAP Hostname'),
self::KEY_PORT => pht('LDAP Port'), self::KEY_PORT => pht('LDAP Port'),
self::KEY_DISTINGUISHED_NAME => pht('Base Distinguished Name'), self::KEY_DISTINGUISHED_NAME => pht('Base Distinguished Name'),
self::KEY_SEARCH_ATTRIBUTE => pht('Search Attribute'), self::KEY_SEARCH_ATTRIBUTES => pht('Search Attributes'),
self::KEY_ANONYMOUS_USERNAME => pht('Anonymous Username'),
self::KEY_ANONYMOUS_PASSWORD => pht('Anonymous Password'),
self::KEY_USERNAME_ATTRIBUTE => pht('Username Attribute'), self::KEY_USERNAME_ATTRIBUTE => pht('Username Attribute'),
self::KEY_REALNAME_ATTRIBUTES => pht('Realname Attributes'), self::KEY_REALNAME_ATTRIBUTES => pht('Realname Attributes'),
self::KEY_VERSION => pht('LDAP Version'), self::KEY_VERSION => pht('LDAP Version'),
self::KEY_REFERRALS => pht('Enable Referrals'), self::KEY_REFERRALS => pht('Enable Referrals'),
self::KEY_START_TLS => pht('Use TLS'), self::KEY_START_TLS => pht('Use TLS'),
self::KEY_SEARCH_FIRST => pht('Search First'),
self::KEY_ANONYMOUS_USERNAME => pht('Anonymous Username'),
self::KEY_ANONYMOUS_PASSWORD => pht('Anonymous Password'),
self::KEY_ACTIVEDIRECTORY_DOMAIN => pht('ActiveDirectory Domain'), self::KEY_ACTIVEDIRECTORY_DOMAIN => pht('ActiveDirectory Domain'),
); );
} }
@ -262,24 +266,16 @@ final class PhabricatorAuthProviderLDAP
self::KEY_DISTINGUISHED_NAME => self::KEY_DISTINGUISHED_NAME =>
pht('Example: %s', pht('Example: %s',
phutil_tag('tt', array(), pht('ou=People, dc=example, dc=com'))), phutil_tag('tt', array(), pht('ou=People, dc=example, dc=com'))),
self::KEY_SEARCH_ATTRIBUTE => self::KEY_USERNAME_ATTRIBUTE =>
pht('Example: %s', pht('Example: %s',
phutil_tag('tt', array(), pht('sn'))), phutil_tag('tt', array(), pht('sn'))),
self::KEY_USERNAME_ATTRIBUTE =>
pht('Optional, if different from search attribute.'),
self::KEY_REALNAME_ATTRIBUTES => self::KEY_REALNAME_ATTRIBUTES =>
pht('Optional. Example: %s', pht('Example: %s',
phutil_tag('tt', array(), pht('firstname, lastname'))), phutil_tag('tt', array(), pht('firstname, lastname'))),
self::KEY_REFERRALS => self::KEY_REFERRALS =>
pht('Follow referrals. Disable this for Windows AD 2003.'), pht('Follow referrals. Disable this for Windows AD 2003.'),
self::KEY_START_TLS => self::KEY_START_TLS =>
pht('Start TLS after binding to the LDAP server.'), pht('Start TLS after binding to the LDAP server.'),
self::KEY_SEARCH_FIRST =>
pht(
'When the user enters their username, search for a matching '.
'record using the "Search Attribute", then try to bind using '.
'the DN for the record. This is useful if usernames are not '.
'part of the record DN.'),
self::KEY_ANONYMOUS_USERNAME => self::KEY_ANONYMOUS_USERNAME =>
pht('Username to bind with before searching.'), pht('Username to bind with before searching.'),
self::KEY_ANONYMOUS_PASSWORD => self::KEY_ANONYMOUS_PASSWORD =>
@ -289,11 +285,76 @@ final class PhabricatorAuthProviderLDAP
$types = array( $types = array(
self::KEY_REFERRALS => 'checkbox', self::KEY_REFERRALS => 'checkbox',
self::KEY_START_TLS => 'checkbox', self::KEY_START_TLS => 'checkbox',
self::KEY_SEARCH_FIRST => 'checkbox', self::KEY_SEARCH_ATTRIBUTES => 'textarea',
self::KEY_REALNAME_ATTRIBUTES => 'list', self::KEY_REALNAME_ATTRIBUTES => 'list',
self::KEY_ANONYMOUS_PASSWORD => 'password', self::KEY_ANONYMOUS_PASSWORD => 'password',
); );
$instructions = array(
self::KEY_SEARCH_ATTRIBUTES => pht(
"When a user types their LDAP username and password into Phabricator, ".
"Phabricator can either bind to LDAP with those credentials directly ".
"(which is simpler, but not as powerful) or bind to LDAP with ".
"anonymous credentials, then search for record matching the supplied ".
"credentials (which is more complicated, but more powerful).\n\n".
"For many installs, direct binding is sufficient. However, you may ".
"want to search first if:\n\n".
" - You want users to be able to login with either their username ".
" or their email address.\n".
" - The login/username is not part of the distinguished name in ".
" your LDAP records.\n".
" - You want to restrict logins to a subset of users (like only ".
" those in certain departments).\n".
" - Your LDAP server is configured in some other way that prevents ".
" direct binding from working correctly.\n\n".
"**To bind directly**, enter the LDAP attribute corresponding to the ".
"login name into this box. Often, this is something like `sn` or ".
"`uid`. This is the simplest configuration, but will only work if the ".
"username is part of the distinguished name, and won't let you apply ".
"complex restrictions to logins.\n\n".
" lang=text,name=Simple Direct Binding\n".
" sn\n\n".
"**To search first**, provide an anonymous username and password ".
"below, then enter one or more search queries into this field, one ".
"per line. After binding, these queries will be used to identify the ".
"record associated with the login name the user typed.\n\n".
"Searches will be tried in order until a matching record is found. ".
"Each query can be a simple attribute name (like `sn` or `mail`), ".
"which will search for a matching record, or it can be a complex ".
"query that uses the string `\${login}` to represent the login ".
"name.\n\n".
"A common simple configuration is just an attribute name, like ".
"`sn`, which will work the same way direct binding works:\n\n".
" lang=text,name=Simple Example\n".
" sn\n\n".
"A slightly more complex configuration might let the user login with ".
"either their login name or email address:\n\n".
" lang=text,name=Match Several Attributes\n".
" mail\n".
" sn\n\n".
"If your LDAP directory is more complex, or you want to perform ".
"sophisticated filtering, you can use one or more queries. Depending ".
"on your directory structure, this example might allow users to login ".
"with either their email address or username, but only if they're in ".
"specific departments:\n\n".
" lang=text,name=Complex Example\n".
" (&(mail=\${login})(|(departmentNumber=1)(departmentNumber=2)))\n".
" (&(sn=\${login})(|(departmentNumber=1)(departmentNumber=2)))\n\n".
"All of the attribute names used here are just examples: your LDAP ".
"server may use different attribute names."),
self::KEY_USERNAME_ATTRIBUTE => pht(
"Optionally, specify a username attribute to use to prefill usernames ".
"when registering a new account. This is purely cosmetic and does not ".
"affect the login process, but you can configure it to make sure ".
"users get the same default username as their LDAP username, so ".
"usernames remain consistent across systems."),
self::KEY_REALNAME_ATTRIBUTES => pht(
"Optionally, specify one or more comma-separated attributes to use to ".
"prefill the \"Real Name\" field when registering a new account. This ".
"is purely cosmetic and does not affect the login process, but can ".
"make registration a little easier."),
);
foreach ($labels as $key => $label) { foreach ($labels as $key => $label) {
$caption = idx($captions, $key); $caption = idx($captions, $key);
$type = idx($types, $key); $type = idx($types, $key);
@ -323,6 +384,13 @@ final class PhabricatorAuthProviderLDAP
->setCaption($caption) ->setCaption($caption)
->setValue($value); ->setValue($value);
break; break;
case 'textarea':
$control = id(new AphrontFormTextAreaControl())
->setName($key)
->setLabel($label)
->setCaption($caption)
->setValue($value);
break;
default: default:
$control = id(new AphrontFormTextControl()) $control = id(new AphrontFormTextControl())
->setName($key) ->setName($key)
@ -332,6 +400,11 @@ final class PhabricatorAuthProviderLDAP
break; break;
} }
$instruction_text = idx($instructions, $key);
if (strlen($instruction_text)) {
$form->appendRemarkupInstructions($instruction_text);
}
$form->appendChild($control); $form->appendChild($control);
} }
} }