From d156a93a8415b0f488cee831a00387ffdf69b20f Mon Sep 17 00:00:00 2001 From: andryyy Date: Tue, 22 Jun 2021 07:17:55 +0200 Subject: [PATCH] [Web] Various fixes; Allow users to login with FIDO2, SOGo SSO is a wip --- data/web/inc/functions.inc.php | 98 +++++++------ data/web/inc/functions.mailbox.inc.php | 8 ++ data/web/inc/triggers.inc.php | 2 +- data/web/js/site/user.js | 4 +- data/web/json_api.php | 41 +++--- data/web/modals/footer.php | 4 +- data/web/user.php | 187 ++++++++++++++++++------- 7 files changed, 229 insertions(+), 115 deletions(-) diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index 95d5571a..23687761 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -1097,8 +1097,7 @@ function set_tfa($_data) { $_data_log = $_data; !isset($_data_log['confirm_password']) ?: $_data_log['confirm_password'] = '*'; $username = $_SESSION['mailcow_cc_username']; - if ($_SESSION['mailcow_cc_role'] != "domainadmin" && - $_SESSION['mailcow_cc_role'] != "admin") { + if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_data_log), @@ -1107,18 +1106,35 @@ function set_tfa($_data) { return false; } $stmt = $pdo->prepare("SELECT `password` FROM `admin` - WHERE `username` = :user"); - $stmt->execute(array(':user' => $username)); + WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); $row = $stmt->fetch(PDO::FETCH_ASSOC); - if (!verify_hash($row['password'], $_data["confirm_password"])) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => 'access_denied' - ); - return false; + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if (!empty($num_results)) { + if (!verify_hash($row['password'], $_data["confirm_password"])) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + } + $stmt = $pdo->prepare("SELECT `password` FROM `mailbox` + WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if (!empty($num_results)) { + if (!verify_hash($row['password'], $_data["confirm_password"])) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } } - switch ($_data["tfa_method"]) { case "yubi_otp": $key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"]; @@ -1241,8 +1257,7 @@ function fido2($_data) { switch ($_data["action"]) { case "register": $username = $_SESSION['mailcow_cc_username']; - if ($_SESSION['mailcow_cc_role'] != "domainadmin" && - $_SESSION['mailcow_cc_role'] != "admin") { + if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_data["action"]), @@ -1273,9 +1288,8 @@ function fido2($_data) { case "get_user_cids": // Used to exclude existing CredentialIds while registering $username = $_SESSION['mailcow_cc_username']; - if ($_SESSION['mailcow_cc_role'] != "domainadmin" && - $_SESSION['mailcow_cc_role'] != "admin") { - return false; + if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) { + return false; } $stmt = $pdo->prepare("SELECT `credentialId` FROM `fido2` WHERE `username` = :username"); $stmt->execute(array(':username' => $username)); @@ -1312,9 +1326,8 @@ function fido2($_data) { break; case "get_friendly_names": $username = $_SESSION['mailcow_cc_username']; - if ($_SESSION['mailcow_cc_role'] != "domainadmin" && - $_SESSION['mailcow_cc_role'] != "admin") { - return false; + if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) { + return false; } $stmt = $pdo->prepare("SELECT SHA2(`credentialId`, 256) AS `cid`, `created`, `certificateSubject`, `friendlyName` FROM `fido2` WHERE `username` = :username"); $stmt->execute(array(':username' => $username)); @@ -1330,14 +1343,13 @@ function fido2($_data) { break; case "unset_fido2_key": $username = $_SESSION['mailcow_cc_username']; - if ($_SESSION['mailcow_cc_role'] != "domainadmin" && - $_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data["action"]), - 'msg' => 'access_denied' - ); - return false; + if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_data["action"]), + 'msg' => 'access_denied' + ); + return false; } $stmt = $pdo->prepare("DELETE FROM `fido2` WHERE `username` = :username AND SHA2(`credentialId`, 256) = :cid"); $stmt->execute(array( @@ -1352,14 +1364,13 @@ function fido2($_data) { break; case "edit_fn": $username = $_SESSION['mailcow_cc_username']; - if ($_SESSION['mailcow_cc_role'] != "domainadmin" && - $_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data["action"]), - 'msg' => 'access_denied' - ); - return false; + if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_data["action"]), + 'msg' => 'access_denied' + ); + return false; } $stmt = $pdo->prepare("UPDATE `fido2` SET `friendlyName` = :friendlyName WHERE SHA2(`credentialId`, 256) = :cid AND `username` = :username"); $stmt->execute(array( @@ -1383,14 +1394,13 @@ function unset_tfa_key($_data) { $_data_log = $_data; $id = intval($_data['unset_tfa_key']); $username = $_SESSION['mailcow_cc_username']; - if ($_SESSION['mailcow_cc_role'] != "domainadmin" && - $_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => 'access_denied' - ); - return false; + if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => 'access_denied' + ); + return false; } try { if (!is_numeric($id)) { diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index 6d5d409c..ebeeebd5 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -4263,6 +4263,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $stmt->execute(array( ':username' => $username )); + $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); + $stmt = $pdo->prepare("DELETE FROM `fido2` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); $stmt = $pdo->prepare("SELECT `address`, `goto` FROM `alias` WHERE `goto` REGEXP :username"); $stmt->execute(array(':username' => '(^|,)'.$username.'($|,)')); diff --git a/data/web/inc/triggers.inc.php b/data/web/inc/triggers.inc.php index 4389ab35..fc8e37b5 100644 --- a/data/web/inc/triggers.inc.php +++ b/data/web/inc/triggers.inc.php @@ -79,7 +79,7 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['acl']['login_as'] == "1") } } -if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "admin" || $_SESSION['mailcow_cc_role'] == "domainadmin")) { +if (isset($_SESSION['mailcow_cc_role'])) { if (isset($_POST["set_tfa"])) { set_tfa($_POST); } diff --git a/data/web/js/site/user.js b/data/web/js/site/user.js index fb771f2c..99eb53fb 100644 --- a/data/web/js/site/user.js +++ b/data/web/js/site/user.js @@ -1,3 +1,5 @@ +// Base64 functions +var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(r){var t,e,o,a,h,n,c,d="",C=0;for(r=Base64._utf8_encode(r);C>2,h=(3&t)<<4|(e=r.charCodeAt(C++))>>4,n=(15&e)<<2|(o=r.charCodeAt(C++))>>6,c=63&o,isNaN(e)?n=c=64:isNaN(o)&&(c=64),d=d+this._keyStr.charAt(a)+this._keyStr.charAt(h)+this._keyStr.charAt(n)+this._keyStr.charAt(c);return d},decode:function(r){var t,e,o,a,h,n,c="",d=0;for(r=r.replace(/[^A-Za-z0-9\+\/\=]/g,"");d>4,e=(15&a)<<4|(h=this._keyStr.indexOf(r.charAt(d++)))>>2,o=(3&h)<<6|(n=this._keyStr.indexOf(r.charAt(d++))),c+=String.fromCharCode(t),64!=h&&(c+=String.fromCharCode(e)),64!=n&&(c+=String.fromCharCode(o));return c=Base64._utf8_decode(c)},_utf8_encode:function(r){r=r.replace(/\r\n/g,"\n");for(var t="",e=0;e127&&o<2048?(t+=String.fromCharCode(o>>6|192),t+=String.fromCharCode(63&o|128)):(t+=String.fromCharCode(o>>12|224),t+=String.fromCharCode(o>>6&63|128),t+=String.fromCharCode(63&o|128))}return t},_utf8_decode:function(r){for(var t="",e=0,o=c1=c2=0;e191&&o<224?(c2=r.charCodeAt(e+1),t+=String.fromCharCode((31&o)<<6|63&c2),e+=2):(c2=r.charCodeAt(e+1),c3=r.charCodeAt(e+2),t+=String.fromCharCode((15&o)<<12|(63&c2)<<6|63&c3),e+=3);return t}}; $(document).ready(function() { // Spam score slider var spam_slider = $('#spam_score')[0]; @@ -75,7 +77,7 @@ jQuery(function($){ $('.clear-last-logins').on('click', function () {if (confirm(lang.delete_ays)) {last_logins('reset');}}) $(".login-history").on('click', function(e) {e.preventDefault(); last_logins('get', $(this).data('days'));$(this).addClass('active').siblings().removeClass('active');}); - function last_logins(action, days = 7) { + function last_logins(action, days = 1) { if (action == 'get') { $('.last-login').html('' + lang.waiting); $.ajax({ diff --git a/data/web/json_api.php b/data/web/json_api.php index ad4d9dbe..cdefb83a 100644 --- a/data/web/json_api.php +++ b/data/web/json_api.php @@ -141,7 +141,7 @@ if (isset($_GET['query'])) { // fido2-registration via POST case "fido2-registration": header('Content-Type: application/json'); - if (isset($_SESSION["mailcow_cc_role"]) && ($_SESSION["mailcow_cc_role"] == "admin" || $_SESSION["mailcow_cc_role"] == "domainadmin")) { + if (isset($_SESSION["mailcow_cc_role"])) { $post = trim(file_get_contents('php://input')); if ($post) { $post = json_decode($post); @@ -302,9 +302,22 @@ if (isset($_GET['query'])) { if ($obj_props['superadmin'] === 1) { $_SESSION["mailcow_cc_role"] = "admin"; } - else { + elseif ($obj_props['superadmin'] === 0) { $_SESSION["mailcow_cc_role"] = "domainadmin"; } + else { + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :username"); + $stmt->execute(array(':username' => $process_fido2['username'])); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if ($row['username'] == $process_fido2['username']) { + $_SESSION["mailcow_cc_role"] = "user"; + } + } + if (empty($_SESSION["mailcow_cc_role"])) { + session_unset(); + session_destroy(); + exit; + } $_SESSION["mailcow_cc_username"] = $process_fido2['username']; $_SESSION["fido2_cid"] = $process_fido2['cid']; unset($_SESSION["challenge"]); @@ -339,17 +352,15 @@ if (isset($_GET['query'])) { switch ($category) { case "u2f-registration": header('Content-Type: application/javascript'); - if (isset($_SESSION["mailcow_cc_role"]) && - ($_SESSION["mailcow_cc_role"] == "admin" || $_SESSION["mailcow_cc_role"] == "domainadmin") && - $_SESSION["mailcow_cc_username"] == $object) { - list($req, $sigs) = $u2f->getRegisterData(get_u2f_registrations($object)); - $_SESSION['regReq'] = json_encode($req); - $_SESSION['regSigs'] = json_encode($sigs); - echo 'var req = ' . json_encode($req) . ';'; - echo 'var registeredKeys = ' . json_encode($sigs) . ';'; - echo 'var appId = req.appId;'; - echo 'var registerRequests = [{version: req.version, challenge: req.challenge}];'; - return; + if (isset($_SESSION["mailcow_cc_role"]) && $_SESSION["mailcow_cc_username"] == $object) { + list($req, $sigs) = $u2f->getRegisterData(get_u2f_registrations($object)); + $_SESSION['regReq'] = json_encode($req); + $_SESSION['regSigs'] = json_encode($sigs); + echo 'var req = ' . json_encode($req) . ';'; + echo 'var registeredKeys = ' . json_encode($sigs) . ';'; + echo 'var appId = req.appId;'; + echo 'var registerRequests = [{version: req.version, challenge: req.challenge}];'; + return; } else { return; @@ -358,9 +369,7 @@ if (isset($_GET['query'])) { // fido2-registration via GET case "fido2-registration": header('Content-Type: application/json'); - if (isset($_SESSION["mailcow_cc_role"]) && - ($_SESSION["mailcow_cc_role"] == "admin" || $_SESSION["mailcow_cc_role"] == "domainadmin") && - $_SESSION["mailcow_cc_username"] == $object) { + if (isset($_SESSION["mailcow_cc_role"])) { // Exclude existing CredentialIds, if any $excludeCredentialIds = fido2(array("action" => "get_user_cids")); $createArgs = $WebAuthn->getCreateArgs($_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], 30, true, $GLOBALS['FIDO2_UV_FLAG_REGISTER'], $excludeCredentialIds); diff --git a/data/web/modals/footer.php b/data/web/modals/footer.php index 3b460435..2c39a378 100644 --- a/data/web/modals/footer.php +++ b/data/web/modals/footer.php @@ -1,5 +1,5 @@
- +

- - +
+
- -
🔑 []
- - -
-
+ + + + +
+
@@ -58,12 +61,13 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'doma
+
- - - - - +
+
+

+
+
:
@@ -86,7 +90,7 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'doma @@ -130,6 +134,8 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == ' $username = $_SESSION['mailcow_cc_username']; $mailboxdata = mailbox('get', 'mailbox_details', $username); $pushover_data = pushover('get', $username); + $tfa_data = get_tfa(); + $fido2_data = fido2(array("action" => "get_friendly_names")); $clientconfigstr = "host=" . urlencode($mailcow_hostname) . "&email=" . urlencode($username) . "&name=" . urlencode($mailboxdata['name']) . "&ui=" . urlencode(strtok($_SERVER['HTTP_HOST'], ':')) . "&port=" . urlencode($autodiscover_config['caldav']['port']); if ($autodiscover_config['useEASforOutlook'] == 'yes') @@ -150,7 +156,14 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '