Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117916437
D3698.1775410380.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
9 KB
Referenced Files
None
Subscribers
None
D3698.1775410380.diff
View Options
diff --git a/src/app/AuthAttempt.php b/src/app/AuthAttempt.php
--- a/src/app/AuthAttempt.php
+++ b/src/app/AuthAttempt.php
@@ -17,12 +17,12 @@
use NullableFields;
use UuidStrKeyTrait;
- // No specific reason
public const REASON_NONE = '';
- // Password mismatch
public const REASON_PASSWORD = 'password';
- // Geolocation whitelist mismatch
public const REASON_GEOLOCATION = 'geolocation';
+ public const REASON_NOTFOUND = 'notfound';
+ public const REASON_2FA = '2fa';
+ public const REASON_2FA_GENERIC = '2fa-generic';
private const STATUS_ACCEPTED = 'ACCEPTED';
private const STATUS_DENIED = 'DENIED';
diff --git a/src/app/Http/Controllers/API/V4/NGINXController.php b/src/app/Http/Controllers/API/V4/NGINXController.php
--- a/src/app/Http/Controllers/API/V4/NGINXController.php
+++ b/src/app/Http/Controllers/API/V4/NGINXController.php
@@ -12,7 +12,7 @@
/**
* Authorize with the provided credentials.
*
- * @param string $login The login name
+ * @param string $login The login name
* @param string $password The password
* @param string $clientIP The client ip
*
@@ -34,47 +34,18 @@
throw new \Exception("No client ip");
}
- $user = \App\User::where('email', $login)->first();
- if (!$user) {
- throw new \Exception("User not found");
+ $result = \App\User::findAndAuthenticate($login, $password, $clientIP);
+
+ if (empty($result['user'])) {
+ throw new \Exception($result['errorMessage'] ?? "Unknown error");
}
// TODO: validate the user's domain is A-OK (active, confirmed, not suspended, ldapready)
// TODO: validate the user is A-OK (active, not suspended, ldapready, imapready)
- // TODO: we could use User::findAndAuthenticate() with some modifications here
-
- if (!Hash::check($password, $user->password)) {
- $attempt = \App\AuthAttempt::recordAuthAttempt($user, $clientIP);
- // Avoid setting a password failure reason if we previously accepted the location.
- if (!$attempt->isAccepted()) {
- $attempt->reason = \App\AuthAttempt::REASON_PASSWORD;
- $attempt->save();
- $attempt->notify();
- }
- throw new \Exception("Password mismatch");
- }
-
- // validate country of origin against restrictions, otherwise bye bye
- if (!$user->validateLocation($clientIP)) {
- \Log::info("Failed authentication attempt due to country code mismatch for user: {$login}");
- $attempt = \App\AuthAttempt::recordAuthAttempt($user, $clientIP);
- $attempt->deny(\App\AuthAttempt::REASON_GEOLOCATION);
- $attempt->notify();
- throw new \Exception("Country code mismatch");
- }
-
// TODO: Apply some sort of limit for Auth-Login-Attempt -- docs say it is the number of
- // attempts over the same authAttempt.
-
- // Check 2fa
- if (\App\CompanionApp::where('user_id', $user->id)->exists()) {
- $authAttempt = \App\AuthAttempt::recordAuthAttempt($user, $clientIP);
- if (!$authAttempt->waitFor2FA()) {
- throw new \Exception("2fa failed");
- }
- }
+ // attempts over the same authAttempt.
- return $user;
+ return $result['user'];
}
@@ -203,7 +174,7 @@
* Create an imap authentication response.
*
* @param \Illuminate\Http\Request $request The API request.
- * @param bool $prefGuam Wether or not guam is enabled.
+ * @param bool $prefGuam Whether or not Guam is enabled.
* @param string $password The password to include in the response.
*
* @return \Illuminate\Http\Response The response
diff --git a/src/app/User.php b/src/app/User.php
--- a/src/app/User.php
+++ b/src/app/User.php
@@ -2,6 +2,7 @@
namespace App;
+use App\AuthAttempt;
use App\Traits\AliasesTrait;
use App\Traits\BelongsToTenantTrait;
use App\Traits\EntitleableTrait;
@@ -657,16 +658,11 @@
}
if ($authenticated) {
- \Log::info("Successful authentication for {$this->email}");
-
// TODO: update last login time
if ($updatePassword && (empty($this->password) || empty($this->password_ldap))) {
$this->password = $password;
$this->save();
}
- } else {
- // TODO: Try actual LDAP?
- \Log::info("Authentication failed for {$this->email}");
}
return $authenticated;
@@ -693,43 +689,73 @@
/**
* Retrieve and authenticate a user
*
- * @param string $username The username.
- * @param string $password The password in plain text.
- * @param string $secondFactor The second factor (secondfactor from current request is used as fallback).
+ * @param string $username The username
+ * @param string $password The password in plain text
+ * @param ?string $clientIP The IP address of the client
*
* @return array ['user', 'reason', 'errorMessage']
*/
- public static function findAndAuthenticate($username, $password, $secondFactor = null): ?array
+ public static function findAndAuthenticate($username, $password, $clientIP = null): array
{
- $user = User::where('email', $username)->first();
+ $error = null;
- // TODO: 'reason' below could be AuthAttempt::REASON_*
- // TODO: $secondFactor argument is not used anywhere
+ if (!$clientIP) {
+ $clientIP = request()->ip();
+ }
+
+ $user = User::where('email', $username)->first();
if (!$user) {
- return ['reason' => 'notfound', 'errorMessage' => "User not found."];
+ $error = AuthAttempt::REASON_NOTFOUND;
}
- if (!$user->validateCredentials($username, $password)) {
- return ['reason' => 'credentials', 'errorMessage' => "Invalid password."];
+ // Check user password
+ if (!$error && !$user->validateCredentials($username, $password)) {
+ $error = AuthAttempt::REASON_PASSWORD;
}
- if (!$user->validateLocation(request()->ip())) {
- return ['reason' => 'geolocation', 'errorMessage' => "Country code mismatch."];
+ // Check user (request) location
+ if (!$error && !$user->validateLocation($clientIP)) {
+ $error = AuthAttempt::REASON_GEOLOCATION;
}
- if (!$secondFactor) {
- // Check the request if there is a second factor provided
- // as fallback.
- $secondFactor = request()->secondfactor;
+ // Check 2FA
+ if (!$error) {
+ try {
+ (new \App\Auth\SecondFactor($user))->validate(request()->secondfactor);
+ } catch (\Exception $e) {
+ $error = AuthAttempt::REASON_2FA_GENERIC;
+ $message = $e->getMessage();
+ }
}
- try {
- (new \App\Auth\SecondFactor($user))->validate($secondFactor);
- } catch (\Exception $e) {
- return ['reason' => 'secondfactor', 'errorMessage' => $e->getMessage()];
+ // Check 2FA - Companion App
+ if (!$error && \App\CompanionApp::where('user_id', $user->id)->exists()) {
+ $attempt = \App\AuthAttempt::recordAuthAttempt($user, $clientIP);
+ if (!$attempt->waitFor2FA()) {
+ $error = AuthAttempt::REASON_2FA;
+ }
}
+ if ($error) {
+ if ($user && empty($attempt)) {
+ $attempt = \App\AuthAttempt::recordAuthAttempt($user, $clientIP);
+ if (!$attempt->isAccepted()) {
+ $attempt->deny($error);
+ $attempt->save();
+ $attempt->notify();
+ }
+ }
+
+ if ($user) {
+ \Log::info("Authentication failed for {$user->email}");
+ }
+
+ return ['reason' => $error, 'errorMessage' => $message ?? \trans("auth.error.{$error}")];
+ }
+
+ \Log::info("Successful authentication for {$user->email}");
+
return ['user' => $user];
}
@@ -740,18 +766,18 @@
*
* @return \App\User User model object if found
*/
- public function findAndValidateForPassport($username, $password): User
+ public static function findAndValidateForPassport($username, $password): User
{
$result = self::findAndAuthenticate($username, $password);
if (isset($result['reason'])) {
- // TODO: Shouldn't we create AuthAttempt record here?
-
- if ($result['reason'] == 'secondfactor') {
+ if ($result['reason'] == AuthAttempt::REASON_2FA_GENERIC) {
// This results in a json response of {'error': 'secondfactor', 'error_description': '$errorMessage'}
throw new OAuthServerException($result['errorMessage'], 6, 'secondfactor', 401);
}
+ // TODO: Display specific error message if 2FA via Companion App was expected?
+
throw OAuthServerException::invalidCredentials();
}
diff --git a/src/resources/lang/en/auth.php b/src/resources/lang/en/auth.php
--- a/src/resources/lang/en/auth.php
+++ b/src/resources/lang/en/auth.php
@@ -18,4 +18,10 @@
'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
'logoutsuccess' => 'Successfully logged out.',
+ 'error.password' => "Invalid password",
+ 'error.geolocation' => "Country code mismatch",
+ 'error.nofound' => "User not found",
+ 'error.2fa' => "Second factor failure",
+ 'error.2fa-generic' => "Second factor failure",
+
];
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Apr 5, 5:33 PM (9 h, 7 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18834044
Default Alt Text
D3698.1775410380.diff (9 KB)
Attached To
Mode
D3698: [Draft] 2FA via CompanionApp for Kolab4 logons
Attached
Detach File
Event Timeline