Changeset View
Changeset View
Standalone View
Standalone View
src/app/Backends/LDAP.php
Show All 16 Lines | class LDAP | ||||
/** @var ?\Net_LDAP3 LDAP connection object */ | /** @var ?\Net_LDAP3 LDAP connection object */ | ||||
protected static $ldap; | protected static $ldap; | ||||
/** | /** | ||||
* Starts a new LDAP connection that will be used by all methods | * Starts a new LDAP connection that will be used by all methods | ||||
* until you call self::disconnect() explicitely. Normally every | * until you call self::disconnect() explicitely. Normally every | ||||
* method uses a separate connection. | * method uses a separate connection. | ||||
* | |||||
* @throws \Exception | |||||
*/ | */ | ||||
public static function connect(): void | public static function connect(): void | ||||
{ | { | ||||
if (empty(self::$ldap)) { | if (empty(self::$ldap)) { | ||||
$config = self::getConfig('admin'); | $config = self::getConfig('admin'); | ||||
self::$ldap = self::initLDAP($config); | self::$ldap = self::initLDAP($config); | ||||
} | } | ||||
} | } | ||||
Show All 9 Lines | public static function disconnect(): void | ||||
} | } | ||||
} | } | ||||
/** | /** | ||||
* Create a domain in LDAP. | * Create a domain in LDAP. | ||||
* | * | ||||
* @param \App\Domain $domain The domain to create. | * @param \App\Domain $domain The domain to create. | ||||
* | * | ||||
* @return void | * @throws \Exception | ||||
*/ | */ | ||||
public static function createDomain(Domain $domain) | public static function createDomain(Domain $domain): void | ||||
{ | { | ||||
$config = self::getConfig('admin'); | $config = self::getConfig('admin'); | ||||
$ldap = self::initLDAP($config); | $ldap = self::initLDAP($config); | ||||
$hostedRootDN = \config('ldap.hosted.root_dn'); | $hostedRootDN = \config('ldap.hosted.root_dn'); | ||||
$mgmtRootDN = \config('ldap.admin.root_dn'); | $mgmtRootDN = \config('ldap.admin.root_dn'); | ||||
$domainBaseDN = "ou={$domain->namespace},{$hostedRootDN}"; | $domainBaseDN = "ou={$domain->namespace},{$hostedRootDN}"; | ||||
Show All 24 Lines | public static function createDomain(Domain $domain): void | ||||
'top', | 'top', | ||||
'domainrelatedobject', | 'domainrelatedobject', | ||||
'inetdomain' | 'inetdomain' | ||||
], | ], | ||||
]; | ]; | ||||
$dn = "associateddomain={$domain->namespace},{$config['domain_base_dn']}"; | $dn = "associateddomain={$domain->namespace},{$config['domain_base_dn']}"; | ||||
self::setDomainAttributes($domain, $entry); | |||||
if (!$ldap->get_entry($dn)) { | if (!$ldap->get_entry($dn)) { | ||||
$ldap->add_entry($dn, $entry); | $result = $ldap->add_entry($dn, $entry); | ||||
if (!$result) { | |||||
self::throwException($ldap, "Failed to create a domain in LDAP"); | |||||
} | |||||
} | } | ||||
// create ou, roles, ous | // create ou, roles, ous | ||||
$entry = [ | $entry = [ | ||||
'description' => $domain->namespace, | 'description' => $domain->namespace, | ||||
'objectclass' => [ | 'objectclass' => [ | ||||
'top', | 'top', | ||||
'organizationalunit' | 'organizationalunit' | ||||
▲ Show 20 Lines • Show All 47 Lines • ▼ Show 20 Lines | public static function createDomain(Domain $domain): void | ||||
'top', | 'top', | ||||
'organizationalunit' | 'organizationalunit' | ||||
] | ] | ||||
] | ] | ||||
); | ); | ||||
} | } | ||||
} | } | ||||
foreach (['kolab-admin', 'billing-user'] as $item) { | foreach (['kolab-admin'] as $item) { | ||||
if (!$ldap->get_entry("cn={$item},{$domainBaseDN}")) { | if (!$ldap->get_entry("cn={$item},{$domainBaseDN}")) { | ||||
$ldap->add_entry( | $ldap->add_entry( | ||||
"cn={$item},{$domainBaseDN}", | "cn={$item},{$domainBaseDN}", | ||||
[ | [ | ||||
'cn' => $item, | 'cn' => $item, | ||||
'description' => "{$item} role", | 'description' => "{$item} role", | ||||
'objectclass' => [ | 'objectclass' => [ | ||||
'top', | 'top', | ||||
'ldapsubentry', | 'ldapsubentry', | ||||
'nsmanagedroledefinition', | 'nsmanagedroledefinition', | ||||
'nsroledefinition', | 'nsroledefinition', | ||||
'nssimpleroledefinition' | 'nssimpleroledefinition' | ||||
] | ] | ||||
] | ] | ||||
); | ); | ||||
} | } | ||||
} | } | ||||
// TODO: Assign kolab-admin role to the owner? | |||||
if (empty(self::$ldap)) { | if (empty(self::$ldap)) { | ||||
$ldap->close(); | $ldap->close(); | ||||
} | } | ||||
} | } | ||||
/** | /** | ||||
* Create a user in LDAP. | * Create a user in LDAP. | ||||
* | * | ||||
Show All 9 Lines | class LDAP | ||||
* * We certainly don't have its password anymore. | * * We certainly don't have its password anymore. | ||||
* | * | ||||
* 2) The hosted kolab account. | * 2) The hosted kolab account. | ||||
* | * | ||||
* 3) The Directory Manager account. | * 3) The Directory Manager account. | ||||
* | * | ||||
* @param \App\User $user The user account to create. | * @param \App\User $user The user account to create. | ||||
* | * | ||||
* @return bool|void | * @throws \Exception | ||||
*/ | */ | ||||
public static function createUser(User $user) | public static function createUser(User $user): void | ||||
{ | { | ||||
$config = self::getConfig('admin'); | $config = self::getConfig('admin'); | ||||
$ldap = self::initLDAP($config); | $ldap = self::initLDAP($config); | ||||
$entry = [ | $entry = [ | ||||
'objectclass' => [ | 'objectclass' => [ | ||||
'top', | 'top', | ||||
'inetorgperson', | 'inetorgperson', | ||||
'inetuser', | 'inetuser', | ||||
'kolabinetorgperson', | 'kolabinetorgperson', | ||||
'mailrecipient', | 'mailrecipient', | ||||
'person' | 'person' | ||||
], | ], | ||||
'mail' => $user->email, | 'mail' => $user->email, | ||||
'uid' => $user->email, | 'uid' => $user->email, | ||||
'nsroledn' => [] | 'nsroledn' => [] | ||||
]; | ]; | ||||
if (!self::getUserEntry($ldap, $user->email, $dn) && $dn) { | if (!self::getUserEntry($ldap, $user->email, $dn) && $dn) { | ||||
self::setUserAttributes($user, $entry); | self::setUserAttributes($user, $entry); | ||||
$ldap->add_entry($dn, $entry); | $result = $ldap->add_entry($dn, $entry); | ||||
if (!$result) { | |||||
self::throwException($ldap, "Failed to create a user in LDAP"); | |||||
} | |||||
} | } | ||||
if (empty(self::$ldap)) { | if (empty(self::$ldap)) { | ||||
$ldap->close(); | $ldap->close(); | ||||
} | } | ||||
} | } | ||||
/** | /** | ||||
* Update a domain in LDAP. | * Delete a domain from LDAP. | ||||
* | * | ||||
* @param \App\Domain $domain The domain to update. | * @param \App\Domain $domain The domain to update. | ||||
* | * | ||||
* @return void | * @throws \Exception | ||||
*/ | */ | ||||
public static function updateDomain(Domain $domain) | public static function deleteDomain(Domain $domain): void | ||||
{ | { | ||||
$config = self::getConfig('admin'); | $config = self::getConfig('admin'); | ||||
$ldap = self::initLDAP($config); | $ldap = self::initLDAP($config); | ||||
$ldapDomain = $ldap->find_domain($domain->namespace); | $hostedRootDN = \config('ldap.hosted.root_dn'); | ||||
$mgmtRootDN = \config('ldap.admin.root_dn'); | |||||
$oldEntry = $ldap->get_entry($ldapDomain['dn']); | $domainBaseDN = "ou={$domain->namespace},{$hostedRootDN}"; | ||||
$newEntry = $oldEntry; | |||||
self::setDomainAttributes($domain, $newEntry); | if ($ldap->get_entry($domainBaseDN)) { | ||||
$result = $ldap->delete_entry_recursive($domainBaseDN); | |||||
$ldap->modify_entry($ldapDomain['dn'], $oldEntry, $newEntry); | if (!$result) { | ||||
self::throwException($ldap, "Failed to delete a domain from LDAP"); | |||||
} | |||||
} | |||||
if ($ldap_domain = $ldap->find_domain($domain->namespace)) { | |||||
if ($ldap->get_entry($ldap_domain['dn'])) { | |||||
$result = $ldap->delete_entry($ldap_domain['dn']); | |||||
if (!$result) { | |||||
self::throwException($ldap, "Failed to delete a domain from LDAP"); | |||||
} | |||||
} | |||||
} | |||||
if (empty(self::$ldap)) { | if (empty(self::$ldap)) { | ||||
$ldap->close(); | $ldap->close(); | ||||
} | } | ||||
} | } | ||||
/** | /** | ||||
* Delete a domain from LDAP. | * Delete a user from LDAP. | ||||
* | * | ||||
* @param \App\Domain $domain The domain to update. | * @param \App\User $user The user account to update. | ||||
* | * | ||||
* @return void | * @throws \Exception | ||||
*/ | */ | ||||
public static function deleteDomain(Domain $domain) | public static function deleteUser(User $user): void | ||||
{ | { | ||||
$config = self::getConfig('admin'); | $config = self::getConfig('admin'); | ||||
$ldap = self::initLDAP($config); | $ldap = self::initLDAP($config); | ||||
$hostedRootDN = \config('ldap.hosted.root_dn'); | if (self::getUserEntry($ldap, $user->email, $dn)) { | ||||
$mgmtRootDN = \config('ldap.admin.root_dn'); | $result = $ldap->delete_entry($dn); | ||||
$domainBaseDN = "ou={$domain->namespace},{$hostedRootDN}"; | |||||
if ($ldap->get_entry($domainBaseDN)) { | |||||
$ldap->delete_entry_recursive($domainBaseDN); | |||||
} | |||||
if ($ldap_domain = $ldap->find_domain($domain->namespace)) { | if (!$result) { | ||||
if ($ldap->get_entry($ldap_domain['dn'])) { | self::throwException($ldap, "Failed to delete a user from LDAP"); | ||||
$ldap->delete_entry($ldap_domain['dn']); | |||||
} | } | ||||
} | } | ||||
if (empty(self::$ldap)) { | if (empty(self::$ldap)) { | ||||
$ldap->close(); | $ldap->close(); | ||||
} | } | ||||
} | } | ||||
/** | /** | ||||
* Delete a user from LDAP. | * Get a domain data from LDAP. | ||||
* | * | ||||
* @param \App\User $user The user account to update. | * @param string $namespace The domain name | ||||
* | * | ||||
* @return void | * @return array|false|null | ||||
* @throws \Exception | |||||
*/ | */ | ||||
public static function deleteUser(User $user) | public static function getDomain(string $namespace) | ||||
{ | { | ||||
$config = self::getConfig('admin'); | $config = self::getConfig('admin'); | ||||
$ldap = self::initLDAP($config); | $ldap = self::initLDAP($config); | ||||
if (self::getUserEntry($ldap, $user->email, $dn)) { | $ldapDomain = $ldap->find_domain($namespace); | ||||
$ldap->delete_entry($dn); | |||||
if ($ldapDomain) { | |||||
$domain = $ldap->get_entry($ldapDomain['dn']); | |||||
} | } | ||||
if (empty(self::$ldap)) { | if (empty(self::$ldap)) { | ||||
$ldap->close(); | $ldap->close(); | ||||
} | } | ||||
return $domain ?? null; | |||||
} | } | ||||
/** | /** | ||||
* Get a user data from LDAP. | * Get a user data from LDAP. | ||||
* | * | ||||
* @param string $email The user email. | * @param string $email The user email. | ||||
* | * | ||||
* @return array|false|null | * @return array|false|null | ||||
* @throws \Exception | |||||
*/ | */ | ||||
public static function getUser(string $email) | public static function getUser(string $email) | ||||
{ | { | ||||
$config = self::getConfig('admin'); | $config = self::getConfig('admin'); | ||||
$ldap = self::initLDAP($config); | $ldap = self::initLDAP($config); | ||||
$user = self::getUserEntry($ldap, $email, $dn, true); | $user = self::getUserEntry($ldap, $email, $dn, true); | ||||
if (empty(self::$ldap)) { | if (empty(self::$ldap)) { | ||||
$ldap->close(); | $ldap->close(); | ||||
} | } | ||||
return $user; | return $user; | ||||
} | } | ||||
/** | /** | ||||
* Update a domain in LDAP. | |||||
* | |||||
* @param \App\Domain $domain The domain to update. | |||||
* | |||||
* @throws \Exception | |||||
*/ | |||||
public static function updateDomain(Domain $domain): void | |||||
{ | |||||
$config = self::getConfig('admin'); | |||||
$ldap = self::initLDAP($config); | |||||
$ldapDomain = $ldap->find_domain($domain->namespace); | |||||
if (!$ldapDomain) { | |||||
self::throwException($ldap, "Failed to update a domain in LDAP (domain not found)"); | |||||
} | |||||
$oldEntry = $ldap->get_entry($ldapDomain['dn']); | |||||
$newEntry = $oldEntry; | |||||
self::setDomainAttributes($domain, $newEntry); | |||||
$result = $ldap->modify_entry($ldapDomain['dn'], $oldEntry, $newEntry); | |||||
if (!is_array($result)) { | |||||
self::throwException($ldap, "Failed to update a domain in LDAP"); | |||||
} | |||||
if (empty(self::$ldap)) { | |||||
$ldap->close(); | |||||
} | |||||
} | |||||
/** | |||||
* Update a user in LDAP. | * Update a user in LDAP. | ||||
* | * | ||||
* @param \App\User $user The user account to update. | * @param \App\User $user The user account to update. | ||||
* | * | ||||
* @return false|void | * @throws \Exception | ||||
*/ | */ | ||||
public static function updateUser(User $user) | public static function updateUser(User $user): void | ||||
{ | { | ||||
$config = self::getConfig('admin'); | $config = self::getConfig('admin'); | ||||
$ldap = self::initLDAP($config); | $ldap = self::initLDAP($config); | ||||
$newEntry = $oldEntry = self::getUserEntry($ldap, $user->email, $dn, true); | $newEntry = $oldEntry = self::getUserEntry($ldap, $user->email, $dn, true); | ||||
if (!$oldEntry) { | if (!$oldEntry) { | ||||
return false; | self::throwException($ldap, "Failed to update a user in LDAP (user not found)"); | ||||
} | } | ||||
self::setUserAttributes($user, $newEntry); | self::setUserAttributes($user, $newEntry); | ||||
$ldap->modify_entry($dn, $oldEntry, $newEntry); | $result = $ldap->modify_entry($dn, $oldEntry, $newEntry); | ||||
if (!is_array($result)) { | |||||
self::throwException($ldap, "Failed to update a user in LDAP"); | |||||
} | |||||
if (empty(self::$ldap)) { | if (empty(self::$ldap)) { | ||||
$ldap->close(); | $ldap->close(); | ||||
} | } | ||||
} | } | ||||
/** | /** | ||||
* Initialize connection to LDAP | * Initialize connection to LDAP | ||||
*/ | */ | ||||
private static function initLDAP(array $config, string $privilege = 'admin') | private static function initLDAP(array $config, string $privilege = 'admin') | ||||
{ | { | ||||
if (self::$ldap) { | if (self::$ldap) { | ||||
return self::$ldap; | return self::$ldap; | ||||
} | } | ||||
$ldap = new \Net_LDAP3($config); | $ldap = new \Net_LDAP3($config); | ||||
$ldap->connect(); | $connected = $ldap->connect(); | ||||
$ldap->bind(\config("ldap.{$privilege}.bind_dn"), \config("ldap.{$privilege}.bind_pw")); | if (!$connected) { | ||||
throw new \Exception("Failed to connect to LDAP"); | |||||
} | |||||
// TODO: error handling | $bound = $ldap->bind(\config("ldap.{$privilege}.bind_dn"), \config("ldap.{$privilege}.bind_pw")); | ||||
if (!$bound) { | |||||
throw new \Exception("Failed to bind to LDAP"); | |||||
} | |||||
return $ldap; | return $ldap; | ||||
} | } | ||||
/** | /** | ||||
* Set domain attributes | * Set domain attributes | ||||
*/ | */ | ||||
private static function setDomainAttributes(Domain $domain, array &$entry) | private static function setDomainAttributes(Domain $domain, array &$entry) | ||||
▲ Show 20 Lines • Show All 112 Lines • ▼ Show 20 Lines | class LDAP | ||||
* | * | ||||
* @param \Net_LDAP3 $ldap Ldap connection | * @param \Net_LDAP3 $ldap Ldap connection | ||||
* @param string $email User email (uid) | * @param string $email User email (uid) | ||||
* @param string $dn Reference to user DN | * @param string $dn Reference to user DN | ||||
* @param bool $full Get extra attributes, e.g. nsroledn | * @param bool $full Get extra attributes, e.g. nsroledn | ||||
* | * | ||||
* @return false|null|array User entry, False on error, NULL if not found | * @return false|null|array User entry, False on error, NULL if not found | ||||
*/ | */ | ||||
protected static function getUserEntry($ldap, $email, &$dn = null, $full = false) | private static function getUserEntry($ldap, $email, &$dn = null, $full = false) | ||||
{ | { | ||||
list($_local, $_domain) = explode('@', $email, 2); | list($_local, $_domain) = explode('@', $email, 2); | ||||
$domain = $ldap->find_domain($_domain); | $domain = $ldap->find_domain($_domain); | ||||
if (!$domain) { | if (!$domain) { | ||||
return false; | return $domain; | ||||
} | } | ||||
$base_dn = $ldap->domain_root_dn($_domain); | $base_dn = $ldap->domain_root_dn($_domain); | ||||
$dn = "uid={$email},ou=People,{$base_dn}"; | $dn = "uid={$email},ou=People,{$base_dn}"; | ||||
$entry = $ldap->get_entry($dn); | $entry = $ldap->get_entry($dn); | ||||
if ($entry && $full) { | if ($entry && $full) { | ||||
▲ Show 20 Lines • Show All 56 Lines • ▼ Show 20 Lines | public static function logHook($level, $msg): void | ||||
if (is_array($msg)) { | if (is_array($msg)) { | ||||
$msg = implode("\n", $msg); | $msg = implode("\n", $msg); | ||||
} | } | ||||
$msg = '[LDAP] ' . $msg; | $msg = '[LDAP] ' . $msg; | ||||
\Log::{$function}($msg); | \Log::{$function}($msg); | ||||
} | } | ||||
/** | |||||
* Throw exception and close the connection when needed | |||||
* | |||||
* @param \Net_LDAP3 $ldap Ldap connection | |||||
* @param string $message Exception message | |||||
* | |||||
* @throws \Exception | |||||
*/ | |||||
private static function throwException($ldap, string $message): void | |||||
{ | |||||
if (empty(self::$ldap) && !empty($ldap)) { | |||||
$ldap->close(); | |||||
} | |||||
throw new \Exception($message); | |||||
} | |||||
} | } |