]> git.ipfire.org Git - thirdparty/bacula.git/commitdiff
baculum: Add capability to assign dedicated bconsole config file to API basic users
authorMarcin Haba <marcin.haba@bacula.pl>
Fri, 12 Nov 2021 03:20:47 +0000 (04:20 +0100)
committerEric Bollengier <eric@baculasystems.com>
Thu, 24 Mar 2022 08:03:27 +0000 (09:03 +0100)
gui/baculum/protected/API/Class/BaculumAPIServer.php
gui/baculum/protected/API/Class/BasicConfig.php [new file with mode: 0644]
gui/baculum/protected/API/Pages/API/config.xml
gui/baculum/protected/API/Pages/Panel/APIBasicUsers.php
gui/baculum/protected/API/Pages/Panel/APIBasicUsers.tpl
gui/baculum/protected/API/Pages/Panel/config.xml
gui/baculum/protected/Common/Class/OAuth2.php
gui/baculum/protected/Common/Portlets/NewAuthClient.php
gui/baculum/protected/Common/Portlets/NewAuthClient.tpl

index 4713b6b25c672cacbfc681789356a5597858de9f..d4acd0462b73a4433a9371d6cc623fd68c3ef434 100644 (file)
@@ -99,6 +99,12 @@ abstract class BaculumAPIServer extends TPage {
                $config = $this->getModule('api_config')->getConfig('api');
                if ($config['auth_type'] === 'basic' && $this->getModule('auth_basic')->isAuthRequest()) {
                        $is_auth = true;
+                       $username = isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : null;
+                       if ($username) {
+                               $props = $this->getModule('basic_config')->getConfig($username);
+                               $this->initAuthParams($props);
+                       }
+
                } elseif ($config['auth_type'] === 'oauth2' && $this->getModule('auth_oauth2')->isAuthRequest()) {
                        $is_auth = $this->authorize();
                }
@@ -234,7 +240,7 @@ abstract class BaculumAPIServer extends TPage {
         */
        private function initAuthParams(array $auth) {
                // if client has own bconsole config, assign it here
-               if (array_key_exists('bconsole_cfg_path', $auth) && !empty($auth['bconsole_cfg_path'])) {
+               if (key_exists('bconsole_cfg_path', $auth) && !empty($auth['bconsole_cfg_path'])) {
                        Bconsole::setCfgPath($auth['bconsole_cfg_path'], true);
                }
        }
diff --git a/gui/baculum/protected/API/Class/BasicConfig.php b/gui/baculum/protected/API/Class/BasicConfig.php
new file mode 100644 (file)
index 0000000..b41ecc9
--- /dev/null
@@ -0,0 +1,213 @@
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum   - Bacula web interface
+ *
+ * Copyright (C) 2013-2021 Kern Sibbald
+ *
+ * The main author of Baculum is Marcin Haba.
+ * The original author of Bacula is Kern Sibbald, with contributions
+ * from many others, a complete list can be found in the file AUTHORS.
+ *
+ * You may use this file and others of this release according to the
+ * license defined in the LICENSE file, which includes the Affero General
+ * Public License, v3.0 ("AGPLv3") and some additional permissions and
+ * terms pursuant to its AGPLv3 Section 7.
+ *
+ * This notice must be preserved when any source code is
+ * conveyed and/or propagated.
+ *
+ * Bacula(R) is a registered trademark of Kern Sibbald.
+ */
+
+Prado::using('Application.Common.Class.ConfigFileModule');
+
+/**
+ * Manage Basic user configuration.
+ * Module is responsible for get/set Basic user config data.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Authorization
+ * @package Baculum API
+ */
+class BasicConfig extends ConfigFileModule {
+
+       /**
+        * Basic user config file path
+        */
+       const CONFIG_FILE_PATH = 'Application.API.Config.basic';
+
+       /**
+        * Basic user config file format
+        */
+       const CONFIG_FILE_FORMAT = 'ini';
+
+       /**
+        * These options are obligatory for Basic config.
+        */
+       private $required_options = [];
+
+       /**
+        * Stores basic user config content.
+        */
+       private $config = null;
+
+       /**
+        * Get (read) Basic user config.
+        *
+        * @access public
+        * @param string $section config section name
+        * @return array config
+        */
+       public function getConfig($section = null) {
+               $config = [];
+               if (is_null($this->config)) {
+                       $this->config = $this->readConfig(self::CONFIG_FILE_PATH, self::CONFIG_FILE_FORMAT);
+               }
+               $is_valid = true;
+               if (!is_null($section)) {
+                       $config = key_exists($section, $this->config) ? $this->config[$section] : [];
+                       $is_valid = $this->validateConfig($config);
+               } else {
+                       foreach ($this->config as $username => $value) {
+                               if ($this->validateConfig($value) === false) {
+                                       $is_valid = false;
+                                       break;
+                               }
+                               $config[$username] = $value;
+                       }
+               }
+               if ($is_valid === false) {
+                       // no validity, no config
+                       $config = [];
+               }
+               return $config;
+       }
+
+       /**
+        * Set (save) Basic user config.
+        *
+        * @access public
+        * @param array $config config
+        * @return boolean true if config saved successfully, otherwise false
+        */
+       public function setConfig(array $config) {
+               $result = $this->writeConfig($config, self::CONFIG_FILE_PATH, self::CONFIG_FILE_FORMAT);
+               if ($result === true) {
+                       $this->config = null;
+               }
+               return $result;
+       }
+
+       /**
+        * Validate API config.
+        * Config validation should be used as early as config data is available.
+        * Validation is done in read config method.
+        *
+        * @access private
+        * @param array $config config
+        * @return boolean true if config valid, otherwise false
+        */
+       private function validateConfig(array $config = array()) {
+               $is_valid = true;
+               /**
+                * Don't use validation from parent class because it logs to file in
+                * case errors and it could cause save to log a private auth params.
+                */
+               for ($i = 0; $i < count($this->required_options); $i++) {
+                       if (!key_exists($this->required_options[$i], $config)) {
+                               $is_valid = false;
+                               $emsg = 'Invalid Basic user config. Missing ' . $this->required_options[$i] . ' option.';
+                               $this->getModule('logging')->log(
+                                       __FUNCTION__,
+                                       $emsg,
+                                       Logging::CATEGORY_APPLICATION,
+                                       __FILE__,
+                                       __LINE__
+                               );
+                               break;
+                       }
+               }
+               return $is_valid;
+       }
+
+       /**
+        * Add single basic user to config.
+        * NOTE: Basic password hashes are stored in separate file.
+        * @see BasicAPIUserConfig
+        *
+        * @param string $username user name
+        * @param string $password password
+        * @param array $params user properties
+        * @return boolean true on success, otherwise false
+        */
+       public function addUser($username, $password, array $props) {
+               $success = false;
+               $config = $this->getConfig();
+               if (!key_exists($username, $config)) {
+                       $config[$username] = $props;
+                       $success = $this->setConfig($config);
+               }
+               if ($success) {
+                       // Set password in the password file
+                       $success = $this->getModule('basic_apiuser')->setUsersConfig(
+                               $username,
+                               $password
+                       );
+               }
+               // TODO: Add rollback and locking
+               return $success;
+       }
+
+       /**
+        * Edit single basic user.
+        *
+        * @param string $username user name
+        * @param string $password password
+        * @param array $params user properties
+        * @return boolean true on success, otherwise false
+        */
+       public function editUser($username, $password, array $props = []) {
+               $success = false;
+               $config = $this->getConfig();
+               if (key_exists($username, $config)) {
+                       // User exists, so edit him
+                       $config[$username] = array_merge($config[$username], $props);
+                       $success = $this->setConfig($config);
+               } else {
+                       // User does not exists, so add him.
+                       // NOTE: Not all users with password defined are in config file.
+                       $config[$username] = $props;
+                       $success = $this->setConfig($config);
+               }
+               if ($success && !empty($password)) {
+                       // Update password in the password file
+                       $success = $this->getModule('basic_apiuser')->setUsersConfig(
+                               $username,
+                               $password
+                       );
+               }
+               // TODO: Add rollback and locking
+               return $success;
+       }
+
+       /**
+        * Remove single basic user.
+        *
+        * @param string $username user name
+        * @return boolean true on success, otherwise false
+        */
+       public function removeUser($username) {
+               $success = false;
+               $config = $this->getConfig();
+               if (key_exists($username, $config)) {
+                       unset($config[$username]);
+                       $success = $this->setConfig($config);
+               }
+               if ($success) {
+                       $success = $this->getModule('basic_apiuser')->removeUser($username);
+               }
+               return $success;
+       }
+}
+?>
index 568c92f61e2ce418c41f5c3dfcecf91a45310655..a44502c1aabbeedfb05a90db8ec2c827e9c2bad5 100644 (file)
@@ -10,6 +10,9 @@
                <module id="oauth2_authid" class="Application.API.Class.OAuth2.AuthIdManager" />
                <module id="oauth2_token" class="Application.API.Class.OAuth2.TokenManager" />
 
+               <!-- Basic user config -->
+               <module id="basic_config" class="Application.API.Class.BasicConfig" />
+
                <!-- API Server modules -->
                <module id="api_server_v1" class="Application.API.Class.APIServerV1" />
                <module id="api_server_v2" class="Application.API.Class.APIServerV2" />
index e4688b03c4e8124e8ca6f3a489e6884967cd67fd..d9c6f278a07a05b1eac4bf24f5d7723988960928 100644 (file)
@@ -76,17 +76,24 @@ class APIBasicUsers extends BaculumAPIPage {
 
        private function getBasicUsers() {
                $basic_users = array();
-               $basic_cfg = $this->getModule('basic_apiuser')->getUsers();
-               foreach($basic_cfg as $user => $pwd) {
-                       $basic_users[] = ['username' => $user];
+               $basic_apiuser = $this->getModule('basic_apiuser')->getUsers();
+               $basic_config = $this->getModule('basic_config')->getConfig();
+               foreach($basic_apiuser as $user => $pwd) {
+                       $bconsole_cfg_path = '';
+                       if (key_exists($user, $basic_config) && key_exists('bconsole_cfg_path', $basic_config[$user])) {
+                               $bconsole_cfg_path = $basic_config[$user]['bconsole_cfg_path'];
+                       }
+                       $basic_users[] = [
+                               'username' => $user,
+                               'bconsole_cfg_path' => $bconsole_cfg_path
+                       ];
                }
                return $basic_users;
        }
 
        public function deleteBasicUser($sender, $param) {
-               $config = $this->getModule('basic_apiuser');
                $username = $param->getCallbackParameter();
-               $config->removeUser($username);
+               $this->getModule('basic_config')->removeUser($username);
                $this->loadBasicUsers(null, null);
        }
 }
index f71ee2ce41efed8756f175a982352104f6512613..5ecbba64db68049518f3640818685a59986f0b8a 100644 (file)
@@ -14,6 +14,7 @@
                                <tr>
                                        <th></th>
                                        <th><%[ Username ]%></th>
+                                       <th><%[ Dedicated Bconsole config ]%></th>
                                        <th><%[ Actions ]%></th>
                                </tr>
                        </thead>
@@ -22,6 +23,7 @@
                                <tr>
                                        <th></th>
                                        <th><%[ Username ]%></th>
+                                       <th><%[ Dedicated Bconsole config ]%></th>
                                        <th><%[ Actions ]%></th>
                                </tr>
                        </tfoot>
@@ -61,6 +63,23 @@ var oBasicUserList = {
                                        defaultContent: '<button type="button" class="w3-button w3-blue"><i class="fa fa-angle-down"></i></button>'
                                },
                                {data: 'username'},
+                               {
+                                       data: 'bconsole_cfg_path',
+                                       render: function(data, type, row) {
+                                               var ret;
+                                               if (type == 'display') {
+                                                       ret = '';
+                                                       if (data) {
+                                                               var check = document.createElement('I');
+                                                               check.className = 'fas fa-check';
+                                                               ret = check.outerHTML;
+                                                       }
+                                               } else {
+                                                       ret = data;
+                                               }
+                                               return ret;
+                                       }
+                               },
                                {
                                        data: 'username',
                                        render: function(data, type, row) {
@@ -110,10 +129,20 @@ var oBasicUserList = {
                        },
                        {
                                className: "dt-center",
-                               targets: [ 2 ]
+                               targets: [ 2, 3 ]
                        }],
                        order: [1, 'asc'],
                });
+       },
+       get_user_props: function(username) {
+               var props = {};
+               for (var i = 0; i < this.data.length; i++) {
+                       if (this.data[i].username === username) {
+                               props = this.data[i];
+                               break;
+                       }
+               }
+               return props;
        }
 };
 </script>
@@ -197,9 +226,8 @@ var oAPIBasicUsers = {
        },
        edit_user: function(username) {
                this.edit_obj.clear_basic_fields();
-               this.edit_obj.set_basic_props({
-                       username: username
-               });
+               var props = oBasicUserList.get_user_props(username);
+               this.edit_obj.set_basic_props(props);
                this.show_edit_user_window(true);
        },
        delete_user: function(username) {
index dfc9b44e7d0c570b93bc1df606e02b3d104adb5a..44cd6f41d183b4cbfcd4aff68c08437ae24bdbcc 100644 (file)
@@ -22,6 +22,7 @@
                </module>
                <!-- auth modules -->
                <module id="basic_apiuser" class="Application.API.Class.BasicAPIUserConfig" />
+               <module id="basic_config" class="Application.API.Class.BasicConfig" />
                <module id="oauth2_config" class="Application.API.Class.OAuth2.OAuth2Config" />
                <!-- component actions modules -->
                <module id="comp_actions" class="Application.API.Class.ComponentActions" />
index 0b55fe45edcb6adbc8a25c06aeede4f8d05074b2..c4da2cfed4ec792f18f8d34edbc012089c819c84 100644 (file)
@@ -84,10 +84,10 @@ abstract class OAuth2 extends CommonModule {
        /**
         * Expiration time in seconds for access token.
         * 
-        * Temportary set to 15 minutes for testst purposes.
-        * In production the value SHOULD BE changed.
+        * It is 1 hour by default.
+        * In production the value can be changed.
         */
-       const ACCESS_TOKEN_EXPIRES_TIME = 120;
+       const ACCESS_TOKEN_EXPIRES_TIME = 3600;
 
        /**
         * Scope pattern.
index 409fdd017ab97cd45524eeefd591918b931d81e3..961138f43b5b2f267bc98f890173de57d9fda18c 100644 (file)
@@ -62,22 +62,27 @@ class NewAuthClient extends PortletTemplate {
 
                $result = false;
                $exists = false;
-               $config = $this->getModule('api_config')->getConfig();
                if ($this->getAuthType() === self::AUTH_TYPE_BASIC) {
+                       $basic_config = $this->getModule('basic_config');
+                       $props = [
+                               'bconsole_cfg_path' => $this->APIBasicBconsoleCfgPath->Text
+                       ];
                        if ($this->Mode == self::MODE_TYPE_ADD) {
                                $users = $this->getModule('basic_apiuser')->getUsers();
                                if (!key_exists($this->APIBasicLogin->Text, $users)) {
-                                       $result = $this->getModule('basic_apiuser')->setUsersConfig(
+                                       $result = $basic_config->addUser(
                                                $this->APIBasicLogin->Text,
-                                               $this->APIBasicPassword->Text
+                                               $this->APIBasicPassword->Text,
+                                               $props
                                        );
                                } else {
                                        $exists = true;
                                }
                        } elseif ($this->Mode === self::MODE_TYPE_EDIT) {
-                               $result = $this->getModule('basic_apiuser')->setUsersConfig(
+                               $result = $basic_config->editUser(
                                        $this->APIBasicLoginHidden->Value,
-                                       $this->APIBasicPassword->Text
+                                       $this->APIBasicPassword->Text,
+                                       $props
                                );
                        }
                } elseif ($this->getAuthType() === self::AUTH_TYPE_OAUTH2) {
index f9e4bd13cbf2e3ce38d1ae4bd7f4d09153b06b13..13fbe95b31bcae4108be9655dbe5b92cdb91930b 100644 (file)
@@ -6,8 +6,8 @@
 <div class="w3-container">
        <div class="w3-padding" style="display: <%=($this->getAuthType() == 'basic' ? '' : 'none')%>">
                <div class="w3-row w3-section">
-                       <div class="w3-col w3-quarter"><com:TLabel ForControl="APIBasicLogin" Text="<%[ API Login: ]%>" /></div>
-                       <div class="w3-col w3-threequarter">
+                       <div class="w3-col w3-third"><com:TLabel ForControl="APIBasicLogin" Text="<%[ API Login: ]%>" /></div>
+                       <div class="w3-col w3-twothird">
                                <com:TActiveTextBox
                                        ID="APIBasicLogin"
                                        CssClass="w3-input w3-border"
@@ -23,7 +23,7 @@
                                        ControlToValidate="APIBasicLogin"
                                        ValidationGroup="<%=$this->ClientID%>Basic"
                                        Text="<%[ Please enter API login. ]%>"
-                                />
+                               />
                                <com:TRegularExpressionValidator
                                        ValidationGroup="<%=$this->ClientID%>Basic"
                                        ControlToValidate="APIBasicLogin"
@@ -34,8 +34,8 @@
                        </div>
                </div>
                <div class="w3-row w3-section">
-                       <div class="w3-col w3-quarter"><com:TLabel ForControl="APIBasicPassword" Text="<%[ API Password: ]%>" /></div>
-                       <div class="w3-col w3-threequarter">
+                       <div class="w3-col w3-third"><com:TLabel ForControl="APIBasicPassword" Text="<%[ API Password: ]%>" /></div>
+                       <div class="w3-col w3-twothird">
                                <com:TActiveTextBox
                                        ID="APIBasicPassword"
                                        TextMode="Password"
                                        ControlToValidate="APIBasicPassword"
                                        ValidationGroup="<%=$this->ClientID%>Basic"
                                        Text="<%[ Please enter API password. ]%>"
-                               />
+                               >
+                                       <prop:ClientSide.OnValidate>
+                                               sender.enabled = <%=$this->Mode == 'add' ? 'true': 'false'%>;
+                                       </prop:ClientSide.OnValidate>
+                               </com:TRequiredFieldValidator>
                                <com:TRegularExpressionValidator
                                        CssClass="validator-block"
                                        Display="Dynamic"
@@ -65,8 +69,8 @@
                        </div>
                </div>
                <div class="w3-row w3-section">
-                       <div class="w3-col w3-quarter"><com:TLabel ForControl="RetypeAPIBasicPassword" Text="<%[ Retype password: ]%>" /></div>
-                       <div class="w3-col w3-threequarter">
+                       <div class="w3-col w3-third"><com:TLabel ForControl="RetypeAPIBasicPassword" Text="<%[ Retype password: ]%>" /></div>
+                       <div class="w3-col w3-twothird">
                                <com:TActiveTextBox
                                        ID="RetypeAPIBasicPassword"
                                        CssClass="w3-input w3-border"
                                        ControlToValidate="RetypeAPIBasicPassword"
                                        ValidationGroup="<%=$this->ClientID%>Basic"
                                        Text="<%[ Please enter retype password. ]%>"
-                               />
+                               >
+                                       <prop:ClientSide.OnValidate>
+                                               sender.enabled = <%=$this->Mode == 'add' ? 'true': 'false'%>;
+                                       </prop:ClientSide.OnValidate>
+                               </com:TRequiredFieldValidator>
                                <com:TRegularExpressionValidator
                                        CssClass="validator-block"
                                        Display="Dynamic"
                                />
                        </div>
                </div>
+               <div class="w3-row w3-section">
+                       <div class="w3-col w3-third"><com:TLabel ForControl="APIBasicBconsoleCfgPath" Text="<%[ Dedicated Bconsole config file path: ]%>" /></div>
+                       <div class="w3-col w3-twothird">
+                               <com:TTextBox
+                                       ID="APIBasicBconsoleCfgPath"
+                                       CssClass="w3-input w3-border"
+                                       Style="width: 70%"
+                                       CausesValidation="false"
+                               /> <%[ (optional) ]%>
+                       </div>
+               </div>
        </div>
        <div style="display: <%=($this->getAuthType() == 'oauth2' ? '' : 'none')%>">
                <div class="w3-row w3-section">
@@ -266,7 +285,8 @@ var <%=$this->ClientID%>oNewAuthClient = {
                        username: '<%=$this->APIBasicLogin->ClientID%>',
                        username_hidden: '<%=$this->APIBasicLoginHidden->ClientID%>',
                        password: '<%=$this->APIBasicPassword->ClientID%>',
-                       password_retype: '<%=$this->RetypeAPIBasicPassword->ClientID%>'
+                       password_retype: '<%=$this->RetypeAPIBasicPassword->ClientID%>',
+                       bconsole_cfg_path: '<%=$this->APIBasicBconsoleCfgPath->ClientID%>'
                },
                oauth2: {
                        client_id: '<%=$this->APIOAuth2ClientId->ClientID%>',
@@ -289,6 +309,9 @@ var <%=$this->ClientID%>oNewAuthClient = {
                if (props.hasOwnProperty('username')) {
                        document.getElementById(this.ids.basic.username).value = props.username;
                        document.getElementById(this.ids.basic.username_hidden).value = props.username;
+                       if (props.hasOwnProperty('bconsole_cfg_path')) {
+                               document.getElementById(this.ids.basic.bconsole_cfg_path).value = props.bconsole_cfg_path;
+                       }
                }
        },
        set_oauth2_props: function(props) {
@@ -320,6 +343,7 @@ var <%=$this->ClientID%>oNewAuthClient = {
                document.getElementById(this.ids.basic.username_hidden).value = '';
                document.getElementById(this.ids.basic.password).value = '';
                document.getElementById(this.ids.basic.password_retype).value = '';
+               document.getElementById(this.ids.basic.bconsole_cfg_path).value = '';
        },
        clear_oauth2_fields: function() {
                document.getElementById(this.ids.oauth2.client_id).value = '';