Skip to content

Commit

Permalink
OAuth improvements (+ add user_create hook)
Browse files Browse the repository at this point in the history
 * Core: Improv.: can specify an array on *_auth_type, lib will elect the best auth type according server's capabilities
 * OAuth: Refact.: migrate login flow into hooks (more evolutive code and less dependency to core code)
 * OAuth: Fix: logger prefix (include prefix during login phase)
 * OAuth: Feat: add user_create hook (map OIDC claims when creating a new user)
 * OAuth: Lean: prepare OAUTHBEARER or XOAUTH2 in Sieve, SMTP, IMAP

Signed-off-by: Edouard Vanbelle <edouard@vanbelle.fr>
  • Loading branch information
EdouardVanbelle committed Dec 18, 2023
1 parent 9f5cc3e commit 67acba6
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 98 deletions.
16 changes: 16 additions & 0 deletions config/defaults.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,11 @@
// Mandatory: OAuth client secret
$config['oauth_client_secret'] = null;

// Optional auth method for Sieve, IMAP and SMTP protocol
// By default will use ['XOAUTH2'] and libraries will use the most appropriate method according server's capabilities
// Important: if Oauth/OIDC is used this will supersede Sieve, IMAP and SMTP auth method
$config['oauth_auth_type'] = ['XOAUTH2'];

// 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`
Expand Down Expand Up @@ -403,6 +408,17 @@
// Optional: cache ttl
$config['oauth_cache_ttl'] = '8h';

// Optional: map OIDC claims to Roundcube keys during the account creation
// format: roundcube_key => array of claims (the first claim found and defined will be used)
// more informations on claims: https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims
// default value:
// [
// 'user_name' => ['name'],
// 'user_email' => ['email'],
// 'language' => ['locale'],
// ]
$config['oauth_user_create_map'] = null;

///// Example config for Gmail

// Register your service at https://console.developers.google.com/
Expand Down
15 changes: 15 additions & 0 deletions plugins/managesieve/lib/Roundcube/rcube_sieve.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,21 @@ public function __construct($username, $password = '', $host = 'localhost', $por
$password = $auth_pw;
}

// case of auth_type as an array, choose the best mehod
if (is_array($auth_type)) {
$supported_methods = $this->sieve->getAuthMechs();
if (is_a($supported_methods, 'PEAR_Error')) {
return $this->_set_error(self::ERROR_OTHER);
}

$prefered_methods = array_map('strtoupper', $auth_type);
foreach ($prefered_methods as $auth_type) {
if (in_array($auth_type, $supported_methods)) {
break;
}
}
}

$result = $this->sieve->login($username, $password, $auth_type ? strtoupper($auth_type) : null, $authz);

if (is_a($result, 'PEAR_Error')) {
Expand Down
73 changes: 16 additions & 57 deletions program/actions/login/oauth.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,70 +32,29 @@ public function run($args = [])
$auth_error = rcube_utils::get_input_string('error', rcube_utils::INPUT_GET);
$auth_state = rcube_utils::get_input_string('state', rcube_utils::INPUT_GET);

// on oauth error
if (!empty($auth_error)) {
$error_message = rcube_utils::get_input_string('error_description', rcube_utils::INPUT_GET) ?: $auth_error;
$rcmail->output->show_message($error_message, 'warning');
return;
}

// auth code return from oauth login
if (!empty($auth_code)) {
$auth = $rcmail->oauth->request_access_token($auth_code, $auth_state);

// oauth success
if ($auth && isset($auth['username'], $auth['authorization'], $auth['token'])) {
// 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
$storage_host = $rcmail->autoselect_host();
if ($rcmail->login($auth['username'], $auth['authorization'], $storage_host, true)) {
// replicate post-login tasks from index.php
$rcmail->session->remove('temp');
$rcmail->session->regenerate_id(false);

// send auth cookie if necessary
$rcmail->session->set_auth_cookie();

// 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();

// allow plugins to control the redirect url after login success
$redir = $rcmail->plugins->exec_hook('login_after', ['_task' => 'mail']);
unset($redir['abort'], $redir['_err']);

// send redirect
header('Location: ' . $rcmail->url($redir, true, false));
exit;
}
else {
$rcmail->output->show_message('loginfailed', 'warning');

// log failed login
$error_code = $rcmail->login_error();
$rcmail->log_login($auth['username'], true, $error_code);

$rcmail->plugins->exec_hook('login_failed', [
'code' => $error_code,
'host' => $storage_host,
'user' => $auth['username'],
]);

$rcmail->kill_session();
// fall through -> login page
}
}
else {
if (!$auth) {
$rcmail->output->show_message('oauthloginfailed', 'warning');
return;
}

// next action will be the login
$args['task'] = 'login';
$args['action'] = 'login';
return $args;
}
// error return from oauth login
elseif (!empty($auth_error)) {
$error_message = rcube_utils::get_input_string('error_description', rcube_utils::INPUT_GET) ?: $auth_error;
$rcmail->output->show_message($error_message, 'warning');
}

// login action: redirect to `oauth_auth_uri`
elseif ($rcmail->task === 'login') {
if ($rcmail->task === 'login') {
// this will always exit() the process
$rcmail->oauth->login_redirect();
}
Expand Down
Loading

0 comments on commit 67acba6

Please sign in to comment.