Skip to content

Commit

Permalink
[Web] add manage identity provider
Browse files Browse the repository at this point in the history
  • Loading branch information
FreddleSpl0it authored and DerLinkman committed Feb 8, 2024
1 parent 67c9c5b commit 6e9980b
Show file tree
Hide file tree
Showing 17 changed files with 362 additions and 116 deletions.
3 changes: 3 additions & 0 deletions data/web/admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@
$f2b_data = fail2ban('get');
// identity provider
$identity_provider_settings = identity_provider('get');
// mbox templates
$mbox_templates = mailbox('get', 'mailbox_templates');

$template = 'admin.twig';
$template_data = [
Expand Down Expand Up @@ -120,6 +122,7 @@
'cors_settings' => $cors_settings,
'is_https' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on',
'identity_provider_settings' => $identity_provider_settings,
'mbox_templates' => $mbox_templates,
'lang_admin' => json_encode($lang['admin']),
'lang_datatables' => json_encode($lang['datatables'])
];
Expand Down
2 changes: 2 additions & 0 deletions data/web/inc/footer.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@
'lang_acl' => json_encode($lang['acl']),
'lang_tfa' => json_encode($lang['tfa']),
'lang_fido2' => json_encode($lang['fido2']),
'lang_success' => json_encode($lang['success']),
'lang_danger' => json_encode($lang['danger']),
'docker_timeout' => $DOCKER_TIMEOUT,
'session_lifetime' => (int)$SESSION_LIFETIME,
'csrf_token' => $_SESSION['CSRF']['TOKEN'],
Expand Down
109 changes: 83 additions & 26 deletions data/web/inc/functions.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -2071,20 +2071,26 @@ function identity_provider($_action, $_data = null) {
function identity_provider($_action, $_data = null, $hide_secret = false) {
global $pdo;


switch ($_action) {
case 'get':
$settings = array();
$stmt = $pdo->prepare("SELECT * FROM `identity_provider`;");
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach($rows as $row){
$settings[$row["key"]] = $row["value"];
if ($row["key"] == 'roles'){
$settings['roles'] = json_decode($row["value"]);
} else if ($row["key"] == 'templates'){
$settings['templates'] = json_decode($row["value"]);
} else {
$settings[$row["key"]] = $row["value"];
}
}
if ($hide_secret){
$settings['client_secret'] = '***********************';
$settings['client_secret'] = '';
}
return $settings;
break;
case 'edit':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
Expand All @@ -2094,45 +2100,96 @@ function identity_provider($_action, $_data = null, $hide_secret = false) {
);
return false;
}

$data_log = $_data;
$data_log['client_secret'] = '*';
$stmt = $pdo->prepare("INSERT INTO identity_provider (`key`, `value`) VALUES (:key, :value) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`);");

// add connection settings
$required_settings = array('server_url', 'authsource', 'realm', 'client_id', 'client_secret', 'redirect_url', 'version');
foreach($required_settings as $setting){
if (!$_data[$setting]){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $data_log),
'msg' => 'required_data_missing'
);
return false;
}
}
try {
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data),
'msg' => '2'
);
$stmt = $pdo->prepare("INSERT INTO identity_provider (`key`, `value`) VALUES (:key, :value) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`);");
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data),
'msg' => '3'
);
} catch (Exception $e){
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data, $e->getMessage()),
'msg' => 'post'
);
return;
}

foreach($_data as $key => $value){
if (!in_array($key, $required_settings)){
if (!in_array($key, $required_settings) || $key == 'roles' || $key == 'templates'){
continue;
}

$stmt->bindParam(':key', $key);
$stmt->bindParam(':value', $value);
$stmt->execute();
}

// add role mappings
if ($_data['roles'] && $_data['templates']){
if (!is_array($_data['roles'])){
$_data['roles'] = array($_data['roles']);
}
if (!is_array($_data['templates'])){
$_data['templates'] = array($_data['templates']);
}
$roles = array();
$templates = array();
foreach($_data['roles'] as $role){
if ($role){
array_push($roles, $role);
}
}
foreach($_data['templates'] as $template){
if ($template){
array_push($templates, $template);
}
}
if (count($roles) == count($templates)){
$roles = json_encode($roles);
$templates = json_encode($templates);

$stmt = $pdo->prepare("INSERT INTO identity_provider (`key`, `value`) VALUES ('roles', :value) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`);");
$stmt->bindParam(':value', $roles);
$stmt->execute();
$stmt = $pdo->prepare("INSERT INTO identity_provider (`key`, `value`) VALUES ('templates', :value) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`);");
$stmt->bindParam(':value', $templates);
$stmt->execute();
}
}

$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $data_log),
'msg' => array('object_modified', '')
);
return true;
break;
case 'test':
$identity_provider_settings = identity_provider('get');
$url = "{$identity_provider_settings['server_url']}/realms/{$identity_provider_settings['realm']}/protocol/openid-connect/token";
$req = http_build_query(array(
'grant_type' => 'password',
'client_id' => $identity_provider_settings['client_id'],
'client_secret' => $identity_provider_settings['client_secret'],
'username' => "test",
'password' => "test",
));
$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);
$res = json_decode(curl_exec($curl), true);
curl_close ($curl);

if ($res["error"] && $res["error"] === 'invalid_grant'){
return true;
}
return false;
break;
}
}

Expand Down
2 changes: 1 addition & 1 deletion data/web/inc/init_db.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,7 @@ function init_db_schema() {
"identity_provider" => array(
"cols" => array(
"key" => "VARCHAR(255) NOT NULL",
"value" => "VARCHAR(255) NOT NULL",
"value" => "TEXT NOT NULL",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP"
),
Expand Down
11 changes: 11 additions & 0 deletions data/web/js/build/013-mailcow.js
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,17 @@ $(document).ready(function() {
localStorage.setItem('theme', 'dark');
}
}

// Reveal Password Input
$(".reveal-password-input").on('click', '.toggle-password', function() {
$(this).parent().find('.toggle-password').children().toggleClass("bi-eye bi-eye-slash");
var input = $(this).parent().find('.password-field')
if (input.attr("type") === "password") {
input.attr("type", "text");
} else {
input.attr("type", "password");
}
});
});


Expand Down
34 changes: 34 additions & 0 deletions data/web/js/site/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -749,4 +749,38 @@ jQuery(function($){
$('#add_f2b_regex_row').click(function() {
add_table_row($('#f2b_regex_table'), "f2b_regex");
});
// IAM test connection
$('#iam_test_connection').click(async function(e){
e.preventDefault();
var res = await fetch("/api/v1/get/status/identity-provider", { method:'GET', cache:'no-cache' });
res = await res.json();
console.log(res);
if (res.type === 'success'){
return mailcow_alert_box(lang_success.iam_test_connection, 'success');
}
return mailcow_alert_box(lang_danger.iam_test_connection, 'danger');
});
$('#iam_rolemap_add').click(async function(e){
e.preventDefault();

var parent = $(this).parent().parent();
$(parent).children().last().clone().appendTo(parent);
var newChild = $(parent).children().last();
$(newChild).find('input').val('');
$(newChild).find('.dropdown-toggle').remove();
$(newChild).find('.dropdown-menu').remove();
$(newChild).find('.bs-title-option').remove();
$(newChild).find('select').selectpicker('destroy');
$(newChild).find('select').selectpicker();

$('.iam_rolemap_del').off('click');
$('.iam_rolemap_del').click(async function(e){
e.preventDefault();
$(this).parent().remove();
});
});
$('.iam_rolemap_del').click(async function(e){
e.preventDefault();
$(this).parent().remove();
});
});
40 changes: 39 additions & 1 deletion data/web/js/site/mailbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,17 @@ $(document).ready(function() {
}
});
});
// @selecting identity provider mbox_add_modal
$('#mbox_add_iam').on('change', function(){
// toggle password fields
if (this.value === 'mailcow'){
$('#mbox_add_pwds').removeClass('d-none');
$('#mbox_add_pwds').find('.form-control').prop('required', true);
} else {
$('#mbox_add_pwds').addClass('d-none');
$('#mbox_add_pwds').find('.form-control').prop('required', false);
}
});
// Sieve data modal
$('#sieveDataModal').on('show.bs.modal', function(e) {
var sieveScript = $(e.relatedTarget).data('sieve-script');
Expand Down Expand Up @@ -269,6 +280,15 @@ $(document).ready(function() {
}
function setMailboxTemplateData(template){
$("#addInputQuota").val(template.quota / 1048576);
$('#mbox_add_iam').selectpicker('val', template.authsource);
// toggle password fields
if (template.authsource === 'mailcow'){
$('#mbox_add_pwds').removeClass('d-none');
$('#mbox_add_pwds').find('.form-control').prop('required', true);
} else {
$('#mbox_add_pwds').addClass('d-none');
$('#mbox_add_pwds').find('.form-control').prop('required', false);
}

if (template.quarantine_notification === "never"){
$('#quarantine_notification_never').prop('checked', true);
Expand Down Expand Up @@ -1035,7 +1055,16 @@ jQuery(function($){
title: lang.domain,
data: 'domain',
defaultContent: '',
className: 'none'
className: 'none',
},
{
title: lang.iam,
data: 'authsource',
defaultContent: '',
className: 'none',
render: function (data, type) {
return '<span class="badge bg-primary">' + data + '<i class="ms-2 bi bi-person-circle"></i></i></span>';
}
},
{
title: lang.tls_enforce_in,
Expand Down Expand Up @@ -1263,6 +1292,15 @@ jQuery(function($){
data: 'attributes.quota',
defaultContent: '',
},
{
title: lang.iam,
data: 'attributes.authsource',
defaultContent: '',
render: function (data, type) {
data = data ? '<span class="badge bg-primary">' + data + '<i class="ms-2 bi bi-person-circle"></i></i></span>' : '<i class="bi bi-x-lg"></i>';
return data;
}
},
{
title: lang.tls_enforce_in,
data: 'attributes.tls_enforce_in',
Expand Down
13 changes: 13 additions & 0 deletions data/web/json_api.php
Original file line number Diff line number Diff line change
Expand Up @@ -1702,6 +1702,19 @@ function process_get_return($data, $object = true) {
'version' => $GLOBALS['MAILCOW_GIT_VERSION']
));
break;
case "identity-provider":
if (identity_provider('test')){
echo json_encode(array(
'type' => 'success',
'msg' => 'connection successfull'
));
} else {
echo json_encode(array(
'type' => 'error',
'msg' => 'connection failed'
));
}
break;
}
}
break;
Expand Down
16 changes: 16 additions & 0 deletions data/web/lang/lang.en-gb.json
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,17 @@
"help_text": "Override help text below login mask (HTML allowed)",
"host": "Host",
"html": "HTML",
"iam": "Identity Provider",
"iam_client_id": "Client Id",
"iam_client_secret": "Client Secret",
"iam_description": "Here, you can configure the integration with an external Keycloak service. The Keycloak user's mailboxes will be automatically created upon their first login, provided that a role mapping has been set.",
"iam_realm": "Realm",
"iam_redirect_url": "Redirect Url",
"iam_rolemapping": "Role Mapping",
"iam_server_url": "Server Url",
"iam_sso": "SSO",
"iam_test_connection": "Test Connection",
"iam_version": "Version",
"import": "Import",
"import_private_key": "Import private key",
"in_use_by": "In use by",
Expand Down Expand Up @@ -395,6 +406,8 @@
"goto_empty": "An alias address must contain at least one valid goto address",
"goto_invalid": "Goto address %s is invalid",
"ham_learn_error": "Ham learn error: %s",
"iam_invalid_sso": "SSO login failed",
"iam_test_connection": "Connection failed",
"imagick_exception": "Error: Imagick exception while reading image",
"img_dimensions_exceeded": "Image exceeds the maximum image size",
"img_invalid": "Cannot validate image file",
Expand Down Expand Up @@ -449,6 +462,7 @@
"redis_error": "Redis error: %s",
"relayhost_invalid": "Map entry %s is invalid",
"release_send_failed": "Message could not be released: %s",
"required_data_missing": "Required data is missing",
"reset_f2b_regex": "Regex filter could not be reset in time, please try again or wait a few more seconds and reload the website.",
"resource_invalid": "Resource name %s is invalid",
"rl_timeframe": "Rate limit time frame is incorrect",
Expand Down Expand Up @@ -825,6 +839,7 @@
"goto_ham": "Learn as <b>ham</b>",
"goto_spam": "Learn as <b>spam</b>",
"hourly": "Hourly",
"iam": "Identity Provider",
"in_use": "In use (%)",
"inactive": "Inactive",
"insert_preset": "Insert example preset \"%s\"",
Expand Down Expand Up @@ -1060,6 +1075,7 @@
"forwarding_host_removed": "Forwarding host %s has been removed",
"global_filter_written": "Filter was successfully written to file",
"hash_deleted": "Hash deleted",
"iam_test_connection": "Connection successfull",
"ip_check_opt_in_modified": "IP check was saved successfully",
"item_deleted": "Item %s successfully deleted",
"item_released": "Item %s released",
Expand Down
4 changes: 2 additions & 2 deletions data/web/templates/admin.twig
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<a class="nav-link dropdown-toggle active" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false">{{ lang.admin.access }}</a>
<ul class="dropdown-menu">
<li><button class="dropdown-item active" data-bs-target="#tab-config-admins" aria-selected="false" aria-controls="tab-config-admins" role="tab" data-bs-toggle="tab">{{ lang.admin.admins }}</button></li>
<li><button class="dropdown-item" data-bs-target="#tab-config-identity-providers" aria-selected="false" aria-controls="tab-config-identity-providers" role="tab" data-bs-toggle="tab">Identity Providers</button></li>
<li><button class="dropdown-item" data-bs-target="#tab-config-identity-provider" aria-selected="false" aria-controls="tab-config-identity-provider" role="tab" data-bs-toggle="tab">Identity Provider</button></li>
<!-- <li><button class="dropdown-item" data-bs-target="#tab-config-ldap-admins" aria-controls="tab-config-ldap-admins" role="tab" data-bs-toggle="tab">{{ lang.admin.admins_ldap }}</button></li> -->
<li><button class="dropdown-item" data-bs-target="#tab-config-oauth2" aria-selected="false" aria-controls="tab-config-oauth2" role="tab" data-bs-toggle="tab">{{ lang.admin.oauth2_apps }}</button></li>
<li><button class="dropdown-item" data-bs-target="#tab-config-rspamd" aria-selected="false" aria-controls="tab-config-rspamd" role="tab" data-bs-toggle="tab">Rspamd UI</button></li>
Expand Down Expand Up @@ -41,7 +41,7 @@
<div class="col-md-12">
<div class="tab-content" style="padding-top:20px">
{% include 'admin/tab-config-admins.twig' %}
{% include 'admin/tab-config-identity-providers.twig' %}
{% include 'admin/tab-config-identity-provider.twig' %}
{# {% include 'admin/tab-ldap.twig' %} #}
{% include 'admin/tab-config-oauth2.twig' %}
{% include 'admin/tab-config-rspamd.twig' %}
Expand Down
Loading

0 comments on commit 6e9980b

Please sign in to comment.