浏览代码

[Web] manage keycloak identity provider

FreddleSpl0it 2 年之前
父节点
当前提交
f6869da3a0

+ 3 - 0
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'])
 ];

+ 68 - 0
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) {

+ 14 - 0
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",

+ 18 - 0
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');

+ 4 - 0
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:

+ 2 - 0
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' %}

+ 58 - 0
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>