* API: support OpenID Connect for authentication against another OIC IdP

also migrating old fallback auth classes to new auth_fallback parameter and reordering providers a bit / give OIC a prominent position
This commit is contained in:
ralf 2023-11-17 10:17:29 +02:00
parent 9f46ee5e62
commit 1b9c543547
5 changed files with 184 additions and 6 deletions

View File

@ -169,9 +169,12 @@ class Auth
}
// now we need a (not yet authenticated) session so SAML / auth source selected "survives" eg. the SAML redirects
if (!empty($type) && !Session::get_sessionid())
if (!Session::get_sessionid() || session_status() === PHP_SESSION_NONE)
{
if (session_status() === PHP_SESSION_NONE)
{
session_start();
}
Session::egw_setcookie(Session::EGW_SESSION_NAME, session_id());
}

View File

@ -0,0 +1,129 @@
<?php
/**
* EGroupware Api: OpenIDConnect authentication (EGroupware against another OIC IdP)
*
* @link https://www.egroupware.org
* @package api
* @subpackage mail
* @author Ralf Becker <rb@egroupware.org>
* @copyright (c) 2023 by Ralf Becker <rb@egroupware.org>
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
*/
namespace EGroupware\Api\Auth;
use EGroupware\Api;
use Jumbojett\OpenIDConnectClient;
use Jumbojett\OpenIDConnectClientException;
class Openidconnect implements BackendSSO
{
protected OpenIDConnectClient $client;
/**
* Constructor
*/
public function __construct()
{
$this->client = new OpenIDConnectClient($GLOBALS['egw_info']['server']['oic_provider'],
$GLOBALS['egw_info']['server']['oic_client_id'],
$GLOBALS['egw_info']['server']['oic_client_secret']);
}
/**
* Attempt SSO login
*
* @return string sessionid on successful login, null otherwise
*/
function login()
{
try {
//error_log(__METHOD__."() session_status()=".session_status().", _SESSION=".json_encode($_SESSION));
$this->client->authenticate();
$account_lid = $this->client->getVerifiedClaims('sub');
$accounts = Api\Accounts::getInstance();
if (!$accounts->name2id($account_lid, 'account_lid', 'u'))
{
// fail if auto-creation of authenticated users is NOT configured
if (empty($GLOBALS['egw_info']['server']['auto_create_acct']))
{
return null;
}
try {
$user_info = $this->client->requestUserInfo();
$GLOBALS['auto_create_acct'] = [
'firstname' => $user_info['given_name'],
'lastname' => $user_info['family_name'],
'email' => $user_info['email'],
// not (yet) used supported keys
//'primary_group' => '',
//'add_group' => '',
//'account_id' => 0,
];
}
catch (OpenIDConnectClientException $e) {
// do NOT fail, if IdP does not support user-info
_egw_log_exception($e);
}
}
// return user session
return $GLOBALS['egw']->session->create($account_lid, null, null, false, false);
}
catch(\Exception $e) {
_egw_log_exception($e);
$GLOBALS['egw']->session->cd_reason = 'OpenIDConnect Error: '.$e->getMessage();
return null;
}
}
/**
* Logout SSO system
*/
function logout()
{
$this->client->signOut($this->client->getIdToken(), null);
}
/**
* Return (which) parts of session needed by current auth backend
*
* If this returns any key(s), the session is NOT destroyed by Api\Session::destroy,
* just everything but the keys is removed.
*
* @return array of needed keys in session
*/
function needSession()
{
return ['openid_connect_state', 'openid_connect_nonce', 'openid_connect_code_verifier',
Api\Session::EGW_APPSESSION_VAR]; // Auth stores backend via Cache::setSession()
}
/**
* password authentication against password stored in sql datababse
*
* @param string $username username of account to authenticate
* @param string $passwd corresponding password
* @param string $passwd_type ='text' 'text' for cleartext passwords (default)
* @return boolean true if successful authenticated, false otherwise
* @ToDo: add config about which claim to use and use this with Client Credentials claim, would allow to use OID like LDAP/AD with password
*/
function authenticate($username, $passwd, $passwd_type='text')
{
return false;
}
/**
* changes password in sql datababse
*
* @param string $old_passwd must be cleartext
* @param string $new_passwd must be cleartext
* @param int $account_id account id of user whose passwd should be changed
* @throws Exception to give a verbose error, why changing password failed
* @return boolean true if password successful changed, false otherwise
*/
function change_password($old_passwd, $new_passwd, $account_id=0)
{
return false;
}
}

View File

@ -116,6 +116,23 @@ else
{
$current_config[$row['config_name']] = $row['config_value'];
}
// migrate old fallback types to auth_fallback="True"
switch ($current_config['auth_type'])
{
case 'fallback':
$current_config['auth_type'] = 'ldap';
$current_config['auth_fallback'] = 'True';
break;
case 'fallbackmail2sql':
$current_config['auth_type'] = 'mail';
$current_config['auth_fallback'] = 'True';
break;
case 'fallbackads2sql':
case 'fallbackad2sql':
$current_config['auth_type'] = 'ads';
$current_config['auth_fallback'] = 'True';
break;
}
}
$setup_tpl->pparse('out','T_config_pre_script');

View File

@ -440,11 +440,13 @@ class setup_cmd_config extends setup_cmd
static $auth_types = array(
'sql' => 'SQL',
'ldap' => 'LDAP',
'mail' => 'Mail',
'ads' => 'Active Directory',
'openidconnect' => 'OpenID Connect',
'saml' => 'SAML',
'mail' => 'Mail',
'http' => 'HTTP',
'fallback' => 'Fallback LDAP --> SQL',
'fallbackmail2sql' => 'Fallback Mail --> SQL',
'fallback' => false, // do NOT show, they get migrated to fallback-auth automatic
'fallbackmail2sql' => false,
'sqlssl' => 'SQL / SSL',
);
static $scan_done = null;
@ -457,11 +459,15 @@ class setup_cmd_config extends setup_cmd
if (preg_match('/^([a-z0-9]+)\.php$/i', $file, $matches) &&
!isset($auth_types[strtolower($matches[1])]) &&
!interface_exists($class='EGroupware\\Api\\Auth\\'.$matches[1]) &&
is_subclass_of($class, Api\Auth\Backend::class))
is_subclass_of($class, Api\Auth\Backend::class) &&
$auth_types[$matches[1]] !== false)
{
$auth_types[strtolower($matches[1])] = $matches[1];
}
}
// remove (obsolete) auth-types marked with false
$auth_types = array_filter($auth_types);
foreach(self::$options['--account-auth'] as &$param)
{
if ($param['name'] == 'auth_type')

View File

@ -465,6 +465,29 @@
<td colspan="2">&nbsp;</td>
</tr>
<tr class="th">
<td colspan="2">
<b>{lang_If_using_OpenIDConnect_(authentication as client agains an other OID IdP)}:</b><br/>
{lang_The_OIC_IdP_must_support_autoconfiguration_under_the_above_given_URL (.well-known/openid-configuration).}
</td>
</tr>
<tr class="row_off">
<td>{lang_URL_of_the_IdP_(without_path)}:</td>
<td><input name="newsettings[oic_provider]" value="{value_oic_provider}" size="80" /></td>
</tr>
<tr class="row_on">
<td>{lang_Client_ID}:</td>
<td><input name="newsettings[oic_client_id]" value="{value_oic_client_id}" size="40" /></td>
</tr>
<tr class="row_off">
<td>{lang_Client_secret}:</td>
<td><input type="password" name="newsettings[oic_client_secret]" value="{value_oic_client_secret}" size="40" /></td>
</tr>
<tr class="row_off">
<td colspan="2">&nbsp;</td>
</tr>
<tr class="th">
<td colspan="2"><b>{lang_Periodic_import_from_ADS_or_LDAP_into_EGroupware_database}:</b></td>
</tr>