diff --git a/src/app/Auth/LDAPUserProvider.php b/src/app/Auth/LDAPUserProvider.php index 5d949760..25aaf2f3 100644 --- a/src/app/Auth/LDAPUserProvider.php +++ b/src/app/Auth/LDAPUserProvider.php @@ -1,71 +1,72 @@ count() == 1) { $user = $entries->select('id', 'email', 'password', 'password_ldap')->first(); return $user; } return null; } public function validateCredentials(Authenticatable $user, array $credentials) { if ($user->email == $credentials['email']) { if (!empty($user->password)) { if (Hash::check($credentials['password'], $user->password)) { // TODO: update last login time // TODO: Update password_ldap if necessary, examine whether writing to // user->password is sufficient? $user->password = $credentials['password']; $user->save(); return true; } else { // TODO: Log login failure return false; } - } else if (!empty($user->password_ldap)) { + } elseif (!empty($user->password_ldap)) { $hash = '{SSHA512}' . base64_encode( pack('H*', hash('sha512', $credentials['password'])) ); if ($hash == $user->password_ldap) { // TODO: update last login time // TODO: Update password if necessary, examine whether writing to // user->password is sufficient? $user->password = $credentials['password']; $user->save(); return true; } else { // TODO: Log login failure return false; } } else { // TODO: Log login failure for missing password. Try actual LDAP? return false; } } return false; } } diff --git a/src/app/Backends/LDAP.php b/src/app/Backends/LDAP.php index 9a6e6ea9..da1211fb 100644 --- a/src/app/Backends/LDAP.php +++ b/src/app/Backends/LDAP.php @@ -1,356 +1,355 @@ 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"; $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, 'objectclass' => [ 'top', 'organizationalunit' ], '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. * * @return void */ public static function createUser($user) { - $config = self::_getConfig('admin'); + $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" ); $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"; } } $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; } $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) { - $config = self::_getConfig('admin'); + $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" ); 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; $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(); } - private static function _getConfig($privilege) + private static function getConfig($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'), 'sort' => false, 'vlv' => false ]; return $config; } } diff --git a/src/app/Domain.php b/src/app/Domain.php index 9355a4da..810c0bbf 100644 --- a/src/app/Domain.php +++ b/src/app/Domain.php @@ -1,162 +1,167 @@ morphOne('App\Entitlement', 'entitleable'); } /** * Return list of public+active domain names */ public static function getPublicDomains(): array { $where = sprintf('(type & %s) AND (status & %s)', Domain::TYPE_PUBLIC, Domain::STATUS_ACTIVE); return self::whereRaw($where)->get(['namespace'])->map(function ($domain) { return $domain->namespace; })->toArray(); } /** * Returns whether this domain is active. * * @return bool */ public function isActive(): bool { return $this->status & self::STATUS_ACTIVE; } /** * Returns whether this domain is confirmed the ownership of. * * @return bool */ public function isConfirmed(): bool { return $this->status & self::STATUS_CONFIRMED; } /** * Returns whether this domain is deleted. * * @return bool */ public function isDeleted(): bool { return $this->status & self::STATUS_DELETED; } /** * Returns whether this domain is registered with us. * * @return bool */ public function isExternal(): bool { return $this->type & self::TYPE_EXTERNAL; } /** * Returns whether this domain is hosted with us. * * @return bool */ public function isHosted(): bool { return $this->type & self::TYPE_HOSTED; } /** * Returns whether this domain is new. * * @return bool */ public function isNew(): bool { return $this->status & self::STATUS_NEW; } /** * Returns whether this domain is public. * * @return bool */ public function isPublic(): bool { return $this->type & self::TYPE_PUBLIC; } /** * Returns whether this domain is suspended. * * @return bool */ public function isSuspended(): bool { return $this->status & self::STATUS_SUSPENDED; } + /** + * Domain status mutator + * + * @throws \Exception + */ public function setStatusAttribute($status) { - $_status = 0; - - switch ($status) { - case "new": - $_status &= self::STATUS_NEW; - break; - case "active": - $_status &= self::STATUS_ACTIVE; - break; - case "confirmed": - $_status &= self::STATUS_CONFIRMED; - break; - case "suspended": - $_status &= self::STATUS_SUSPENDED; - break; - case "deleted": - $_status &= self::STATUS_DELETED; - break; - default: - throw new \Exception("Invalid domain status: {$status}"); - break; + $new_status = 0; + + $allowed_values = [ + self::STATUS_NEW, + self::STATUS_ACTIVE, + self::STATUS_CONFIRMED, + self::STATUS_SUSPENDED, + self::STATUS_DELETED, + ]; + + foreach ($allowed_values as $value) { + if ($status & $value) { + $new_status &= $value; + $status ^= $value; + } } + + if ($status > 0) { + throw new \Exception("Invalid domain status: {$status}"); + } + + $this->attributes['status'] = $new_status; } } diff --git a/src/app/Http/Middleware/RequestLogger.php b/src/app/Http/Middleware/RequestLogger.php index 58b632d3..e2a6c613 100644 --- a/src/app/Http/Middleware/RequestLogger.php +++ b/src/app/Http/Middleware/RequestLogger.php @@ -1,23 +1,24 @@ fullUrl(); $method = $request->getMethod(); error_log("C: $method $url -> " . var_export($request->bearerToken(), true)); error_log("S: " . var_export($response->getContent(), true)); } } } diff --git a/src/app/Jobs/ProcessDomainCreate.php b/src/app/Jobs/ProcessDomainCreate.php index 6c1c5288..55fff3b7 100644 --- a/src/app/Jobs/ProcessDomainCreate.php +++ b/src/app/Jobs/ProcessDomainCreate.php @@ -1,43 +1,45 @@ domain = $domain; } /** * Execute the job. * * @return void */ public function handle() { LDAP::createDomain($this->domain); } } diff --git a/src/app/Jobs/ProcessUserCreate.php b/src/app/Jobs/ProcessUserCreate.php index 70f237b9..e9b4564a 100644 --- a/src/app/Jobs/ProcessUserCreate.php +++ b/src/app/Jobs/ProcessUserCreate.php @@ -1,46 +1,45 @@ user = $user; } /** * Execute the job. * * @return void */ public function handle() { LDAP::createUser($this->user); } } diff --git a/src/app/Jobs/ProcessUserUpdate.php b/src/app/Jobs/ProcessUserUpdate.php index bb145c3e..0aa30f4d 100644 --- a/src/app/Jobs/ProcessUserUpdate.php +++ b/src/app/Jobs/ProcessUserUpdate.php @@ -1,45 +1,44 @@ user = $user; } /** * Execute the job. * * @return void */ public function handle() { LDAP::updateUser($this->user); } } diff --git a/src/database/seeds/UserSeeder.php b/src/database/seeds/UserSeeder.php index 14b400f1..a9f4429f 100644 --- a/src/database/seeds/UserSeeder.php +++ b/src/database/seeds/UserSeeder.php @@ -1,133 +1,132 @@ 'kolab.org', 'status' => Domain::STATUS_NEW + Domain::STATUS_ACTIVE + Domain::STATUS_CONFIRMED, 'type' => Domain::TYPE_EXTERNAL ] ); $user = User::create( [ 'name' => "John Doe", 'email' => 'john@kolab.org', 'password' => 'simple123', 'email_verified_at' => now() ] ); $user_wallets = $user->wallets()->get(); $sku_domain = Sku::where('title', 'domain')->first(); $sku_mailbox = Sku::where('title', 'mailbox')->first(); $entitlement_domain = Entitlement::create( [ 'owner_id' => $user->id, 'wallet_id' => $user_wallets[0]->id, 'sku_id' => $sku_domain->id, 'entitleable_id' => $domain->id, 'entitleable_type' => Domain::class ] ); $entitlement_mailbox = Entitlement::create( [ 'owner_id' => $user->id, 'wallet_id' => $user_wallets[0]->id, 'sku_id' => $sku_mailbox->id, 'entitleable_id' => $user->id, 'entitleable_type' => User::class + ] + ); $user->setSettings( [ "first_name" => "John", "last_name" => "Doe", "currency" => "USD", "country" => "US" ] ); - // 10'000 users result in a table size of 11M - //foreach (range(1, 1000) as $number) { - // factory(User::class, 1000)->create(); - //} - factory(User::class, 100)->create(); + //factory(User::class, 100)->create(); //factory(User::class, 3)->create(); - +/* $uids = [ 'adomaitis' => [ "first_name" => "Liutauras", "last_name" => "Adomaitis", "currency" => "EUR", "country" => "LT", ], 'bohlender' => [ "first_name" => "Michael", "last_name" => "Bohlender", "currency" => "EUR", "country" => "DE", ], 'leickel' => [ "first_name" => "Lioba", "last_name" => "Leickel", "currency" => "EUR", "country" => "DE", ], 'machniak' => [ "first_name" => "Aleksander", "last_name" => "Machniak", "currency" => "EUR", "country" => "PL", ], 'mollekopf' => [ "first_name" => "Christian", "last_name" => "Mollekopf", ], 'petersen' => [ "first_name" => "Mads", "last_name" => "Petersen", ], 'vanmeeuwen' => [ "first_name" => "Jeroen", "last_name" => "van Meeuwen", ], 'winniewski' => [ "first_name" => "Nanita", "last_name" => "Winniewski", "currency" => "EUR", "country" => "DE" ] ]; foreach ($uids as $uid => $settings) { $user = User::create( [ 'name' => $uid, 'email' => "{$uid}@kolabsystems.com", 'password' => 'simple123', 'email_verified_at' => now() ] ); $user->setSettings($settings); } +*/ } }