Page MenuHomePhorge

D5891.1779443035.diff
No OneTemporary

Authored By
Unknown
Size
6 KB
Referenced Files
None
Subscribers
None

D5891.1779443035.diff

diff --git a/src/app/Auth/OAuth.php b/src/app/Auth/OAuth.php
--- a/src/app/Auth/OAuth.php
+++ b/src/app/Auth/OAuth.php
@@ -6,9 +6,13 @@
use App\Support\Facades\Roundcube;
use App\User;
use App\Utils;
+use Firebase\JWT\JWT;
+use Firebase\JWT\Key;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
+use Laravel\Passport\TokenRepository;
+use Laravel\Passport\Passport;
use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\Exception\OAuthServerException;
use Nyholm\Psr7\Response as Psr7Response;
@@ -208,4 +212,70 @@
return $response;
}
+
+ /**
+ * Logout (ends OAuth session)
+ *
+ * @param Request $request The API request
+ *
+ * @return string Redirect URL if specified
+ */
+ public static function logout(Request $request): string
+ {
+ // https://openid.net/specs/openid-connect-session-1_0-17.html
+ // The spec. recommends id_token_hint for an ID token to revoke
+ if ($request->id_token_hint) {
+ $id_token = self::parseIdToken($request->id_token_hint);
+ // or parse and validate the token this way (note: this returns an object not array)
+ // $id_token = JWT::decode($request->id_token_hint, new Key(\config('passport.public_key'), 'HS256'));
+
+ $client = PassportClient::find($id_token['aud']);
+ if (!$client) {
+ return '';
+ }
+
+ // FIXME: How do I find the token used in the JWT token?
+ // This way we can end all client sessions for this user, but we would
+ // rather revoke only the requested token. Aren't we?
+ /*
+ $client->tokens()->where('user_id', $request->user()->id)
+ ->where('revoked', false)
+ ->get()
+ ->each(static function ($token) {
+ $token->revoke();
+ $token->refreshToken?->revoke();
+ });
+ */
+
+ // TODO: We probably should revoke tokens stored in $id_token['auth.token'],
+ // but it's not possible (yet), as they aren't stored anywhere
+
+ // TODO: Client can use post_logout_redirect_uri parameter to redirect back
+ // (Remember to validate it against registered clients metadata).
+ // Roundcube uses it, however redirecting back to Roundcube isn't a good idea, imo.
+ } else {
+ // https://openid.net/specs/openid-connect-backchannel-1_0.html
+ // TODO: Roundcube supports backchannel standard, so we could end webmail
+ // session when user log out of the Cockpit.
+ }
+
+ return '';
+ }
+
+ /**
+ * Parse JWT token into an array (no validation)
+ */
+ public static function parseIdToken($token)
+ {
+ if (!is_string($token) || substr_count($token, '.') != 2) {
+ return null;
+ }
+
+ [$headb64, $bodyb64, $cryptob64] = explode('.', $token);
+
+ $header = json_decode(base64_decode(strtr($headb64, '-_', '+/'), true), true);
+ $body = json_decode(base64_decode(strtr($bodyb64, '-_', '+/'), true), true);
+
+ return array_merge($header, $body);
+ }
}
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
@@ -154,7 +154,7 @@
*
* Revokes the authentication token.
*/
- public function logout(): JsonResponse
+ public function logout(Request $request): JsonResponse
{
$tokenId = $this->guard()->user()->token()->id;
$tokenRepository = app(TokenRepository::class);
@@ -166,6 +166,9 @@
// Revoke all of the token's refresh tokens...
$refreshTokenRepository->revokeRefreshTokensByAccessTokenId($tokenId);
+ // Revoke OIDC token(s)
+ OAuth::logout($request);
+
return response()->json([
'status' => 'success',
'message' => self::trans('auth.logoutsuccess'),
diff --git a/src/resources/js/user/routes.js b/src/resources/js/user/routes.js
--- a/src/resources/js/user/routes.js
+++ b/src/resources/js/user/routes.js
@@ -99,6 +99,7 @@
},
{
path: '/logout',
+ alias: '/oauth/logout',
name: 'logout',
component: LogoutComponent
},
diff --git a/src/resources/vue/Logout.vue b/src/resources/vue/Logout.vue
--- a/src/resources/vue/Logout.vue
+++ b/src/resources/vue/Logout.vue
@@ -3,7 +3,8 @@
<script>
export default {
mounted () {
- axios.post('/api/auth/logout')
+ // Note: Post query parameters that are expected in the /oauth/logout
+ axios.post('/api/auth/logout', this.$route.query)
.then(response => {
this.$toast.success(response.data.message)
})
diff --git a/src/routes/web.php b/src/routes/web.php
--- a/src/routes/web.php
+++ b/src/routes/web.php
@@ -51,6 +51,9 @@
->middleware(['auth:api', 'scope:email'])
->name('openid.userinfo'); // needed for .well-known/openid-configuration handler
+ Route::get('/logout', [Utils::class, 'defaultView'])
+ ->name('openid.end_session_endpoint'); // needed for .well-known/openid-configuration handler
+
Route::get('/authorize', [Utils::class, 'defaultView'])
->name('passport.authorizations.authorize'); // needed for .well-known/openid-configuration handler
}
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
@@ -2,6 +2,7 @@
namespace Tests\Feature\Controller;
+use App\Auth\OAuth;
use App\Auth\PassportClient;
use App\Domain;
use App\IP4Net;
@@ -602,7 +603,7 @@
$this->assertTrue(!empty($params['id_token']));
$this->assertTrue(!empty($params['expires_in']));
- $token = $this->parseIdToken($params['id_token']);
+ $token = OAuth::parseIdToken($params['id_token']);
$this->assertSame('JWT', $token['typ']);
$this->assertSame('RS256', $token['alg']);
@@ -654,17 +655,4 @@
$this->post("/oauth/authorize", [])->assertStatus(405);
$this->post("/oauth/token/refresh", [])->assertStatus(405);
}
-
- /**
- * Parse JWT token into an array
- */
- private function parseIdToken($token): array
- {
- [$headb64, $bodyb64, $cryptob64] = explode('.', $token);
-
- $header = json_decode(base64_decode(strtr($headb64, '-_', '+/'), true), true);
- $body = json_decode(base64_decode(strtr($bodyb64, '-_', '+/'), true), true);
-
- return array_merge($header, $body);
- }
}

File Metadata

Mime Type
text/plain
Expires
Fri, May 22, 9:43 AM (15 h, 18 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18927520
Default Alt Text
D5891.1779443035.diff (6 KB)

Event Timeline