Compare commits

...

3 Commits

7 changed files with 149 additions and 32 deletions

View File

@ -1,12 +1,13 @@
# Whitelist generated by Postwhite v3.4 on Mon Sep 1 00:23:07 UTC 2025
# Whitelist generated by Postwhite v3.4 on Wed Oct 1 00:21:33 UTC 2025
# https://github.com/stevejenkins/postwhite/
# 2165 total rules
# 2216 total rules
2a00:1450:4000::/36 permit
2a01:111:f400::/48 permit
2a01:111:f403::/49 permit
2a01:111:f403:8000::/50 permit
2a01:111:f403:2800::/53 permit
2a01:111:f403:8000::/51 permit
2a01:111:f403::/49 permit
2a01:111:f403:c000::/51 permit
2a01:111:f403:d000::/53 permit
2a01:111:f403:f000::/52 permit
2a01:238:20a:202:5370::1 permit
2a01:238:20a:202:5372::1 permit
@ -55,7 +56,8 @@
8.40.222.0/23 permit
8.40.222.250/31 permit
12.130.86.238 permit
13.107.246.40 permit
13.107.213.41 permit
13.107.246.41 permit
13.110.208.0/21 permit
13.110.209.0/24 permit
13.110.216.0/22 permit
@ -174,6 +176,7 @@
35.161.32.253 permit
35.162.73.231 permit
35.167.93.243 permit
35.174.145.124 permit
35.176.132.251 permit
35.205.92.9 permit
35.228.216.85 permit
@ -183,7 +186,6 @@
37.218.249.47 permit
37.218.251.62 permit
39.156.163.64/29 permit
40.90.65.81 permit
40.92.0.0/15 permit
40.92.0.0/16 permit
40.107.0.0/16 permit
@ -271,9 +273,6 @@
50.56.130.221 permit
50.56.130.222 permit
50.112.246.219 permit
51.77.79.158 permit
51.83.17.38 permit
51.89.119.103 permit
52.1.14.157 permit
52.5.230.59 permit
52.6.74.205 permit
@ -324,8 +323,6 @@
52.234.172.96/28 permit
52.235.253.128 permit
52.236.28.240/28 permit
54.36.149.183 permit
54.38.221.122 permit
54.90.148.255 permit
54.165.19.38 permit
54.174.52.0/24 permit
@ -686,6 +683,8 @@
82.165.159.45 permit
82.165.159.130 permit
82.165.159.131 permit
85.9.206.169 permit
85.9.210.45 permit
85.158.136.0/21 permit
85.215.255.39 permit
85.215.255.40 permit
@ -1234,16 +1233,14 @@
99.83.190.102 permit
103.9.96.0/22 permit
103.28.42.0/24 permit
103.122.78.238 permit
103.84.217.238 permit
103.89.75.238 permit
103.151.192.0/23 permit
103.168.172.128/27 permit
103.237.104.0/22 permit
104.43.243.237 permit
104.44.112.128/25 permit
104.47.0.0/17 permit
104.47.20.0/23 permit
104.47.75.0/24 permit
104.47.108.0/23 permit
104.130.96.0/28 permit
104.130.122.0/23 permit
106.10.144.64/27 permit
@ -1378,7 +1375,6 @@
108.174.6.215 permit
108.175.18.45 permit
108.175.30.45 permit
108.177.96.0/20 permit
108.179.144.0/20 permit
109.224.244.0/24 permit
109.237.142.0/24 permit
@ -1544,6 +1540,7 @@
148.105.0.0/16 permit
148.105.8.0/21 permit
149.72.0.0/16 permit
149.72.234.184 permit
149.72.248.236 permit
149.97.173.180 permit
150.230.98.160 permit
@ -1599,6 +1596,7 @@
159.183.0.0/16 permit
159.183.68.71 permit
159.183.79.38 permit
159.183.129.172 permit
160.1.62.192 permit
161.38.192.0/20 permit
161.38.204.0/22 permit
@ -1616,6 +1614,7 @@
163.114.134.16 permit
163.114.135.16 permit
163.116.128.0/17 permit
163.192.116.87 permit
164.152.23.32 permit
164.152.25.241 permit
164.177.132.168/30 permit
@ -1655,6 +1654,7 @@
169.148.131.0/24 permit
169.148.138.0/24 permit
169.148.142.10 permit
169.148.142.33 permit
169.148.144.0/25 permit
169.148.144.10 permit
169.148.146.0/23 permit
@ -1666,11 +1666,7 @@
170.10.132.56/29 permit
170.10.132.64/29 permit
170.10.133.0/24 permit
172.217.0.0/20 permit
172.217.32.0/20 permit
172.217.128.0/19 permit
172.217.160.0/20 permit
172.217.192.0/19 permit
172.253.56.0/21 permit
172.253.112.0/20 permit
173.0.84.0/29 permit
@ -2209,17 +2205,17 @@
2607:13c0:0002:0000:0000:0000:0000:1000/116 permit
2607:13c0:0004:0000:0000:0000:0000:0000/116 permit
2607:f8b0:4000::/36 permit
2620:109:c003:104::215 permit
2620:109:c003:104::/64 permit
2620:109:c006:104::215 permit
2620:109:c003:104::215 permit
2620:109:c006:104::/64 permit
2620:109:c006:104::215 permit
2620:109:c00d:104::/64 permit
2620:10d:c090:400::8:1 permit
2620:10d:c091:400::8:1 permit
2620:10d:c09b:400::8:1 permit
2620:10d:c09c:400::8:1 permit
2620:119:50c0:207::215 permit
2620:119:50c0:207::/64 permit
2620:119:50c0:207::215 permit
2800:3f0:4000::/36 permit
49.12.4.251 permit # checks.mailcow.email
2a01:4f8:c17:7906::10 permit # checks.mailcow.email

View File

@ -7,13 +7,19 @@ document.addEventListener('DOMContentLoaded', function () {
});
// logout function
function mc_logout() {
fetch("/", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: "logout=1"
}).then(() => window.location.href = '/');
// Create and submit a logout form to trigger the logout process
var form = document.createElement('form');
form.method = 'POST';
form.action = '/';
var logoutInput = document.createElement('input');
logoutInput.type = 'hidden';
logoutInput.name = 'logout';
logoutInput.value = '1';
form.appendChild(logoutInput);
document.body.appendChild(form);
form.submit();
}
// Custom SOGo JS

View File

@ -2317,6 +2317,10 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
if (!array_key_exists('login_provisioning', $settings)) {
$settings['login_provisioning'] = 1;
}
// set end_session_url default if not exists
if (!array_key_exists('end_session_url', $settings)) {
$settings['end_session_url'] = '';
}
// return default client_scopes for generic-oidc if none is set
if ($settings["authsource"] == "generic-oidc" && empty($settings["client_scopes"])){
$settings["client_scopes"] = "openid profile email mailcow_template";
@ -2391,6 +2395,7 @@ 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'];
$_data['end_session_url'] = (isset($_data['end_session_url']) && trim($_data['end_session_url']) !== '') ? trim($_data['end_session_url']) : null;
$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', 'login_provisioning');
break;
case "generic-oidc":
@ -2398,6 +2403,7 @@ function identity_provider($_action = null, $_data = null, $_extra = 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 mailcow_template";
$_data['end_session_url'] = (isset($_data['end_session_url']) && trim($_data['end_session_url']) !== '') ? trim($_data['end_session_url']) : null;
$required_settings = array('authsource', 'authorize_url', 'token_url', 'client_id', 'client_secret', 'redirect_url', 'userinfo_url', 'client_scopes', 'ignore_ssl_error', 'login_provisioning');
break;
case "ldap":
@ -2456,6 +2462,12 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
$stmt->execute();
}
// add end_session_url
$_data['end_session_url'] = (isset($_data['end_session_url']) && trim($_data['end_session_url']) !== '') ? trim($_data['end_session_url']) : "";
$stmt = $pdo->prepare("INSERT INTO identity_provider (`key`, `value`) VALUES ('end_session_url', :value) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`);");
$stmt->bindParam(':value', $_data['end_session_url']);
$stmt->execute();
// add mappers
if (isset($_data['mappers']) && isset($_data['templates'])){
$_data['mappers'] = (!is_array($_data['mappers'])) ? array($_data['mappers']) : $_data['mappers'];
@ -2774,6 +2786,11 @@ 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['iam_auth_source'] = $iam_settings['authsource'];
// Store ID token if available for logout
if (isset($token->getValues()['id_token'])) {
$_SESSION['iam_id_token'] = $token->getValues()['id_token'];
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']),
@ -2850,6 +2867,11 @@ 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['iam_auth_source'] = $iam_settings['authsource'];
// Store ID token if available for logout
if (isset($token->getValues()['id_token'])) {
$_SESSION['iam_id_token'] = $token->getValues()['id_token'];
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']),
@ -2963,6 +2985,37 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
));
return $res['access_token'];
break;
case "get-logout-url":
// Generate logout URL for OIDC providers
if ($iam_settings['authsource'] != 'keycloak' && $iam_settings['authsource'] != 'generic-oidc') {
return false;
}
// Build the logout URL according to oidc spec
// If end_session_url is empty, OIDC logout is disabled
if (empty($iam_settings['end_session_url'])) {
return false;
}
$post_logout_redirect_uri = (!empty($_data['post_logout_redirect_uri'])) ? $_data['post_logout_redirect_uri'] : null;
$params = [];
if ($post_logout_redirect_uri) {
$params['post_logout_redirect_uri'] = $post_logout_redirect_uri;
}
// Add id_token_hint if available in session
if (isset($_SESSION['iam_id_token'])) {
$params['id_token_hint'] = $_SESSION['iam_id_token'];
}
$logout_url = $iam_settings['end_session_url'];
if (!empty($params)) {
$logout_url .= '?' . http_build_query($params);
}
return $logout_url;
break;
}
}
function reset_password($action, $data = null) {

View File

@ -112,12 +112,44 @@ if (isset($_POST["logout"])) {
}
else {
$role = $_SESSION["mailcow_cc_role"];
// Check if user was authenticated via OIDC and OIDC logout is enabled
$oidc_logout_url = null;
if (isset($_SESSION['iam_auth_source']) && ($_SESSION['iam_auth_source'] == 'keycloak' || $_SESSION['iam_auth_source'] == 'generic-oidc')) {
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php';
// Determine the schema
$schema = 'http';
if ((isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == "https") ||
isset($_SERVER['HTTPS'])) {
$schema = 'https';
}
// Determine post-logout redirect URI based on role
$post_logout_redirect_uri = $schema . '://' . $_SERVER['HTTP_HOST'];
if ($role == "admin") {
$post_logout_redirect_uri .= '/admin';
} elseif ($role == "domainadmin") {
$post_logout_redirect_uri .= '/domainadmin';
} else {
$post_logout_redirect_uri .= '/';
}
$iam_provider = identity_provider('init');
$iam_settings = identity_provider('get');
$oidc_logout_url = identity_provider('get-logout-url', array('post_logout_redirect_uri' => $post_logout_redirect_uri));
}
session_regenerate_id(true);
session_unset();
session_destroy();
session_write_close();
if ($role == "admin") {
header("Location: /admin");
// Redirect to OIDC logout URL if available, otherwise use standard logout
if ($oidc_logout_url) {
header("Location: " . $oidc_logout_url);
} elseif($role == "admin") {
header("Location: /admin");
}
elseif ($role == "domainadmin") {
header("Location: /domainadmin");

View File

@ -243,6 +243,8 @@
"iam_test_connection": "Verbindung Testen",
"iam_token_url": "Token Endpunkt",
"iam_userinfo_url": "User info Endpunkt",
"iam_end_session_url": "End-Session-Endpunkt",
"iam_end_session_url_info": "URL für RP-Initiated Logout nach OpenID Connect Spezifikation. Leer lassen, um OIDC-Logout zu deaktivieren.",
"iam_username_field": "Username Feld",
"iam_binddn": "Bind DN",
"iam_use_ssl": "Benutze SSL",

View File

@ -250,6 +250,8 @@
"iam_test_connection": "Test Connection",
"iam_token_url": "Token endpoint",
"iam_userinfo_url": "User info endpoint",
"iam_end_session_url": "End session endpoint",
"iam_end_session_url_info": "URL for RP-Initiated Logout according to OpenID Connect specification. Leave empty to disable OIDC logout.",
"iam_username_field": "Username Field",
"iam_binddn": "Bind DN",
"iam_use_ssl": "Use SSL",

View File

@ -257,6 +257,19 @@
<input class="form-control" type="number" min="1" name="sync_interval" style="width: 80px;" {% if iam_settings.sync_interval %}value="{{ iam_settings.sync_interval }}"{% else %}value="15"{% endif %}>
</div>
</div>
<div class="row mb-2">
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label" for="iam_keycloak_end_session_url">{{ lang.admin.iam_end_session_url }}:</label>
</div>
<div class="col-12 col-md-9 col-lg-4">
<input type="text" class="form-control" id="iam_keycloak_end_session_url" name="end_session_url" value="{{ iam_settings.end_session_url }}" placeholder="https://keycloak.example.com/realms/mailcow/protocol/openid-connect/logout">
<p class="text-muted">
<small>
{{ lang.admin.iam_end_session_url_info }}
</small>
</p>
</div>
</div>
<div class="row mt-4 mb-2">
<div class="offset-md-3 col-12 col-md-9 d-flex flex-wrap">
<div class="btn-group mb-2">
@ -460,6 +473,19 @@
</div>
</div>
</div>
<div class="row mb-4">
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label" for="iam_generic_end_session_url">{{ lang.admin.iam_end_session_url }}:</label>
</div>
<div class="col-12 col-md-9 col-lg-4">
<input type="text" class="form-control" id="iam_generic_end_session_url" name="end_session_url" value="{{ iam_settings.end_session_url }}" placeholder="https://auth.example.com/connect/endsession">
<p class="text-muted">
<small>
{{ lang.admin.iam_end_session_url_info }}
</small>
</p>
</div>
</div>
<div class="row mt-4 mb-2">
<div class="offset-md-3 col-12 col-md-9 d-flex flex-wrap">
<div class="btn-group mb-2">