Skip to content

Commit

Permalink
OAuth: Support standard authentication with short-living password rec…
Browse files Browse the repository at this point in the history
…eived with OIDC token (#9530)
  • Loading branch information
alecpl committed Aug 29, 2024
1 parent a134c83 commit 0cf65ad
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
- OAuth: Fix: missing config `oauth_provider_name` in rcmail_oauth's constructor (#9217)
- OAuth: Fix Bearer authentication for Kinde (#9244)
- OAuth: Refactor: move display to the rcmail_oauth class and use `loginform_content` hook (#9217)
- OAuth: Support standard authentication with long-living password received with OIDC token (#9530)
- Additional_Message_Headers: Added %u, %d and %l variables (#8746, #8732)
- ACL: Set default of 'acl_specials' option to ['anyone'] (#8911)
- Enigma: Support Kolab's Web Of Anti-Trust feature (#8626)
Expand Down
7 changes: 7 additions & 0 deletions config/defaults.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,13 @@
'language' => ['locale'],
];

// Optional: For backends that don't support XOAUTH2/OAUTHBEARER method we can still use
// OpenIDC protocol to get a short-living password (claim) for the user to log into IMAP/SMTP.
// That password have to have (at least) the same expiration time as the token, and will be
// renewed on token refresh.
// Note: The claim have to be added to 'oauth_scope' above.
$config['oauth_password_claim'] = null;

// /// Example config for Gmail

// Register your service at https://console.developers.google.com/
Expand Down
53 changes: 45 additions & 8 deletions program/include/rcmail_oauth.php
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ public function __construct($options = [])
'auth_parameters' => $this->rcmail->config->get('oauth_auth_parameters', []),
'login_redirect' => $this->rcmail->config->get('oauth_login_redirect', false),
'pkce' => $this->rcmail->config->get('oauth_pkce', 'S256'),
'password_claim' => $this->rcmail->config->get('oauth_password_claim'),
'debug' => $this->rcmail->config->get('oauth_debug', false),
];

Expand Down Expand Up @@ -621,6 +622,18 @@ public function request_access_token($auth_code, $state = null)
}
}

$data['auth_type'] = $this->auth_type;

// Backends with no XOAUTH2/OAUTHBEARER support
if ($pass_claim = $this->options['password_claim']) {
if (empty($identity[$pass_claim])) {
throw new Exception("Password claim ({$pass_claim}) not found");
}
$authorization = $identity[$pass_claim];
unset($identity[$pass_claim]);
unset($data['auth_type']);
}

// store the full identity (usually contains `sub`, `name`, `preferred_username`, `given_name`, `family_name`, `locale`, `email`)
$data['identity'] = $identity;

Expand Down Expand Up @@ -702,6 +715,12 @@ public function refresh_access_token(array $token)

[$authorization, $identity] = $this->parse_tokens('refresh_token', $data, $token);

// Backends with no XOAUTH2/OAUTHBEARER support
if (($pass_claim = $this->options['password_claim']) && isset($identity[$pass_claim])) {
$authorization = $identity[$pass_claim];
unset($identity[$pass_claim]);
}

// update access token stored as password
$_SESSION['password'] = $this->rcmail->encrypt($authorization);

Expand Down Expand Up @@ -814,9 +833,11 @@ protected function parse_tokens($grant_type, &$data, $previous_data = null)
if (!empty($data['id_token'])) {
$identity = $this->jwt_decode($data['id_token']);

// sanity check, ensure that the identity have the same nonce
if (!isset($identity['nonce']) || $identity['nonce'] !== $_SESSION['oauth_nonce']) {
throw new RuntimeException("identity's nonce mismatch");
// Ensure that the identity have the same 'nonce', but not on token refresh (per the OIDC spec.)
if ($grant_type != 'refresh_token' || isset($identity['nonce'])) {
if (!isset($identity['nonce']) || $identity['nonce'] !== $_SESSION['oauth_nonce']) {
throw new RuntimeException("identity's nonce mismatch");
}
}
}

Expand Down Expand Up @@ -864,6 +885,11 @@ protected function mask_auth_data(&$data): void
if (isset($data['refresh_token'])) {
$data['refresh_token'] = $this->rcmail->encrypt($data['refresh_token']);
}

// encrypt the ID token, it may contain sensitive info (that we don't need at this point)
if (isset($data['id_token'])) {
$data['id_token'] = $this->rcmail->encrypt($data['id_token']);
}
}

/**
Expand Down Expand Up @@ -945,12 +971,17 @@ public function storage_init($options)
}

if ($this->login_phase) {
$options['auth_type'] = $this->auth_type;
if (isset($this->login_phase['token']['auth_type'])) {
$options['auth_type'] = $this->login_phase['token']['auth_type'];
}
} elseif (isset($_SESSION['oauth_token'])) {
if ($this->check_token_validity($_SESSION['oauth_token']) === self::TOKEN_REFRESHED) {
$options['password'] = $this->rcmail->decrypt($_SESSION['password']);
}
$options['auth_type'] = $this->auth_type;

if (isset($_SESSION['oauth_token']['auth_type'])) {
$options['auth_type'] = $_SESSION['oauth_token']['auth_type'];
}
}

return $options;
Expand Down Expand Up @@ -979,7 +1010,10 @@ public function smtp_connect($options)

$options['smtp_user'] = '%u';
$options['smtp_pass'] = '%p';
$options['smtp_auth_type'] = $this->auth_type;

if (isset($_SESSION['oauth_token']['auth_type'])) {
$options['smtp_auth_type'] = $_SESSION['oauth_token']['auth_type'];
}
}

return $options;
Expand All @@ -997,7 +1031,10 @@ public function managesieve_connect($options)
if (isset($_SESSION['oauth_token'])) {
// check token validity
$this->check_token_validity($_SESSION['oauth_token']);
$options['auth_type'] = $this->auth_type;

if (isset($_SESSION['oauth_token']['auth_type'])) {
$options['auth_type'] = $_SESSION['oauth_token']['auth_type'];
}
}

return $options;
Expand Down Expand Up @@ -1199,7 +1236,7 @@ public function handle_logout(): void
];

if (isset($_SESSION['oauth_token']['id_token'])) {
$params['id_token_hint'] = $_SESSION['oauth_token']['id_token'];
$params['id_token_hint'] = $this->rcmail->decrypt($_SESSION['oauth_token']['id_token']);
}

$this->logout_redirect_url = $this->options['logout_uri'] . '?' . http_build_query($params);
Expand Down

0 comments on commit 0cf65ad

Please sign in to comment.