2012-06-13 17:52:05 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Copyright 2012 Facebook, Inc.
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
final class PhabricatorLDAPProvider {
|
2012-07-26 23:32:51 +02:00
|
|
|
// http://www.php.net/manual/en/function.ldap-errno.php#20665 states
|
|
|
|
// that the number could be 31 or 49, in testing it has always been 49
|
|
|
|
const LDAP_INVALID_CREDENTIALS = 49;
|
|
|
|
|
2012-06-13 17:52:05 +02:00
|
|
|
private $userData;
|
|
|
|
private $connection;
|
|
|
|
|
|
|
|
public function __construct() {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public function __destruct() {
|
|
|
|
if (isset($this->connection)) {
|
|
|
|
ldap_unbind($this->connection);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function isProviderEnabled() {
|
|
|
|
return PhabricatorEnv::getEnvConfig('ldap.auth-enabled');
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getHostname() {
|
|
|
|
return PhabricatorEnv::getEnvConfig('ldap.hostname');
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getBaseDN() {
|
|
|
|
return PhabricatorEnv::getEnvConfig('ldap.base_dn');
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getSearchAttribute() {
|
|
|
|
return PhabricatorEnv::getEnvConfig('ldap.search_attribute');
|
|
|
|
}
|
|
|
|
|
2012-07-26 03:55:48 +02:00
|
|
|
public function getUsernameAttribute() {
|
2012-07-26 23:32:51 +02:00
|
|
|
return PhabricatorEnv::getEnvConfig('ldap.username-attribute');
|
2012-07-26 03:55:48 +02:00
|
|
|
}
|
|
|
|
|
2012-06-13 17:52:05 +02:00
|
|
|
public function getLDAPVersion() {
|
|
|
|
return PhabricatorEnv::getEnvConfig('ldap.version');
|
|
|
|
}
|
2012-07-18 22:38:24 +02:00
|
|
|
|
|
|
|
public function getLDAPReferrals() {
|
|
|
|
return PhabricatorEnv::getEnvConfig('ldap.referrals');
|
|
|
|
}
|
2012-06-13 17:52:05 +02:00
|
|
|
|
|
|
|
public function retrieveUserEmail() {
|
|
|
|
return $this->userData['mail'][0];
|
|
|
|
}
|
2012-07-17 21:06:33 +02:00
|
|
|
|
2012-06-13 17:52:05 +02:00
|
|
|
public function retrieveUserRealName() {
|
2012-07-04 04:10:38 +02:00
|
|
|
return $this->retrieveUserRealNameFromData($this->userData);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function retrieveUserRealNameFromData($data) {
|
2012-06-13 17:52:05 +02:00
|
|
|
$name_attributes = PhabricatorEnv::getEnvConfig(
|
|
|
|
'ldap.real_name_attributes');
|
|
|
|
|
|
|
|
$real_name = '';
|
|
|
|
if (is_array($name_attributes)) {
|
|
|
|
foreach ($name_attributes AS $attribute) {
|
2012-07-04 04:10:38 +02:00
|
|
|
if (isset($data[$attribute][0])) {
|
|
|
|
$real_name .= $data[$attribute][0] . ' ';
|
2012-06-13 17:52:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
trim($real_name);
|
2012-07-04 04:10:38 +02:00
|
|
|
} else if (isset($data[$name_attributes][0])) {
|
|
|
|
$real_name = $data[$name_attributes][0];
|
2012-06-13 17:52:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($real_name == '') {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $real_name;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function retrieveUsername() {
|
|
|
|
return $this->userData[$this->getSearchAttribute()][0];
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getConnection() {
|
|
|
|
if (!isset($this->connection)) {
|
|
|
|
$this->connection = ldap_connect($this->getHostname());
|
|
|
|
|
|
|
|
if (!$this->connection) {
|
|
|
|
throw new Exception('Could not connect to LDAP host at ' .
|
|
|
|
$this->getHostname());
|
|
|
|
}
|
|
|
|
|
|
|
|
ldap_set_option($this->connection, LDAP_OPT_PROTOCOL_VERSION,
|
|
|
|
$this->getLDAPVersion());
|
2012-07-18 22:38:24 +02:00
|
|
|
ldap_set_option($this->connection, LDAP_OPT_REFERRALS,
|
|
|
|
$this->getLDAPReferrals());
|
2012-06-13 17:52:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return $this->connection;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getUserData() {
|
|
|
|
return $this->userData;
|
|
|
|
}
|
|
|
|
|
2012-07-26 23:32:51 +02:00
|
|
|
private function invalidLDAPUserErrorMessage($errno, $errmsg) {
|
|
|
|
return "LDAP Error #".$errno.": ".$errmsg;
|
|
|
|
}
|
|
|
|
|
2012-07-17 21:06:33 +02:00
|
|
|
public function auth($username, PhutilOpaqueEnvelope $password) {
|
|
|
|
if (strlen(trim($username)) == 0) {
|
|
|
|
throw new Exception('Username can not be empty');
|
2012-06-13 17:52:05 +02:00
|
|
|
}
|
|
|
|
|
2012-07-26 03:55:48 +02:00
|
|
|
if (PhabricatorEnv::getEnvConfig('ldap.search-first')) {
|
2012-07-26 23:32:51 +02:00
|
|
|
// To protect against people phishing for accounts we catch the
|
|
|
|
// exception and present the default exception that would be presented
|
|
|
|
// in the case of a failed bind.
|
|
|
|
try {
|
|
|
|
$user = $this->getUser($this->getUsernameAttribute(), $username);
|
|
|
|
$username = $user[$this->getSearchAttribute()][0];
|
|
|
|
} catch (PhabricatorLDAPUnknownUserException $e) {
|
|
|
|
throw new Exception(
|
|
|
|
$this->invalidLDAPUserErrorMessage(
|
|
|
|
self::LDAP_INVALID_CREDENTIALS,
|
|
|
|
ldap_err2str(self::LDAP_INVALID_CREDENTIALS)));
|
|
|
|
}
|
2012-07-26 03:55:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$conn = $this->getConnection();
|
|
|
|
|
2012-07-13 14:16:16 +02:00
|
|
|
$activeDirectoryDomain =
|
|
|
|
PhabricatorEnv::getEnvConfig('ldap.activedirectory_domain');
|
|
|
|
|
|
|
|
if ($activeDirectoryDomain) {
|
|
|
|
$dn = $username . '@' . $activeDirectoryDomain;
|
|
|
|
} else {
|
2012-07-17 23:05:26 +02:00
|
|
|
$dn = ldap_sprintf(
|
|
|
|
'%Q=%s,%Q',
|
|
|
|
$this->getSearchAttribute(),
|
|
|
|
$username,
|
|
|
|
$this->getBaseDN());
|
2012-07-13 14:16:16 +02:00
|
|
|
}
|
|
|
|
|
2012-07-17 21:06:33 +02:00
|
|
|
// NOTE: It is very important we suppress any messages that occur here,
|
|
|
|
// because it logs passwords if it reaches an error log of any sort.
|
|
|
|
DarkConsoleErrorLogPluginAPI::enableDiscardMode();
|
|
|
|
$result = @ldap_bind($conn, $dn, $password->openEnvelope());
|
|
|
|
DarkConsoleErrorLogPluginAPI::disableDiscardMode();
|
2012-06-13 17:52:05 +02:00
|
|
|
|
|
|
|
if (!$result) {
|
2012-07-17 21:06:33 +02:00
|
|
|
throw new Exception(
|
2012-07-26 23:32:51 +02:00
|
|
|
$this->invalidLDAPUserErrorMessage(
|
|
|
|
ldap_errno($conn),
|
|
|
|
ldap_error($conn)));
|
2012-06-13 17:52:05 +02:00
|
|
|
}
|
|
|
|
|
2012-07-26 03:55:48 +02:00
|
|
|
$this->userData = $this->getUser($this->getSearchAttribute(), $username);
|
2012-06-13 17:52:05 +02:00
|
|
|
return $this->userData;
|
|
|
|
}
|
|
|
|
|
2012-07-26 03:55:48 +02:00
|
|
|
private function getUser($attribute, $username) {
|
2012-07-17 23:05:26 +02:00
|
|
|
$conn = $this->getConnection();
|
|
|
|
|
|
|
|
$query = ldap_sprintf(
|
|
|
|
'%Q=%S',
|
2012-07-26 03:55:48 +02:00
|
|
|
$attribute,
|
2012-07-17 23:05:26 +02:00
|
|
|
$username);
|
|
|
|
|
|
|
|
$result = ldap_search($conn, $this->getBaseDN(), $query);
|
2012-06-13 17:52:05 +02:00
|
|
|
|
|
|
|
if (!$result) {
|
|
|
|
throw new Exception('Search failed. Please check your LDAP and HTTP '.
|
|
|
|
'logs for more information.');
|
|
|
|
}
|
|
|
|
|
2012-07-17 23:05:26 +02:00
|
|
|
$entries = ldap_get_entries($conn, $result);
|
2012-06-13 17:52:05 +02:00
|
|
|
|
|
|
|
if ($entries === false) {
|
|
|
|
throw new Exception('Could not get entries');
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($entries['count'] > 1) {
|
|
|
|
throw new Exception('Found more then one user with this ' .
|
2012-07-26 03:55:48 +02:00
|
|
|
$attribute);
|
2012-06-13 17:52:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($entries['count'] == 0) {
|
2012-07-26 23:32:51 +02:00
|
|
|
throw new PhabricatorLDAPUnknownUserException('Could not find user');
|
2012-06-13 17:52:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return $entries[0];
|
|
|
|
}
|
2012-07-04 04:10:38 +02:00
|
|
|
|
|
|
|
public function search($query) {
|
|
|
|
$result = ldap_search($this->getConnection(), $this->getBaseDN(),
|
|
|
|
$query);
|
|
|
|
|
|
|
|
if (!$result) {
|
|
|
|
throw new Exception('Search failed. Please check your LDAP and HTTP '.
|
|
|
|
'logs for more information.');
|
|
|
|
}
|
|
|
|
|
|
|
|
$entries = ldap_get_entries($this->getConnection(), $result);
|
|
|
|
|
|
|
|
if ($entries === false) {
|
|
|
|
throw new Exception('Could not get entries');
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($entries['count'] == 0) {
|
|
|
|
throw new Exception('No results found');
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$rows = array();
|
|
|
|
|
|
|
|
for($i = 0; $i < $entries['count']; $i++) {
|
|
|
|
$row = array();
|
|
|
|
$entry = $entries[$i];
|
2012-07-13 14:16:16 +02:00
|
|
|
|
2012-07-17 21:06:33 +02:00
|
|
|
// Get username, email and realname
|
2012-07-04 04:10:38 +02:00
|
|
|
$username = $entry[$this->getSearchAttribute()][0];
|
|
|
|
if(empty($username)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
$row[] = $username;
|
|
|
|
$row[] = $entry['mail'][0];
|
2012-07-17 21:06:33 +02:00
|
|
|
$row[] = $this->retrieveUserRealNameFromData($entry);
|
2012-07-04 04:10:38 +02:00
|
|
|
|
|
|
|
|
|
|
|
$rows[] = $row;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $rows;
|
|
|
|
|
|
|
|
}
|
2012-06-13 17:52:05 +02:00
|
|
|
}
|