public
nobgit
read
NobMail
Based on mailcow: dockerized
Languages
Repository composition by tracked source files.
PHP
49%
JavaScript
35%
HTML
9%
CSS
4%
Shell
2%
Python
1%
Lua
0%
Perl
0%
Ruby
0%
SCSS
0%
Create file
Wiki Documentation
Clone
https://nobgit.com/orgs/nobgit/nobmail.git
ssh://[email protected]:2222/orgs/nobgit/nobmail.git
Commit
[Web] move iam sso functions
593e581c
data/web/inc/functions.auth.inc.php | 178 ------------------------------
data/web/inc/functions.inc.php | 209 ++++++++++++++++++++++++++++++++++++
data/web/inc/prerequisites.inc.php | 4 +-
data/web/inc/triggers.inc.php | 27 ++---
4 files changed, 222 insertions(+), 196 deletions(-)
Diff
diff --git a/data/web/inc/functions.auth.inc.php b/data/web/inc/functions.auth.inc.php
index d68a7b6e..fbb79ed8 100644
--- a/data/web/inc/functions.auth.inc.php
+++ b/data/web/inc/functions.auth.inc.php
@@ -1,14 +1,4 @@
<?php
-function unset_auth_session(){
- unset($_SESSION['keycloak_token']);
- unset($_SESSION['keycloak_refresh_token']);
- unset($_SESSION['mailcow_cc_username']);
- unset($_SESSION['mailcow_cc_role']);
- unset($_SESSION['oauth2state']);
- unset($_SESSION['pending_mailcow_cc_username']);
- unset($_SESSION['pending_mailcow_cc_role']);
- unset($_SESSION['pending_tfa_methods']);
-}
function check_login($user, $pass, $app_passwd_data = false, $is_internal = false) {
global $pdo;
global $redis;
@@ -503,171 +493,3 @@ function keycloak_mbox_login_rest($user, $pass, $iam_settings, $is_internal = fa
);
return 'user';
}
-// Authorization Code Flow
-function keycloak_verify_token(){
- global $keycloak_provider;
- global $pdo;
-
- try {
- $token = $keycloak_provider->getAccessToken('authorization_code', ['code' => $_GET['code']]);
- } catch (Exception $e) {
- $_SESSION['return'][] = array(
- 'type' => 'danger',
- 'log' => array(__FUNCTION__),
- 'msg' => array('login_failed', $e->getMessage())
- );
- return false;
- }
-
- $_SESSION['keycloak_token'] = $token->getToken();
- $_SESSION['keycloak_refresh_token'] = $token->getRefreshToken();
- // decode jwt data
- $info = json_decode(base64_decode(str_replace('_', '/', str_replace('-','+',explode('.', $token->getToken())[1]))), true);
- // check if email address is given
- if (!$info['email']){
- return false;
- }
-
-
- // token valid, get mailbox
- $stmt = $pdo->prepare("SELECT * FROM `mailbox`
- INNER JOIN domain on mailbox.domain = domain.domain
- WHERE `kind` NOT REGEXP 'location|thing|group'
- AND `mailbox`.`active`='1'
- AND `domain`.`active`='1'
- AND `username` = :user
- AND `authsource`='keycloak'");
- $stmt->execute(array(':user' => $info['email']));
- $row = $stmt->fetch(PDO::FETCH_ASSOC);
- if ($row){
- // success
- $_SESSION['mailcow_cc_username'] = $info['email'];
- $_SESSION['mailcow_cc_role'] = "user";
- $_SESSION['return'][] = array(
- 'type' => 'success',
- 'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']),
- 'msg' => array('logged_in_as', $_SESSION['mailcow_cc_username'])
- );
- return true;
- }
- // get mapped template, if not set return false
- // also return false if no mappers were defined
- $identity_provider_settings = identity_provider('get');
- $user_template = $info['mailcow_template'];
- if (empty($identity_provider_settings['mappers']) || empty($user_template)){
- unset_auth_session();
- $_SESSION['return'][] = array(
- 'type' => 'danger',
- 'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']),
- 'msg' => 'login_failed'
- );
- return false;
- }
- $mbox_template = null;
- // check if matching rolemapping exist
- foreach ($identity_provider_settings['mappers'] as $index => $mapper){
- if ($user_template == $mapper) {
- $mbox_template = $identity_provider_settings['templates'][$index];
- break;
- }
- }
- if (!$mbox_template){
- // no matching template found
- unset_auth_session();
- $_SESSION['return'][] = array(
- 'type' => 'danger',
- 'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']),
- 'msg' => 'login_failed'
- );
- return false;
- }
- $stmt = $pdo->prepare("SELECT * FROM `templates`
- WHERE `template` = :template AND type = 'mailbox'");
- $stmt->execute(array(
- ":template" => $mbox_template
- ));
- $mbox_template_data = $stmt->fetch(PDO::FETCH_ASSOC);
- if (empty($mbox_template_data)){
- unset_auth_session();
- $_SESSION['return'][] = array(
- 'type' => 'danger',
- 'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']),
- 'msg' => 'login_failed'
- );
- return false;
- }
-
- // create mailbox
- $mbox_template_data = json_decode($mbox_template_data["attributes"], true);
- $mbox_template_data['domain'] = explode('@', $info['email'])[1];
- $mbox_template_data['local_part'] = explode('@', $info['email'])[0];
- $mbox_template_data['authsource'] = 'keycloak';
- $_SESSION['iam_create_login'] = true;
- $create_res = mailbox('add', 'mailbox', $mbox_template_data);
- $_SESSION['iam_create_login'] = false;
- if (!$create_res){
- unset_auth_session();
- $_SESSION['return'][] = array(
- 'type' => 'danger',
- 'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']),
- 'msg' => 'login_failed'
- );
- return false;
- }
-
- $_SESSION['mailcow_cc_username'] = $info['email'];
- $_SESSION['mailcow_cc_role'] = "user";
- $_SESSION['return'][] = array(
- 'type' => 'success',
- 'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']),
- 'msg' => array('logged_in_as', $_SESSION['mailcow_cc_username'])
- );
- return true;
-}
-function keycloak_refresh(){
- global $keycloak_provider;
-
- try {
- $token = $keycloak_provider->getAccessToken('refresh_token', ['refresh_token' => $_SESSION['keycloak_refresh_token']]);
- } catch (Exception $e) {
- $_SESSION['return'][] = array(
- 'type' => 'danger',
- 'log' => array(__FUNCTION__),
- 'msg' => array('login_failed', $e->getMessage())
- );
- return false;
- }
-
- $_SESSION['keycloak_token'] = $token->getToken();
- $_SESSION['keycloak_refresh_token'] = $token->getRefreshToken();
- // decode jwt data
- $info = json_decode(base64_decode(str_replace('_', '/', str_replace('-','+',explode('.', $_SESSION['keycloak_token'])[1]))), true);
- if (!$info['email']){
- unset_auth_session();
- $_SESSION['return'][] = array(
- 'type' => 'danger',
- 'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']),
- 'msg' => 'refresh_login_failed'
- );
- }
-
- $_SESSION['mailcow_cc_username'] = $info['email'];
- $_SESSION['mailcow_cc_role'] = "user";
- return true;
-}
-function keycloak_get_redirect(){
- global $keycloak_provider;
-
- $authUrl = $keycloak_provider->getAuthorizationUrl();
- $_SESSION['oauth2state'] = $keycloak_provider->getState();
-
- return $authUrl;
-}
-function keycloak_get_logout(){
- global $keycloak_provider;
-
- $logoutUrl = $keycloak_provider->getLogoutUrl();
- $logoutUrl = $logoutUrl . "&post_logout_redirect_uri=https://" . $_SERVER['SERVER_NAME'];
-
- return $logoutUrl;
-}
diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php
index b88133e5..61d21ae3 100644
--- a/data/web/inc/functions.inc.php
+++ b/data/web/inc/functions.inc.php
@@ -2233,8 +2233,217 @@ function identity_provider($_action, $_data = null, $hide_secret = false) {
}
return $provider;
break;
+ case "verify-sso":
+ $provider = $_data['iam_provider'];
+
+ try {
+ $token = $provider->getAccessToken('authorization_code', ['code' => $_GET['code']]);
+ } catch (Exception $e) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__),
+ 'msg' => array('login_failed', $e->getMessage())
+ );
+ return false;
+ }
+ try {
+ $_SESSION['iam_token'] = $token->getToken();
+ $_SESSION['iam_refresh_token'] = $token->getRefreshToken();
+ $info = $provider->getResourceOwner($token)->toArray();
+ } catch (Throwable $e) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__),
+ 'msg' => array('login_failed', $e->getMessage())
+ );
+ return false;
+ }
+
+ // check if email address is given
+ if (empty($info['email'])) return false;
+
+ // token valid, get mailbox
+ $stmt = $pdo->prepare("SELECT * FROM `mailbox`
+ INNER JOIN domain on mailbox.domain = domain.domain
+ WHERE `kind` NOT REGEXP 'location|thing|group'
+ AND `mailbox`.`active`='1'
+ AND `domain`.`active`='1'
+ AND `username` = :user
+ AND `authsource`='keycloak' OR `authsource`='generic-oidc'");
+ $stmt->execute(array(':user' => $info['email']));
+ $row = $stmt->fetch(PDO::FETCH_ASSOC);
+ if ($row){
+ // success
+ $_SESSION['mailcow_cc_username'] = $info['email'];
+ $_SESSION['mailcow_cc_role'] = "user";
+ $_SESSION['return'][] = array(
+ 'type' => 'success',
+ 'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']),
+ 'msg' => array('logged_in_as', $_SESSION['mailcow_cc_username'])
+ );
+ return true;
+ }
+
+ // get mapped template, if not set return false
+ // also return false if no mappers were defined
+ $provider = identity_provider('get');
+ $user_template = $info['mailcow_template'];
+ if (empty($provider['mappers']) || empty($user_template)){
+ clear_session();
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']),
+ 'msg' => 'login_failed'
+ );
+ return false;
+ }
+
+ // check if matching attribute exist
+ if (array_search($user_template, $provider['mappers']) === false) {
+ clear_session();
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']),
+ 'msg' => 'login_failed'
+ );
+ return false;
+ }
+
+ // create mailbox
+ $create_res = mailbox('add', 'mailbox_from_template', array(
+ 'domain' => explode('@', $info['email'])[1],
+ 'local_part' => explode('@', $info['email'])[0],
+ 'authsource' => identity_provider('get')['authsource'],
+ 'template' => $user_template
+ ));
+ if (!$create_res){
+ clear_session();
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']),
+ 'msg' => 'login_failed'
+ );
+ return false;
+ }
+
+ $_SESSION['mailcow_cc_username'] = $info['email'];
+ $_SESSION['mailcow_cc_role'] = "user";
+ $_SESSION['return'][] = array(
+ 'type' => 'success',
+ 'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']),
+ 'msg' => array('logged_in_as', $_SESSION['mailcow_cc_username'])
+ );
+ return true;
+ break;
+ case "refresh-token":
+ $provider = $_data['iam_provider'];
+
+ try {
+ $token = $provider->getAccessToken('refresh_token', ['refresh_token' => $_SESSION['iam_refresh_token']]);
+ } catch (Exception $e) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__),
+ 'msg' => array('login_failed', $e->getMessage())
+ );
+ return false;
+ }
+ try {
+ $_SESSION['iam_token'] = $token->getToken();
+ $_SESSION['iam_refresh_token'] = $token->getRefreshToken();
+ $info = $provider->getResourceOwner($token)->toArray();
+ } catch (Throwable $e) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__),
+ 'msg' => array('login_failed', $e->getMessage())
+ );
+ return false;
+ }
+
+ if (empty($info['email'])){
+ clear_session();
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']),
+ 'msg' => 'refresh_login_failed'
+ );
+ return false;
+ }
+
+ $_SESSION['mailcow_cc_username'] = $info['email'];
+ $_SESSION['mailcow_cc_role'] = "user";
+ return true;
+ break;
+ case "get-redirect":
+ $provider = $_data['iam_provider'];
+ $authUrl = $provider->getAuthorizationUrl();
+ $_SESSION['oauth2state'] = $provider->getState();
+ return $authUrl;
+ break;
+ case "get-keycloak-admin-token":
+ // get access_token for service account of mailcow client
+ $iam_settings = identity_provider('get');
+ if ($iam_settings['authsource'] !== 'keycloak') return false;
+ if (isset($iam_settings['access_token'])) {
+ // check if access_token is valid
+ $url = "{$iam_settings['server_url']}/realms/{$iam_settings['realm']}/protocol/openid-connect/token/introspect";
+ $req = http_build_query(array(
+ 'token' => $iam_settings['access_token'],
+ 'client_id' => $iam_settings['client_id'],
+ 'client_secret' => $iam_settings['client_secret']
+ ));
+ $curl = curl_init();
+ curl_setopt($curl, CURLOPT_URL, $url);
+ curl_setopt($curl, CURLOPT_POST, 1);
+ curl_setopt($curl, CURLOPT_POSTFIELDS, $req);
+ curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($curl, CURLOPT_TIMEOUT, 5);
+ $res = json_decode(curl_exec($curl), true);
+ $code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+ curl_close ($curl);
+ if ($code == 200 && $res['active'] == true) {
+ // token is valid
+ return $iam_settings['access_token'];
+ }
+ }
+
+ $url = "{$iam_settings['server_url']}/realms/{$iam_settings['realm']}/protocol/openid-connect/token";
+ $req = http_build_query(array(
+ 'grant_type' => 'client_credentials',
+ 'client_id' => $iam_settings['client_id'],
+ 'client_secret' => $iam_settings['client_secret']
+ ));
+ $curl = curl_init();
+ curl_setopt($curl, CURLOPT_URL, $url);
+ curl_setopt($curl, CURLOPT_POST, 1);
+ curl_setopt($curl, CURLOPT_POSTFIELDS, $req);
+ curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($curl, CURLOPT_TIMEOUT, 5);
+ $res = json_decode(curl_exec($curl), true);
+ $code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+ curl_close ($curl);
+ if ($code != 200) {
+ return false;
+ }
+
+ $stmt = $pdo->prepare("INSERT INTO identity_provider (`key`, `value`) VALUES (:key, :value) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`);");
+ $stmt->execute(array(
+ ':key' => 'access_token',
+ ':value' => $res['access_token']
+ ));
+ return $res['access_token'];
+ break;
}
}
+function clear_session(){
+ session_regenerate_id(true);
+ session_unset();
+ session_destroy();
+ session_write_close();
+}
function get_logs($application, $lines = false) {
if ($lines === false) {
diff --git a/data/web/inc/prerequisites.inc.php b/data/web/inc/prerequisites.inc.php
index 90cff4b2..fb30956f 100644
--- a/data/web/inc/prerequisites.inc.php
+++ b/data/web/inc/prerequisites.inc.php
@@ -178,8 +178,8 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.auth.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php';
-// Init Keycloak Provider
-$keycloak_provider = identity_provider('init');
+// Init Identity Provider
+$iam_provider = identity_provider('init');
// IMAP lib
// use Ddeboer\Imap\Server;
diff --git a/data/web/inc/triggers.inc.php b/data/web/inc/triggers.inc.php
index 50722a79..c8333f97 100644
--- a/data/web/inc/triggers.inc.php
+++ b/data/web/inc/triggers.inc.php
@@ -1,20 +1,20 @@
<?php
-// handle keycloak authentication
-if ($keycloak_provider){
- if (isset($_GET['keycloak_sso'])){
- // redirect to keycloak for sso
- $redirect_uri = keycloak_get_redirect();
+// handle iam authentication
+if ($iam_provider){
+ if (isset($_GET['iam_sso'])){
+ // redirect for sso
+ $redirect_uri = identity_provider('get-redirect', array('iam_provider' => $iam_provider));
header('Location: ' . $redirect_uri);
die();
}
- if ($_SESSION['keycloak_token'] && $_SESSION['keycloak_refresh_token']) {
+ if ($_SESSION['iam_token'] && $_SESSION['iam_refresh_token']) {
// Session found, try to refresh
- $isRefreshed = keycloak_refresh();
+ $isRefreshed = identity_provider('refresh-token', array('iam_provider' => $iam_provider));
if (!$isRefreshed){
- // Session could not be refreshed, clear and redirect to keycloak
- unset_auth_session();
- $redirect_uri = keycloak_get_redirect();
+ // Session could not be refreshed, clear and redirect to provider
+ clear_session();
+ $redirect_uri = identity_provider('get-redirect', array('iam_provider' => $iam_provider));
header('Location: ' . $redirect_uri);
die();
}
@@ -22,12 +22,7 @@ if ($keycloak_provider){
// Check given state against previously stored one to mitigate CSRF attack
// Recieved access token in $_GET['code']
// extract info and verify user
- $isValid = keycloak_verify_token();
-
- if (!$isValid){
- // Token could not be verified, redirect to keycloak
- $_SESSION['invalid_keycloak_sso'] = true;
- }
+ identity_provider('verify-sso', array('iam_provider' => $iam_provider));
}
}