From 05e4bd7602e2d7fecdab638e8e3a8aa2562c5f53 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Fri, 29 Nov 2024 15:50:35 +0100 Subject: [PATCH 01/12] [Web] use global vars for iam_provider and iam_settings --- data/web/admin.php | 2 - data/web/autodiscover.php | 1 + data/web/edit.php | 1 - data/web/inc/functions.auth.inc.php | 22 +++---- data/web/inc/functions.inc.php | 89 ++++++++++++----------------- data/web/inc/prerequisites.inc.php | 3 +- data/web/inc/triggers.inc.php | 8 +-- data/web/index.php | 4 +- data/web/json_api.php | 2 +- 9 files changed, 60 insertions(+), 72 deletions(-) diff --git a/data/web/admin.php b/data/web/admin.php index 9af7b6449..2081f34a6 100644 --- a/data/web/admin.php +++ b/data/web/admin.php @@ -86,8 +86,6 @@ $cors_settings['allowed_origins'] = str_replace(", ", "\n", $cors_settings['allo $cors_settings['allowed_methods'] = explode(", ", $cors_settings['allowed_methods']); $f2b_data = fail2ban('get'); -// identity provider -$iam_settings = identity_provider('get'); // mbox templates $mbox_templates = mailbox('get', 'mailbox_templates'); diff --git a/data/web/autodiscover.php b/data/web/autodiscover.php index 323f89e78..0b03eb3ce 100644 --- a/data/web/autodiscover.php +++ b/data/web/autodiscover.php @@ -55,6 +55,7 @@ $pdo = new PDO($dsn, $database_user, $database_pass, $opt); // Init Identity Provider $iam_provider = identity_provider('init'); +$iam_settings = identity_provider('get'); $login_user = strtolower(trim($_SERVER['PHP_AUTH_USER'])); $login_pass = trim(htmlspecialchars_decode($_SERVER['PHP_AUTH_PW'])); diff --git a/data/web/edit.php b/data/web/edit.php index fb7051b4c..b6359d172 100644 --- a/data/web/edit.php +++ b/data/web/edit.php @@ -119,7 +119,6 @@ if (isset($_SESSION['mailcow_cc_role'])) { $quarantine_category = mailbox('get', 'quarantine_category', $mailbox); $get_tls_policy = mailbox('get', 'tls_policy', $mailbox); $rlyhosts = relayhost('get'); - $iam_settings = identity_provider('get'); $template = 'edit/mailbox.twig'; $template_data = [ 'acl' => $_SESSION['acl'], diff --git a/data/web/inc/functions.auth.inc.php b/data/web/inc/functions.auth.inc.php index a0424f3ed..74cd6d956 100644 --- a/data/web/inc/functions.auth.inc.php +++ b/data/web/inc/functions.auth.inc.php @@ -162,6 +162,8 @@ function domainadmin_login($user, $pass){ } function user_login($user, $pass, $extra = null){ global $pdo; + global $iam_provider; + global $iam_settings; $is_internal = $extra['is_internal']; @@ -186,12 +188,11 @@ function user_login($user, $pass, $extra = null){ // user does not exist, try call idp login and create user if possible via rest flow if (!$row){ - $iam_settings = identity_provider('get'); if ($iam_settings['authsource'] == 'keycloak' && intval($iam_settings['mailpassword_flow']) == 1){ - $result = keycloak_mbox_login_rest($user, $pass, $iam_settings, array('is_internal' => $is_internal, 'create' => true)); + $result = keycloak_mbox_login_rest($user, $pass, array('is_internal' => $is_internal, 'create' => true)); if ($result !== false) return $result; } else if ($iam_settings['authsource'] == 'ldap') { - $result = ldap_mbox_login($user, $pass, $iam_settings, array('is_internal' => $is_internal, 'create' => true)); + $result = ldap_mbox_login($user, $pass, array('is_internal' => $is_internal, 'create' => true)); if ($result !== false) return $result; } } @@ -202,9 +203,8 @@ function user_login($user, $pass, $extra = null){ switch ($row['authsource']) { case 'keycloak': // user authsource is keycloak, try using via rest flow - $iam_settings = identity_provider('get'); if (intval($iam_settings['mailpassword_flow']) == 1){ - $result = keycloak_mbox_login_rest($user, $pass, $iam_settings, array('is_internal' => $is_internal)); + $result = keycloak_mbox_login_rest($user, $pass, array('is_internal' => $is_internal)); if ($result !== false) { // check for tfa authenticators $authenticators = get_tfa($user); @@ -243,8 +243,7 @@ function user_login($user, $pass, $extra = null){ break; case 'ldap': // user authsource is ldap - $iam_settings = identity_provider('get'); - $result = ldap_mbox_login($user, $pass, $iam_settings, array('is_internal' => $is_internal)); + $result = ldap_mbox_login($user, $pass, array('is_internal' => $is_internal)); if ($result !== false) { // check for tfa authenticators $authenticators = get_tfa($user); @@ -397,8 +396,10 @@ function apppass_login($user, $pass, $app_passwd_data, $extra = null){ // Keycloak REST Api Flow - auth user by mailcow_password attribute // This password will be used for direct UI, IMAP and SMTP Auth // To use direct user credentials, only Authorization Code Flow is valid -function keycloak_mbox_login_rest($user, $pass, $iam_settings, $extra = null){ +function keycloak_mbox_login_rest($user, $pass, $extra = null){ global $pdo; + global $iam_provider; + global $iam_settings; $is_internal = $extra['is_internal']; $create = $extra['create']; @@ -474,10 +475,11 @@ function keycloak_mbox_login_rest($user, $pass, $iam_settings, $extra = null){ return 'user'; } -function ldap_mbox_login($user, $pass, $iam_settings, $extra = null){ +function ldap_mbox_login($user, $pass, $extra = null){ global $pdo; + global $iam_provider; + global $iam_settings; - $iam_provider = identity_provider(); $is_internal = $extra['is_internal']; $create = $extra['create']; diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index fc703c353..c7cab469d 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -1072,6 +1072,8 @@ function set_tfa($_data) { global $pdo; global $yubi; global $tfa; + global $iam_settings; + $_data_log = $_data; $access_denied = null; !isset($_data_log['confirm_password']) ?: $_data_log['confirm_password'] = '*'; @@ -1100,7 +1102,6 @@ function set_tfa($_data) { $row = $stmt->fetch(PDO::FETCH_ASSOC); if ($row) { if ($row['authsource'] == 'ldap'){ - $iam_settings = identity_provider('get'); if (!ldap_mbox_login($username, $_data["confirm_password"], $iam_settings)) $access_denied = true; else $access_denied = false; } else { @@ -2129,20 +2130,13 @@ function uuid4() { function identity_provider($_action = null, $_data = null, $_extra = null) { global $pdo; global $iam_provider; + global $iam_settings; $data_log = $_data; if (isset($data_log['client_secret'])) $data_log['client_secret'] = '*'; if (isset($data_log['access_token'])) $data_log['access_token'] = '*'; switch ($_action) { - case NULL: - if ($iam_provider) { - return $iam_provider; - } else { - $iam_provider = identity_provider("init"); - return $iam_provider; - } - break; case 'get': $settings = array(); $stmt = $pdo->prepare("SELECT * FROM `identity_provider`;"); @@ -2414,20 +2408,20 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { return true; break; case "init": - $iam_settings = identity_provider('get'); + $settings = identity_provider('get'); $provider = null; - switch ($iam_settings['authsource']) { + switch ($settings['authsource']) { case "keycloak": - if ($iam_settings['server_url'] && $iam_settings['realm'] && $iam_settings['client_id'] && - $iam_settings['client_secret'] && $iam_settings['redirect_url'] && $iam_settings['version']){ + if ($settings['server_url'] && $settings['realm'] && $settings['client_id'] && + $settings['client_secret'] && $settings['redirect_url'] && $settings['version']){ $provider = new Stevenmaguire\OAuth2\Client\Provider\Keycloak([ - 'authServerUrl' => $iam_settings['server_url'], - 'realm' => $iam_settings['realm'], - 'clientId' => $iam_settings['client_id'], - 'clientSecret' => $iam_settings['client_secret'], - 'redirectUri' => $iam_settings['redirect_url'], - 'version' => $iam_settings['version'], + 'authServerUrl' => $settings['server_url'], + 'realm' => $settings['realm'], + 'clientId' => $settings['client_id'], + 'clientSecret' => $settings['client_secret'], + 'redirectUri' => $settings['redirect_url'], + 'version' => $settings['version'], // 'encryptionAlgorithm' => 'RS256', // optional // 'encryptionKeyPath' => '../key.pem' // optional // 'encryptionKey' => 'contents_of_key_or_certificate' // optional @@ -2435,34 +2429,34 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { } break; case "generic-oidc": - if ($iam_settings['client_id'] && $iam_settings['client_secret'] && $iam_settings['redirect_url'] && - $iam_settings['authorize_url'] && $iam_settings['token_url'] && $iam_settings['userinfo_url']){ + if ($settings['client_id'] && $settings['client_secret'] && $settings['redirect_url'] && + $settings['authorize_url'] && $settings['token_url'] && $settings['userinfo_url']){ $provider = new \League\OAuth2\Client\Provider\GenericProvider([ - 'clientId' => $iam_settings['client_id'], - 'clientSecret' => $iam_settings['client_secret'], - 'redirectUri' => $iam_settings['redirect_url'], - 'urlAuthorize' => $iam_settings['authorize_url'], - 'urlAccessToken' => $iam_settings['token_url'], - 'urlResourceOwnerDetails' => $iam_settings['userinfo_url'], - 'scopes' => $iam_settings['client_scopes'] + 'clientId' => $settings['client_id'], + 'clientSecret' => $settings['client_secret'], + 'redirectUri' => $settings['redirect_url'], + 'urlAuthorize' => $settings['authorize_url'], + 'urlAccessToken' => $settings['token_url'], + 'urlResourceOwnerDetails' => $settings['userinfo_url'], + 'scopes' => $settings['client_scopes'] ]); } break; case "ldap": - if ($iam_settings['host'] && $iam_settings['port'] && $iam_settings['basedn'] && - $iam_settings['binddn'] && $iam_settings['bindpass']){ + if ($settings['host'] && $settings['port'] && $settings['basedn'] && + $settings['binddn'] && $settings['bindpass']){ $options = array(); - if ($iam_settings['ignore_ssl_error']) { + if ($settings['ignore_ssl_error']) { $options[LDAP_OPT_X_TLS_REQUIRE_CERT] = LDAP_OPT_X_TLS_NEVER; } $provider = new \LdapRecord\Connection([ - 'hosts' => [$iam_settings['host']], - 'port' => $iam_settings['port'], - 'base_dn' => $iam_settings['basedn'], - 'username' => $iam_settings['binddn'], - 'password' => $iam_settings['bindpass'], - 'use_ssl' => $iam_settings['use_ssl'], - 'use_tls' => $iam_settings['use_tls'], + 'hosts' => [$settings['host']], + 'port' => $settings['port'], + 'base_dn' => $settings['basedn'], + 'username' => $settings['binddn'], + 'password' => $settings['bindpass'], + 'use_ssl' => $settings['use_ssl'], + 'use_tls' => $settings['use_tls'], 'options' => $options ]); try { @@ -2477,8 +2471,6 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { return $provider; break; case "verify-sso": - $provider = $_data['iam_provider']; - $iam_settings = identity_provider('get'); if ($iam_settings['authsource'] != 'keycloak' && $iam_settings['authsource'] != 'generic-oidc'){ $_SESSION['return'][] = array( 'type' => 'danger', @@ -2489,10 +2481,10 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { } try { - $token = $provider->getAccessToken('authorization_code', ['code' => $_GET['code']]); + $token = $iam_provider->getAccessToken('authorization_code', ['code' => $_GET['code']]); $_SESSION['iam_token'] = $token->getToken(); $_SESSION['iam_refresh_token'] = $token->getRefreshToken(); - $info = $provider->getResourceOwner($token)->toArray(); + $info = $iam_provider->getResourceOwner($token)->toArray(); } catch (Throwable $e) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -2577,13 +2569,11 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { return true; break; case "refresh-token": - $provider = $_data['iam_provider']; - try { - $token = $provider->getAccessToken('refresh_token', ['refresh_token' => $_SESSION['iam_refresh_token']]); + $token = $iam_provider->getAccessToken('refresh_token', ['refresh_token' => $_SESSION['iam_refresh_token']]); $_SESSION['iam_token'] = $token->getToken(); $_SESSION['iam_refresh_token'] = $token->getRefreshToken(); - $info = $provider->getResourceOwner($token)->toArray(); + $info = $iam_provider->getResourceOwner($token)->toArray(); } catch (Throwable $e) { clear_session(); $_SESSION['return'][] = array( @@ -2609,17 +2599,14 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { return true; break; case "get-redirect": - $iam_settings = identity_provider('get'); if ($iam_settings['authsource'] != 'keycloak' && $iam_settings['authsource'] != 'generic-oidc') return false; - $provider = $_data['iam_provider']; - $authUrl = $provider->getAuthorizationUrl(); - $_SESSION['oauth2state'] = $provider->getState(); + $authUrl = $iam_provider->getAuthorizationUrl(); + $_SESSION['oauth2state'] = $iam_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 diff --git a/data/web/inc/prerequisites.inc.php b/data/web/inc/prerequisites.inc.php index fb30956f4..e67271412 100644 --- a/data/web/inc/prerequisites.inc.php +++ b/data/web/inc/prerequisites.inc.php @@ -180,6 +180,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php'; // Init Identity Provider $iam_provider = identity_provider('init'); +$iam_settings = identity_provider('get'); // IMAP lib // use Ddeboer\Imap\Server; @@ -323,7 +324,7 @@ $UI_TEXTS = customize('get', 'ui_texts'); if (file_exists('/web/css/themes/'.$UI_THEME.'-bootstrap.css')) $css_minifier->add('/web/css/themes/'.$UI_THEME.'-bootstrap.css'); else - $css_minifier->add('/web/css/themes/lumen-bootstrap.css'); + $css_minifier->add('/web/css/themes/lumen-bootstrap.css'); // minify css build files foreach ($css_dir as $css_file) { $css_minifier->add('/web/css/build/' . $css_file); diff --git a/data/web/inc/triggers.inc.php b/data/web/inc/triggers.inc.php index 7f330ba1c..b0c2237d6 100644 --- a/data/web/inc/triggers.inc.php +++ b/data/web/inc/triggers.inc.php @@ -3,18 +3,18 @@ if ($iam_provider){ if (isset($_GET['iam_sso'])){ // redirect for sso - $redirect_uri = identity_provider('get-redirect', array('iam_provider' => $iam_provider)); + $redirect_uri = identity_provider('get-redirect'); $redirect_uri = !empty($redirect_uri) ? $redirect_uri : '/'; header('Location: ' . $redirect_uri); die(); } if ($_SESSION['iam_token'] && $_SESSION['iam_refresh_token']) { // Session found, try to refresh - $isRefreshed = identity_provider('refresh-token', array('iam_provider' => $iam_provider)); + $isRefreshed = identity_provider('refresh-token'); if (!$isRefreshed){ // Session could not be refreshed, redirect to provider - $redirect_uri = identity_provider('get-redirect', array('iam_provider' => $iam_provider)); + $redirect_uri = identity_provider('get-redirect'); $redirect_uri = !empty($redirect_uri) ? $redirect_uri : '/'; header('Location: ' . $redirect_uri); die(); @@ -23,7 +23,7 @@ if ($iam_provider){ // Check given state against previously stored one to mitigate CSRF attack // Recieved access token in $_GET['code'] // extract info and verify user - identity_provider('verify-sso', array('iam_provider' => $iam_provider)); + identity_provider('verify-sso'); } } diff --git a/data/web/index.php b/data/web/index.php index 08857006f..0282e4835 100644 --- a/data/web/index.php +++ b/data/web/index.php @@ -15,7 +15,7 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == ' header('Location: /mailbox'); exit(); } -elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') { +elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') { $user_details = mailbox("get", "mailbox_details", $_SESSION['mailcow_cc_username']); $is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false; if (intval($user_details['attributes']['sogo_access']) == 1 && !$is_dual) { @@ -32,7 +32,7 @@ $_SESSION['index_query_string'] = $_SERVER['QUERY_STRING']; $has_iam_sso = false; if ($iam_provider){ - $has_iam_sso = identity_provider("get-redirect", array('iam_provider' => $iam_provider)) ? true : false; + $has_iam_sso = identity_provider("get-redirect") ? true : false; } diff --git a/data/web/json_api.php b/data/web/json_api.php index a3103ffa1..4e9d2a0ed 100644 --- a/data/web/json_api.php +++ b/data/web/json_api.php @@ -1708,7 +1708,7 @@ if (isset($_GET['query'])) { $score = array("score" => preg_replace("/\s+/", "", $score)); process_get_return($score); case "identity_provider": - process_get_return(identity_provider('get')); + process_get_return($iam_settings); break; break; // return no route found if no case is matched From b2db8e6b314d586a062b248b9553f533f4ed67a5 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Fri, 29 Nov 2024 16:52:34 +0100 Subject: [PATCH 02/12] [Dovecot] init identity provider before user login --- data/conf/dovecot/auth/mailcowauth.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/data/conf/dovecot/auth/mailcowauth.php b/data/conf/dovecot/auth/mailcowauth.php index 2c3c01b30..6d7660577 100644 --- a/data/conf/dovecot/auth/mailcowauth.php +++ b/data/conf/dovecot/auth/mailcowauth.php @@ -12,7 +12,7 @@ $return = array("success" => false); if(!isset($post['username']) || !isset($post['password']) || !isset($post['real_rip'])){ error_log("MAILCOWAUTH: Bad Request"); http_response_code(400); // Bad Request - echo json_encode($return); + echo json_encode($return); exit(); } @@ -35,7 +35,7 @@ try { catch (PDOException $e) { error_log("MAILCOWAUTH: " . $e . PHP_EOL); http_response_code(500); // Internal Server Error - echo json_encode($return); + echo json_encode($return); exit; } @@ -57,7 +57,6 @@ if ($isSOGoRequest) { error_log('MAILCOWAUTH: SOGo SSO auth for user ' . $post['username']); $result = true; } - } if ($result === false){ $result = apppass_login($post['username'], $post['password'], $protocol, array( @@ -67,6 +66,10 @@ if ($result === false){ if ($result) error_log('MAILCOWAUTH: App auth for user ' . $post['username']); } if ($result === false){ + // Init Identity Provider + $iam_provider = identity_provider('init'); + $iam_settings = identity_provider('get'); + error_log('MAILCOWAUTH Try: User auth for user ' . $post['username']); $result = user_login($post['username'], $post['password'], $protocol, array('is_internal' => true)); if ($result) error_log('MAILCOWAUTH: User auth for user ' . $post['username']); } @@ -80,6 +83,6 @@ if ($result) { } -echo json_encode($return); +echo json_encode($return); session_destroy(); exit; From ec4b9b088c875f967fdc160a21481361a101127b Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Fri, 29 Nov 2024 18:59:07 +0100 Subject: [PATCH 03/12] [Web] support multiple ldap hosts separated by comma --- data/web/inc/functions.inc.php | 5 +- data/web/lang/lang.en-gb.json | 1 + .../admin/tab-config-identity-provider.twig | 153 +++++++++++++----- 3 files changed, 116 insertions(+), 43 deletions(-) diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index c7cab469d..6ae49e7bc 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -2240,6 +2240,7 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { $required_settings = array('authsource', 'authorize_url', 'token_url', 'client_id', 'client_secret', 'redirect_url', 'userinfo_url', 'client_scopes'); break; case "ldap": + $_data['host'] = (!empty($_data['host'])) ? str_replace(" ", "", $_data['host']) : ""; $_data['port'] = (!empty($_data['port'])) ? intval($_data['port']) : 389; $_data['username_field'] = (!empty($_data['username_field'])) ? strtolower($_data['username_field']) : "mail"; $_data['attribute_field'] = (!empty($_data['attribute_field'])) ? strtolower($_data['attribute_field']) : ""; @@ -2356,7 +2357,7 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { $options[LDAP_OPT_X_TLS_REQUIRE_CERT] = LDAP_OPT_X_TLS_NEVER; } $provider = new \LdapRecord\Connection([ - 'hosts' => [$_data['host']], + 'hosts' => explode(",", $_data['host']), 'port' => $_data['port'], 'base_dn' => $_data['basedn'], 'username' => $_data['binddn'], @@ -2450,7 +2451,7 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { $options[LDAP_OPT_X_TLS_REQUIRE_CERT] = LDAP_OPT_X_TLS_NEVER; } $provider = new \LdapRecord\Connection([ - 'hosts' => [$settings['host']], + 'hosts' => explode(",", $settings['host']), 'port' => $settings['port'], 'base_dn' => $settings['basedn'], 'username' => $settings['binddn'], diff --git a/data/web/lang/lang.en-gb.json b/data/web/lang/lang.en-gb.json index 5c9c1b21b..c9a7f9dfa 100644 --- a/data/web/lang/lang.en-gb.json +++ b/data/web/lang/lang.en-gb.json @@ -225,6 +225,7 @@ "iam_description": "Configure an external Provider for Authentication
User's mailboxes will be automatically created upon their first login, provided that an attribute mapping has been set.", "iam_extra_permission": "For the following settings to work, the mailcow client in Keycloak needs a Service account and the permission to view-users.", "iam_host": "Host", + "iam_host_info": "Enter one or more LDAP hosts, separated by commas.", "iam_import_users": "Import Users", "iam_mapping": "Attribute Mapping", "iam_bindpass": "Bind Password", diff --git a/data/web/templates/admin/tab-config-identity-provider.twig b/data/web/templates/admin/tab-config-identity-provider.twig index e5103d970..e2cc56838 100644 --- a/data/web/templates/admin/tab-config-identity-provider.twig +++ b/data/web/templates/admin/tab-config-identity-provider.twig @@ -9,7 +9,9 @@

{{ lang.admin.iam_description|raw }}

- +
+ +
- +
+ +
- +
+ +
- +
+ +
- +
+ +
@@ -53,19 +63,25 @@
- +
+ +
- +
+ +
- +
+ +
Attribute @@ -121,13 +137,15 @@ {% endif %}
- +
{{ lang.admin.iam_extra_permission|raw }}
- +
+ +
@@ -140,7 +158,9 @@
- +
+ +
@@ -148,7 +168,9 @@
- +
+ +
@@ -156,14 +178,16 @@
- +
+ +
-
+
@@ -176,31 +200,41 @@
- +
+ +
- +
+ +
- +
+ +
- +
+ +
- +
+ +
@@ -209,19 +243,25 @@
- +
+ +
- +
+ +
- +
+ +
Attribute @@ -278,7 +318,7 @@
-
+
@@ -291,19 +331,26 @@
- -
- +
+ + +
+
+
- +
+ +
- +
+ +
@@ -311,7 +358,9 @@
- +
+ +
@@ -319,7 +368,9 @@
- +
+ +
@@ -327,37 +378,49 @@
- +
+ +
- +
+ +
- +
+ +
- +
+ +
- +
+ +
- +
+ +
@@ -366,7 +429,9 @@
- +
+ +
Attribute @@ -422,7 +487,9 @@ {% endif %}
- +
+ +
@@ -430,7 +497,9 @@
- +
+ +
@@ -438,14 +507,16 @@
- +
+ +
-
+
From c8c4cfd939a5c4fd91cbe0c892a9cc9614314f0e Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Sat, 30 Nov 2024 14:37:07 +0100 Subject: [PATCH 04/12] [Web] add ignore ssl option for keycloak and generic-oidc provider --- data/web/inc/functions.inc.php | 23 +++++++++++++++---- .../admin/tab-config-identity-provider.twig | 20 ++++++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index 6ae49e7bc..dfe3a15ad 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -2222,6 +2222,7 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { return false; } + $_data['ignore_ssl_error'] = isset($_data['ignore_ssl_error']) ? boolval($_data['ignore_ssl_error']) : false; switch ($_data['authsource']) { case "keycloak": $_data['server_url'] = (!empty($_data['server_url'])) ? rtrim($_data['server_url'], '/') : null; @@ -2230,14 +2231,14 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { $_data['import_users'] = isset($_data['import_users']) ? intval($_data['import_users']) : 0; $_data['sync_interval'] = (!empty($_data['sync_interval'])) ? intval($_data['sync_interval']) : 15; $_data['sync_interval'] = $_data['sync_interval'] < 1 ? 1 : $_data['sync_interval']; - $required_settings = array('authsource', 'server_url', 'realm', 'client_id', 'client_secret', 'redirect_url', 'version', 'mailpassword_flow', 'periodic_sync', 'import_users', 'sync_interval'); + $required_settings = array('authsource', 'server_url', 'realm', 'client_id', 'client_secret', 'redirect_url', 'version', 'mailpassword_flow', 'periodic_sync', 'import_users', 'sync_interval', 'ignore_ssl_error'); break; case "generic-oidc": $_data['authorize_url'] = (!empty($_data['authorize_url'])) ? $_data['authorize_url'] : null; $_data['token_url'] = (!empty($_data['token_url'])) ? $_data['token_url'] : null; $_data['userinfo_url'] = (!empty($_data['userinfo_url'])) ? $_data['userinfo_url'] : null; $_data['client_scopes'] = (!empty($_data['client_scopes'])) ? $_data['client_scopes'] : "openid profile email"; - $required_settings = array('authsource', 'authorize_url', 'token_url', 'client_id', 'client_secret', 'redirect_url', 'userinfo_url', 'client_scopes'); + $required_settings = array('authsource', 'authorize_url', 'token_url', 'client_id', 'client_secret', 'redirect_url', 'userinfo_url', 'client_scopes', 'ignore_ssl_error'); break; case "ldap": $_data['host'] = (!empty($_data['host'])) ? str_replace(" ", "", $_data['host']) : ""; @@ -2249,7 +2250,6 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { $_data['import_users'] = isset($_data['import_users']) ? intval($_data['import_users']) : 0; $_data['use_ssl'] = isset($_data['use_ssl']) ? boolval($_data['use_ssl']) : false; $_data['use_tls'] = isset($_data['use_tls']) && !$_data['use_ssl'] ? boolval($_data['use_tls']) : false; - $_data['ignore_ssl_error'] = isset($_data['ignore_ssl_error']) ? boolval($_data['ignore_ssl_error']) : false; $_data['sync_interval'] = (!empty($_data['sync_interval'])) ? intval($_data['sync_interval']) : 15; $_data['sync_interval'] = $_data['sync_interval'] < 1 ? 1 : $_data['sync_interval']; $required_settings = array('authsource', 'host', 'port', 'basedn', 'username_field', 'filter', 'attribute_field', 'binddn', 'bindpass', 'periodic_sync', 'import_users', 'sync_interval', 'use_ssl', 'use_tls', 'ignore_ssl_error'); @@ -2416,6 +2416,13 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { case "keycloak": if ($settings['server_url'] && $settings['realm'] && $settings['client_id'] && $settings['client_secret'] && $settings['redirect_url'] && $settings['version']){ + $guzzyClient = new GuzzleHttp\Client([ + 'defaults' => [ + \GuzzleHttp\RequestOptions::CONNECT_TIMEOUT => 5, + \GuzzleHttp\RequestOptions::ALLOW_REDIRECTS => true], + \GuzzleHttp\RequestOptions::VERIFY => !$settings['ignore_ssl_error'], + ] + ); $provider = new Stevenmaguire\OAuth2\Client\Provider\Keycloak([ 'authServerUrl' => $settings['server_url'], 'realm' => $settings['realm'], @@ -2427,11 +2434,19 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { // 'encryptionKeyPath' => '../key.pem' // optional // 'encryptionKey' => 'contents_of_key_or_certificate' // optional ]); + $provider->setHttpClient($guzzyClient); } break; case "generic-oidc": if ($settings['client_id'] && $settings['client_secret'] && $settings['redirect_url'] && $settings['authorize_url'] && $settings['token_url'] && $settings['userinfo_url']){ + $guzzyClient = new GuzzleHttp\Client([ + 'defaults' => [ + \GuzzleHttp\RequestOptions::CONNECT_TIMEOUT => 5, + \GuzzleHttp\RequestOptions::ALLOW_REDIRECTS => true], + \GuzzleHttp\RequestOptions::VERIFY => !$settings['ignore_ssl_error'], + ] + ); $provider = new \League\OAuth2\Client\Provider\GenericProvider([ 'clientId' => $settings['client_id'], 'clientSecret' => $settings['client_secret'], @@ -2441,6 +2456,7 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { 'urlResourceOwnerDetails' => $settings['userinfo_url'], 'scopes' => $settings['client_scopes'] ]); + $provider->setHttpClient($guzzyClient); } break; case "ldap": @@ -2468,7 +2484,6 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { } break; } - return $provider; break; case "verify-sso": diff --git a/data/web/templates/admin/tab-config-identity-provider.twig b/data/web/templates/admin/tab-config-identity-provider.twig index e2cc56838..e2cea7fa2 100644 --- a/data/web/templates/admin/tab-config-identity-provider.twig +++ b/data/web/templates/admin/tab-config-identity-provider.twig @@ -157,6 +157,16 @@

+
+
+ +
+
+
+ +
+
+
@@ -316,6 +326,16 @@
{% endif %}
+
+
+ +
+
+
+ +
+
+
From d61a08c2a9bc108cd085f8320bc1c3b09db17ede Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Sat, 30 Nov 2024 14:39:05 +0100 Subject: [PATCH 05/12] [Web] hide auth heading for external managed users --- data/web/templates/user/tab-user-auth.twig | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/data/web/templates/user/tab-user-auth.twig b/data/web/templates/user/tab-user-auth.twig index 0f30d333c..5c90bedf5 100644 --- a/data/web/templates/user/tab-user-auth.twig +++ b/data/web/templates/user/tab-user-auth.twig @@ -43,7 +43,7 @@ {{ mailboxdata.percent_in_use }}%
- +
{{ lang.user.protocols }}: @@ -64,12 +64,12 @@
{% if not skip_sogo %} {% endif %} @@ -81,12 +81,12 @@
{% if not skip_sogo %} {% endif %} @@ -99,10 +99,10 @@
+ {% if mailboxdata.authsource == "mailcow" %} {{ lang.user.authentication }}
{# Password Change #} - {% if mailboxdata.authsource == "mailcow" %}
From 45c13c687b7c294d24708cd44eb05062d0862d33 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Sun, 1 Dec 2024 16:36:16 +0100 Subject: [PATCH 06/12] [Web] update user based on template after login --- data/conf/phpfpm/crons/keycloak-sync.php | 16 +++--- data/conf/phpfpm/crons/ldap-sync.php | 6 ++- data/web/inc/functions.acl.inc.php | 10 ++-- data/web/inc/functions.auth.inc.php | 2 +- data/web/inc/functions.inc.php | 55 ++++++++++++--------- data/web/inc/functions.mailbox.inc.php | 62 ++++++++++++------------ data/web/inc/functions.ratelimit.inc.php | 4 +- 7 files changed, 85 insertions(+), 70 deletions(-) diff --git a/data/conf/phpfpm/crons/keycloak-sync.php b/data/conf/phpfpm/crons/keycloak-sync.php index 3a7b1da7b..a7e46c4fd 100644 --- a/data/conf/phpfpm/crons/keycloak-sync.php +++ b/data/conf/phpfpm/crons/keycloak-sync.php @@ -114,7 +114,7 @@ $iam_provider = identity_provider('init'); while (true) { // Get admin access token $admin_token = identity_provider("get-keycloak-admin-token"); - + // Make the API request to retrieve the users $url = "{$iam_settings['server_url']}/admin/realms/{$iam_settings['realm']}/users?first=$start&max=$max"; $ch = curl_init(); @@ -127,7 +127,7 @@ while (true) { $response = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); - + if ($code != 200){ logMsg("err", "Recieved HTTP {$code}"); session_destroy(); @@ -157,8 +157,8 @@ while (true) { logMsg("warning", "No attributes in keycloak found for user " . $user['email']); continue; } - if (!isset($user['attributes']['mailcow_template']) || - !is_array($user['attributes']['mailcow_template']) || + if (!isset($user['attributes']['mailcow_template']) || + !is_array($user['attributes']['mailcow_template']) || count($user['attributes']['mailcow_template']) == 0) { logMsg("warning", "No mailcow_template in keycloak found for user " . $user['email']); continue; @@ -195,7 +195,8 @@ while (true) { 'local_part' => explode('@', $user['email'])[0], 'name' => $user['firstName'] . " " . $user['lastName'], 'authsource' => 'keycloak', - 'template' => $mbox_template + 'template' => $mbox_template, + 'hasAccess' => true )); } else if ($row && intval($iam_settings['periodic_sync']) == 1) { // mailbox user does exist, sync attribtues... @@ -203,7 +204,8 @@ while (true) { mailbox('edit', 'mailbox_from_template', array( 'username' => $user['email'], 'name' => $user['firstName'] . " " . $user['lastName'], - 'template' => $mbox_template + 'template' => $mbox_template, + 'hasAccess' => true )); } else { // skip mailbox user @@ -212,7 +214,7 @@ while (true) { sleep(0.025); } - + // Update the pagination variables for the next batch $start += $max; sleep(1); diff --git a/data/conf/phpfpm/crons/ldap-sync.php b/data/conf/phpfpm/crons/ldap-sync.php index 75eddc7a1..da5533b39 100644 --- a/data/conf/phpfpm/crons/ldap-sync.php +++ b/data/conf/phpfpm/crons/ldap-sync.php @@ -159,7 +159,8 @@ foreach ($response as $user) { 'local_part' => explode('@', $user[$iam_settings['username_field']][0])[0], 'name' => $user['displayname'][0], 'authsource' => 'ldap', - 'template' => $mbox_template + 'template' => $mbox_template, + 'hasAccess' => true )); } else if ($row && intval($iam_settings['periodic_sync']) == 1) { // mailbox user does exist, sync attribtues... @@ -167,7 +168,8 @@ foreach ($response as $user) { mailbox('edit', 'mailbox_from_template', array( 'username' => $user[$iam_settings['username_field']][0], 'name' => $user['displayname'][0], - 'template' => $mbox_template + 'template' => $mbox_template, + 'hasAccess' => true )); } else { // skip mailbox user diff --git a/data/web/inc/functions.acl.inc.php b/data/web/inc/functions.acl.inc.php index ffce9f44c..ffc7408fe 100644 --- a/data/web/inc/functions.acl.inc.php +++ b/data/web/inc/functions.acl.inc.php @@ -1,5 +1,5 @@ 'danger', 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), @@ -34,7 +34,7 @@ function acl($_action, $_scope = null, $_data = null) { } // Read all available acl options by calling acl(get) // Set all available acl options we cannot find in the post data to 0, else 1 - $is_now = acl('get', 'user', $username); + $is_now = acl('get', 'user', $username, $_extra); if (!empty($is_now)) { foreach ($is_now as $acl_now_name => $acl_now_val) { $set_acls[$acl_now_name] = (isset($acl_post[$acl_now_name])) ? 1 : 0; @@ -130,7 +130,7 @@ function acl($_action, $_scope = null, $_data = null) { case 'get': switch ($_scope) { case 'user': - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + if (!$_extra['hasAccess'] && !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { return false; } $stmt = $pdo->prepare("SELECT * FROM `user_acl` WHERE `username` = :username"); diff --git a/data/web/inc/functions.auth.inc.php b/data/web/inc/functions.auth.inc.php index 74cd6d956..83c0a32eb 100644 --- a/data/web/inc/functions.auth.inc.php +++ b/data/web/inc/functions.auth.inc.php @@ -467,7 +467,7 @@ function keycloak_mbox_login_rest($user, $pass, $extra = null){ $create_res = mailbox('add', 'mailbox_from_template', array( 'domain' => explode('@', $user)[1], 'local_part' => explode('@', $user)[0], - 'name' => $user_res['firstName'] . " " . $user_res['lastName'], + 'name' => $user_res['name'], 'authsource' => 'keycloak', 'template' => $iam_settings['templates'][$mapper_key] )); diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index dfe3a15ad..943d53e97 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -2512,27 +2512,6 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { // 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 - set_user_loggedin_session($info['email']); - $_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 $user_template = $info['mailcow_template']; @@ -2558,13 +2537,43 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { 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 + // update user + mailbox('edit', 'mailbox_from_template', array( + 'username' => $info['email'], + 'name' => $info['name'], + 'template' => $iam_settings['templates'][$mapper_key], + 'hasAccess' => true + )); + set_user_loggedin_session($info['email']); + $_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; + } + // create mailbox $create_res = mailbox('add', 'mailbox_from_template', array( 'domain' => explode('@', $info['email'])[1], 'local_part' => explode('@', $info['email'])[0], - 'name' => $info['firstName'] . " " . $info['lastName'], + 'name' => $info['name'], 'authsource' => $iam_settings['authsource'], - 'template' => $iam_settings['templates'][$mapper_key] + 'template' => $iam_settings['templates'][$mapper_key], + 'hasAccess' => true )); if (!$create_res){ clear_session(); diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index 1b5b577f8..5c8d50d41 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -1045,7 +1045,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $password2 = ''; $password_hashed = ''; } - if (!$_extra['iam_create_login'] && ((!isset($_SESSION['acl']['unlimited_quota']) || $_SESSION['acl']['unlimited_quota'] != "1") && $quota_m === 0)) { + if (!$_extra['hasAccess'] && ((!isset($_SESSION['acl']['unlimited_quota']) || $_SESSION['acl']['unlimited_quota'] != "1") && $quota_m === 0)) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -1101,7 +1101,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain) && !$_extra['iam_create_login']) { + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain) && !$_extra['hasAccess']) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -1364,6 +1364,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $attribute_hash = sha1(json_encode($mbox_template_data["attributes"])); $mbox_template_data = json_decode($mbox_template_data["attributes"], true); $mbox_template_data['domain'] = $_data['domain']; + $mbox_template_data['name'] = $_data['name']; $mbox_template_data['local_part'] = $_data['local_part']; $mbox_template_data['authsource'] = $_data['authsource']; $mbox_template_data['attribute_hash'] = $attribute_hash; @@ -1381,7 +1382,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } } - return mailbox('add', 'mailbox', $mailbox_attributes, array('iam_create_login' => true)); + return mailbox('add', 'mailbox', $mailbox_attributes, array('hasAccess' => $_data['hasAccess'])); break; case 'resource': $domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46); @@ -1749,7 +1750,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { else { $usernames = $_data['username']; } - if (!isset($_SESSION['acl']['tls_policy']) || $_SESSION['acl']['tls_policy'] != "1" ) { + if (!$_extra['hasAccess'] && (!isset($_SESSION['acl']['tls_policy']) || $_SESSION['acl']['tls_policy'] != "1")) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -1758,7 +1759,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { return false; } foreach ($usernames as $username) { - if (!filter_var($username, FILTER_VALIDATE_EMAIL) || !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + if (!$_extra['hasAccess'] && (!filter_var($username, FILTER_VALIDATE_EMAIL) || !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username))) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -1766,7 +1767,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } - $is_now = mailbox('get', 'tls_policy', $username); + $is_now = mailbox('get', 'tls_policy', $username, $_extra); if (!empty($is_now)) { $tls_enforce_in = (isset($_data['tls_enforce_in'])) ? intval($_data['tls_enforce_in']) : $is_now['tls_enforce_in']; $tls_enforce_out = (isset($_data['tls_enforce_out'])) ? intval($_data['tls_enforce_out']) : $is_now['tls_enforce_out']; @@ -1803,7 +1804,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { else { $usernames = $_data['username']; } - if (!isset($_SESSION['acl']['quarantine_notification']) || $_SESSION['acl']['quarantine_notification'] != "1" ) { + if (!$_extra['hasAccess'] && (!isset($_SESSION['acl']['quarantine_notification']) || $_SESSION['acl']['quarantine_notification'] != "1")) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -1812,7 +1813,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { return false; } foreach ($usernames as $username) { - if (!filter_var($username, FILTER_VALIDATE_EMAIL) || !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + if (!$_extra['hasAccess'] && (!filter_var($username, FILTER_VALIDATE_EMAIL) || !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username))) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -1820,7 +1821,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } - $is_now = mailbox('get', 'quarantine_notification', $username); + $is_now = mailbox('get', 'quarantine_notification', $username, $_extra); if (!empty($is_now)) { $quarantine_notification = (isset($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : $is_now['quarantine_notification']; } @@ -1862,7 +1863,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { else { $usernames = $_data['username']; } - if (!isset($_SESSION['acl']['quarantine_category']) || $_SESSION['acl']['quarantine_category'] != "1" ) { + if (!$_extra['hasAccess'] && (!isset($_SESSION['acl']['quarantine_category']) || $_SESSION['acl']['quarantine_category'] != "1")) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -1871,7 +1872,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { return false; } foreach ($usernames as $username) { - if (!filter_var($username, FILTER_VALIDATE_EMAIL) || !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + if (!$_extra['hasAccess'] && (!filter_var($username, FILTER_VALIDATE_EMAIL) || !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username))) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -1879,7 +1880,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } - $is_now = mailbox('get', 'quarantine_category', $username); + $is_now = mailbox('get', 'quarantine_category', $username, $_extra); if (!empty($is_now)) { $quarantine_category = (isset($_data['quarantine_category'])) ? $_data['quarantine_category'] : $is_now['quarantine_category']; } @@ -2923,7 +2924,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } - $is_now = mailbox('get', 'mailbox_details', $username); + $is_now = mailbox('get', 'mailbox_details', $username, $_extra); if (isset($_data['protocol_access'])) { $_data['protocol_access'] = (array)$_data['protocol_access']; $_data['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : 0; @@ -2963,7 +2964,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { continue; } // if already 0 == ok - if ((!isset($_SESSION['acl']['unlimited_quota']) || $_SESSION['acl']['unlimited_quota'] != "1") && ($quota_m == 0 && $is_now['quota'] != 0)) { + if (!$_extra['hasAccess'] && (!isset($_SESSION['acl']['unlimited_quota']) || $_SESSION['acl']['unlimited_quota'] != "1") && ($quota_m == 0 && $is_now['quota'] != 0)) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -2971,7 +2972,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + if (!$_extra['hasAccess'] && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -2998,7 +2999,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } $extra_acls = array(); if (isset($_data['extended_sender_acl'])) { - if (!isset($_SESSION['acl']['extend_sender_acl']) || $_SESSION['acl']['extend_sender_acl'] != "1" ) { + if (!$_extra['hasAccess'] && (!isset($_SESSION['acl']['extend_sender_acl']) || $_SESSION['acl']['extend_sender_acl'] != "1")) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -3493,7 +3494,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } $attribute_hash = sha1(json_encode($mbox_template_data["attributes"])); - $is_now = mailbox('get', 'mailbox_details', $_data['username']); + $is_now = mailbox('get', 'mailbox_details', $_data['username'], array('hasAccess' => $_data['hasAccess'])); $name = ltrim(rtrim($_data['name'], '>'), '<'); if ($is_now['attributes']['attribute_hash'] == $attribute_hash && $is_now['name'] == $name) return true; @@ -3529,19 +3530,20 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } $mailbox_attributes['quota'] = intval($mailbox_attributes['quota'] / 1048576); - $result = mailbox('edit', 'mailbox', $mailbox_attributes); + $result = mailbox('edit', 'mailbox', $mailbox_attributes, array('hasAccess' => $_data['hasAccess'])); if ($result === false) return $result; - $result = mailbox('edit', 'tls_policy', $tls_attributes); + $result = mailbox('edit', 'tls_policy', $tls_attributes, array('hasAccess' => $_data['hasAccess'])); if ($result === false) return $result; - $result = mailbox('edit', 'quarantine_notification', $quarantine_attributes); + $result = mailbox('edit', 'quarantine_notification', $quarantine_attributes, array('hasAccess' => $_data['hasAccess'])); if ($result === false) return $result; - $result = mailbox('edit', 'quarantine_category', $quarantine_attributes); + $result = mailbox('edit', 'quarantine_category', $quarantine_attributes, array('hasAccess' => $_data['hasAccess'])); if ($result === false) return $result; - $result = ratelimit('edit', 'mailbox', $ratelimit_attributes); + $result = ratelimit('edit', 'mailbox', $ratelimit_attributes, array('hasAccess' => $_data['hasAccess'])); if ($result === false) return $result; - $result = acl('edit', 'user', $acl_attributes); + $result = acl('edit', 'user', $acl_attributes, array('hasAccess' => $_data['hasAccess'])); if ($result === false) return $result; + $_SESSION['return'] = array(); return true; break; case 'mailbox_templates': @@ -4077,7 +4079,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { case 'tls_policy': $attrs = array(); if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + if (!$_extra['hasAccess'] && !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { return false; } } @@ -4096,7 +4098,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { case 'quarantine_notification': $attrs = array(); if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + if (!$_extra['hasAccess'] && !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { return false; } } @@ -4112,7 +4114,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { case 'quarantine_category': $attrs = array(); if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + if (!$_extra['hasAccess'] && (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data))) { return false; } } @@ -4793,7 +4795,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } break; case 'mailbox_details': - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + if (!$_extra['hasAccess'] && !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { return false; } $mailboxdata = array(); @@ -4891,7 +4893,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { else if ($SaslLogs['service'] == 'pop3') { $last_pop3_login = strtotime($SaslLogs['datetime']); } - else if ($SaslLogs['service'] == 'SSO') { + else if ($SaslLogs['service'] == 'SSO') { $last_sso_login = strtotime($SaslLogs['datetime']); } } @@ -4904,7 +4906,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { if (!isset($last_pop3_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) { $last_pop3_login = 0; } - if (!isset($last_sso_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) { + if (!isset($last_sso_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) { $last_sso_login = 0; } $mailboxdata['last_imap_login'] = $last_imap_login; @@ -4956,7 +4958,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { return $mailboxdata; break; case 'mailbox_templates': - if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin" && !$_extra['iam_create_login']) { + if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin" && !$_extra['hasAccess']) { return false; } $_data = (isset($_data)) ? intval($_data) : null; diff --git a/data/web/inc/functions.ratelimit.inc.php b/data/web/inc/functions.ratelimit.inc.php index bc05cd362..c59accb54 100644 --- a/data/web/inc/functions.ratelimit.inc.php +++ b/data/web/inc/functions.ratelimit.inc.php @@ -4,7 +4,7 @@ function ratelimit($_action, $_scope, $_data = null, $_extra = null) { $_data_log = $_data; switch ($_action) { case 'edit': - if ((!isset($_SESSION['acl']['ratelimit']) || $_SESSION['acl']['ratelimit'] != "1") && !$_extra['iam_create_login']) { + if ((!isset($_SESSION['acl']['ratelimit']) || $_SESSION['acl']['ratelimit'] != "1") && !$_extra['hasAccess']) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -93,7 +93,7 @@ function ratelimit($_action, $_scope, $_data = null, $_extra = null) { continue; } if ((!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object) - || ($_SESSION['mailcow_cc_role'] != 'admin' && $_SESSION['mailcow_cc_role'] != 'domainadmin')) && !$_extra['iam_create_login']) { + || ($_SESSION['mailcow_cc_role'] != 'admin' && $_SESSION['mailcow_cc_role'] != 'domainadmin')) && !$_extra['hasAccess']) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), From ccc8595665ea28ba92c3e6d66bd40b2d65890eca Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Sun, 1 Dec 2024 16:51:56 +0100 Subject: [PATCH 07/12] [SOGo] redirect to /user if unauthenticated --- data/conf/sogo/custom-sogo.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/conf/sogo/custom-sogo.js b/data/conf/sogo/custom-sogo.js index e794372f0..1070efa40 100644 --- a/data/conf/sogo/custom-sogo.js +++ b/data/conf/sogo/custom-sogo.js @@ -2,7 +2,7 @@ document.addEventListener('DOMContentLoaded', function () { var loginForm = document.forms.namedItem("loginForm"); if (loginForm) { - window.location.href = '/'; + window.location.href = '/user'; } angularReady = false; @@ -34,7 +34,7 @@ document.addEventListener('DOMContentLoaded', function () { function mcElementsExists() { if (document.getElementById("mc_backlink")) return true; - else + else return false; } function addMCElements() { From 6fa1c9f63df04b95a3e634f6b0fe417278c53ff3 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Mon, 2 Dec 2024 10:24:15 +0100 Subject: [PATCH 08/12] [Web] protect /get/identity-provider --- data/web/json_api.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/data/web/json_api.php b/data/web/json_api.php index 4e9d2a0ed..36b39b0e6 100644 --- a/data/web/json_api.php +++ b/data/web/json_api.php @@ -1707,8 +1707,13 @@ if (isset($_GET['query'])) { if ($score) $score = array("score" => preg_replace("/\s+/", "", $score)); process_get_return($score); - case "identity_provider": - process_get_return($iam_settings); + break; + case "identity-provider": + if($_SESSION['mailcow_cc_role'] === 'admin') { + process_get_return($iam_settings); + } else { + process_get_return(null); + } break; break; // return no route found if no case is matched @@ -2086,7 +2091,6 @@ if (isset($_GET['query'])) { break; case "cors": process_edit_return(cors('edit', $attr)); - case "identity_provider": case "identity-provider": process_edit_return(identity_provider('edit', $attr)); break; From f36184df64eb6481c23a9fc11fd06569c62d118c Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Mon, 2 Dec 2024 10:35:45 +0100 Subject: [PATCH 09/12] [Web] update mailbox on idp login --- data/web/inc/functions.auth.inc.php | 50 ++++++++++++++++-------- data/web/inc/functions.inc.php | 59 ++++++++++++++--------------- 2 files changed, 63 insertions(+), 46 deletions(-) diff --git a/data/web/inc/functions.auth.inc.php b/data/web/inc/functions.auth.inc.php index 83c0a32eb..9bea24995 100644 --- a/data/web/inc/functions.auth.inc.php +++ b/data/web/inc/functions.auth.inc.php @@ -449,18 +449,26 @@ function keycloak_mbox_login_rest($user, $pass, $extra = null){ return false; } - // get mapped template, if not set return false - // also return false if no mappers were defined + // get mapped template $user_template = $user_res['attributes']['mailcow_template'][0]; - if ($create && (empty($iam_settings['mappers']) || !$user_template)){ - return false; - } else if (!$create) { - // login success - dont create mailbox + $mapper_key = array_search($user_template, $iam_settings['mappers']); + + if (!$create) { + // login success + if ($mapper_key !== false) { + // update user + mailbox('edit', 'mailbox_from_template', array( + 'username' => $user, + 'name' => $user_res['name'], + 'template' => $iam_settings['templates'][$mapper_key], + 'hasAccess' => true + )); + } return 'user'; } // check if matching attribute exist - $mapper_key = array_search($user_template, $iam_settings['mappers']); + if (empty($iam_settings['mappers']) || !$user_template) return false; if ($mapper_key === false) return false; // create mailbox @@ -469,7 +477,8 @@ function keycloak_mbox_login_rest($user, $pass, $extra = null){ 'local_part' => explode('@', $user)[0], 'name' => $user_res['name'], 'authsource' => 'keycloak', - 'template' => $iam_settings['templates'][$mapper_key] + 'template' => $iam_settings['templates'][$mapper_key], + 'hasAccess' => true )); if (!$create_res) return false; @@ -536,18 +545,26 @@ function ldap_mbox_login($user, $pass, $extra = null){ return false; } - // get mapped template, if not set return false - // also return false if no mappers were defined + // get mapped template $user_template = $user_res[$iam_settings['attribute_field']][0]; - if ($create && (empty($iam_settings['mappers']) || !$user_template)){ - return false; - } else if (!$create) { - // login success - dont create mailbox + $mapper_key = array_search($user_template, $iam_settings['mappers']); + + if (!$create) { + // login success + if ($mapper_key !== false) { + // update user + mailbox('edit', 'mailbox_from_template', array( + 'username' => $user, + 'name' => $user_res['displayname'][0], + 'template' => $iam_settings['templates'][$mapper_key], + 'hasAccess' => true + )); + } return 'user'; } // check if matching attribute exist - $mapper_key = array_search($user_template, $iam_settings['mappers']); + if (empty($iam_settings['mappers']) || !$user_template) return false; if ($mapper_key === false) return false; // create mailbox @@ -556,7 +573,8 @@ function ldap_mbox_login($user, $pass, $extra = null){ 'local_part' => explode('@', $user)[0], 'name' => $user_res['displayname'][0], 'authsource' => 'ldap', - 'template' => $iam_settings['templates'][$mapper_key] + 'template' => $iam_settings['templates'][$mapper_key], + 'hasAccess' => true )); if (!$create_res) return false; diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index 943d53e97..dece39eef 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -2512,31 +2512,9 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { // check if email address is given if (empty($info['email'])) return false; - // get mapped template, if not set return false - // also return false if no mappers were defined + // get mapped template $user_template = $info['mailcow_template']; - if (empty($iam_settings['mappers']) || empty($user_template)){ - clear_session(); - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $info['email']), - 'msg' => array('login_failed', 'empty attribute mapping or missing template attribute') - ); - return false; - } - - // check if matching attribute exist $mapper_key = array_search($user_template, $iam_settings['mappers']); - if ($mapper_key === false) { - clear_session(); - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $info['email']), - 'msg' => array('login_failed', 'specified template not found') - ); - return false; - } - // token valid, get mailbox $stmt = $pdo->prepare("SELECT * FROM `mailbox` @@ -2550,13 +2528,15 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { $row = $stmt->fetch(PDO::FETCH_ASSOC); if ($row){ // success - // update user - mailbox('edit', 'mailbox_from_template', array( - 'username' => $info['email'], - 'name' => $info['name'], - 'template' => $iam_settings['templates'][$mapper_key], - 'hasAccess' => true - )); + if ($mapper_key !== false) { + // update user + mailbox('edit', 'mailbox_from_template', array( + 'username' => $info['email'], + 'name' => $info['name'], + 'template' => $iam_settings['templates'][$mapper_key], + 'hasAccess' => true + )); + } set_user_loggedin_session($info['email']); $_SESSION['return'][] = array( 'type' => 'success', @@ -2566,6 +2546,25 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { return true; } + if (empty($iam_settings['mappers']) || empty($user_template)){ + clear_session(); + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $info['email']), + 'msg' => array('login_failed', 'empty attribute mapping or missing template attribute') + ); + return false; + } + if ($mapper_key === false) { + clear_session(); + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $info['email']), + 'msg' => array('login_failed', 'specified template not found') + ); + return false; + } + // create mailbox $create_res = mailbox('add', 'mailbox_from_template', array( 'domain' => explode('@', $info['email'])[1], From 83e53eb524c816b9b99209bb1a5000ea8821b9e1 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Mon, 2 Dec 2024 11:55:17 +0100 Subject: [PATCH 10/12] [Web] fix incomplete session on broken logins --- data/web/inc/functions.inc.php | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index dece39eef..711bd5787 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -2498,8 +2498,8 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { try { $token = $iam_provider->getAccessToken('authorization_code', ['code' => $_GET['code']]); - $_SESSION['iam_token'] = $token->getToken(); - $_SESSION['iam_refresh_token'] = $token->getRefreshToken(); + $plain_token = $token->getToken(); + $plain_refreshtoken = $token->getRefreshToken(); $info = $iam_provider->getResourceOwner($token)->toArray(); } catch (Throwable $e) { $_SESSION['return'][] = array( @@ -2538,6 +2538,8 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { )); } set_user_loggedin_session($info['email']); + $_SESSION['iam_token'] = $plain_token; + $_SESSION['iam_refresh_token'] = $plain_refreshtoken; $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']), @@ -2585,6 +2587,8 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { } set_user_loggedin_session($info['email']); + $_SESSION['iam_token'] = $plain_token; + $_SESSION['iam_refresh_token'] = $plain_refreshtoken; $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']), @@ -2595,8 +2599,8 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { case "refresh-token": try { $token = $iam_provider->getAccessToken('refresh_token', ['refresh_token' => $_SESSION['iam_refresh_token']]); - $_SESSION['iam_token'] = $token->getToken(); - $_SESSION['iam_refresh_token'] = $token->getRefreshToken(); + $plain_token = $token->getToken(); + $plain_refreshtoken = $token->getRefreshToken(); $info = $iam_provider->getResourceOwner($token)->toArray(); } catch (Throwable $e) { clear_session(); @@ -2618,8 +2622,9 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { return false; } - $_SESSION['mailcow_cc_username'] = $info['email']; - $_SESSION['mailcow_cc_role'] = "user"; + set_user_loggedin_session($info['email']); + $_SESSION['iam_token'] = $plain_token; + $_SESSION['iam_refresh_token'] = $plain_refreshtoken; return true; break; case "get-redirect": From 896a9638d6b55c15081a8ca0ee172797988640e5 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Mon, 2 Dec 2024 14:16:43 +0100 Subject: [PATCH 11/12] Fix mailcowauth --- data/conf/dovecot/auth/mailcowauth.php | 23 +++++++++++++++++++++-- data/web/inc/functions.ratelimit.inc.php | 8 ++++---- docker-compose.yml | 1 + 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/data/conf/dovecot/auth/mailcowauth.php b/data/conf/dovecot/auth/mailcowauth.php index 6d7660577..4f10ed535 100644 --- a/data/conf/dovecot/auth/mailcowauth.php +++ b/data/conf/dovecot/auth/mailcowauth.php @@ -22,6 +22,24 @@ if (file_exists('../../../web/inc/vars.local.inc.php')) { } require_once '../../../web/inc/lib/vendor/autoload.php'; + +// Init Redis +$redis = new Redis(); +try { + if (!empty(getenv('REDIS_SLAVEOF_IP'))) { + $redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT')); + } + else { + $redis->connect('redis-mailcow', 6379); + } +} +catch (Exception $e) { + error_log("MAILCOWAUTH: " . $e . PHP_EOL); + http_response_code(500); // Internal Server Error + echo json_encode($return); + exit; +} + // Init database $dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name; $opt = [ @@ -44,6 +62,8 @@ require_once 'functions.inc.php'; require_once 'functions.auth.inc.php'; require_once 'sessions.inc.php'; require_once 'functions.mailbox.inc.php'; +require_once 'functions.ratelimit.inc.php'; +require_once 'functions.acl.inc.php'; $isSOGoRequest = $post['real_rip'] == getenv('IPV4_NETWORK') . '.248'; @@ -69,8 +89,7 @@ if ($result === false){ // Init Identity Provider $iam_provider = identity_provider('init'); $iam_settings = identity_provider('get'); - error_log('MAILCOWAUTH Try: User auth for user ' . $post['username']); - $result = user_login($post['username'], $post['password'], $protocol, array('is_internal' => true)); + $result = user_login($post['username'], $post['password'], array('is_internal' => true)); if ($result) error_log('MAILCOWAUTH: User auth for user ' . $post['username']); } diff --git a/data/web/inc/functions.ratelimit.inc.php b/data/web/inc/functions.ratelimit.inc.php index c59accb54..5779ad77c 100644 --- a/data/web/inc/functions.ratelimit.inc.php +++ b/data/web/inc/functions.ratelimit.inc.php @@ -92,8 +92,8 @@ function ratelimit($_action, $_scope, $_data = null, $_extra = null) { ); continue; } - if ((!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object) - || ($_SESSION['mailcow_cc_role'] != 'admin' && $_SESSION['mailcow_cc_role'] != 'domainadmin')) && !$_extra['hasAccess']) { + if (((!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object) + || ($_SESSION['mailcow_cc_role'] != 'admin' && $_SESSION['mailcow_cc_role'] != 'domainadmin'))) && !$_extra['hasAccess']) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), @@ -139,7 +139,7 @@ function ratelimit($_action, $_scope, $_data = null, $_extra = null) { case 'get': switch ($_scope) { case 'domain': - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data) && !$_extra['hasAccess']) { return false; } try { @@ -164,7 +164,7 @@ function ratelimit($_action, $_scope, $_data = null, $_extra = null) { return false; break; case 'mailbox': - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data) + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data && !$_extra['hasAccess']) || ($_SESSION['mailcow_cc_role'] != 'admin' && $_SESSION['mailcow_cc_role'] != 'domainadmin')) { return false; } diff --git a/docker-compose.yml b/docker-compose.yml index a55f0c02c..85395b645 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -127,6 +127,7 @@ services: - ./data/web/inc/sessions.inc.php:/mailcowauth/sessions.inc.php:z - ./data/web/inc/functions.mailbox.inc.php:/mailcowauth/functions.mailbox.inc.php:z - ./data/web/inc/functions.ratelimit.inc.php:/mailcowauth/functions.ratelimit.inc.php:z + - ./data/web/inc/functions.acl.inc.php:/mailcowauth/functions.acl.inc.php:z - rspamd-vol-1:/var/lib/rspamd - mysql-socket-vol-1:/var/run/mysqld/ - ./data/conf/sogo/:/etc/sogo/:z From 1d6513ffbabdde974bca853d15f4ecd889111c09 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Wed, 4 Dec 2024 14:49:31 +0100 Subject: [PATCH 12/12] [Web] fix idp login alerts and updates --- data/web/inc/functions.auth.inc.php | 10 ++++++++-- data/web/inc/functions.mailbox.inc.php | 22 ++++++++++++++-------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/data/web/inc/functions.auth.inc.php b/data/web/inc/functions.auth.inc.php index 9bea24995..f9d82f1ad 100644 --- a/data/web/inc/functions.auth.inc.php +++ b/data/web/inc/functions.auth.inc.php @@ -480,7 +480,10 @@ function keycloak_mbox_login_rest($user, $pass, $extra = null){ 'template' => $iam_settings['templates'][$mapper_key], 'hasAccess' => true )); - if (!$create_res) return false; + if (!$create_res){ + clear_session(); + return false; + } return 'user'; } @@ -576,7 +579,10 @@ function ldap_mbox_login($user, $pass, $extra = null){ 'template' => $iam_settings['templates'][$mapper_key], 'hasAccess' => true )); - if (!$create_res) return false; + if (!$create_res){ + clear_session(); + return false; + } return 'user'; } diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index 5c8d50d41..cd62d5d7c 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -1075,6 +1075,9 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $quarantine_category = (isset($_data['quarantine_category'])) ? strval($_data['quarantine_category']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']); $quota_b = ($quota_m * 1048576); $attribute_hash = (!empty($_data['attribute_hash'])) ? $_data['attribute_hash'] : ''; + if (in_array($authsource, array('keycloak', 'generic-oidc', 'ldap'))){ + $force_pw_update = 0; + } $mailbox_attrs = json_encode( array( 'force_pw_update' => strval($force_pw_update), @@ -2935,12 +2938,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { if (!empty($is_now)) { $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; (int)$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($is_now['attributes']['force_pw_update']); - (int)$sogo_access = (isset($_data['sogo_access']) && isset($_SESSION['acl']['sogo_access']) && $_SESSION['acl']['sogo_access'] == "1") ? intval($_data['sogo_access']) : intval($is_now['attributes']['sogo_access']); - (int)$imap_access = (isset($_data['imap_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['imap_access']) : intval($is_now['attributes']['imap_access']); - (int)$pop3_access = (isset($_data['pop3_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['pop3_access']) : intval($is_now['attributes']['pop3_access']); - (int)$smtp_access = (isset($_data['smtp_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['smtp_access']) : intval($is_now['attributes']['smtp_access']); - (int)$sieve_access = (isset($_data['sieve_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['sieve_access']) : intval($is_now['attributes']['sieve_access']); - (int)$relayhost = (isset($_data['relayhost']) && isset($_SESSION['acl']['mailbox_relayhost']) && $_SESSION['acl']['mailbox_relayhost'] == "1") ? intval($_data['relayhost']) : intval($is_now['attributes']['relayhost']); + (int)$sogo_access = ((isset($_data['sogo_access']) && isset($_SESSION['acl']['sogo_access']) && $_SESSION['acl']['sogo_access'] == "1") || $_extra['hasAccess']) ? intval($_data['sogo_access']) : intval($is_now['attributes']['sogo_access']); + (int)$imap_access = ((isset($_data['imap_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") || $_extra['hasAccess']) ? intval($_data['imap_access']) : intval($is_now['attributes']['imap_access']); + (int)$pop3_access = ((isset($_data['pop3_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") || $_extra['hasAccess']) ? intval($_data['pop3_access']) : intval($is_now['attributes']['pop3_access']); + (int)$smtp_access = ((isset($_data['smtp_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") || $_extra['hasAccess']) ? intval($_data['smtp_access']) : intval($is_now['attributes']['smtp_access']); + (int)$sieve_access = ((isset($_data['sieve_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") || $_extra['hasAccess']) ? intval($_data['sieve_access']) : intval($is_now['attributes']['sieve_access']); + (int)$relayhost = ((isset($_data['relayhost']) && isset($_SESSION['acl']['mailbox_relayhost']) && $_SESSION['acl']['mailbox_relayhost'] == "1") || $_extra['hasAccess']) ? intval($_data['relayhost']) : intval($is_now['attributes']['relayhost']); (int)$quota_m = (isset_has_content($_data['quota'])) ? intval($_data['quota']) : ($is_now['quota'] / 1048576); $name = (!empty($_data['name'])) ? ltrim(rtrim($_data['name'], '>'), '<') : $is_now['name']; $domain = $is_now['domain']; @@ -2953,6 +2956,9 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { if (in_array($_data['authsource'], array('mailcow', 'keycloak', 'generic-oidc', 'ldap'))){ $authsource = $_data['authsource']; } + if (in_array($authsource, array('keycloak', 'generic-oidc', 'ldap'))){ + $force_pw_update = 0; + } $pw_recovery_email = (isset($_data['pw_recovery_email']) && $authsource == 'mailcow') ? $_data['pw_recovery_email'] : $is_now['attributes']['recovery_email']; } else { @@ -2980,7 +2986,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } - $DomainData = mailbox('get', 'domain_details', $domain); + $DomainData = mailbox('get', 'domain_details', $domain, $_extra); if ($quota_m > ($is_now['max_new_quota'] / 1048576)) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -4629,7 +4635,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { case 'domain_details': $domaindata = array(); $_data = idn_to_ascii(strtolower(trim($_data)), 0, INTL_IDNA_VARIANT_UTS46); - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + if (!$_extra['hasAccess'] && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { return false; } $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain");