Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117884665
D5253.1775352926.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
10 KB
Referenced Files
None
Subscribers
None
D5253.1775352926.diff
View Options
diff --git a/src/app/Auth/PassportClient.php b/src/app/Auth/PassportClient.php
--- a/src/app/Auth/PassportClient.php
+++ b/src/app/Auth/PassportClient.php
@@ -22,6 +22,7 @@
if ($this->allowed_scopes) {
return $this->allowed_scopes;
}
+
return [];
}
}
diff --git a/src/app/Backends/Roundcube.php b/src/app/Backends/Roundcube.php
--- a/src/app/Backends/Roundcube.php
+++ b/src/app/Backends/Roundcube.php
@@ -13,6 +13,7 @@
private const FILESTORE_TABLE = 'filestore';
private const USERS_TABLE = 'users';
private const IDENTITIES_TABLE = 'identities';
+ private const SHARED_CACHE_TABLE = 'cache_shared';
/** @var array List of GnuPG files to store */
private static $enigma_files = ['pubring.gpg', 'secring.gpg', 'pubring.kbx'];
@@ -33,6 +34,28 @@
return DB::connection('roundcube');
}
+ /**
+ * Store a shared cache entry (used in the kolab plugin)
+ *
+ * @param string $key Cache key name
+ * @param string|array $value Cache value
+ * @param int $ttl TTL (in seconds)
+ */
+ public static function cacheSet(string $key, $value, int $ttl): void
+ {
+ if (is_array($value)) {
+ $value = json_encode($value);
+ }
+
+ $db = self::dbh();
+
+ $db->table(self::SHARED_CACHE_TABLE)->insert([
+ 'cache_key' => 'kolab.' . $key,
+ 'data' => $value,
+ 'expires' => DB::raw("now() + INTERVAL {$ttl} SECOND"),
+ ]);
+ }
+
/**
* Create delegator's identities for the delegatee
*
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
@@ -4,6 +4,7 @@
use App\Auth\PassportClient;
use App\Http\Controllers\Controller;
+use App\Support\Facades\Roundcube;
use App\User;
use App\Utils;
use Illuminate\Http\JsonResponse;
@@ -115,17 +116,23 @@
*
* @return JsonResponse
*/
- public function oauthApprove(ServerRequestInterface $psrRequest, Request $request, AuthorizationServer $server)
+ public function oauthApprove(ServerRequestInterface $psrRequest, Request $request, AuthorizationServer $server, $user = null)
{
+ if (!$user) {
+ $user = $this->guard()->user();
+ } else {
+ $custom_user = true;
+ }
+
$clientId = $request->input('client_id');
- $user = $this->guard()->user();
- $cacheKey = "oauth-seen-{$user->id}-{$clientId}";
try {
if ($request->response_type != 'code') {
throw new \Exception('Invalid response_type');
}
+ $cacheKey = "oauth-seen-{$user->id}-{$clientId}";
+
// OpenID handler reads parameters from the request query string (GET)
$request->query->replace($request->input());
@@ -155,7 +162,9 @@
// TODO: If we wanted to give users ability to remove this "approved" state for a client,
// we would have to store these records in SQL table. It would become handy especially
// if we give users possibility to register external OAuth apps.
- Cache::put($cacheKey, 1, now()->addDays(14));
+ if (empty($custom_user)) {
+ Cache::put($cacheKey, 1, now()->addDays(14));
+ }
} catch (OAuthServerException $e) {
// Note: We don't want 401 or 400 codes here, use 422 which is used in our API
$code = $e->getHttpStatusCode();
@@ -243,6 +252,78 @@
return response()->json($response);
}
+ /**
+ * 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);
+ }
+
+ // TODO: Check if the requesting user has permission to impersonate.
+ // For now only wallet controllers can do it
+ if (!$this->guard()->user()->canDelete($user)) {
+ return $this->errorResponse(403);
+ }
+
+ // Use OAuth client for Webmail
+ $client = PassportClient::where('name', 'Webmail SSO client')->whereNull('user_id')->first();
+
+ if (!$client) {
+ return $this->errorResponse(404);
+ }
+
+ // Abuse the self::oauthApprove() handler to init the OAuth session (code)
+ $request->merge([
+ 'client_id' => $client->id,
+ 'redirect_uri' => $client->redirect,
+ 'scope' => 'email openid auth.token',
+ 'state' => Utils::uuidStr(),
+ 'nonce' => Utils::uuidStr(),
+ 'response_type' => 'code',
+ 'ifSeen' => false,
+ ]);
+
+ $response = $this->oauthApprove($psrRequest, $request, $server, $user);
+
+ // Check status, on error remove the redirect url
+ if ($response->status() != 200) {
+ return self::errorResponse($response->status(), $response->getData()->error);
+ }
+
+ $url = $response->getData()->redirectUrl;
+
+ // Store state+nonce in Roundcube database (for the kolab plugin)
+ // for request origin validation and token validation there
+ // Get the code from the URL
+ parse_str(parse_url($url, \PHP_URL_QUERY), $query);
+
+ Roundcube::cacheSet(
+ 'helpdesk.' . md5($query['code']),
+ [
+ 'state' => $request->state,
+ 'nonce' => $request->nonce,
+ ],
+ 30 // TTL
+ );
+
+ // Tell the kolab plugin that the request origin is helpdesk mode, it will read
+ // the cache entry and make sure the token is accepted by Roundcube OAuth code.
+ $response->setData(['redirectUrl' => $url . '&helpdesk=1']);
+
+ return $response;
+ }
+
/**
* Log the user out (Invalidate the token)
*
diff --git a/src/config/openid.php b/src/config/openid.php
--- a/src/config/openid.php
+++ b/src/config/openid.php
@@ -5,10 +5,8 @@
return [
'passport' => [
- /*
- * Place your Passport and OpenID Connect scopes here.
- * To receive an `id_token`, you should at least provide the openid scope.
- */
+ // Place your Passport and OpenID Connect scopes here.
+ // To receive an `id_token`, you should at least provide the openid scope.
'tokens_can' => [
'openid' => 'Enable OpenID Connect',
'email' => 'Information about your email address',
@@ -43,26 +41,22 @@
],
'routes' => [
- /*
- * When set to true, this package will expose the OpenID Connect Discovery endpoint.
- * - /.well-known/openid-configuration
- */
+ // When set to true, this package will expose the OpenID Connect Discovery endpoint.
+ // - /.well-known/openid-configuration
'discovery' => true,
+
// When set to true, this package will expose the JSON Web Key Set endpoint.
'jwks' => false,
- /*
- * Optional URL to change the JWKS path to align with your custom Passport routes.
- * Defaults to /oauth/jwks
- */
+
+ // Optional URL to change the JWKS path to align with your custom Passport routes.
+ // Defaults to /oauth/jwks
'jwks_url' => '/oauth/jwks',
],
// Settings for the discovery endpoint
'discovery' => [
- /*
- * Hide scopes that aren't from the OpenID Core spec from the Discovery,
- * default = false (all scopes are listed)
- */
+ // Hide scopes that aren't from the OpenID Core spec from the Discovery,
+ // default = false (all scopes are listed)
'hide_scopes' => false,
],
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,7 @@
<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 class="btn-outline-secondary float-end" type="button" @click="loginAs">Log into webmail</btn>
</form>
</div>
<div v-if="Object.keys(settingsSections).length > 0" class="tab-pane" id="settings" role="tabpanel" aria-labelledby="tab-settings">
@@ -567,6 +568,16 @@
}
}
})
+ },
+ loginAs() {
+ axios.post('/api/v4/users/' + this.user_id + '/login-as')
+ .then(response => {
+ if (response.data.redirectUrl) {
+ // Display loading widget, redirecting may take a while
+ this.$root.startLoading(['#app > .container', { small: false, text: this.$t('msg.redirecting') }])
+ window.location.href = response.data.redirectUrl
+ }
+ })
}
}
}
diff --git a/src/routes/api.php b/src/routes/api.php
--- a/src/routes/api.php
+++ b/src/routes/api.php
@@ -154,6 +154,7 @@
Route::post('users/{id}/config', [API\V4\UsersController::class, 'setConfig']);
Route::get('users/{id}/skus', [API\V4\UsersController::class, 'skus']);
Route::get('users/{id}/status', [API\V4\UsersController::class, 'status']);
+ Route::post('users/{id}/login-as', [API\AuthController::class, 'loginAs']);
if (\config('app.with_delegation')) {
Route::get('users/{id}/delegations', [API\V4\UsersController::class, 'delegations']);
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Apr 5, 1:35 AM (3 d, 7 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18831853
Default Alt Text
D5253.1775352926.diff (10 KB)
Attached To
Mode
D5253: [POC] Helpdesk mode
Attached
Detach File
Event Timeline