Page MenuHomePhorge

D5253.1775352926.diff
No OneTemporary

Authored By
Unknown
Size
10 KB
Referenced Files
None
Subscribers
None

D5253.1775352926.diff

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

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)

Event Timeline