Skip to content

Commit

Permalink
OAuth improvements (#9217)
Browse files Browse the repository at this point in the history
- OAuth: Add `oauth_config_uri` - support OAuth/OpenIDC discovery (#8201)
- OAuth: Add `oauth_logout_uri` - allow invalidating the OAUTH-Session on logout (#8057)
- OAuth: Support for OpenID Connect RP-Initiated Logout (#9109)
- OAuth: Add support of OAUTHBEARER (#9217)
- OAuth: Add `oauth_debug` option (#9217)
- OAuth: Fix: missing config `oauth_provider_name` in rcmail_oauth's constructor (#9217)
- OAuth: Refactor: move display to the rcmail_oauth class and use `loginform_content` hook (#9217)

Signed-off-by: Edouard Vanbelle <edouard@vanbelle.fr>
Co-authored-by: Aleksander Machniak <alec@alec.pl>
  • Loading branch information
EdouardVanbelle and alecpl authored Dec 17, 2023
1 parent f5d7673 commit 588a879
Show file tree
Hide file tree
Showing 8 changed files with 1,087 additions and 249 deletions.
28 changes: 27 additions & 1 deletion config/defaults.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,9 @@
// ----------------------------------

// Enable OAuth2 by defining a provider. Use 'generic' here
// if enabled you can activate the Backchannel Logout specifying:
// https://<your roundcube instance>/index.php/login/backchannel to your Identity provider
// if you are using the backchannel, you mmust activate `oauth_cache`
$config['oauth_provider'] = null;

// Provider name to be displayed on the login button
Expand All @@ -349,15 +352,29 @@
// Mandatory: OAuth client secret
$config['oauth_client_secret'] = null;

// Optional: the OIDC discovery URI (the 'https://.../.well-known/openid-configuration')
// if specified, the discovery will supersede `oauth_issuer`, `auth_auth_uri`, `oauth_token_uri`, `oauth_identity_uri`, `oauth_logout_uri`, `oauth_jwks_uri`)
// it is recommanded to activate a cache via `oauth_cache` and `oauth_cache_ttl`
$config['oauth_config_uri'] = null;

// Optional: if defined will be used to check answer from issuer
$config['oauth_issuer'] = null;

// Optional: if defined will download JWKS Certificate and check JWT signatures
$config['oauth_jwks_uri'] = null;

// Mandatory: URI for OAuth user authentication (redirect)
$config['oauth_auth_uri'] = null;

// Mandatory: Endpoint for OAuth authentication requests (server-to-server)
// Mandatory or Optional if $oauth_config_uri is specified: Endpoint for OAuth authentication requests (server-to-server)
$config['oauth_token_uri'] = null;

// Optional: Endpoint to query user identity if not provided in auth response
$config['oauth_identity_uri'] = null;

// Optional: Endpoint for OIDC Logout propagation
$config['oauth_logout_uri'] = null;

// Optional: timeout for HTTP requests to OAuth server
$config['oauth_timeout'] = 10;

Expand All @@ -377,6 +394,15 @@
// Boolean: automatically redirect to OAuth login when opening Roundcube without a valid session
$config['oauth_login_redirect'] = false;

// Optional: boolean, if true will generate debug information to <default log path>/oauth.log
$config['oauth_debug'] = false;

// Mandatory for backchannel, highly recommended when using `oauth_config_uri` or `oauth_jwks_uri` (Type of cache. Supported values: 'db', 'apc' and 'memcache' or 'memcached')
$config['oauth_cache'] = 'db';

// Optional: cache ttl
$config['oauth_cache_ttl'] = '8h';

///// Example config for Gmail

// Register your service at https://console.developers.google.com/
Expand Down
6 changes: 0 additions & 6 deletions index.php
Original file line number Diff line number Diff line change
Expand Up @@ -193,12 +193,6 @@
}
}

// handle oauth login requests
else if ($RCMAIL->task == 'login' && $RCMAIL->action == 'oauth' && $RCMAIL->oauth->is_enabled()) {
$oauth_handler = new rcmail_action_login_oauth();
$oauth_handler->run();
}

// end session
else if ($RCMAIL->task == 'logout' && isset($_SESSION['user_id'])) {
$RCMAIL->request_security_check(rcube_utils::INPUT_GET | rcube_utils::INPUT_POST);
Expand Down
6 changes: 4 additions & 2 deletions program/actions/login/oauth.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ public function run($args = [])

// oauth success
if ($auth && isset($auth['username'], $auth['authorization'], $auth['token'])) {
// enforce XOAUTH2 auth type
$rcmail->config->set('imap_auth_type', 'XOAUTH2');
// enforce OAUTHBEARER/XOAUTH2 auth type
$rcmail->config->set('imap_auth_type', $rcmail->oauth->get_auth_type());
$rcmail->config->set('login_password_maxlen', strlen($auth['authorization']));

// use access_token and user info for IMAP login
Expand All @@ -55,6 +55,8 @@ public function run($args = [])
// save OAuth token in session
$_SESSION['oauth_token'] = $auth['token'];

$rcmail->oauth->log_debug('login successful for OIDC sub=%s with username=%s which is rcube-id=%s', $auth['token']['identity']['sub'], $auth['username'], $rcmail->user->ID);

// log successful login
$rcmail->log_login();

Expand Down
99 changes: 99 additions & 0 deletions program/actions/login/oauth_backchannel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Implementation of backchannel logout from IDP |
| |
| @see https://openid.net/specs/openid-connect-backchannel-1_0.html |
| |
| URL to declare: <roundcube instance>/index.php/login/backchannel |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/

class rcmail_action_login_oauth_backchannel extends rcmail_action
{
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();

// default message
$answer = ['error' => 'invalid_request', 'error_description' => "Error, no action"];

//Beware we are in back-channel from OP (IDP)
$logout_token = rcube_utils::get_input_string('logout_token', rcube_utils::INPUT_POST);

if (!empty($logout_token)) {
try {
$event = $rcmail->oauth->jwt_decode($logout_token);

/* return event example
{
"typ":"Logout", // event type
"iat":1700263584, // emition date
"jti":"4a953d6e-dc6b-4cc1-8d29-cb54b2351d0a", // token identifier
"iss":"https://....", // issuer identifier
"aud":"my client id", // audience = client id
"sub":"82c8f487-df95-4960-972c-4e680c3c72f5", // subject
"sid":"28101815-0017-4ade-a550-e054bde07ded", // session
"events":{"http://schemas.openid.net/event/backchannel-logout":[]}
}
*/

if ($event['typ'] !== 'Logout') {
throw new RuntimeException('handle only Logout events');
}
if (!isset($event['sub'])) {
throw new RuntimeException('event has no "sub"');
}

$rcmail->oauth->log_debug('backchannel: logout event received, schedule a revocation for token\'s sub: %s', $event['sub']);
$rcmail->oauth->schedule_token_revocation($event['sub']);

http_response_code(200); // 204 works also
header('Content-Type: application/json; charset=UTF-8');
header('Cache-Control: no-store');
echo '{}';
exit;
}
catch (\Exception $e) {
rcube::raise_error([
'message' => $e->getMessage(),
'file' => __FILE__,
'line' => __LINE__,
], true, false
);
$answer['error_description'] = "Error decoding JWT";
}
}
else {
rcube::raise_error([
'message' => sprintf('oidc backchannel called from %s without any parameter', rcube_utils::remote_addr()),
'file' => __FILE__,
'line' => __LINE__,
], true, false
);
}

http_response_code(400);
header('Content-Type: application/json; charset=UTF-8');
header('Cache-Control: no-store');
echo json_encode($answer);
exit;
}
}
Loading

0 comments on commit 588a879

Please sign in to comment.