From d934efaa86112a53d66d04cfd11580a935042651 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Tue, 16 May 2023 10:40:52 +0200 Subject: [PATCH] [Web] move iam sso functions --- data/web/inc/functions.auth.inc.php | 178 ----------------------- data/web/inc/functions.inc.php | 209 ++++++++++++++++++++++++++++ data/web/inc/prerequisites.inc.php | 4 +- data/web/inc/triggers.inc.php | 27 ++-- 4 files changed, 222 insertions(+), 196 deletions(-) diff --git a/data/web/inc/functions.auth.inc.php b/data/web/inc/functions.auth.inc.php index d68a7b6e..fbb79ed8 100644 --- a/data/web/inc/functions.auth.inc.php +++ b/data/web/inc/functions.auth.inc.php @@ -1,14 +1,4 @@ getAccessToken('authorization_code', ['code' => $_GET['code']]); - } catch (Exception $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__), - 'msg' => array('login_failed', $e->getMessage()) - ); - return false; - } - - $_SESSION['keycloak_token'] = $token->getToken(); - $_SESSION['keycloak_refresh_token'] = $token->getRefreshToken(); - // decode jwt data - $info = json_decode(base64_decode(str_replace('_', '/', str_replace('-','+',explode('.', $token->getToken())[1]))), true); - // check if email address is given - if (!$info['email']){ - return false; - } - - - // token valid, get mailbox - $stmt = $pdo->prepare("SELECT * FROM `mailbox` - INNER JOIN domain on mailbox.domain = domain.domain - WHERE `kind` NOT REGEXP 'location|thing|group' - AND `mailbox`.`active`='1' - AND `domain`.`active`='1' - AND `username` = :user - AND `authsource`='keycloak'"); - $stmt->execute(array(':user' => $info['email'])); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - if ($row){ - // success - $_SESSION['mailcow_cc_username'] = $info['email']; - $_SESSION['mailcow_cc_role'] = "user"; - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']), - 'msg' => array('logged_in_as', $_SESSION['mailcow_cc_username']) - ); - return true; - } - // get mapped template, if not set return false - // also return false if no mappers were defined - $identity_provider_settings = identity_provider('get'); - $user_template = $info['mailcow_template']; - if (empty($identity_provider_settings['mappers']) || empty($user_template)){ - unset_auth_session(); - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']), - 'msg' => 'login_failed' - ); - return false; - } - $mbox_template = null; - // check if matching rolemapping exist - foreach ($identity_provider_settings['mappers'] as $index => $mapper){ - if ($user_template == $mapper) { - $mbox_template = $identity_provider_settings['templates'][$index]; - break; - } - } - if (!$mbox_template){ - // no matching template found - unset_auth_session(); - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']), - 'msg' => 'login_failed' - ); - return false; - } - $stmt = $pdo->prepare("SELECT * FROM `templates` - WHERE `template` = :template AND type = 'mailbox'"); - $stmt->execute(array( - ":template" => $mbox_template - )); - $mbox_template_data = $stmt->fetch(PDO::FETCH_ASSOC); - if (empty($mbox_template_data)){ - unset_auth_session(); - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']), - 'msg' => 'login_failed' - ); - return false; - } - - // create mailbox - $mbox_template_data = json_decode($mbox_template_data["attributes"], true); - $mbox_template_data['domain'] = explode('@', $info['email'])[1]; - $mbox_template_data['local_part'] = explode('@', $info['email'])[0]; - $mbox_template_data['authsource'] = 'keycloak'; - $_SESSION['iam_create_login'] = true; - $create_res = mailbox('add', 'mailbox', $mbox_template_data); - $_SESSION['iam_create_login'] = false; - if (!$create_res){ - unset_auth_session(); - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']), - 'msg' => 'login_failed' - ); - return false; - } - - $_SESSION['mailcow_cc_username'] = $info['email']; - $_SESSION['mailcow_cc_role'] = "user"; - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']), - 'msg' => array('logged_in_as', $_SESSION['mailcow_cc_username']) - ); - return true; -} -function keycloak_refresh(){ - global $keycloak_provider; - - try { - $token = $keycloak_provider->getAccessToken('refresh_token', ['refresh_token' => $_SESSION['keycloak_refresh_token']]); - } catch (Exception $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__), - 'msg' => array('login_failed', $e->getMessage()) - ); - return false; - } - - $_SESSION['keycloak_token'] = $token->getToken(); - $_SESSION['keycloak_refresh_token'] = $token->getRefreshToken(); - // decode jwt data - $info = json_decode(base64_decode(str_replace('_', '/', str_replace('-','+',explode('.', $_SESSION['keycloak_token'])[1]))), true); - if (!$info['email']){ - unset_auth_session(); - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']), - 'msg' => 'refresh_login_failed' - ); - } - - $_SESSION['mailcow_cc_username'] = $info['email']; - $_SESSION['mailcow_cc_role'] = "user"; - return true; -} -function keycloak_get_redirect(){ - global $keycloak_provider; - - $authUrl = $keycloak_provider->getAuthorizationUrl(); - $_SESSION['oauth2state'] = $keycloak_provider->getState(); - - return $authUrl; -} -function keycloak_get_logout(){ - global $keycloak_provider; - - $logoutUrl = $keycloak_provider->getLogoutUrl(); - $logoutUrl = $logoutUrl . "&post_logout_redirect_uri=https://" . $_SERVER['SERVER_NAME']; - - return $logoutUrl; -} diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index ecef3501..57d2666e 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -2064,8 +2064,217 @@ function identity_provider($_action, $_data = null, $hide_secret = false) { } return $provider; break; + case "verify-sso": + $provider = $_data['iam_provider']; + + try { + $token = $provider->getAccessToken('authorization_code', ['code' => $_GET['code']]); + } catch (Exception $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__), + 'msg' => array('login_failed', $e->getMessage()) + ); + return false; + } + try { + $_SESSION['iam_token'] = $token->getToken(); + $_SESSION['iam_refresh_token'] = $token->getRefreshToken(); + $info = $provider->getResourceOwner($token)->toArray(); + } catch (Throwable $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__), + 'msg' => array('login_failed', $e->getMessage()) + ); + return false; + } + + // check if email address is given + if (empty($info['email'])) return false; + + // token valid, get mailbox + $stmt = $pdo->prepare("SELECT * FROM `mailbox` + INNER JOIN domain on mailbox.domain = domain.domain + WHERE `kind` NOT REGEXP 'location|thing|group' + AND `mailbox`.`active`='1' + AND `domain`.`active`='1' + AND `username` = :user + AND `authsource`='keycloak' OR `authsource`='generic-oidc'"); + $stmt->execute(array(':user' => $info['email'])); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if ($row){ + // success + $_SESSION['mailcow_cc_username'] = $info['email']; + $_SESSION['mailcow_cc_role'] = "user"; + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']), + 'msg' => array('logged_in_as', $_SESSION['mailcow_cc_username']) + ); + return true; + } + + // get mapped template, if not set return false + // also return false if no mappers were defined + $provider = identity_provider('get'); + $user_template = $info['mailcow_template']; + if (empty($provider['mappers']) || empty($user_template)){ + clear_session(); + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']), + 'msg' => 'login_failed' + ); + return false; + } + + // check if matching attribute exist + if (array_search($user_template, $provider['mappers']) === false) { + clear_session(); + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']), + 'msg' => 'login_failed' + ); + return false; + } + + // create mailbox + $create_res = mailbox('add', 'mailbox_from_template', array( + 'domain' => explode('@', $info['email'])[1], + 'local_part' => explode('@', $info['email'])[0], + 'authsource' => identity_provider('get')['authsource'], + 'template' => $user_template + )); + if (!$create_res){ + clear_session(); + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']), + 'msg' => 'login_failed' + ); + return false; + } + + $_SESSION['mailcow_cc_username'] = $info['email']; + $_SESSION['mailcow_cc_role'] = "user"; + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']), + 'msg' => array('logged_in_as', $_SESSION['mailcow_cc_username']) + ); + return true; + break; + case "refresh-token": + $provider = $_data['iam_provider']; + + try { + $token = $provider->getAccessToken('refresh_token', ['refresh_token' => $_SESSION['iam_refresh_token']]); + } catch (Exception $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__), + 'msg' => array('login_failed', $e->getMessage()) + ); + return false; + } + try { + $_SESSION['iam_token'] = $token->getToken(); + $_SESSION['iam_refresh_token'] = $token->getRefreshToken(); + $info = $provider->getResourceOwner($token)->toArray(); + } catch (Throwable $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__), + 'msg' => array('login_failed', $e->getMessage()) + ); + return false; + } + + if (empty($info['email'])){ + clear_session(); + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']), + 'msg' => 'refresh_login_failed' + ); + return false; + } + + $_SESSION['mailcow_cc_username'] = $info['email']; + $_SESSION['mailcow_cc_role'] = "user"; + return true; + break; + case "get-redirect": + $provider = $_data['iam_provider']; + $authUrl = $provider->getAuthorizationUrl(); + $_SESSION['oauth2state'] = $provider->getState(); + return $authUrl; + break; + case "get-keycloak-admin-token": + // get access_token for service account of mailcow client + $iam_settings = identity_provider('get'); + if ($iam_settings['authsource'] !== 'keycloak') return false; + if (isset($iam_settings['access_token'])) { + // check if access_token is valid + $url = "{$iam_settings['server_url']}/realms/{$iam_settings['realm']}/protocol/openid-connect/token/introspect"; + $req = http_build_query(array( + 'token' => $iam_settings['access_token'], + 'client_id' => $iam_settings['client_id'], + 'client_secret' => $iam_settings['client_secret'] + )); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $url); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $req); + curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded')); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_TIMEOUT, 5); + $res = json_decode(curl_exec($curl), true); + $code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close ($curl); + if ($code == 200 && $res['active'] == true) { + // token is valid + return $iam_settings['access_token']; + } + } + + $url = "{$iam_settings['server_url']}/realms/{$iam_settings['realm']}/protocol/openid-connect/token"; + $req = http_build_query(array( + 'grant_type' => 'client_credentials', + 'client_id' => $iam_settings['client_id'], + 'client_secret' => $iam_settings['client_secret'] + )); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $url); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $req); + curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded')); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_TIMEOUT, 5); + $res = json_decode(curl_exec($curl), true); + $code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close ($curl); + if ($code != 200) { + return false; + } + + $stmt = $pdo->prepare("INSERT INTO identity_provider (`key`, `value`) VALUES (:key, :value) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`);"); + $stmt->execute(array( + ':key' => 'access_token', + ':value' => $res['access_token'] + )); + return $res['access_token']; + break; } } +function clear_session(){ + session_regenerate_id(true); + session_unset(); + session_destroy(); + session_write_close(); +} function get_logs($application, $lines = false) { if ($lines === false) { diff --git a/data/web/inc/prerequisites.inc.php b/data/web/inc/prerequisites.inc.php index 0fd4f61e..341346aa 100644 --- a/data/web/inc/prerequisites.inc.php +++ b/data/web/inc/prerequisites.inc.php @@ -174,8 +174,8 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.auth.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php'; -// Init Keycloak Provider -$keycloak_provider = identity_provider('init'); +// Init Identity Provider +$iam_provider = identity_provider('init'); // IMAP lib // use Ddeboer\Imap\Server; diff --git a/data/web/inc/triggers.inc.php b/data/web/inc/triggers.inc.php index a60c2e32..9b5a9516 100644 --- a/data/web/inc/triggers.inc.php +++ b/data/web/inc/triggers.inc.php @@ -1,20 +1,20 @@ $iam_provider)); header('Location: ' . $redirect_uri); die(); } - if ($_SESSION['keycloak_token'] && $_SESSION['keycloak_refresh_token']) { + if ($_SESSION['iam_token'] && $_SESSION['iam_refresh_token']) { // Session found, try to refresh - $isRefreshed = keycloak_refresh(); + $isRefreshed = identity_provider('refresh-token', array('iam_provider' => $iam_provider)); if (!$isRefreshed){ - // Session could not be refreshed, clear and redirect to keycloak - unset_auth_session(); - $redirect_uri = keycloak_get_redirect(); + // Session could not be refreshed, clear and redirect to provider + clear_session(); + $redirect_uri = identity_provider('get-redirect', array('iam_provider' => $iam_provider)); header('Location: ' . $redirect_uri); die(); } @@ -22,12 +22,7 @@ if ($keycloak_provider){ // Check given state against previously stored one to mitigate CSRF attack // Recieved access token in $_GET['code'] // extract info and verify user - $isValid = keycloak_verify_token(); - - if (!$isValid){ - // Token could not be verified, redirect to keycloak - $_SESSION['invalid_keycloak_sso'] = true; - } + identity_provider('verify-sso', array('iam_provider' => $iam_provider)); } }