diff --git a/src/app/Http/Controllers/API/NGINXController.php b/src/app/Http/Controllers/API/NGINXController.php index 983d3e06..d54d0ebb 100644 --- a/src/app/Http/Controllers/API/NGINXController.php +++ b/src/app/Http/Controllers/API/NGINXController.php @@ -1,129 +1,242 @@ headers); + // TODO: Apply some sort of limit for Auth-Login-Attempt -- docs say it is the number of + // attempts over the same connection. + + switch ($request->headers->get('Auth-Protocol')) { + case "imap": + return $this->authenticateIMAP($request); + case "smtp": + return $this->authenticateSMTP($request); + default: + return $this->byebye($request); + } + } + + private function authenticateIMAP(Request $request) + { + $login = $request->headers->get('Auth-User', null); + + if (empty($login)) { + return $this->byebye($request, __LINE__); + } + // validate user exists, otherwise bye bye + $user = \App\User::where('email', $login)->first(); + + if (!$user) { + return $this->byebye($request, __LINE__); + } + // validate password, otherwise bye bye + $password = $request->headers->get('Auth-Pass', null); + + if (empty($password)) { + return $this->byebye($request, __LINE__); + } + + $result = Hash::check($password, $user->password); + + if (!$result) { + return $this->byebye($request, __LINE__); + } + // validate country of origin against restrictions, otherwise bye bye + $clientIP = $request->headers->get('Client-Ip', null); + + $countryCodes = json_decode($user->getSetting('limit_geo', "[]")); + + \Log::debug("Countries for {$user->email}: " . var_export($countryCodes, true)); + + if (!empty($countryCodes)) { + // fake the country is NL, and the limitation is CH + if ($clientIP == '127.0.0.1' && $login == "piet@kolab.org") { + $country = "NL"; + } else { + // TODO: GeoIP reliance + $country = "CH"; + } + + if (!in_array($country, $countryCodes)) { + return $this->byebye($request, __LINE__); + } + } + // determine 2fa preference - // determine guam preference (for $request->headers->get('Auth-Protocol') == 'imap' + $pref2fa = $user->getSetting('2fa_plz', false); + + if ($pref2fa) { + $result = $this->waitFor2fa($request); + + if (!$result) { + return $this->byebye($request, __LINE__); + } + } + + $prefGuam = $user->getSetting('guam_plz', false); + + if ($prefGuam) { + $port = 9143; + } else { + $port = 10143; + } + + $response = response("")->withHeaders( + [ + "Auth-Status" => "OK", + "Auth-Server" => "127.0.0.1", + "Auth-Port" => $port, + "Auth-Pass" => $password + ] + ); + + \Log::debug("Response with headers:\n{$response->headers}"); + + return $response; + + // limit the rate at which new second factor authentication is required; + // - change of client ip address justifies another OTP, + // - a $x hour interval justifies another OTP /** * ports: * * 143 nginx * 465 nginx * 587 nginx * 993 nginx * * 9143 guam starttls (thus also plain) * 9993 guam ssl * 10143 cyrus-imapd allows plaintext * 10465 postfix ssl * 10587 postfix starttls * 11143 cyrus-imapd starttls required * 11993 cyrus-imapd ssl */ switch ($request->headers->get("Auth-Protocol")) { case "imap": // without guam $response = response("")->withHeaders( [ "Auth-Status" => 'OK', "Auth-Server" => '127.0.0.1', "Auth-Port" => '12143', "Auth-Pass" => $request->headers->get('Auth-Pass') ] ); // with guam $response = response("")->withHeaders( [ "Auth-Status" => 'OK', "Auth-Server" => '127.0.0.1', "Auth-Port" => '9143', "Auth-Pass" => $request->headers->get('Auth-Pass') ] ); break; case "smtp": $response = response("")->withHeaders( [ "Auth-Status" => "OK", "Auth-Server" => '127.0.0.1', "Auth-Port" => '10465', "Auth-Pass" => $request->headers->get('Auth-Pass') ] ); break; } + \Log::debug($response->headers); + + return $response; + } + + private function byebye(Request $request, $code = null) + { + $response = response("")->withHeaders( + [ + // TODO code only for development + "Auth-Status" => "NO {$code}", + "Auth-Wait" => 3 + ] + ); + + \Log::debug("Response with headers:\n{$response->headers}"); + + return $response; + } + + private function waitFor2fa(Request $request) + { $code = \App\SignupCode::create( [ 'data' => [ 'email' => $request->headers->get('Auth-User') ], 'expires_at' => Carbon::now()->addMinutes(2) ] ); \Log::debug("visit http://127.0.0.1:8000/api/confirm/{$code->short_code}"); - $found = true; + $confirmed = false; $maxTries = 300; do { $confirmCode = \App\SignupCode::find($code->code); if (!$confirmCode) { - $found = false; + $confirmed = true; break; } sleep(1); $maxTries--; - } while ($found && $maxTries > 0); + } while (!$confirmed && $maxTries > 0); - \Log::debug($response->headers); - - return $response; + return $confirmed; } } diff --git a/src/database/seeds/UserSeeder.php b/src/database/seeds/UserSeeder.php index 96b34b87..c4334fc8 100644 --- a/src/database/seeds/UserSeeder.php +++ b/src/database/seeds/UserSeeder.php @@ -1,116 +1,148 @@ 'kolab.org', 'status' => Domain::STATUS_NEW + Domain::STATUS_ACTIVE + Domain::STATUS_CONFIRMED + Domain::STATUS_VERIFIED, 'type' => Domain::TYPE_EXTERNAL ] ); $john = User::create( [ 'name' => 'John Doe', 'email' => 'john@kolab.org', 'password' => 'simple123', 'email_verified_at' => now() ] ); $john->setSettings( [ 'first_name' => 'John', 'last_name' => 'Doe', 'currency' => 'USD', 'country' => 'US', 'billing_address' => "601 13th Street NW\nSuite 900 South\nWashington, DC 20005", 'external_email' => 'john.doe.external@gmail.com', 'phone' => '+1 509-248-1111', ] ); $john->setAliases(['john.doe@kolab.org']); $wallet = $john->wallets->first(); - $package_domain = \App\Package::where('title', 'domain-hosting')->first(); - $package_kolab = \App\Package::where('title', 'kolab')->first(); + $packageDomain = \App\Package::where('title', 'domain-hosting')->first(); + $packageKolab = \App\Package::where('title', 'kolab')->first(); - $domain->assignPackage($package_domain, $john); - $john->assignPackage($package_kolab); + $domain->assignPackage($packageDomain, $john); + $john->assignPackage($packageKolab); $jack = User::create( [ 'name' => 'Jack Daniels', 'email' => 'jack@kolab.org', 'password' => 'simple123', 'email_verified_at' => now() ] ); $jack->setSettings( [ 'first_name' => 'Jack', 'last_name' => 'Daniels', 'currency' => 'USD', 'country' => 'US' ] ); $jack->setAliases(['jack.daniels@kolab.org']); - $john->assignPackage($package_kolab, $jack); + $john->assignPackage($packageKolab, $jack); foreach ($john->entitlements as $entitlement) { $entitlement->created_at = Carbon::now()->subMonths(1); $entitlement->updated_at = Carbon::now()->subMonths(1); $entitlement->save(); } $ned = User::create( [ 'name' => 'Edward Flanders', 'email' => 'ned@kolab.org', 'password' => 'simple123', 'email_verified_at' => now() ] ); $ned->setSettings( [ 'first_name' => 'Edward', 'last_name' => 'Flanders', 'currency' => 'USD', 'country' => 'US' ] ); - $john->assignPackage($package_kolab, $ned); + $john->assignPackage($packageKolab, $ned); // Ned is a controller on Jack's wallet $john->wallets()->first()->addController($ned); + $juan = User::create( + [ + 'name' => 'Juan Despacido', + 'email' => 'juan@kolab.org', + 'password' => 'simple123', + 'email_verified_at' => now() + ] + ); + + $juan->setSettings( + [ + 'limit_geo' => json_encode(["CH"]), + 'guam_plz' => true, + '2fa_plz' => true + ] + ); + + $john->assignPackage($packageKolab, $juan); + + $piet = User::create( + [ + 'name' => 'Piet Klaassen', + 'email' => 'piet@kolab.org', + 'password' => 'simple123', + 'email_verified_at' => now() + ] + ); + + $piet->setSetting('limit_geo', json_encode(["NL"])); + + $john->assignPackage($packageKolab, $piet); + factory(User::class, 10)->create(); } }