Page MenuHomePhorge

D5883.1779471595.diff
No OneTemporary

Authored By
Unknown
Size
9 KB
Referenced Files
None
Subscribers
None

D5883.1779471595.diff

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
*/
@@ -292,6 +309,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

Mime Type
text/plain
Expires
Fri, May 22, 5:39 PM (23 h, 3 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18930034
Default Alt Text
D5883.1779471595.diff (9 KB)

Event Timeline