Changeset View
Changeset View
Standalone View
Standalone View
src/app/Backends/LDAP.php
Show First 20 Lines • Show All 71 Lines • ▼ Show 20 Lines | class LDAP | ||||
* @throws \Exception | * @throws \Exception | ||||
*/ | */ | ||||
public static function createDomain(Domain $domain): void | public static function createDomain(Domain $domain): void | ||||
{ | { | ||||
$config = self::getConfig('admin'); | $config = self::getConfig('admin'); | ||||
$ldap = self::initLDAP($config); | $ldap = self::initLDAP($config); | ||||
$mgmtRootDN = \config('ldap.admin.root_dn'); | $mgmtRootDN = \config('ldap.admin.root_dn'); | ||||
$domainBaseDN = self::baseDN($domain->namespace); | $hostedRootDN = \config('ldap.hosted.root_dn'); | ||||
$domainBaseDN = "ou={$domain->namespace},{$hostedRootDN}"; | |||||
$aci = [ | $aci = [ | ||||
'(targetattr = "*")' | '(targetattr = "*")' | ||||
. '(version 3.0; acl "Deny Unauthorized"; deny (all)' | . '(version 3.0; acl "Deny Unauthorized"; deny (all)' | ||||
. '(userdn != "ldap:///uid=kolab-service,ou=Special Users,' . $mgmtRootDN | . '(userdn != "ldap:///uid=kolab-service,ou=Special Users,' . $mgmtRootDN | ||||
. ' || ldap:///ou=People,' . $domainBaseDN . '??sub?(objectclass=inetorgperson)") ' | . ' || ldap:///ou=People,' . $domainBaseDN . '??sub?(objectclass=inetorgperson)") ' | ||||
. 'AND NOT roledn = "ldap:///cn=kolab-admin,' . $mgmtRootDN . '";)', | . 'AND NOT roledn = "ldap:///cn=kolab-admin,' . $mgmtRootDN . '";)', | ||||
▲ Show 20 Lines • Show All 78 Lines • ▼ Show 20 Lines | public static function createDomain(Domain $domain): void | ||||
$ldap, | $ldap, | ||||
$domainBaseDN, | $domainBaseDN, | ||||
$entry, | $entry, | ||||
"Failed to create domain {$domain->namespace} in LDAP (" . __LINE__ . ")" | "Failed to create domain {$domain->namespace} in LDAP (" . __LINE__ . ")" | ||||
); | ); | ||||
} | } | ||||
foreach (['Groups', 'People', 'Resources', 'Shared Folders'] as $item) { | foreach (['Groups', 'People', 'Resources', 'Shared Folders'] as $item) { | ||||
$itemDN = self::baseDN($domain->namespace, $item); | $itemDN = "ou={$item},{$domainBaseDN}"; | ||||
if (!$ldap->get_entry($itemDN)) { | if (!$ldap->get_entry($itemDN)) { | ||||
$itemEntry = [ | $itemEntry = [ | ||||
'ou' => $item, | 'ou' => $item, | ||||
'description' => $item, | 'description' => $item, | ||||
'objectclass' => [ | 'objectclass' => [ | ||||
'top', | 'top', | ||||
'organizationalunit' | 'organizationalunit' | ||||
] | ] | ||||
▲ Show 20 Lines • Show All 48 Lines • ▼ Show 20 Lines | class LDAP | ||||
*/ | */ | ||||
public static function createGroup(Group $group): void | public static function createGroup(Group $group): void | ||||
{ | { | ||||
$config = self::getConfig('admin'); | $config = self::getConfig('admin'); | ||||
$ldap = self::initLDAP($config); | $ldap = self::initLDAP($config); | ||||
$domainName = explode('@', $group->email, 2)[1]; | $domainName = explode('@', $group->email, 2)[1]; | ||||
$cn = $ldap->quote_string($group->name); | $cn = $ldap->quote_string($group->name); | ||||
$dn = "cn={$cn}," . self::baseDN($domainName, 'Groups'); | $dn = "cn={$cn}," . self::baseDN($ldap, $domainName, 'Groups'); | ||||
$entry = [ | $entry = [ | ||||
'mail' => $group->email, | 'mail' => $group->email, | ||||
'objectclass' => [ | 'objectclass' => [ | ||||
'top', | 'top', | ||||
'groupofuniquenames', | 'groupofuniquenames', | ||||
'kolabgroupofuniquenames' | 'kolabgroupofuniquenames' | ||||
], | ], | ||||
Show All 22 Lines | class LDAP | ||||
*/ | */ | ||||
public static function createResource(Resource $resource): void | public static function createResource(Resource $resource): void | ||||
{ | { | ||||
$config = self::getConfig('admin'); | $config = self::getConfig('admin'); | ||||
$ldap = self::initLDAP($config); | $ldap = self::initLDAP($config); | ||||
$domainName = explode('@', $resource->email, 2)[1]; | $domainName = explode('@', $resource->email, 2)[1]; | ||||
$cn = $ldap->quote_string($resource->name); | $cn = $ldap->quote_string($resource->name); | ||||
$dn = "cn={$cn}," . self::baseDN($domainName, 'Resources'); | $dn = "cn={$cn}," . self::baseDN($ldap, $domainName, 'Resources'); | ||||
$entry = [ | $entry = [ | ||||
'mail' => $resource->email, | 'mail' => $resource->email, | ||||
'objectclass' => [ | 'objectclass' => [ | ||||
'top', | 'top', | ||||
'kolabresource', | 'kolabresource', | ||||
'kolabsharedfolder', | 'kolabsharedfolder', | ||||
'mailrecipient', | 'mailrecipient', | ||||
Show All 24 Lines | class LDAP | ||||
*/ | */ | ||||
public static function createSharedFolder(SharedFolder $folder): void | public static function createSharedFolder(SharedFolder $folder): void | ||||
{ | { | ||||
$config = self::getConfig('admin'); | $config = self::getConfig('admin'); | ||||
$ldap = self::initLDAP($config); | $ldap = self::initLDAP($config); | ||||
$domainName = explode('@', $folder->email, 2)[1]; | $domainName = explode('@', $folder->email, 2)[1]; | ||||
$cn = $ldap->quote_string($folder->name); | $cn = $ldap->quote_string($folder->name); | ||||
$dn = "cn={$cn}," . self::baseDN($domainName, 'Shared Folders'); | $dn = "cn={$cn}," . self::baseDN($ldap, $domainName, 'Shared Folders'); | ||||
$entry = [ | $entry = [ | ||||
'mail' => $folder->email, | 'mail' => $folder->email, | ||||
'objectclass' => [ | 'objectclass' => [ | ||||
'top', | 'top', | ||||
'kolabsharedfolder', | 'kolabsharedfolder', | ||||
'mailrecipient', | 'mailrecipient', | ||||
], | ], | ||||
▲ Show 20 Lines • Show All 81 Lines • ▼ Show 20 Lines | class LDAP | ||||
* | * | ||||
* @throws \Exception | * @throws \Exception | ||||
*/ | */ | ||||
public static function deleteDomain(Domain $domain): void | public static function deleteDomain(Domain $domain): void | ||||
{ | { | ||||
$config = self::getConfig('admin'); | $config = self::getConfig('admin'); | ||||
$ldap = self::initLDAP($config); | $ldap = self::initLDAP($config); | ||||
$domainBaseDN = self::baseDN($domain->namespace); | $domainBaseDN = self::baseDN($ldap, $domain->namespace); | ||||
if ($ldap->get_entry($domainBaseDN)) { | if ($ldap->get_entry($domainBaseDN)) { | ||||
$result = $ldap->delete_entry_recursive($domainBaseDN); | $result = $ldap->delete_entry_recursive($domainBaseDN); | ||||
if (!$result) { | if (!$result) { | ||||
self::throwException( | self::throwException( | ||||
$ldap, | $ldap, | ||||
"Failed to delete domain {$domain->namespace} from LDAP (" . __LINE__ . ")" | "Failed to delete domain {$domain->namespace} from LDAP (" . __LINE__ . ")" | ||||
▲ Show 20 Lines • Show All 495 Lines • ▼ Show 20 Lines | class LDAP | ||||
{ | { | ||||
$settings = $group->getSettings(['sender_policy']); | $settings = $group->getSettings(['sender_policy']); | ||||
$entry['kolaballowsmtpsender'] = json_decode($settings['sender_policy'] ?: '[]', true); | $entry['kolaballowsmtpsender'] = json_decode($settings['sender_policy'] ?: '[]', true); | ||||
$entry['cn'] = $group->name; | $entry['cn'] = $group->name; | ||||
$entry['uniquemember'] = []; | $entry['uniquemember'] = []; | ||||
$groupDomain = explode('@', $group->email, 2)[1]; | $groupDomain = explode('@', $group->email, 2)[1]; | ||||
$domainBaseDN = self::baseDN($groupDomain); | $domainBaseDN = self::baseDN($ldap, $groupDomain); | ||||
$validMembers = []; | $validMembers = []; | ||||
foreach ($group->members as $member) { | foreach ($group->members as $member) { | ||||
list($local, $domainName) = explode('@', $member); | list($local, $domainName) = explode('@', $member); | ||||
$memberDN = "uid={$member},ou=People,{$domainBaseDN}"; | $memberDN = "uid={$member},ou=People,{$domainBaseDN}"; | ||||
$memberEntry = $ldap->get_entry($memberDN); | $memberEntry = $ldap->get_entry($memberDN); | ||||
▲ Show 20 Lines • Show All 200 Lines • ▼ Show 20 Lines | |||||
* @param string $email Group email (mail) | * @param string $email Group email (mail) | ||||
* @param string $dn Reference to group DN | * @param string $dn Reference to group DN | ||||
* | * | ||||
* @return null|array Group entry, False on error, NULL if not found | * @return null|array Group entry, False on error, NULL if not found | ||||
*/ | */ | ||||
private static function getGroupEntry($ldap, $email, &$dn = null) | private static function getGroupEntry($ldap, $email, &$dn = null) | ||||
{ | { | ||||
$domainName = explode('@', $email, 2)[1]; | $domainName = explode('@', $email, 2)[1]; | ||||
$base_dn = self::baseDN($domainName, 'Groups'); | $base_dn = self::baseDN($ldap, $domainName, 'Groups'); | ||||
$attrs = ['dn', 'cn', 'mail', 'uniquemember', 'objectclass', 'kolaballowsmtpsender']; | $attrs = ['dn', 'cn', 'mail', 'uniquemember', 'objectclass', 'kolaballowsmtpsender']; | ||||
// For groups we're using search() instead of get_entry() because | // For groups we're using search() instead of get_entry() because | ||||
// a group name is not constant, so e.g. on update we might have | // a group name is not constant, so e.g. on update we might have | ||||
// the new name, but not the old one. Email address is constant. | // the new name, but not the old one. Email address is constant. | ||||
return self::searchEntry($ldap, $base_dn, "(mail=$email)", $attrs, $dn); | return self::searchEntry($ldap, $base_dn, "(mail=$email)", $attrs, $dn); | ||||
} | } | ||||
/** | /** | ||||
* Get a resource entry from LDAP. | * Get a resource entry from LDAP. | ||||
* | * | ||||
* @param \Net_LDAP3 $ldap Ldap connection | * @param \Net_LDAP3 $ldap Ldap connection | ||||
* @param string $email Resource email (mail) | * @param string $email Resource email (mail) | ||||
* @param string $dn Reference to the resource DN | * @param string $dn Reference to the resource DN | ||||
* | * | ||||
* @return null|array Resource entry, NULL if not found | * @return null|array Resource entry, NULL if not found | ||||
*/ | */ | ||||
private static function getResourceEntry($ldap, $email, &$dn = null) | private static function getResourceEntry($ldap, $email, &$dn = null) | ||||
{ | { | ||||
$domainName = explode('@', $email, 2)[1]; | $domainName = explode('@', $email, 2)[1]; | ||||
$base_dn = self::baseDN($domainName, 'Resources'); | $base_dn = self::baseDN($ldap, $domainName, 'Resources'); | ||||
$attrs = ['dn', 'cn', 'mail', 'objectclass', 'kolabtargetfolder', | $attrs = ['dn', 'cn', 'mail', 'objectclass', 'kolabtargetfolder', | ||||
'kolabfoldertype', 'kolabinvitationpolicy', 'owner', 'acl']; | 'kolabfoldertype', 'kolabinvitationpolicy', 'owner', 'acl']; | ||||
// For resources we're using search() instead of get_entry() because | // For resources we're using search() instead of get_entry() because | ||||
// a resource name is not constant, so e.g. on update we might have | // a resource name is not constant, so e.g. on update we might have | ||||
// the new name, but not the old one. Email address is constant. | // the new name, but not the old one. Email address is constant. | ||||
return self::searchEntry($ldap, $base_dn, "(mail=$email)", $attrs, $dn); | return self::searchEntry($ldap, $base_dn, "(mail=$email)", $attrs, $dn); | ||||
} | } | ||||
/** | /** | ||||
* Get a shared folder entry from LDAP. | * Get a shared folder entry from LDAP. | ||||
* | * | ||||
* @param \Net_LDAP3 $ldap Ldap connection | * @param \Net_LDAP3 $ldap Ldap connection | ||||
* @param string $email Resource email (mail) | * @param string $email Resource email (mail) | ||||
* @param string $dn Reference to the shared folder DN | * @param string $dn Reference to the shared folder DN | ||||
* | * | ||||
* @return null|array Shared folder entry, NULL if not found | * @return null|array Shared folder entry, NULL if not found | ||||
*/ | */ | ||||
private static function getSharedFolderEntry($ldap, $email, &$dn = null) | private static function getSharedFolderEntry($ldap, $email, &$dn = null) | ||||
{ | { | ||||
$domainName = explode('@', $email, 2)[1]; | $domainName = explode('@', $email, 2)[1]; | ||||
$base_dn = self::baseDN($domainName, 'Shared Folders'); | $base_dn = self::baseDN($ldap, $domainName, 'Shared Folders'); | ||||
$attrs = ['dn', 'cn', 'mail', 'objectclass', 'kolabtargetfolder', 'kolabfoldertype', 'acl']; | $attrs = ['dn', 'cn', 'mail', 'objectclass', 'kolabtargetfolder', 'kolabfoldertype', 'acl']; | ||||
// For shared folders we're using search() instead of get_entry() because | // For shared folders we're using search() instead of get_entry() because | ||||
// a folder name is not constant, so e.g. on update we might have | // a folder name is not constant, so e.g. on update we might have | ||||
// the new name, but not the old one. Email address is constant. | // the new name, but not the old one. Email address is constant. | ||||
return self::searchEntry($ldap, $base_dn, "(mail=$email)", $attrs, $dn); | return self::searchEntry($ldap, $base_dn, "(mail=$email)", $attrs, $dn); | ||||
} | } | ||||
/** | /** | ||||
* Get user entry from LDAP. | * Get user entry from 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 ?array User entry, NULL if not found | * @return ?array User entry, NULL if not found | ||||
*/ | */ | ||||
private static function getUserEntry($ldap, $email, &$dn = null, $full = false) | private static function getUserEntry($ldap, $email, &$dn = null, $full = false) | ||||
{ | { | ||||
$domainName = explode('@', $email, 2)[1]; | $domainName = explode('@', $email, 2)[1]; | ||||
$base_dn = $ldap->domain_root_dn($domainName); | $dn = "uid={$email}," . self::baseDN($ldap, $domainName, 'People'); | ||||
$dn = "uid={$email},ou=People,{$base_dn}"; | |||||
$entry = $ldap->get_entry($dn); | $entry = $ldap->get_entry($dn); | ||||
if ($entry && $full) { | if ($entry && $full) { | ||||
if (!array_key_exists('nsroledn', $entry)) { | if (!array_key_exists('nsroledn', $entry)) { | ||||
$roles = $ldap->get_entry_attributes($dn, ['nsroledn']); | $roles = $ldap->get_entry_attributes($dn, ['nsroledn']); | ||||
if (!empty($roles)) { | if (!empty($roles)) { | ||||
$entry['nsroledn'] = (array) $roles['nsroledn']; | $entry['nsroledn'] = (array) $roles['nsroledn']; | ||||
▲ Show 20 Lines • Show All 131 Lines • ▼ Show 20 Lines | private static function throwException($ldap, string $message): void | ||||
if (empty(self::$ldap) && !empty($ldap)) { | if (empty(self::$ldap) && !empty($ldap)) { | ||||
$ldap->close(); | $ldap->close(); | ||||
} | } | ||||
throw new \Exception($message); | throw new \Exception($message); | ||||
} | } | ||||
/** | /** | ||||
* Create a base DN string for specified object | * Create a base DN string for a specified object. | ||||
* Note: It makes sense with an existing domain only. | |||||
* | * | ||||
* @param \Net_LDAP3 $ldap Ldap connection | |||||
* @param string $domainName Domain namespace | * @param string $domainName Domain namespace | ||||
* @param ?string $ouName Optional name of the sub-tree (OU) | * @param ?string $ouName Optional name of the sub-tree (OU) | ||||
* | * | ||||
* @return string Full base DN | * @return string Full base DN | ||||
*/ | */ | ||||
private static function baseDN(string $domainName, string $ouName = null): string | private static function baseDN($ldap, string $domainName, string $ouName = null): string | ||||
{ | { | ||||
$hostedRootDN = \config('ldap.hosted.root_dn'); | $dn = $ldap->domain_root_dn($domainName); | ||||
$dn = "ou={$domainName},{$hostedRootDN}"; | |||||
if ($ouName) { | if ($ouName) { | ||||
$dn = "ou={$ouName},{$dn}"; | $dn = "ou={$ouName},{$dn}"; | ||||
} | } | ||||
return $dn; | return $dn; | ||||
} | } | ||||
} | } |