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] manage keycloak identity provider
f6869da3
data/web/admin.php | 3 +
data/web/inc/functions.inc.php | 68 ++++++++++++++++++++++
data/web/inc/init_db.inc.php | 14 +++++
data/web/inc/prerequisites.inc.php | 18 ++++++
data/web/json_api.php | 4 ++
data/web/templates/admin.twig | 2 +
.../admin/tab-config-identity-providers.twig | 58 ++++++++++++++++++
7 files changed, 167 insertions(+)
create mode 100644 data/web/templates/admin/tab-config-identity-providers.twig
Diff
diff --git a/data/web/admin.php b/data/web/admin.php
index d0fcbc99..278ab61b 100644
--- a/data/web/admin.php
+++ b/data/web/admin.php
@@ -86,6 +86,8 @@ $cors_settings['allowed_origins'] = str_replace(", ", "\n", $cors_settings['allo
$cors_settings['allowed_methods'] = explode(", ", $cors_settings['allowed_methods']);
$f2b_data = fail2ban('get');
+// identity provider
+$identity_provider_settings = identity_provider('get');
$template = 'admin.twig';
$template_data = [
@@ -117,6 +119,7 @@ $template_data = [
'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'],
'cors_settings' => $cors_settings,
'is_https' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on',
+ 'identity_provider_settings' => $identity_provider_settings,
'lang_admin' => json_encode($lang['admin']),
'lang_datatables' => json_encode($lang['datatables'])
];
diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php
index 28cdc179..11e0b505 100644
--- a/data/web/inc/functions.inc.php
+++ b/data/web/inc/functions.inc.php
@@ -2067,6 +2067,74 @@ function uuid4() {
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
+function identity_provider($_action, $_data = null) {
+ global $pdo;
+
+ if ($_SESSION['mailcow_cc_role'] != "admin") {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_data),
+ 'msg' => 'access_denied'
+ );
+ return false;
+ }
+
+ 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"];
+ }
+ $_SESSION['return'][] = array(
+ 'type' => 'success',
+ 'log' => array(__FUNCTION__, $_action, $settings),
+ 'msg' => 'admin_api_modified'
+ );
+ return $settings;
+ case 'edit':
+ $required_settings = array('server_url', 'authsource', 'realm', 'client_id', 'client_secret', 'redirect_url', 'version');
+ foreach($required_settings as $setting){
+ if (!$_data[$setting]){
+ 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)){
+ continue;
+ }
+
+ $stmt->bindParam(':key', $key);
+ $stmt->bindParam(':value', $value);
+ $stmt->execute();
+ }
+ return true;
+ break;
+ }
+}
function get_logs($application, $lines = false) {
if ($lines === false) {
diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php
index f7bd86d2..5cfb1cd6 100644
--- a/data/web/inc/init_db.inc.php
+++ b/data/web/inc/init_db.inc.php
@@ -568,6 +568,20 @@ function init_db_schema() {
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
+ "identity_provider" => array(
+ "cols" => array(
+ "key" => "VARCHAR(255) NOT NULL",
+ "value" => "VARCHAR(255) NOT NULL",
+ "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
+ "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP"
+ ),
+ "keys" => array(
+ "primary" => array(
+ "" => array("key")
+ )
+ ),
+ "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
+ ),
"logs" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",
diff --git a/data/web/inc/prerequisites.inc.php b/data/web/inc/prerequisites.inc.php
index 1a18ba92..eac35be7 100644
--- a/data/web/inc/prerequisites.inc.php
+++ b/data/web/inc/prerequisites.inc.php
@@ -180,6 +180,24 @@ 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
+$identity_provider_settings = identity_provider('get');
+$keycloak_provider = null;
+if ($identity_provider_settings['server_url'] && $identity_provider_settings['realm'] && $identity_provider_settings['client_id'] &&
+ $identity_provider_settings['client_secret'] && $identity_provider_settings['redirect_url'] && $identity_provider_settings['version']){
+ $keycloak_provider = new Stevenmaguire\OAuth2\Client\Provider\Keycloak([
+ 'authServerUrl' => $identity_provider_settings['server_url'],
+ 'realm' => $identity_provider_settings['realm'],
+ 'clientId' => $identity_provider_settings['client_id'],
+ 'clientSecret' => $identity_provider_settings['client_secret'],
+ 'redirectUri' => $identity_provider_settings['redirect_url'],
+ 'version' => $identity_provider_settings['version'],
+ // 'encryptionAlgorithm' => 'RS256', // optional
+ // 'encryptionKeyPath' => '../key.pem' // optional
+ // 'encryptionKey' => 'contents_of_key_or_certificate' // optional
+ ]);
+}
+
// IMAP lib
// use Ddeboer\Imap\Server;
// $imap_server = new Server('dovecot', 143, '/imap/tls/novalidate-cert');
diff --git a/data/web/json_api.php b/data/web/json_api.php
index 0b80bafd..769775ce 100644
--- a/data/web/json_api.php
+++ b/data/web/json_api.php
@@ -1710,6 +1710,8 @@ if (isset($_GET['query'])) {
if ($score)
$score = array("score" => preg_replace("/\s+/", "", $score));
process_get_return($score);
+ case "identity_provider":
+ process_get_return(identity_provider('get'));
break;
break;
// return no route found if no case is matched
@@ -2082,6 +2084,8 @@ if (isset($_GET['query'])) {
break;
case "cors":
process_edit_return(cors('edit', $attr));
+ case "identity_provider":
+ process_edit_return(identity_provider('edit', $attr));
break;
// return no route found if no case is matched
default:
diff --git a/data/web/templates/admin.twig b/data/web/templates/admin.twig
index 33f2422b..53fdeadc 100644
--- a/data/web/templates/admin.twig
+++ b/data/web/templates/admin.twig
@@ -7,6 +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-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>
@@ -40,6 +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-ldap.twig' %} #}
{% include 'admin/tab-config-oauth2.twig' %}
{% include 'admin/tab-config-rspamd.twig' %}
diff --git a/data/web/templates/admin/tab-config-identity-providers.twig b/data/web/templates/admin/tab-config-identity-providers.twig
new file mode 100644
index 00000000..c81978db
--- /dev/null
+++ b/data/web/templates/admin/tab-config-identity-providers.twig
@@ -0,0 +1,58 @@
+<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>