mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2024-12-23 02:04:46 +02:00
[Web] add manage identity provider
This commit is contained in:
parent
67c9c5b8ed
commit
6e9980bf0f
@ -88,6 +88,8 @@ $cors_settings['allowed_methods'] = explode(", ", $cors_settings['allowed_method
|
||||
$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 = [
|
||||
@ -120,6 +122,7 @@ $template_data = [
|
||||
'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'])
|
||||
];
|
||||
|
@ -65,6 +65,8 @@ $globalVariables = [
|
||||
'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'],
|
||||
|
@ -2071,7 +2071,6 @@ function identity_provider($_action, $_data = null) {
|
||||
function identity_provider($_action, $_data = null, $hide_secret = false) {
|
||||
global $pdo;
|
||||
|
||||
|
||||
switch ($_action) {
|
||||
case 'get':
|
||||
$settings = array();
|
||||
@ -2079,12 +2078,19 @@ function identity_provider($_action, $_data = null, $hide_secret = false) {
|
||||
$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(
|
||||
@ -2094,36 +2100,24 @@ 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;
|
||||
}
|
||||
|
||||
@ -2131,8 +2125,71 @@ function identity_provider($_action, $_data = null, $hide_secret = false) {
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
),
|
||||
|
@ -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");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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');
|
||||
@ -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);
|
||||
@ -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,
|
||||
@ -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',
|
||||
|
@ -1702,6 +1702,19 @@ if (isset($_GET['query'])) {
|
||||
'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;
|
||||
|
@ -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",
|
||||
@ -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",
|
||||
@ -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",
|
||||
@ -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\"",
|
||||
@ -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",
|
||||
|
@ -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>
|
||||
@ -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' %}
|
||||
|
95
data/web/templates/admin/tab-config-identity-provider.twig
Normal file
95
data/web/templates/admin/tab-config-identity-provider.twig
Normal file
@ -0,0 +1,95 @@
|
||||
<div role="tabpanel" class="tab-pane fade" id="tab-config-identity-provider" role="tabpanel" aria-labelledby="tab-config-identity-provider">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header d-flex fs-5">
|
||||
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-config-identity-provider" data-bs-toggle="collapse" aria-controls="collapse-tab-config-identity-provider">
|
||||
{{ lang.admin.iam }}
|
||||
</button>
|
||||
<span class="d-none d-md-block">{{ lang.admin.iam }}</span>
|
||||
</div>
|
||||
<div id="collapse-tab-config-identity-provider" class="card-body collapse" data-bs-parent="#admin-content">
|
||||
<p class="offset-sm-3 mb-4">{{ lang.admin.iam_description }}</p>
|
||||
<form class="form-horizontal" autocapitalize="none" data-id="iam_sso" autocorrect="off" role="form" method="post">
|
||||
<input type="hidden" name="authsource" value="keycloak">
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-3 text-sm-end" for="iam_url">{{ lang.admin.iam_server_url }}:</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" class="form-control" id="iam_server_url" name="server_url" value="{{ identity_provider_settings.server_url }}" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-3 text-sm-end" for="iam_realm">{{ lang.admin.iam_realm }}:</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" class="form-control" id="iam_realm" name="realm" value="{{ identity_provider_settings.realm }}" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-3 text-sm-end" for="iam_client_id">{{ lang.admin.iam_client_id }}:</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" class="form-control" id="iam_client_id" name="client_id" value="{{ identity_provider_settings.client_id }}" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-3 text-sm-end" for="iam_client_secret">{{ lang.admin.iam_client_secret }}:</label>
|
||||
<div class="col-sm-4">
|
||||
<div class="reveal-password-input input-group">
|
||||
<input type="password" class="password-field form-control" id="iam_client_secret" name="client_secret" value="{{ identity_provider_settings.client_secret }}" required>
|
||||
<button class="toggle-password btn btn-secondary" type="button"><i class="bi bi-eye"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-3 text-sm-end" for="iam_redirect_url">{{ lang.admin.iam_redirect_url }}:</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" class="form-control" id="iam_redirect_url" name="redirect_url" value="{{ identity_provider_settings.redirect_url }}" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
<label class="control-label col-sm-3 text-sm-end" for="iam_version">{{ lang.admin.iam_version }}:</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" class="form-control" id="iam_version" name="version" value="{{ identity_provider_settings.version }}" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-3 text-sm-end" for="iam_version">{{ lang.admin.iam_rolemapping }}:</label>
|
||||
<div class="col-4 d-flex mb-2">
|
||||
<span class="w-100 me-2">Role</span>
|
||||
<span class="w-100 ms-2">Template</span>
|
||||
<button id="iam_rolemap_add" class="btn btn-sm d-block d-sm-inline btn-secondary ms-2"><i class="bi bi-plus-lg"></i></button>
|
||||
</div>
|
||||
{% for key, role in identity_provider_settings.roles %}
|
||||
<div class="offset-sm-3 col-4 d-flex mb-2">
|
||||
<input type="text" class="form-control me-2" name="roles" value="{{ identity_provider_settings.roles[key] }}">
|
||||
<select data-live-search="true" name="templates" class="form-control" title="{{ lang.mailbox.template }}">
|
||||
{% for mbox_template in mbox_templates %}
|
||||
<option{% if mbox_template.template == identity_provider_settings.templates[key] %} selected{% endif %}>
|
||||
{{ mbox_template.template }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<button class="iam_rolemap_del btn btn-sm d-block d-sm-inline btn-secondary ms-2"><i class="bi bi-x-lg"></i></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="offset-sm-3 col-4 d-flex mb-2">
|
||||
<input type="text" class="form-control me-2" name="roles" value="">
|
||||
<select data-live-search="true" name="templates" class="form-control" title="{{ lang.mailbox.template }}">
|
||||
{% for mbox_template in mbox_templates %}
|
||||
<option>
|
||||
{{ mbox_template.template }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<button class="iam_rolemap_del btn btn-sm d-block d-sm-inline btn-secondary ms-2"><i class="bi bi-x-lg"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-4 mb-2">
|
||||
<div class="offset-sm-3 col-sm-9">
|
||||
<div class="btn-group">
|
||||
<button id="iam_test_connection" class="btn btn-sm d-block d-sm-inline btn-secondary"><i class="bi bi-play"></i> {{ lang.admin.iam_test_connection }}</button>
|
||||
<button class="btn btn-sm d-block d-sm-inline btn-success" data-item="iam_sso" data-action="edit_selected" data-id="iam_sso" data-api-url='edit/identity_provider' data-api-attr='{}' href="#"><i class="bi bi-check-lg"></i> {{ lang.admin.save }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,58 +0,0 @@
|
||||
<div role="tabpanel" class="tab-pane fade" id="tab-config-identity-providers" role="tabpanel" aria-labelledby="tab-config-identity-providers">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header d-flex fs-5">
|
||||
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-config-identity-providers" data-bs-toggle="collapse" aria-controls="collapse-tab-config-identity-providers">
|
||||
{{ lang.admin.oauth2_apps }}
|
||||
</button>
|
||||
<span class="d-none d-md-block">{{ lang.admin.oauth2_apps }}</span>
|
||||
</div>
|
||||
<div id="collapse-tab-config-identity-providers" class="card-body collapse" data-bs-parent="#admin-content">
|
||||
<form class="form-horizontal" autocapitalize="none" data-id="keycloak_sso" autocorrect="off" role="form" method="post">
|
||||
<input type="hidden" name="authsource" value="keycloak">
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-3 text-sm-end" for="keycloak_url">Server URL:</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" class="form-control" id="keycloak_url" name="server_url" value="{{ identity_provider_settings.server_url }}" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-3 text-sm-end" for="keycloak_realm">Realm:</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" class="form-control" id="keycloak_realm" name="realm" value="{{ identity_provider_settings.realm }}" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-3 text-sm-end" for="keycloak_client_id">Client Id:</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" class="form-control" id="keycloak_client_id" name="client_id" value="{{ identity_provider_settings.client_id }}" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-3 text-sm-end" for="keycloak_client_secret">Client Secret:</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" class="form-control" id="keycloak_client_secret" name="client_secret" value="{{ identity_provider_settings.client_secret }}" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-3 text-sm-end" for="keycloak_redirect_url">Redirect Url:</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" class="form-control" id="keycloak_redirect_url" name="redirect_url" value="{{ identity_provider_settings.redirect_url }}" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-3 text-sm-end" for="keycloak_version">Keycloak Version:</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" class="form-control" id="keycloak_version" name="version" value="{{ identity_provider_settings.version }}" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-4 mb-2">
|
||||
<div class="offset-sm-3 col-sm-9">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-sm d-block d-sm-inline btn-success" data-item="keycloak_sso" data-action="edit_selected" data-id="keycloak_sso" data-api-url='edit/identity_provider' data-api-attr='{}' href="#"><i class="bi bi-check-lg"></i> {{ lang.admin.save }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -153,6 +153,8 @@
|
||||
var lang_acl = {{ lang_acl|raw }};
|
||||
var lang_tfa = {{ lang_tfa|raw }};
|
||||
var lang_fido2 = {{ lang_fido2|raw }};
|
||||
var lang_success = {{ lang_success|raw }};
|
||||
var lang_danger = {{ lang_danger|raw }};
|
||||
var docker_timeout = {{ docker_timeout|raw }} * 1000;
|
||||
var mailcow_cc_role = '{{ mailcow_cc_role }}';
|
||||
var last_login = '{{ last_login }}';
|
||||
|
@ -19,6 +19,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="authsource">{{ lang.admin.iam }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select class="full-width-select" data-live-search="true" id="mbox_template_iam" name="authsource" required>
|
||||
<option {% if template.attributes.authsource == 'mailcow' %}selected{% endif %}>mailcow</option>
|
||||
<option {% if template.attributes.authsource == 'keycloak' %}selected{% endif %}>keycloak</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2">{{ lang.add.tags }}</label>
|
||||
<div class="col-sm-10">
|
||||
|
@ -26,9 +26,9 @@
|
||||
<input type="hidden" value="0" name="sogo_access">
|
||||
<input type="hidden" value="0" name="protocol_access">
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2">{{ lang.edit.full_name }}</label>
|
||||
<label class="control-label col-sm-2">{{ lang.admin.iam }}</label>
|
||||
<div class="col-sm-10">
|
||||
<span>{{ result.authsource }}</span>
|
||||
<h4><span class="badge bg-primary">{{ result.authsource }}<i class="ms-2 bi bi-person-circle"></i></i></span></h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
|
@ -26,6 +26,13 @@
|
||||
<div class="my-4 alert alert-info ">{{ lang.login.mobileconfig_info }}</div>
|
||||
{% endif %}
|
||||
<form method="post" autofill="off">
|
||||
{% if invalid_keycloak_sso %}
|
||||
<div class="d-flex mt-3 w-100">
|
||||
<div class="alert alert-danger w-100" role="alert">
|
||||
{{ lang.danger.iam_invalid_sso}}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="d-flex mt-3">
|
||||
<label class="visually-hidden" for="login_user">{{ lang.login.username }}</label>
|
||||
<div class="input-group">
|
||||
@ -40,10 +47,16 @@
|
||||
<input name="pass_user" type="password" id="pass_user" class="form-control" placeholder="{{ lang.login.password }}" required="" autocomplete="current-password">
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mt-4" style="position: relative">
|
||||
<div class="d-grid gap-2 d-sm-block">
|
||||
<div class="d-flex mt-4" style="position: relative">
|
||||
<div class="btn-group">
|
||||
<button type="submit" class="btn btn-xs-lg btn-success" value="Login">{{ lang.login.login }}</button>
|
||||
<button type="button" class="btn btn-xs-lg btn-success" id="fido2-login"><i class="bi bi-shield-fill-check"></i> {{ lang.login.fido2_webauthn }}</button>
|
||||
<button type="button" class="btn btn-xs-lg btn-success dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="#" id="fido2-login"><i class="bi bi-shield-fill-check"></i> {{ lang.login.fido2_webauthn }}</a></li>
|
||||
{% if has_keycloak_sso %}
|
||||
<li><a class="dropdown-item" href="/?keycloak_sso=1"><i class="bi bi-cloud-arrow-up-fill"></i> {{ lang.admin.iam_sso }}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
{% if not oauth2_request %}
|
||||
<div class="d-grid d-sm-block">
|
||||
|
@ -12,6 +12,12 @@
|
||||
<input type="hidden" value="0" name="sogo_access">
|
||||
<input type="hidden" value="0" name="protocol_access">
|
||||
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2 text-sm-end text-sm-end" for="name">{{ lang.add.full_name }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="name">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2 text-sm-end text-sm-end" for="local_part">{{ lang.add.mailbox_username }}</label>
|
||||
<div class="col-sm-10">
|
||||
@ -28,38 +34,34 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2 text-sm-end text-sm-end" for="authsource">{{ lang.add.domain }}</label>
|
||||
<div class="row mb-4">
|
||||
<label class="control-label col-sm-2 text-sm-end text-sm-end" for="description">{{ lang.mailbox.template }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select class="full-width-select" data-live-search="true" id="addAuthsource" name="authsource" required>
|
||||
<select data-live-search="true" id="mailbox_templates" class="form-control" title="{{ lang.mailbox.template }}">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2 text-sm-end text-sm-end" for="authsource">{{ lang.admin.iam }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select class="full-width-select" data-live-search="true" id="mbox_add_iam" name="authsource" required>
|
||||
<option selected>mailcow</option>
|
||||
<option>keycloak</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
<label class="control-label col-sm-2 text-sm-end text-sm-end" for="name">{{ lang.add.full_name }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="name">
|
||||
<div id="mbox_add_pwds">
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2 text-sm-end text-sm-end" for="password">{{ lang.add.password }} (<a href="#" class="generate_password">{{ lang.add.generate }}</a>)</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" data-pwgen-field="true" data-hibp="true" class="form-control" name="password" placeholder="" autocomplete="new-password" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2 text-sm-end text-sm-end" for="password">{{ lang.add.password }} (<a href="#" class="generate_password">{{ lang.add.generate }}</a>)</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" data-pwgen-field="true" data-hibp="true" class="form-control" name="password" placeholder="" autocomplete="new-password" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
<label class="control-label col-sm-2 text-sm-end text-sm-end" for="password2">{{ lang.add.password_repeat }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" data-pwgen-field="true" class="form-control" name="password2" placeholder="" autocomplete="new-password" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2 text-sm-end text-sm-end" for="description">{{ lang.mailbox.template }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select data-live-search="true" id="mailbox_templates" class="form-control" title="{{ lang.mailbox.template }}">
|
||||
</select>
|
||||
<div class="row mb-4">
|
||||
<label class="control-label col-sm-2 text-sm-end text-sm-end" for="password2">{{ lang.add.password_repeat }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" data-pwgen-field="true" class="form-control" name="password2" placeholder="" autocomplete="new-password" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
@ -234,6 +236,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2 text-sm-end text-sm-end" for="authsource">{{ lang.admin.iam }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select class="full-width-select" data-live-search="true" id="mbox_template_iam" name="authsource">
|
||||
<option selected>mailcow</option>
|
||||
<option>keycloak</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2 text-sm-end text-sm-end">{{ lang.add.tags }}</label>
|
||||
<div class="col-sm-10">
|
||||
|
Loading…
Reference in New Issue
Block a user