Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F124964696
D5883.1779123867.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
D5883.1779123867.diff
View Options
diff --git a/src/app/Http/Controllers/API/AuthController.php b/src/app/Http/Controllers/API/AuthController.php
--- a/src/app/Http/Controllers/API/AuthController.php
+++ b/src/app/Http/Controllers/API/AuthController.php
@@ -40,18 +40,28 @@
* @param User $user User model object
* @param string $password Plain text password
* @param string|null $secondFactor Second factor code if available
+ * @param string|null $scope Scope for token
*/
- public static function logonResponse(User $user, string $password, ?string $secondFactor = null)
+ public static function logonResponse(User $user, string $password, ?string $secondFactor = null, ?string $scope = null)
{
$mode = request()->mode; // have to be before we make a request below
+ if (empty($scope)) {
+ $scope = 'api';
+ }
+
+ if ($scope != 'api' && $scope != 'config') {
+ \Log::debug("[Auth] Invalid scope: {$scope}");
+ return response()->json(['status' => 'error', 'message' => self::trans('auth.failed')], 401);
+ }
+
$proxyRequest = Request::create('/oauth/token', 'POST', [
'username' => $user->email,
'password' => $password,
'grant_type' => 'password',
'client_id' => \config('auth.proxy.client_id'),
'client_secret' => \config('auth.proxy.client_secret'),
- 'scope' => 'api',
+ 'scope' => $scope,
'secondfactor' => $secondFactor,
]);
@@ -76,6 +86,7 @@
[
'email' => 'required|min:3',
'password' => 'required|min:1',
+ 'scope' => 'nullable|min:1',
]
);
@@ -95,7 +106,7 @@
return response()->json(['status' => 'error', 'message' => self::trans('auth.failed')], 401);
}
- return self::logonResponse($user, $request->password, $request->secondfactor);
+ return self::logonResponse($user, $request->password, $request->secondfactor, $request->scope);
}
/**
diff --git a/src/app/Providers/PassportServiceProvider.php b/src/app/Providers/PassportServiceProvider.php
--- a/src/app/Providers/PassportServiceProvider.php
+++ b/src/app/Providers/PassportServiceProvider.php
@@ -26,6 +26,7 @@
'api' => 'Access API',
'mfa' => 'Access MFA API',
'fs' => 'Access Files API',
+ 'config' => 'Access Config API',
];
Passport::tokensCan(array_merge($scopes, \config('openid.passport.tokens_can')));
diff --git a/src/app/User.php b/src/app/User.php
--- a/src/app/User.php
+++ b/src/app/User.php
@@ -25,7 +25,6 @@
use Illuminate\Support\Facades\Hash;
use Laravel\Passport\HasApiTokens;
use League\OAuth2\Server\Exception\OAuthServerException;
-use Symfony\Component\HttpFoundation\IpUtils;
/**
* The eloquent definition of a User.
@@ -948,11 +947,6 @@
}
}
- if ($withChecks) {
- $is_trusted = IpUtils::checkIp($clientIP, \config('app.trusted_client_hosts'));
- $withChecks = !$is_trusted;
- \Log::debug("Authentication from {$clientIP}: " . ($is_trusted ? 'trusted' : 'not trusted'));
- }
if ($withChecks) {
// Check user (request) location
@@ -1016,8 +1010,9 @@
public static function findAndValidateForPassport($username, $password): self
{
$verifyMFA = true;
- if (request()->scope == "mfa") {
- \Log::info("Not validating MFA because this is a request for an mfa scope.");
+ $scope = request()->scope;
+ if ($scope == "mfa" || $scope == "config") {
+ \Log::info("Not validating MFA because this is a request for an $scope scope.");
// Don't verify MFA if this is only an mfa token.
// If we didn't do this, we couldn't pair backup devices.
$verifyMFA = false;
diff --git a/src/config/app.php b/src/config/app.php
--- a/src/config/app.php
+++ b/src/config/app.php
@@ -297,5 +297,4 @@
'test_verification_code' => (string) env('TEST_VERIFICATION_CODE', ''),
'kolabobjects_storage' => (string) env('KOLABOBJECTS_STORAGE', false),
- 'trusted_client_hosts' => explode(',', env('TRUSTED_CLIENT_HOSTS', '')),
];
diff --git a/src/routes/api.php b/src/routes/api.php
--- a/src/routes/api.php
+++ b/src/routes/api.php
@@ -197,7 +197,9 @@
// to prevent an attacker from pairing a new device with a stolen token.
Route::get('companions/{id}/pairing', [API\V4\CompanionAppsController::class, 'pairing']);
- Route::get('config/webmail', [API\V4\ConfigController::class, 'webmail']);
+ Route::get('config/webmail', [API\V4\ConfigController::class, 'webmail'])
+ ->withoutMiddleware(['auth:api', 'scope:api'])
+ ->middleware(['auth:api', 'scope:config,api']);
Route::post('device/{token}/claim', [API\V4\DeviceController::class, 'claim']);
Route::post('device/{token}/unclaim', [API\V4\DeviceController::class, 'unclaim']);
diff --git a/src/tests/Feature/Controller/AuthTest.php b/src/tests/Feature/Controller/AuthTest.php
--- a/src/tests/Feature/Controller/AuthTest.php
+++ b/src/tests/Feature/Controller/AuthTest.php
@@ -3,6 +3,7 @@
namespace Tests\Feature\Controller;
use App\Auth\PassportClient;
+use App\Auth\SecondFactor;
use App\Domain;
use App\IP4Net;
use App\User;
@@ -200,6 +201,22 @@
return $json['access_token'];
}
+ /**
+ * Test 2fa account login attempt
+ */
+ public function testLogin2faAccount(): void
+ {
+ $user = $this->getTestUser('ned@kolab.org');
+ $post = ['email' => $user->email, 'password' => 'simple123'];
+ $response = $this->post("api/auth/login", $post);
+ $response->assertStatus(422);
+
+ $code = SecondFactor::code('ned@kolab.org');
+ $post = ['email' => $user->email, 'password' => 'simple123', 'secondfactor' => $code];
+ $response = $this->post("api/auth/login", $post);
+ $response->assertStatus(200);
+ }
+
/**
* Test service account login attempt
*/
@@ -293,6 +310,35 @@
$response->assertStatus(401);
}
+ /**
+ * Test /api/auth/login with config-api scope
+ */
+ public function testConfigApiScope(): void
+ {
+ $post = ['email' => 'john@kolab.org', 'password' => 'simple123', 'scope' => 'config'];
+ $response = $this->post("api/auth/login", $post);
+ $response->assertStatus(200);
+ $json = $response->json();
+ $token = $json['access_token'];
+
+ // Protected by api scope, so no access
+ $response = $this->withHeaders(['Authorization' => 'Bearer ' . $token])->get("api/auth/info");
+ $response->assertStatus(403);
+
+ // Protected by config scope
+ $response = $this->withHeaders(['Authorization' => 'Bearer ' . $token])->get("api/v4/config/webmail");
+ $response->assertStatus(200);
+
+ // Get token despite 2fa requirements for config only
+ $post = ['email' => 'ned@kolab.org', 'password' => 'simple123', 'scope' => 'config'];
+ $response = $this->post("api/auth/login", $post);
+ $response->assertStatus(200);
+ $json = $response->json();
+ $token = $json['access_token'];
+ $response = $this->withHeaders(['Authorization' => 'Bearer ' . $token])->get("api/v4/config/webmail");
+ $response->assertStatus(200);
+ }
+
/**
* Test /api/auth/refresh
*/
diff --git a/src/tests/Feature/Controller/ConfigTest.php b/src/tests/Feature/Controller/ConfigTest.php
--- a/src/tests/Feature/Controller/ConfigTest.php
+++ b/src/tests/Feature/Controller/ConfigTest.php
@@ -19,7 +19,7 @@
$response = $this->get('api/v4/config/webmail');
$response->assertStatus(401);
- $response = $this->actingAs($john)->get('api/v4/config/webmail');
+ $response = $this->actingAs($john, null, ['config'])->get('api/v4/config/webmail');
$response->assertStatus(200);
$json = $response->json();
@@ -28,7 +28,7 @@
$this->assertNull($json['debug']);
// Ned has groupware, activesync and 2FA
- $response = $this->actingAs($ned)->get('api/v4/config/webmail');
+ $response = $this->actingAs($ned, null, ['config'])->get('api/v4/config/webmail');
$response->assertStatus(200);
$json = $response->json();
@@ -37,7 +37,7 @@
// Joe has no groupware subscription
$setting = $joe->settings()->updateOrCreate(['key' => 'debug'], ['value' => 'roundcube,syncroton']);
- $response = $this->actingAs($joe)->get('api/v4/config/webmail');
+ $response = $this->actingAs($joe, null, ['config'])->get('api/v4/config/webmail');
$response->assertStatus(200);
$json = $response->json();
@@ -49,7 +49,7 @@
$setting->timestamps = false;
$setting->updated_at = now()->subHours(ConfigController::DEBUG_TTL + 1);
$setting->save();
- $response = $this->actingAs($joe)->get('api/v4/config/webmail');
+ $response = $this->actingAs($joe, null, ['config'])->get('api/v4/config/webmail');
$response->assertStatus(200);
$json = $response->json();
diff --git a/src/tests/TestCase.php b/src/tests/TestCase.php
--- a/src/tests/TestCase.php
+++ b/src/tests/TestCase.php
@@ -30,11 +30,11 @@
/**
* Set the user as which we want to authenticate
*/
- public function actingAs(Authenticatable $user, $guard = null)
+ public function actingAs(Authenticatable $user, $guard = null, $scopes = ['api'])
{
Passport::actingAs(
$user,
- ['api']
+ $scopes
);
return parent::actingAs($user, $guard);
}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, May 18, 5:04 PM (3 d, 1 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18928338
Default Alt Text
D5883.1779123867.diff (9 KB)
Attached To
Mode
D5883: Allow config api access without 2fa
Attached
Detach File
Event Timeline