Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117890028
D5331.1775362501.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
15 KB
Referenced Files
None
Subscribers
None
D5331.1775362501.diff
View Options
diff --git a/src/app/Http/Controllers/API/V4/Admin/UsersController.php b/src/app/Http/Controllers/API/V4/Admin/UsersController.php
--- a/src/app/Http/Controllers/API/V4/Admin/UsersController.php
+++ b/src/app/Http/Controllers/API/V4/Admin/UsersController.php
@@ -2,7 +2,6 @@
namespace App\Http\Controllers\API\V4\Admin;
-use App\Auth\OAuth;
use App\Domain;
use App\Entitlement;
use App\EventLog;
@@ -20,8 +19,6 @@
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
-use League\OAuth2\Server\AuthorizationServer;
-use Psr\Http\Message\ServerRequestInterface;
class UsersController extends \App\Http\Controllers\API\V4\UsersController
{
@@ -158,35 +155,6 @@
return response()->json($result);
}
- /**
- * Webmail Login-As session initialization (via SSO)
- *
- * @param string $id The account to log into
- * @param ServerRequestInterface $psrRequest PSR request
- * @param Request $request The API request
- * @param AuthorizationServer $server Authorization server
- *
- * @return JsonResponse
- */
- public function loginAs($id, ServerRequestInterface $psrRequest, Request $request, AuthorizationServer $server)
- {
- $user = User::find($id);
-
- if (!$this->checkTenant($user)) {
- return $this->errorResponse(404);
- }
-
- if ($this->guard()->user()->role != User::ROLE_ADMIN) {
- return $this->errorResponse(403);
- }
-
- if (!$user->hasSku('mailbox')) {
- return $this->errorResponse(403);
- }
-
- return OAuth::loginAs($user, $psrRequest, $request, $server);
- }
-
/**
* Reset 2-Factor Authentication for the user
*
diff --git a/src/app/Http/Controllers/API/V4/UsersController.php b/src/app/Http/Controllers/API/V4/UsersController.php
--- a/src/app/Http/Controllers/API/V4/UsersController.php
+++ b/src/app/Http/Controllers/API/V4/UsersController.php
@@ -2,6 +2,7 @@
namespace App\Http\Controllers\API\V4;
+use App\Auth\OAuth;
use App\Domain;
use App\Entitlement;
use App\Group;
@@ -25,6 +26,8 @@
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
+use League\OAuth2\Server\AuthorizationServer;
+use Psr\Http\Message\ServerRequestInterface;
class UsersController extends RelationController
{
@@ -181,6 +184,39 @@
]);
}
+ /**
+ * Webmail Login-As session initialization (via SSO)
+ *
+ * @param string $id The account to log into
+ * @param ServerRequestInterface $psrRequest PSR request
+ * @param Request $request The API request
+ * @param AuthorizationServer $server Authorization server
+ *
+ * @return JsonResponse
+ */
+ public function loginAs($id, ServerRequestInterface $psrRequest, Request $request, AuthorizationServer $server)
+ {
+ if (!\config('app.with_loginas')) {
+ return $this->errorResponse(404);
+ }
+
+ $user = User::find($id);
+
+ if (!$this->checkTenant($user)) {
+ return $this->errorResponse(404);
+ }
+
+ if (!$this->guard()->user()->canDelete($user)) {
+ return $this->errorResponse(403);
+ }
+
+ if (!$user->hasSku('mailbox')) {
+ return $this->errorResponse(403);
+ }
+
+ return OAuth::loginAs($user, $psrRequest, $request, $server);
+ }
+
/**
* Display information on the user account specified by $id.
*
@@ -268,6 +304,7 @@
'enableWalletMandates' => $isController,
'enableWalletPayments' => $isController && $plan?->mode != Plan::MODE_MANDATE,
'enableCompanionapps' => $hasBeta && \config('app.with_companion_app'),
+ 'enableLoginAs' => $isController && \config('app.with_loginas'),
];
return array_merge($process, $result);
diff --git a/src/config/app.php b/src/config/app.php
--- a/src/config/app.php
+++ b/src/config/app.php
@@ -255,7 +255,7 @@
'with_subscriptions' => (bool) env('APP_WITH_SUBSCRIPTIONS', true),
'with_wallet' => (bool) env('APP_WITH_WALLET', true),
'with_delegation' => (bool) env('APP_WITH_DELEGATION', true),
-
+ 'with_loginas' => (bool) env('APP_WITH_LOGINAS', false),
'with_distlists' => (bool) env('APP_WITH_DISTLISTS', true),
'with_shared_folders' => (bool) env('APP_WITH_SHARED_FOLDERS', true),
'with_resources' => (bool) env('APP_WITH_RESOURCES', true),
diff --git a/src/resources/vue/Admin/User.vue b/src/resources/vue/Admin/User.vue
--- a/src/resources/vue/Admin/User.vue
+++ b/src/resources/vue/Admin/User.vue
@@ -83,9 +83,6 @@
<btn id="button-resync" class="btn-outline-primary" @click="resyncUser">
{{ $t('btn.resync') }}
</btn>
- <btn id="button-login-as" class="btn-outline-primary" @click="loginAs">
- {{ $t('user.login-as') }}
- </btn>
</div>
</div>
</div>
@@ -553,14 +550,6 @@
this.$root.clearFormValidation($('#email-dialog'))
this.$refs.emailDialog.show()
},
- loginAs() {
- axios.post('/api/v4/users/' + this.user.id + '/login-as')
- .then(response => {
- if (response.data.redirectUrl) {
- window.open(response.data.redirectUrl)
- }
- })
- },
setMandateState() {
let mandate = this.wallet.mandate
if (mandate && mandate.id) {
diff --git a/src/resources/vue/User/Info.vue b/src/resources/vue/User/Info.vue
--- a/src/resources/vue/User/Info.vue
+++ b/src/resources/vue/User/Info.vue
@@ -86,6 +86,9 @@
<subscription-select v-if="user.id" class="col-sm-8 pt-sm-1" :object="user" ref="skus"></subscription-select>
</div>
<btn class="btn-primary" type="submit" icon="check">{{ $t('btn.submit') }}</btn>
+ <btn id="button-login-as" class="btn-outline-secondary float-end" @click="loginAs" v-if="$route.name != 'settings' && $root.hasPermission('loginAs')">
+ {{ $t('user.login-as') }}
+ </btn>
</form>
</div>
<div v-if="Object.keys(settingsSections).length > 0" class="tab-pane" id="settings" role="tabpanel" aria-labelledby="tab-settings">
@@ -619,7 +622,15 @@
}
}
})
- }
+ },
+ loginAs() {
+ axios.post('/api/v4/users/' + this.user_id + '/login-as')
+ .then(response => {
+ if (response.data.redirectUrl) {
+ window.open(response.data.redirectUrl)
+ }
+ })
+ }
}
}
</script>
diff --git a/src/routes/api.php b/src/routes/api.php
--- a/src/routes/api.php
+++ b/src/routes/api.php
@@ -129,7 +129,6 @@
Route::apiResource('users', API\V4\Admin\UsersController::class);
Route::get('users/{id}/discounts', [API\V4\Admin\DiscountsController::class, 'userDiscounts']);
- Route::post('users/{id}/login-as', [API\V4\Admin\UsersController::class, 'loginAs']);
Route::post('users/{id}/reset-2fa', [API\V4\Admin\UsersController::class, 'reset2FA']);
Route::post('users/{id}/reset-geolock', [API\V4\Admin\UsersController::class, 'resetGeoLock']);
Route::post('users/{id}/resync', [API\V4\Admin\UsersController::class, 'resync']);
@@ -262,6 +261,7 @@
Route::apiResource('users', API\V4\UsersController::class);
Route::post('users/{id}/config', [API\V4\UsersController::class, 'setConfig']);
+ Route::post('users/{id}/login-as', [API\V4\UsersController::class, 'loginAs']);
Route::get('users/{id}/skus', [API\V4\UsersController::class, 'skus']);
Route::get('users/{id}/status', [API\V4\UsersController::class, 'status']);
diff --git a/src/tests/Feature/Controller/Admin/UsersTest.php b/src/tests/Feature/Controller/Admin/UsersTest.php
--- a/src/tests/Feature/Controller/Admin/UsersTest.php
+++ b/src/tests/Feature/Controller/Admin/UsersTest.php
@@ -319,45 +319,6 @@
$this->assertTrue($json['list'][0]['isDeleted']);
}
- /**
- * Test login-as request (POST /api/v4/users/<user-id>/login-as)
- */
- public function testLoginAs(): void
- {
- Queue::fake();
-
- $user = $this->getTestUser('UsersControllerTest1@userscontroller.com');
- $admin = $this->getTestUser('jeroen@jeroen.jeroen');
-
- // Test non-existing user
- $response = $this->actingAs($admin)->post("/api/v4/users/123456/login-as", []);
- $response->assertStatus(404);
-
- // Test unauthorized access to admin API
- $response = $this->actingAs($user)->post("/api/v4/users/{$user->id}/login-as", []);
- $response->assertStatus(403);
-
- // Test user w/o mailbox SKU
- $response = $this->actingAs($admin)->post("/api/v4/users/{$user->id}/login-as", []);
- $response->assertStatus(403);
-
- $sku = Sku::withObjectTenantContext($user)->where(['title' => 'mailbox'])->first();
- $user->assignSku($sku);
-
- // Test login-as
- $response = $this->actingAs($admin)->post("/api/v4/users/{$user->id}/login-as", []);
- $response->assertStatus(200);
-
- $json = $response->json();
-
- parse_str(parse_url($json['redirectUrl'], \PHP_URL_QUERY), $params);
-
- $this->assertSame('success', $json['status']);
- $this->assertSame('1', $params['helpdesk']);
-
- // TODO: Assert the Roundcube cache entry
- }
-
/**
* Test reseting 2FA (POST /api/v4/users/<user-id>/reset-2fa)
*/
diff --git a/src/tests/Feature/Controller/User/DelegationTest.php b/src/tests/Feature/Controller/User/DelegationTest.php
--- a/src/tests/Feature/Controller/User/DelegationTest.php
+++ b/src/tests/Feature/Controller/User/DelegationTest.php
@@ -33,7 +33,7 @@
Queue::fake();
$john = $this->getTestUser('john@kolab.org');
- $jane = $this->getTestUser('jane@kolab.org');
+ $joe = $this->getTestUser('joe@kolab.org');
$jack = $this->getTestUser('jack@kolab.org');
$ned = $this->getTestUser('ned@kolab.org');
@@ -66,7 +66,7 @@
$this->assertSame(["The specified email address is not a valid delegation target."], $json['errors']['email']);
// Invalid options
- $post = ['email' => $jane->email, 'options' => ['ufo' => 're']];
+ $post = ['email' => $joe->email, 'options' => ['ufo' => 're']];
$response = $this->actingAs($john)->post("api/v4/users/{$john->id}/delegations", $post);
$response->assertStatus(422);
@@ -76,7 +76,7 @@
$this->assertSame(["The specified delegation options are invalid."], $json['errors']['options']);
// Valid input
- $post = ['email' => $jane->email, 'options' => ['mail' => 'read-only']];
+ $post = ['email' => $joe->email, 'options' => ['mail' => 'read-only']];
$response = $this->actingAs($john)->post("api/v4/users/{$john->id}/delegations", $post);
$response->assertStatus(200);
@@ -86,7 +86,7 @@
$this->assertSame("Delegation created successfully.", $json['message']);
$delegatee = $john->delegatees()->first();
- $this->assertSame($jane->email, $delegatee->email);
+ $this->assertSame($joe->email, $delegatee->email);
$this->assertSame(['mail' => 'read-only'], $delegatee->delegation->options);
// Valid input (action taken by another wallet controller)
@@ -108,13 +108,13 @@
Queue::fake();
$john = $this->getTestUser('john@kolab.org');
- $jane = $this->getTestUser('jane@kolab.org');
+ $joe = $this->getTestUser('joe@kolab.org');
$jack = $this->getTestUser('jack@kolab.org');
$ned = $this->getTestUser('ned@kolab.org');
$user = $this->getTestUser('deleted@kolabnow.com');
Delegation::create(['user_id' => $john->id, 'delegatee_id' => $jack->id]);
- Delegation::create(['user_id' => $john->id, 'delegatee_id' => $jane->id, 'options' => ['mail' => 'r']]);
+ Delegation::create(['user_id' => $john->id, 'delegatee_id' => $joe->id, 'options' => ['mail' => 'r']]);
// Test unauth access
$response = $this->get("api/v4/users/{$john->id}/delegations");
@@ -138,7 +138,7 @@
$this->assertSame(2, $json['count']);
$this->assertSame($jack->email, $json['list'][0]['email']);
$this->assertSame([], $json['list'][0]['options']);
- $this->assertSame($jane->email, $json['list'][1]['email']);
+ $this->assertSame($joe->email, $json['list'][1]['email']);
$this->assertSame(['mail' => 'r'], $json['list'][1]['options']);
// Test request made by the delegators wallet controller
diff --git a/src/tests/Feature/Controller/UsersTest.php b/src/tests/Feature/Controller/UsersTest.php
--- a/src/tests/Feature/Controller/UsersTest.php
+++ b/src/tests/Feature/Controller/UsersTest.php
@@ -286,6 +286,66 @@
// TODO: Test paging
}
+ /**
+ * Test login-as request (POST /api/v4/users/<user-id>/login-as)
+ */
+ public function testLoginAs(): void
+ {
+ Queue::fake();
+
+ $john = $this->getTestUser('john@kolab.org');
+ $jack = $this->getTestUser('jack@kolab.org');
+ $jane = $this->getTestUser('jane@kolabnow.com');
+ $ned = $this->getTestUser('ned@kolab.org');
+ $wallet = $john->wallet();
+
+ \config(['app.with_loginas' => true]);
+
+ // Test non-existing user
+ $response = $this->actingAs($john)->post("/api/v4/users/123456/login-as", []);
+ $response->assertStatus(404);
+
+ // Test unauthorized access
+ $response = $this->actingAs($jack)->post("/api/v4/users/{$ned->id}/login-as", []);
+ $response->assertStatus(403);
+
+ // Test unauthorized access (Jane is not in the same account yet)
+ $response = $this->actingAs($john)->post("/api/v4/users/{$jane->id}/login-as", []);
+ $response->assertStatus(403);
+
+ $sku = Sku::withObjectTenantContext($john)->where(['title' => 'storage'])->first();
+ $jane->assignSku($sku, 1, $wallet);
+
+ // Test user w/o mailbox SKU
+ $response = $this->actingAs($john)->post("/api/v4/users/{$jane->id}/login-as", []);
+ $response->assertStatus(403);
+
+ $sku = Sku::withObjectTenantContext($john)->where(['title' => 'mailbox'])->first();
+ $jane->assignSku($sku, 1, $wallet);
+
+ // Test login-as
+ $response = $this->actingAs($john)->post("/api/v4/users/{$jane->id}/login-as", []);
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ parse_str(parse_url($json['redirectUrl'], \PHP_URL_QUERY), $params);
+
+ $this->assertSame('success', $json['status']);
+ $this->assertSame('1', $params['helpdesk']);
+
+ // TODO: Assert the Roundcube cache entry
+
+ // Test login-as acting as wallet controller
+ $response = $this->actingAs($ned)->post("/api/v4/users/{$jane->id}/login-as", []);
+ $response->assertStatus(200);
+
+ // Test with disabled feature
+ \config(['app.with_loginas' => false]);
+ $response = $this->actingAs($john)->post("/api/v4/users/{$jack->id}/login-as", []);
+ $response->assertStatus(404);
+ }
+
/**
* Test fetching user data/profile (GET /api/v4/users/<user-id>)
*/
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Apr 5, 4:15 AM (22 h, 13 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18832311
Default Alt Text
D5331.1775362501.diff (15 KB)
Attached To
Mode
D5331: Move "Login as" feature into user UI
Attached
Detach File
Event Timeline