diff --git a/src/app/Backends/LDAP.php b/src/app/Backends/LDAP.php index 3ece6d4b..27cc8925 100644 --- a/src/app/Backends/LDAP.php +++ b/src/app/Backends/LDAP.php @@ -1,355 +1,369 @@ connect(); - - $ldap->bind( - //config('ldap.admin.bind_dn'), - "cn=Directory Manager", - //config('ldap.admin.bind_pw') - "Welcome2KolabSystems" - ); - - //$hosted_root_dn = config('ldap.hosted.root_dn'); - $hosted_root_dn = "dc=hosted,dc=com"; - $mgmt_root_dn = "dc=mgmt,dc=com"; + $hosted_root_dn = \config('ldap.hosted.root_dn'); + $mgmt_root_dn = \config('ldap.admin.root_dn'); $domain_base_dn = "ou={$domain->namespace},{$hosted_root_dn}"; $aci = [ '(targetattr = "*")' . '(version 3.0; acl "Deny Unauthorized"; deny (all)' . '(userdn != "ldap:///uid=kolab-service,ou=Special Users,' . $mgmt_root_dn . ' || ldap:///ou=People,' . $domain_base_dn . '??sub?(objectclass=inetorgperson)") ' . 'AND NOT roledn = "ldap:///cn=kolab-admin,' . $mgmt_root_dn . '";)', '(targetattr != "userPassword")' . '(version 3.0;acl "Search Access";allow (read,compare,search)' . '(userdn = "ldap:///uid=kolab-service,ou=Special Users,' . $mgmt_root_dn . ' || ldap:///ou=People,' . $domain_base_dn . '??sub?(objectclass=inetorgperson)");)', '(targetattr = "*")' . '(version 3.0;acl "Kolab Administrators";allow (all)' . '(roledn = "ldap:///cn=kolab-admin,' . $domain_base_dn . ' || ldap:///cn=kolab-admin,' . $mgmt_root_dn . '");)' ]; $entry = [ 'aci' => $aci, 'associateddomain' => $domain->namespace, 'inetdomainbasedn' => $domain_base_dn, 'objectclass' => [ 'top', 'domainrelatedobject', 'inetdomain' ], ]; $dn = "associateddomain={$domain->namespace},{$config['domain_base_dn']}"; if (!$ldap->get_entry($dn)) { $ldap->add_entry($dn, $entry); } // create ou, roles, ous $entry = [ - 'description' => $domain->namespace, + 'description' => $domain->namespace, 'objectclass' => [ 'top', 'organizationalunit' ], - 'ou' => $domain->namespace, + 'ou' => $domain->namespace, ]; $entry['aci'] = array( '(targetattr = "*")' . '(version 3.0;acl "Deny Unauthorized"; deny (all)' . '(userdn != "ldap:///uid=kolab-service,ou=Special Users,' . $mgmt_root_dn . ' || ldap:///ou=People,' . $domain_base_dn . '??sub?(objectclass=inetorgperson)") ' . 'AND NOT roledn = "ldap:///cn=kolab-admin,' . $mgmt_root_dn . '";)', '(targetattr != "userPassword")' . '(version 3.0;acl "Search Access";allow (read,compare,search,write)' . '(userdn = "ldap:///uid=kolab-service,ou=Special Users,' . $mgmt_root_dn . ' || ldap:///ou=People,' . $domain_base_dn . '??sub?(objectclass=inetorgperson)");)', '(targetattr = "*")' . '(version 3.0;acl "Kolab Administrators";allow (all)' . '(roledn = "ldap:///cn=kolab-admin,' . $domain_base_dn . ' || ldap:///cn=kolab-admin,' . $mgmt_root_dn . '");)', '(target = "ldap:///ou=*,' . $domain_base_dn . '")' . '(targetattr="objectclass || aci || ou")' . '(version 3.0;acl "Allow Domain sub-OU Registration"; allow (add)' . '(userdn = "ldap:///uid=kolab-service,ou=Special Users,' . $mgmt_root_dn . '");)', '(target = "ldap:///uid=*,ou=People,' . $domain_base_dn . '")(targetattr="*")' . '(version 3.0;acl "Allow Domain First User Registration"; allow (add)' . '(userdn = "ldap:///uid=kolab-service,ou=Special Users,' . $mgmt_root_dn . '");)', '(target = "ldap:///cn=*,' . $domain_base_dn . '")(targetattr="objectclass || cn")' . '(version 3.0;acl "Allow Domain Role Registration"; allow (add)' . '(userdn = "ldap:///uid=kolab-service,ou=Special Users,' . $mgmt_root_dn . '");)', ); if (!$ldap->get_entry($domain_base_dn)) { $ldap->add_entry($domain_base_dn, $entry); } foreach (['Groups', 'People', 'Resources', 'Shared Folders'] as $item) { if (!$ldap->get_entry("ou={$item},{$domain_base_dn}")) { $ldap->add_entry( "ou={$item},{$domain_base_dn}", [ 'ou' => $item, 'description' => $item, 'objectclass' => [ 'top', 'organizationalunit' ] ] ); } } foreach (['kolab-admin', 'imap-user', 'activesync-user', 'billing-user'] as $item) { if (!$ldap->get_entry("cn={$item},{$domain_base_dn}")) { $ldap->add_entry( "cn={$item},{$domain_base_dn}", [ 'cn' => $item, 'description' => "{$item} role", 'objectclass' => [ 'top', 'ldapsubentry', 'nsmanagedroledefinition', 'nsroledefinition', 'nssimpleroledefinition' ] ] ); } } $ldap->close(); } /** * Create a user in LDAP. * * Only need to add user if in any of the local domains? Figure that out here for now. Should * have Context-Based Access Controls before the job is queued though, probably. * * Use one of three modes; * * 1) The authenticated user account. * * * Only valid if the authenticated user is a domain admin. * * We don't know the originating user here. * * We certainly don't have its password anymore. * * 2) The hosted kolab account. * * 3) The Directory Manager account. * - * @param User $user The user account to create. + * @param \App\User $user The user account to create. * * @return void */ - public static function createUser($user) + public static function createUser(User $user) { $config = self::getConfig('admin'); + $ldap = self::initLDAP($config); - $ldap = new \Net_LDAP3($config); - - $ldap->connect(); - - $ldap->bind( - //config('ldap.admin.bind_dn'), - "cn=Directory Manager", - //config('ldap.admin.bind_pw') - "Welcome2KolabSystems" - ); - - $firstName = $user->getSetting('first_name'); - $lastName = $user->getSetting('last_name'); + list($_local, $_domain) = explode('@', $user->email, 2); - $cn = "unknown"; - $displayname = ""; + $domain = $ldap->find_domain($_domain); - if ($firstName) { - if ($lastName) { - $cn = "{$firstName} {$lastName}"; - $displayname = "{$lastName}, {$firstName}"; - } else { - $lastName = "unknown"; - $cn = "{$firstName}"; - $displayname = "{$firstName}"; - } - } else { - $firstName = ""; - if ($lastName) { - $cn = "{$lastName}"; - $displayname = "{$lastName}"; - } else { - $lastName = "unknown"; - } + if (!$domain) { + return false; } $entry = [ - 'cn' => $cn, - 'displayname' => $displayname, - 'givenname' => $firstName, 'objectclass' => [ 'top', 'inetorgperson', 'kolabinetorgperson', 'mailrecipient', 'person' ], 'mail' => $user->email, - 'sn' => $lastName, 'uid' => $user->email, - 'userpassword' => $user->password_ldap, ]; - list($_local, $_domain) = explode('@', $user->email, 2); - - $domain = $ldap->find_domain($_domain); - - if (!$domain) { - return false; - } + self::setUserAttributes($user, $entry); $base_dn = $ldap->domain_root_dn($_domain); $dn = "uid={$user->email},ou=People,{$base_dn}"; if (!$ldap->get_entry($dn)) { $ldap->add_entry($dn, $entry); } $ldap->close(); } /** * Update a domain in LDAP. * * @param \App\Domain $domain The domain to update. * * @return void */ public static function updateDomain($domain) { // } /** * Update a user in LDAP. * * @param \App\User $user The user account to update. * * @return void */ - public static function updateUser($user) + public static function updateUser(User $user) { $config = self::getConfig('admin'); - - $ldap = new \Net_LDAP3($config); - - $ldap->connect(); - - $ldap->bind( - //config('ldap.admin.bind_dn'), - "cn=Directory Manager", - //config('ldap.admin.bind_pw') - "Welcome2KolabSystems" - ); + $ldap = self::initLDAP($config); list($_local, $_domain) = explode('@', $user->email, 2); $domain = $ldap->find_domain($_domain); if (!$domain) { return false; } $base_dn = $ldap->domain_root_dn($_domain); $dn = "uid={$user->email},ou=People,{$base_dn}"; $old_entry = $ldap->get_entry($dn); - $new_entry = $old_entry; + self::setUserAttributes($user, $new_entry); + + $ldap->modify_entry($dn, $old_entry, $new_entry); + + $ldap->close(); + } + + /** + * Initialize connection to LDAP + */ + private static function initLDAP(array $config, string $privilege = 'admin') + { + $ldap = new \Net_LDAP3($config); + + $ldap->connect(); + + $ldap->bind(\config("ldap.{$privilege}.bind_dn"), \config("ldap.{$privilege}.bind_pw")); + + // TODO: error handling + + return $ldap; + } + + /** + * Set common user attributes + */ + private static function setUserAttributes(User $user, array &$entry) + { $firstName = $user->getSetting('first_name'); $lastName = $user->getSetting('last_name'); $cn = "unknown"; $displayname = ""; if ($firstName) { if ($lastName) { $cn = "{$firstName} {$lastName}"; $displayname = "{$lastName}, {$firstName}"; } else { $lastName = "unknown"; $cn = "{$firstName}"; $displayname = "{$firstName}"; } } else { $firstName = ""; if ($lastName) { $cn = "{$lastName}"; $displayname = "{$lastName}"; } else { $lastName = "unknown"; } } - $new_entry['cn'] = $cn; - $new_entry['displayname'] = $displayname; - $new_entry['givenname'] = $firstName; - $new_entry['sn'] = $lastName; - $new_entry['userpassword'] = $user->password_ldap; - - $ldap->modify_entry($dn, $old_entry, $new_entry); - - $ldap->close(); + $entry['cn'] = $cn; + $entry['displayname'] = $displayname; + $entry['givenname'] = $firstName; + $entry['sn'] = $lastName; + $entry['userpassword'] = $user->password_ldap; } - private static function getConfig($privilege) + /** + * Get LDAP configuration for specified access level + */ + private static function getConfig(string $privilege) { $config = [ - 'domain_base_dn' => config('ldap.domain_base_dn'), - 'domain_filter' => config('ldap.domain_filter'), - 'domain_name_attribute' => config('ldap.domain_name_attribute'), - 'hosts' => config('ldap.hosts'), + 'domain_base_dn' => \config('ldap.domain_base_dn'), + 'domain_filter' => \config('ldap.domain_filter'), + 'domain_name_attribute' => \config('ldap.domain_name_attribute'), + 'hosts' => \config('ldap.hosts'), 'sort' => false, - 'vlv' => false + 'vlv' => false, + 'log_hook' => 'App\Backends\LDAP::logHook', ]; return $config; } + + /** + * Logging callback + */ + public static function logHook($level, $msg): void + { + if ( + ($level == LOG_INFO || $level == LOG_DEBUG || $level == LOG_NOTICE) + && !\config('app.debug') + ) { + return; + } + + switch ($level) { + case LOG_CRIT: + $function = 'critical'; + break; + case LOG_EMERG: + $function = 'emergency'; + break; + case LOG_ERR: + $function = 'error'; + break; + case LOG_ALERT: + $function = 'alert'; + break; + case LOG_WARNING: + $function = 'warning'; + break; + case LOG_INFO: + $function = 'info'; + break; + case LOG_DEBUG: + $function = 'debug'; + break; + case LOG_NOTICE: + $function = 'notice'; + break; + default: + $function = 'info'; + } + + if (is_array($msg)) { + $msg = implode("\n", $msg); + } + + $msg = '[LDAP] ' . $msg; + + \Log::{$function}($msg); + } }