From 1b9c543547896be7d8c3fa2aae8bf711816c7510 Mon Sep 17 00:00:00 2001 From: ralf Date: Fri, 17 Nov 2023 10:17:29 +0200 Subject: [PATCH] * 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 --- api/src/Auth.php | 7 +- api/src/Auth/Openidconnect.php | 129 +++++++++++++++++++++++ setup/config.php | 17 +++ setup/inc/class.setup_cmd_config.inc.php | 14 ++- setup/templates/default/config.tpl | 23 ++++ 5 files changed, 184 insertions(+), 6 deletions(-) create mode 100644 api/src/Auth/Openidconnect.php diff --git a/api/src/Auth.php b/api/src/Auth.php index b698646264..00203e22ef 100644 --- a/api/src/Auth.php +++ b/api/src/Auth.php @@ -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) { - session_start(); + if (session_status() === PHP_SESSION_NONE) + { + session_start(); + } Session::egw_setcookie(Session::EGW_SESSION_NAME, session_id()); } diff --git a/api/src/Auth/Openidconnect.php b/api/src/Auth/Openidconnect.php new file mode 100644 index 0000000000..6ba41473d7 --- /dev/null +++ b/api/src/Auth/Openidconnect.php @@ -0,0 +1,129 @@ + + * @copyright (c) 2023 by Ralf Becker + * @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; + } +} \ No newline at end of file diff --git a/setup/config.php b/setup/config.php index 98ce4b8c71..65d6836dd0 100644 --- a/setup/config.php +++ b/setup/config.php @@ -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'); diff --git a/setup/inc/class.setup_cmd_config.inc.php b/setup/inc/class.setup_cmd_config.inc.php index 5892e2fd71..c6e537dc8f 100644 --- a/setup/inc/class.setup_cmd_config.inc.php +++ b/setup/inc/class.setup_cmd_config.inc.php @@ -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') diff --git a/setup/templates/default/config.tpl b/setup/templates/default/config.tpl index b9b9e8da8d..887f1eb6d3 100644 --- a/setup/templates/default/config.tpl +++ b/setup/templates/default/config.tpl @@ -465,6 +465,29 @@   + + + {lang_If_using_OpenIDConnect_(authentication as client agains an other OID IdP)}:
+ {lang_The_OIC_IdP_must_support_autoconfiguration_under_the_above_given_URL (.well-known/openid-configuration).} + + + + {lang_URL_of_the_IdP_(without_path)}: + + + + {lang_Client_ID}: + + + + {lang_Client_secret}: + + + + +   + + {lang_Periodic_import_from_ADS_or_LDAP_into_EGroupware_database}: