]> git.ipfire.org Git - thirdparty/bacula.git/commitdiff
baculum: New user management. LDAP support. Role-based access control.
authorMarcin Haba <marcin.haba@bacula.pl>
Tue, 19 May 2020 17:31:43 +0000 (19:31 +0200)
committerMarcin Haba <marcin.haba@bacula.pl>
Tue, 19 May 2020 18:46:11 +0000 (20:46 +0200)
90 files changed:
gui/baculum/.gitignore
gui/baculum/Makefile
gui/baculum/examples/deb-template/baculum-web-lighttpd.conf
gui/baculum/examples/deb/baculum-web-lighttpd.conf
gui/baculum/examples/rpm-template/baculum-web-lighttpd.conf
gui/baculum/examples/rpm/baculum-web-lighttpd.conf
gui/baculum/examples/selinux/baculum-web.te
gui/baculum/protected/API/Class/APIConfig.php
gui/baculum/protected/API/Class/BaculumAPIPage.php
gui/baculum/protected/API/Class/BasicAPIUserConfig.php
gui/baculum/protected/API/Pages/Panel/APIHome.php
gui/baculum/protected/API/Pages/Panel/APIInstallWizard.php
gui/baculum/protected/Common/Class/Apr1Md5.php [new file with mode: 0644]
gui/baculum/protected/Common/Class/BClientScript.php
gui/baculum/protected/Common/Class/BCrypt.php [new file with mode: 0644]
gui/baculum/protected/Common/Class/BaculumPage.php
gui/baculum/protected/Common/Class/BaculumUrlMapping.php
gui/baculum/protected/Common/Class/BasicUserConfig.php
gui/baculum/protected/Common/Class/Crypto.php [new file with mode: 0644]
gui/baculum/protected/Common/Class/Interfaces.php
gui/baculum/protected/Common/Class/Ldap.php [new file with mode: 0644]
gui/baculum/protected/Common/Class/Miscellaneous.php
gui/baculum/protected/Common/Class/SessionRecord.php
gui/baculum/protected/Common/Class/Sha1.php [new file with mode: 0644]
gui/baculum/protected/Common/Class/Sha256.php [new file with mode: 0644]
gui/baculum/protected/Common/Class/Sha512.php [new file with mode: 0644]
gui/baculum/protected/Common/Class/Ssha1.php [new file with mode: 0644]
gui/baculum/protected/Common/Portlets/NewAuthClient.php
gui/baculum/protected/Web/Class/BaculumAPIClient.php
gui/baculum/protected/Web/Class/BaculumWebPage.php
gui/baculum/protected/Web/Class/BasicWebUserConfig.php
gui/baculum/protected/Web/Class/PageCategory.php [new file with mode: 0644]
gui/baculum/protected/Web/Class/WebBasicUserManager.php [new file with mode: 0644]
gui/baculum/protected/Web/Class/WebConfig.php
gui/baculum/protected/Web/Class/WebLdapUserManager.php [new file with mode: 0644]
gui/baculum/protected/Web/Class/WebRoleConfig.php [new file with mode: 0644]
gui/baculum/protected/Web/Class/WebUser.php [new file with mode: 0644]
gui/baculum/protected/Web/Class/WebUserConfig.php [new file with mode: 0644]
gui/baculum/protected/Web/Class/WebUserManager.php [new file with mode: 0644]
gui/baculum/protected/Web/Class/WebUserRoles.php [new file with mode: 0644]
gui/baculum/protected/Web/Init.php
gui/baculum/protected/Web/JavaScript/misc.js
gui/baculum/protected/Web/Lang/en/messages.mo
gui/baculum/protected/Web/Lang/en/messages.po
gui/baculum/protected/Web/Lang/ja/messages.mo
gui/baculum/protected/Web/Lang/ja/messages.po
gui/baculum/protected/Web/Lang/pl/messages.mo
gui/baculum/protected/Web/Lang/pl/messages.po
gui/baculum/protected/Web/Lang/pt/messages.mo
gui/baculum/protected/Web/Lang/pt/messages.po
gui/baculum/protected/Web/Layouts/Simple.php [new file with mode: 0644]
gui/baculum/protected/Web/Layouts/Simple.tpl [new file with mode: 0644]
gui/baculum/protected/Web/Pages/ApplicationSettings.php
gui/baculum/protected/Web/Pages/ConfigureHosts.php
gui/baculum/protected/Web/Pages/FileSetList.php
gui/baculum/protected/Web/Pages/FileSetView.php
gui/baculum/protected/Web/Pages/LoginPage.page [new file with mode: 0644]
gui/baculum/protected/Web/Pages/LoginPage.php [new file with mode: 0644]
gui/baculum/protected/Web/Pages/Monitor.php
gui/baculum/protected/Web/Pages/NewJobWizard.php
gui/baculum/protected/Web/Pages/NewResource.php
gui/baculum/protected/Web/Pages/PoolList.php
gui/baculum/protected/Web/Pages/PoolView.php
gui/baculum/protected/Web/Pages/Requirements.php
gui/baculum/protected/Web/Pages/ScheduleList.php
gui/baculum/protected/Web/Pages/ScheduleView.php
gui/baculum/protected/Web/Pages/Security.page [new file with mode: 0644]
gui/baculum/protected/Web/Pages/Security.php [new file with mode: 0644]
gui/baculum/protected/Web/Pages/StatisticsList.php
gui/baculum/protected/Web/Pages/StatisticsView.php
gui/baculum/protected/Web/Pages/StorageList.php
gui/baculum/protected/Web/Pages/StorageView.php
gui/baculum/protected/Web/Pages/Users.page [deleted file]
gui/baculum/protected/Web/Pages/Users.php [deleted file]
gui/baculum/protected/Web/Pages/VolumeList.php
gui/baculum/protected/Web/Pages/VolumeView.php
gui/baculum/protected/Web/Pages/WebConfigWizard.page
gui/baculum/protected/Web/Pages/WebConfigWizard.php
gui/baculum/protected/Web/Pages/config.xml
gui/baculum/protected/Web/Portlets/BaculaHosts.php
gui/baculum/protected/Web/Portlets/DirectiveListBox.tpl
gui/baculum/protected/Web/Portlets/MainSideBar.php
gui/baculum/protected/Web/Portlets/MainSideBar.tpl
gui/baculum/protected/Web/Portlets/RunJob.php
gui/baculum/protected/Web/Portlets/Users.php [deleted file]
gui/baculum/protected/Web/Portlets/Users.tpl [deleted file]
gui/baculum/protected/Web/endpoints.xml
gui/baculum/protected/application.xml
gui/baculum/themes/Baculum-v2/css/baculum.css
gui/baculum/themes/Baculum-v2/logo_xl.png [new file with mode: 0644]

index eea02cf0209ad06d8d643aa828ef296b57ae604e..3ba8c0b61509c68d7c1af1ebb6ccb8bd1e688c71 100644 (file)
@@ -3,6 +3,8 @@ protected/runtime/*
 protected/Web/Logs/*
 protected/Web/Config/baculum.users
 protected/Web/Config/hosts.conf
+protected/Web/Config/users.conf
+protected/Web/Config/roles.conf
 protected/Web/Config/settings.conf
 protected/Web/Config/session.dump
 protected/API/Config/baculum.users
index 224d35a96b5fd23b8f43f06a3b69c20f52718dca..584279cfc5ca39d4a7007d147cf277600bf05886 100644 (file)
@@ -70,7 +70,8 @@ localeweblang = en pl pt ja
 localeapilang = en pl pt
 localewebdirsrc = $(datadir)/$(webdir)/Lang
 localeapidirsrc = $(datadir)/$(apidir)/Lang
-localefile = messages.mo
+localemofile = messages.mo
+localepofile = messages.po
 
 excluded_dirs = '.*/\(3rdParty\|tinymce-405\).*'
 excluded_files = '.*\(\.htaccess\)$$'
@@ -117,14 +118,14 @@ prepare_locale: prepare_build
        for lang in $(localeweblang) ; do \
                mkdir -p $(DESTDIR)$(LOCALEDIR)/$$lang/LC_MESSAGES ; \
                mkdir -p $(DESTDIR)$(WWWDIR)/$(localewebdirsrc)/$$lang ; \
-               install -m 644 $(localewebdirsrc)/$$lang/$(localefile) $(DESTDIR)$(LOCALEDIR)/$$lang/LC_MESSAGES/$(NAME)-web.mo ; \
-               ln -s $(DESTDIR)$(LOCALEDIR)/$$lang/LC_MESSAGES/$(NAME)-web.mo $(DESTDIR)$(WWWDIR)/$(localewebdirsrc)/$$lang/$(localefile) ; \
+               install -m 644 $(localewebdirsrc)/$$lang/$(localemofile) $(DESTDIR)$(LOCALEDIR)/$$lang/LC_MESSAGES/$(NAME)-web.mo ; \
+               ln -s $(DESTDIR)$(LOCALEDIR)/$$lang/LC_MESSAGES/$(NAME)-web.mo $(DESTDIR)$(WWWDIR)/$(localewebdirsrc)/$$lang/$(localemofile) ; \
        done
        for lang in $(localeapilang) ; do \
                mkdir -p $(DESTDIR)$(LOCALEDIR)/$$lang/LC_MESSAGES ; \
                mkdir -p $(DESTDIR)$(WWWDIR)/$(localeapidirsrc)/$$lang ; \
-               install -m 644 $(localeapidirsrc)/$$lang/$(localefile) $(DESTDIR)$(LOCALEDIR)/$$lang/LC_MESSAGES/$(NAME)-api.mo ; \
-               ln -s $(DESTDIR)$(LOCALEDIR)/$$lang/LC_MESSAGES/$(NAME)-api.mo $(DESTDIR)$(WWWDIR)/$(localeapidirsrc)/$$lang/$(localefile) ; \
+               install -m 644 $(localeapidirsrc)/$$lang/$(localemofile) $(DESTDIR)$(LOCALEDIR)/$$lang/LC_MESSAGES/$(NAME)-api.mo ; \
+               ln -s $(DESTDIR)$(LOCALEDIR)/$$lang/LC_MESSAGES/$(NAME)-api.mo $(DESTDIR)$(WWWDIR)/$(localeapidirsrc)/$$lang/$(localemofile) ; \
        done
 
 prepare_samples:
@@ -160,3 +161,11 @@ setup: prepare_samples prepare_check_script
        sed -i -e "s#%CONFDIR#$(CONFDIR)#g" $(DESTDIR)$(UNITDIR)/$(NAME)-api-lighttpd.service
        sed -i -e "s#%CONFDIR#$(CONFDIR)#g" $(DESTDIR)$(UNITDIR)/$(NAME)-web-lighttpd.service
        find $(DESTDIR)/ -type f -name .gitignore -exec rm -f {} \;
+
+lang_mo:
+       for lang in $(localeapilang) ; do \
+               msgfmt -o $(localeapidirsrc)/$$lang/$(localemofile) $(localeapidirsrc)/$$lang/$(localepofile); \
+       done
+       for lang in $(localeweblang) ; do \
+               msgfmt -o $(localewebdirsrc)/$$lang/$(localemofile) $(localewebdirsrc)/$$lang/$(localepofile); \
+       done
index 0cf6112dd10061478ec9e6414528a40483bd8703..e79e71f4d7f03e2a05805e3a3d36d42948cbff74 100644 (file)
@@ -82,7 +82,7 @@ fastcgi.server = (
 url.rewrite-once = (
        "^/themes/(.+)$" => "/themes/$1",
        "^/assets/(.+)$" => "/assets/$1",
-       "^/$" => "/index.php?web",
+       "^/$" => "/index.php/web",
        "^/(.+)$" => "/index.php/$1"
 )
 
index 787b436f663e45b31aa4882172c9ba681e767384..f0cbd871a3c0f27ca1be6b44df9082820ff0b179 100644 (file)
@@ -82,7 +82,7 @@ fastcgi.server = (
 url.rewrite-once = (
        "^/themes/(.+)$" => "/themes/$1",
        "^/assets/(.+)$" => "/assets/$1",
-       "^/$" => "/index.php?web",
+       "^/$" => "/index.php/web",
        "^/(.+)$" => "/index.php/$1"
 )
 
index 97bc237c2a56eb419c8edb26ac5cc2acf6d3457f..0bbf833c39f8741208c0d7531f1c5459a1574122 100644 (file)
@@ -82,7 +82,7 @@ fastcgi.server = (
 url.rewrite-once = (
        "^/themes/(.+)$" => "/themes/$1",
        "^/assets/(.+)$" => "/assets/$1",
-       "^/$" => "/index.php?web",
+       "^/$" => "/index.php/web",
        "^/(.+)$" => "/index.php/$1"
 )
 
index ee3883456e04cbec710fb26c1a54cd3931e0aba9..f02fac68b094b0ea9b83e18a44f914cbf61e632d 100644 (file)
@@ -82,7 +82,7 @@ fastcgi.server = (
 url.rewrite-once = (
        "^/themes/(.+)$" => "/themes/$1",
        "^/assets/(.+)$" => "/assets/$1",
-       "^/$" => "/index.php?web",
+       "^/$" => "/index.php/web",
        "^/(.+)$" => "/index.php/$1"
 )
 
index a4273eea4833e4de9400f01823f5cc0062b5ef2d..a57e94fb5c0a1717d460b6930e96fdadc9534b2d 100644 (file)
@@ -1,9 +1,10 @@
-module baculum-web 1.0.0;
+module baculum-web 1.0.1;
 
 require {
        type httpd_t;
        type unreserved_port_t;
        type httpd_cache_t;
+       type ldap_port_t;
        class tcp_socket { name_bind name_connect };
        class dir { search read write create getattr };
        class file { read write create };
@@ -12,5 +13,6 @@ require {
 #============= httpd_t ==============
 
 allow httpd_t unreserved_port_t:tcp_socket { name_bind name_connect };
+allow httpd_t ldap_port_t:tcp_socket { name_connect };
 allow httpd_t httpd_cache_t:dir { read create };
 allow httpd_t httpd_cache_t:file { read write create };
index 50543c5d861ff0c7e4ae04c6e8878fc6979e317a..014ac2c592462a322046c27c1dc28cd0bed46574 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -35,7 +35,7 @@ class APIConfig extends ConfigFileModule {
        /**
         * Default application language
         */
-       const DEFAULT_LANGUAGE = 'en';
+       const DEF_LANG = 'en';
 
        /**
         * API config file path
index f6d974464efa38dbf8a5bfde944e8b7dc0768572..32b0b401dd04c1a117ae7bb80ce938e77f75de95 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -38,7 +38,7 @@ class BaculumAPIPage extends BaculumPage {
        public function onPreInit($param) {
                parent::onPreInit($param);
                $config = $this->getModule('api_config')->getConfig('api');
-               $lang = array_key_exists('lang', $config) ? $config['lang'] : APIConfig::DEFAULT_LANGUAGE;
+               $lang = array_key_exists('lang', $config) ? $config['lang'] : APIConfig::DEF_LANG;
                $this->Application->getGlobalization()->Culture = $lang;
        }
 }
index 6f040891ea5a889e4659e64f2f9b7796fda87522..cbe630f44a8bdd088c087a65694e90bc5a5e4a8d 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -37,7 +37,8 @@ class BasicAPIUserConfig extends BasicUserConfig {
        const USERS_FILE_NAME = 'Application.API.Config.baculum';
        const USERS_FILE_EXTENSION = '.users';
 
-       protected function getConfigPath() {
-               return Prado::getPathOfNamespace(self::USERS_FILE_NAME, self::USERS_FILE_EXTENSION);
+       public function getConfigPath() {
+               // First check if custom config path is set, if not, then use default users file
+               return parent::getConfigPath() ?: Prado::getPathOfNamespace(self::USERS_FILE_NAME, self::USERS_FILE_EXTENSION);
        }
 }
index d0246ff5fa947708db21eb4e69463435d2d1a2f3..30f9aef3ea387ad46d8b67dd262020ae6a9c6338 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -107,7 +107,7 @@ class APIHome extends BaculumAPIPage {
                                $values[] = "{$oauth2_cfg[$ids[$i]]['client_id']} ({$oauth2_cfg[$ids[$i]]['name']})";
                        }
                } elseif ($config['api']['auth_type'] === 'basic') {
-                       $api_user_cfg = $this->getModule('basic_apiuser')->getAllUsers();
+                       $api_user_cfg = $this->getModule('basic_apiuser')->getUsers();
                        $values = $ids = array_keys($api_user_cfg);
                }
                $this->AuthParamsCombo->DataSource = array_combine($ids, $values);
@@ -116,7 +116,7 @@ class APIHome extends BaculumAPIPage {
 
        private function getBasicUsers() {
                $basic_users = array();
-               $basic_cfg = $this->getModule('basic_apiuser')->getAllUsers();
+               $basic_cfg = $this->getModule('basic_apiuser')->getUsers();
                foreach($basic_cfg as $user => $pwd) {
                        $basic_users[] = array('username' => $user);
                }
index 99766f82c92ae97230fa9dc4a67f12f63133febf..20ded02a28688e7c4bbcf9a051faefd79c227cf2 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -244,7 +244,7 @@ class APIInstallWizard extends BaculumAPIPage {
                        $cfg_data['api']['auth_type'] =  'oauth2';
                }
                $cfg_data['api']['debug'] = isset($this->config['api']['debug']) ? $this->config['api']['debug'] : "0";
-               $cfg_data['api']['lang'] = isset($_SESSION['language']) ? $_SESSION['language'] : APIConfig::DEFAULT_LANGUAGE;
+               $cfg_data['api']['lang'] = isset($_SESSION['language']) ? $_SESSION['language'] : APIConfig::DEF_LANG;
                $cfg_data['db']['enabled'] = (integer)($this->DatabaseYes->Checked === true);
                $cfg_data['db']['type'] = $this->DBType->SelectedValue;
                $cfg_data['db']['name'] = $this->DBName->Text;
diff --git a/gui/baculum/protected/Common/Class/Apr1Md5.php b/gui/baculum/protected/Common/Class/Apr1Md5.php
new file mode 100644 (file)
index 0000000..c12106b
--- /dev/null
@@ -0,0 +1,85 @@
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum   - Bacula web interface
+ *
+ * Copyright (C) 2013-2020 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.CommonModule');
+
+/**
+ * Cryptographic APR1-MD5 hashing function module.
+ * Module is responsible for providing APR1-MD5 support.
+ *
+ * @category Module
+ * @package Baculum Common
+ */
+class Apr1Md5 extends CommonModule {
+
+       /**
+        * Get hashed password using APR1-MD5 algorithm.
+        * This function is based on common sample using PHP implementation APR1-MD5.
+        * The original author is unknown.
+        * @see https://stackoverflow.com/questions/1038791/how-to-programmatically-build-an-apr1-md5-using-php
+        *
+        * @param string $password plain text password
+        * @return string hashed password
+        */
+       public function crypt($password) {
+               $salt = $this->getModule('crypto')->getRandomString(8);
+               $len = strlen($password);
+               $text = sprintf('%s$apr1$%s', $password, $salt);
+               $bin = pack('H32', md5($password . $salt . $password));
+               for ($i = $len; $i > 0; $i -= 16) {
+                       $text .= substr($bin, 0, min(16, $i));
+               }
+               for ($i = $len; $i > 0; $i >>= 1) {
+                       $text .= ($i & 1) ? chr(0) : $password[0];
+               }
+               $bin = pack('H32', md5($text));
+               for ($i = 0; $i < 1000; $i++) {
+                       $new = ($i & 1) ? $password : $bin;
+                       if ($i % 3) {
+                               $new .= $salt;
+                       }
+                       if ($i % 7) {
+                               $new .= $password;
+                       }
+                       $new .= ($i & 1) ? $bin : $password;
+                       $bin = pack('H32', md5($new));
+               }
+               $tmp = null;
+               for ($i = 0; $i < 5; $i++) {
+                       $k = $i + 6;
+                       $j = $i + 12;
+                       if ($j == 16) {
+                               $j = 5;
+                       }
+                       $tmp = $bin[$i] . $bin[$k] . $bin[$j] . $tmp;
+               }
+               $tmp = chr(0) . chr(0) . $bin[11] . $tmp;
+               $str = strrev(substr(base64_encode($tmp), 2));
+               $tmp = strtr(
+                       $str,
+                       'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
+                       './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
+               );
+               return sprintf('$apr1$%s$%s', $salt, $tmp);
+       }
+}
+?>
index baf6878b546e990298f472d01a1ad972c52757aa..8d916f4df5717408236ab8c1a17c4d5bb24090c1 100644 (file)
@@ -31,7 +31,7 @@ Prado::using('System.Web.UI.WebControls.TClientScript');
  */
 class BClientScript extends TClientScript {
 
-       const SCRIPTS_VERSION = 6;
+       const SCRIPTS_VERSION = 7;
 
        public function getScriptUrl()
        {
diff --git a/gui/baculum/protected/Common/Class/BCrypt.php b/gui/baculum/protected/Common/Class/BCrypt.php
new file mode 100644 (file)
index 0000000..2a7d6b8
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum   - Bacula web interface
+ *
+ * Copyright (C) 2013-2020 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.CommonModule');
+
+/**
+ * Cryptographic BCrypt hashing function module
+ * Module is responsible for providing BCrypt support.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Module
+ * @package Baculum Common
+ */
+class BCrypt extends CommonModule {
+
+       // bcrypt uses not standard base64 alphabet
+       const BCRYPT_BASE64_CODE = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+
+       // bcrypt cost parameter - number of iterations during hashing
+       const BCRYPT_COST = 10;
+
+       /**
+        * Get hashed password using BCrypt algorithm and salt.
+        *
+        * @param string $password plain text password
+        * @return string hashed password
+        */
+       public function crypt($password) {
+               // Suffle string
+               $rand_string = str_shuffle(self::BCRYPT_BASE64_CODE);
+
+               // BCrypt salt - 22 characters
+               $salt_str = substr($rand_string, 0, 22);
+
+               $salt = sprintf(
+                       '$2y$%d$%s$',
+                       self::BCRYPT_COST,
+                       $salt_str
+               );
+               return crypt($password, $salt);
+       }
+}
+?>
index 2cdb8fd10baab2a2db4628e4d25fd2d6bcb111c0..789310934c39fe2127500e4bced620053791b3fa 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -129,27 +129,29 @@ class BaculumPage extends TPage {
        }
 
        /**
-        * Log in as specific user.
+        * Get full main URL to log in with user and password.
+        * It is useful to work with Basic authentication (login or logout for example).
         *
-        * Note, usually after this method call there required is using exit() just
-        * after method execution. Otherwise the HTTP redirection may be canceled on some
-        * web servers.
-        *
-        * @access public
         * @param string $user user name to log in
-        * @param string $string plain text user's password
-        * @return none
+        * @param string $password plain text user's password
+        * @return string full login URL
         */
-       public function switchToUser($user, $password) {
-               $http_protocol = isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) ? 'https' : 'http';
+       public function getFullLoginUrl($user, $password) {
+               $protocol = isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) ? 'https' : 'http';
                $host = $_SERVER['SERVER_NAME'];
                $port = $_SERVER['SERVER_PORT'];
-               $url_prefix = $this->Application->getModule('url_manager')->getUrlPrefix();
+               $url_prefix = $this->getModule('url_manager')->getUrlPrefix();
                $url_prefix = str_replace('/index.php', '', $url_prefix);
-               $location = sprintf("%s://%s:%s@%s:%d%s", $http_protocol, $user, $password, $host, $port, $url_prefix);
-
-               // Log in by header
-               header("Location: $location");
+               $location = sprintf(
+                       '%s://%s:%s@%s:%d%s',
+                       $protocol,
+                       $user,
+                       $password,
+                       $host,
+                       $port,
+                       $url_prefix
+               );
+               return $location;
        }
 
        public function setStyleSheetFiles(){
index a6f34a0351d63e7272f5ee381840ce758e6b9064..0c54fd9b912902cd18672603e486e17c35ba60d7 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -59,6 +59,20 @@ class BaculumUrlMapping extends TUrlMapping {
                $this->setServiceUrlManager();
        }
 
+       /**
+        * Get all pages for current service.
+        * Pages are taken directly from configuration file.
+        *
+        * @return array all pages for service.
+        */
+       public function getPages() {
+               $pages = [];
+               foreach ($this->_patterns as $pattern) {
+                       $pages[] = $pattern->getServiceParameter();
+               }
+               return $pages;
+       }
+
        private function getServiceID() {
                $service_id = null;
                $url = $this->getRequestedUrl();
index ac0bb15fd988d5b88427586fa286c909d267e23e..ba9119c6f5eedcb5dca7b07104563c762593c652 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -29,7 +29,12 @@ Prado::using('Application.Common.Class.CommonModule');
  * @category Module
  * @package Baculum Common
  */
-abstract class BasicUserConfig extends CommonModule {
+class BasicUserConfig extends CommonModule {
+
+       /**
+        * Stores user config path.
+        */
+       protected $config_path;
        
        /**
         * User name allowed characters pattern
@@ -40,7 +45,19 @@ abstract class BasicUserConfig extends CommonModule {
         * Get config file path to store users' parameters.
         * @return string config path
         */
-       abstract protected function getConfigPath();
+       public function getConfigPath() {
+               return $this->config_path;
+       }
+
+       /**
+        * Set config file path.
+        *
+        * @param string $path path to config file
+        * @return none
+        */
+       public function setConfigPath($path) {
+               $this->config_path = $path;
+       }
 
        /**
         * Save user to users configuration file.
@@ -50,38 +67,40 @@ abstract class BasicUserConfig extends CommonModule {
         * @param string $password user's password
         * @param boolean $clear_config determine if clear config before save
         * @param mixed $old_user previous username before change
+        * @param array $opts setting user options
         * @return boolean true if user saved successfully, otherwise false
         */
-       public function setUsersConfig($user, $password, $clear_config = false, $old_user = null) {
+       public function setUsersConfig($user, $password, $clear_config = false, $old_user = null, $opts = []) {
                if ($clear_config === true) {
                        $this->clearUsersConfig();
                }
 
-               $all_users = $this->getAllUsers();
-               $password = $this->getModule('misc')->getHashedPassword($password);
+               $all_users = $this->getUsers();
+
+               $alg = key_exists('hash_alg', $opts) ? $opts['hash_alg'] : null;
+               $password = $this->getModule('crypto')->getHashedPassword($password, $alg);
 
-               $userExists = array_key_exists($user, $all_users);
+               $user_exists = key_exists($user, $all_users);
 
 
-               if ($userExists === true) {
+               if ($user_exists === true) {
                        // update user password;
                        $all_users[$user] = $password;
                }
 
                if (!is_null($old_user) && $old_user !== $user) {
                        // delete old username with password from configuration file
-                       if (array_key_exists($old_user, $all_users)) {
+                       if (key_exists($old_user, $all_users)) {
                                unset($all_users[$old_user]);
                        }
                }
 
                // add new user if does not exist
-               if ($userExists === false) {
+               if ($user_exists === false) {
                        $all_users[$user] = $password;
                }
 
-               $result = $this->saveUserConfig($all_users);
-               return $result;
+               return $this->saveUserConfig($all_users);
        }
 
        /**
@@ -90,15 +109,19 @@ abstract class BasicUserConfig extends CommonModule {
         * and encrypted passwords as values.
         *
         * @access public
+        * @param string $patter regular expression pattern
         * @return array users/passwords list
         */
-       public function getAllUsers() {
-               $all_users = array();
+       public function getUsers($pattern = '') {
+               $all_users = [];
                if ($this->isUsersConfig() === true) {
                        $users = file($this->getConfigPath(), FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
-
                        for($i = 0; $i < count($users); $i++) {
                                if (preg_match("/^(?P<user>\S+)\:(?P<hash>\S+)$/", $users[$i], $match) === 1) {
+                                       if ($pattern && !fnmatch($pattern, $match['user'])) {
+                                               // wildcard pattern doesn't match, skip it
+                                               continue;
+                                       }
                                        $all_users[$match['user']] = $match['hash'];
                                }
                        }
@@ -123,7 +146,7 @@ abstract class BasicUserConfig extends CommonModule {
                $usersToFile = implode("\n", $users);
                $old_umask = umask(0);
                umask(0077);
-               $result = file_put_contents($this->getConfigPath(), $usersToFile) !== false;
+               $result = file_put_contents($this->getConfigPath(), $usersToFile, LOCK_EX) !== false;
                umask($old_umask);
                return $result;
        }
@@ -139,7 +162,7 @@ abstract class BasicUserConfig extends CommonModule {
         */
        public function removeUser($username) {
                $result = false;
-               $all_users = $this->getAllUsers();
+               $all_users = $this->getUsers();
                if (array_key_exists($username, $all_users)) {
                        unset($all_users[$username]);
                        $result = $this->saveUserConfig($all_users);
@@ -147,6 +170,23 @@ abstract class BasicUserConfig extends CommonModule {
                return $result;
        }
 
+       /**
+        * Remove multiple user from users file.
+        *
+        * @param array $usernames user names to remove
+        * @return boolean true if users removed successfully, otherwise false
+        */
+       public function removeUsers(array $usernames) {
+               $result = false;
+               $all_users = $this->getUsers();
+               for ($i = 0; $i < count($usernames); $i++) {
+                       if (key_exists($usernames[$i], $all_users)) {
+                               unset($all_users[$usernames[$i]]);
+                       }
+               }
+               return $this->saveUserConfig($all_users);
+       }
+
        /**
         * Check if users configuration file exists.
         *
@@ -164,7 +204,7 @@ abstract class BasicUserConfig extends CommonModule {
         * @return boolean true if file cleared successfully, otherwise false
         */
        public function clearUsersConfig() {
-               $result = file_put_contents($this->getConfigPath(), '') !== false;
+               $result = file_put_contents($this->getConfigPath(), '', LOCK_EX) !== false;
                return $result;
        }
 }
diff --git a/gui/baculum/protected/Common/Class/Crypto.php b/gui/baculum/protected/Common/Class/Crypto.php
new file mode 100644 (file)
index 0000000..e1bc951
--- /dev/null
@@ -0,0 +1,105 @@
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum   - Bacula web interface
+ *
+ * Copyright (C) 2013-2020 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.CommonModule');
+
+/**
+ * Cryptographic tools.
+ * Module is responsible for providing basic cryptograhic tool set.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Module
+ * @package Baculum Common
+ */
+class Crypto extends CommonModule {
+
+       /**
+        * Supported hash algorithms.
+        */
+       const HASH_ALG_APR1_MD5 = 'apr-md5';
+       const HASH_ALG_SHA1 = 'sha1';
+       const HASH_ALG_SSHA1 = 'ssha1';
+       const HASH_ALG_SHA256 = 'sha256';
+       const HASH_ALG_SHA512 = 'sha512';
+       const HASH_ALG_BCRYPT = 'bcrypt';
+
+       /**
+        * Get (pseudo)random string.
+        *
+        * Useful for log out user from HTTP Basic auth by providing random password.
+        *
+        * @access public
+        * @return string random string from range [a-zA-Z0-9]
+        */
+       public function getRandomString($length = null) {
+               $characters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+               $rand_string = str_shuffle($characters);
+               if (is_int($length) && $length <= 62) {
+                       $rand_string = substr($rand_string, 0, $length);
+               }
+               return $rand_string;
+       }
+
+       /**
+        * Get hashed password to use in web server auth.
+        * If no hash algorithm given, use APR1-MD5.
+        *
+        * @access public
+        * @param string $password plain text password
+        * @param string $hash_alg hash algorithm (apr1-md5|sha1)
+        * @return string hashed password
+        */
+       public function getHashedPassword($password, $hash_alg = null) {
+               $mod = '';
+               switch ($hash_alg) {
+                       case self::HASH_ALG_BCRYPT: {
+                               $mod = 'bcrypt';
+                               break;
+                       }
+                       case self::HASH_ALG_APR1_MD5: {
+                               $mod = 'apr1md5';
+                               break;
+                       }
+                       case self::HASH_ALG_SHA1: {
+                               $mod = 'sha1';
+                               break;
+                       }
+                       case self::HASH_ALG_SSHA1: {
+                               $mod = 'ssha1';
+                               break;
+                       }
+                       case self::HASH_ALG_SHA256: {
+                               $mod = 'sha256';
+                               break;
+                       }
+                       case self::HASH_ALG_SHA512: {
+                               $mod = 'sha512';
+                               break;
+                       }
+                       default: {
+                               $mod = 'apr1md5';
+                       }
+               }
+               return $this->getModule($mod)->crypt($password);
+       }
+}
+?>
index b397a2e4f9de1b87bae474f4b7b914d401eca3e3..62dfa9002719318e9fdea99ad9559d77fcb4fcf6 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -65,4 +65,14 @@ interface AuthModule {
 
        public function getRequestHeaderValue($header);
 }
+
+/**
+ * Defined user manager methods.
+ */
+interface UserManager {
+
+       public function init($config);
+
+       public function validateUser($username, $password);
+}
 ?>
diff --git a/gui/baculum/protected/Common/Class/Ldap.php b/gui/baculum/protected/Common/Class/Ldap.php
new file mode 100644 (file)
index 0000000..0a55f50
--- /dev/null
@@ -0,0 +1,304 @@
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum   - Bacula web interface
+ *
+ * Copyright (C) 2013-2020 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.CommonModule');
+
+/**
+ * The module supports basic operations on LDAP server.
+ * To work it uses php-ldap module.
+ * @see https://www.php.net/manual/en/book.ldap.php
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Module
+ * @package Baculum Common
+ */
+class Ldap extends CommonModule {
+
+       /**
+        * Authentication methods.
+        */
+       const AUTH_METHOD_ANON = 'anonymous';
+       const AUTH_METHOD_SIMPLE = 'simple';
+
+       /**
+        * LDAP protocol types.
+        */
+       const PROTOCOL_PLAIN = 'ldap';
+       const PROTOCOL_SSL = 'ldaps';
+
+       /**
+        * LDAP attributes.
+        */
+       const DN_ATTR = 'dn';
+
+       /**
+        * Stores LDAP connection parameters.
+        */
+       private $params = [];
+
+       /**
+        * Stores last error message if error occurs.
+        */
+       private $error = '';
+
+       /**
+        * Stores obligatory (required) parameters.
+        */
+       private $req_params = array(
+               'address',
+               'port',
+               'protocol_ver',
+               'auth_method'
+       );
+
+       /**
+        * Stores LDAP connection resource.
+        */
+       private $conn;
+
+       /**
+        * Connect to LDAP server.
+        * Note, it is used to initialize connection parameters, but itself it
+        * doesn't open any LDAP conection.
+        *
+        * @return resource|false LDAP connection resource or false if any error occurs
+        */
+       public function connect() {
+               $ldapuri = $this->getLdapUri();
+               $conn = @ldap_connect($ldapuri);
+               $this->conn = $conn;
+               if ($conn) {
+                       $this->setConnectionOpts();
+               } else {
+                       $this->setLdapError();
+               }
+               return $conn;
+       }
+
+       /**
+        * Binds to LDAP directory.
+        * Supported are anonymous bind and bind with credentials (RDN/password).
+        * Note, if an error occurs, the error message is available in
+        * the error property.
+        *
+        * @param string $rdn distinguished name
+        * @param string $password user password
+        * @return boolean true on successfull connection, false otherwise
+        */
+       public function bind($rdn = null, $password = null) {
+               $success = false;
+               if ($this->conn) {
+                       $success = @ldap_bind($this->conn, $rdn, $password);
+               }
+               if (!$success) {
+                       $this->setLdapError();
+               }
+               return $success;
+       }
+
+       /**
+        * Set LDAP parameters (both connection and attributes' parameters).
+        * Parameters are set after successful validation.
+        *
+        * @param array $param parameters to work with LDAP
+        * @return none
+        */
+       public function setParams(array $params) {
+               if ($this->validateConnectionParams($params)) {
+                       $this->params = $params;
+               }
+       }
+
+       /**
+        * Set LDAP connection specific options.
+        *
+        * @return none
+        */
+       public function setConnectionOpts() {
+               ldap_set_option($this->conn, LDAP_OPT_PROTOCOL_VERSION, $this->params['protocol_ver']);
+               ldap_set_option($this->conn, LDAP_OPT_REFERRALS, 0);
+       }
+
+       /**
+        * Administrative bind action.
+        * This method should be used always to connect to LDAP (anonymous or with credentials).
+        * Note, if an error occurs, the error message is available in
+        * the error property.
+        *
+        * @return boolean true on successfull connection, false otherwise
+        */
+       public function adminBind() {
+               $success = false;
+               if ($this->connect()) {
+                       if ($this->params['auth_method'] === self::AUTH_METHOD_ANON) {
+                               $success = $this->bind();
+                       } elseif ($this->params['auth_method'] === self::AUTH_METHOD_SIMPLE) {
+                               $success = $this->bind($this->params['bind_dn'], $this->params['bind_password']);
+                       }
+               }
+               return $success;
+       }
+
+       /**
+        * Login with username and password.
+        * Note, if an error occurs, the error message is available in
+        * the error property.
+        *
+        * @param string $username username to login
+        * @param string $password password to login
+        * @return boolean true if login finished successfully, false otherwise
+        */
+       public function login($username, $password) {
+               $success = false;
+               if ($this->adminBind()) {
+                       $filter = sprintf('(%s=%s)', $this->params['user_attr'], $username);
+                       $user = $this->search($this->params['base_dn'], $filter);
+                       $cnt = ldap_count_entries($this->conn, $user);
+                       if ($cnt === 1) {
+                               $entry = ldap_first_entry($this->conn, $user);
+                               if ($entry) {
+                                       $user_dn = ldap_get_dn($this->conn, $entry);
+                                       $success = $this->bind($user_dn, $password);
+                               }
+                       }
+               }
+               return $success;
+       }
+
+       /**
+        * Search in LDAP directory using filters.
+        * Note, if an error occurs, the error message is available in
+        * the error property.
+        *
+        * @param string $base_dn base distinguished name
+        * @param string $filter the search filter, ex. '(objectClass=inetOrgPerson)'
+        * @param array $attr required object attributes
+        * @return resource search result identifier or false otherwise
+        */
+       public function search($base_dn, $filter, $attr = array('*')) {
+               $search = @ldap_search($this->conn, $base_dn, $filter, $attr, 0, 0, 0);
+               if (!$search) {
+                       $this->setLdapError();
+               }
+               return $search;
+       }
+
+       /**
+        * Find and get users by filter and attributes.
+        * Note, if an error occurs, the error message is available in
+        * the error property.
+        *
+        * @param string $filter the search filter, ex. '(objectClass=inetOrgPerson)'
+        * @param array $attr required object attributes
+        */
+       public function findUserAttr($filter, $attr = array('*')) {
+               $user_attr = [];
+               if ($this->adminBind()) {
+                       $search = $this->search($this->params['base_dn'], $filter, $attr);
+                       if ($search) {
+                               $user_attr = ldap_get_entries($this->conn, $search);
+                       }
+               }
+               return $user_attr;
+       }
+
+       /**
+        * Get LDAP URI used to connecting to LDAP server.
+        *
+        * @return string full LDAP URI (ex. ldap://host:port)
+        */
+       public function getLdapUri() {
+               $protocol = self::PROTOCOL_PLAIN;
+               if (key_exists('ldaps', $this->params) && $this->params['ldaps'] === 1) {
+                       $protocol = self::PROTOCOL_SSL;
+               }
+               return sprintf(
+                       '%s://%s:%d',
+                       $protocol,
+                       $this->params['address'],
+                       $this->params['port']
+               );
+       }
+
+       /**
+        * Set LDAP errors in error property.
+        * Used to get the most information from LDAP server.
+        *
+        * @return none
+        */
+       public function setLdapError() {
+               ldap_get_option($this->conn, LDAP_OPT_DIAGNOSTIC_MESSAGE, $error);
+               $emsg = sprintf(
+                       'Error: %d: %s. %s',
+                       ldap_errno($this->conn),
+                       ldap_error($this->conn),
+                       $error
+               );
+               $this->error = $emsg;
+               $this->getModule('logging')->log(
+                       __FUNCTION__,
+                       $emsg,
+                       Logging::CATEGORY_EXTERNAL,
+                       __FILE__,
+                       __LINE__
+               );
+       }
+
+       /**
+        * Get LDAP error message.
+        * No error is represented by empty string.
+        *
+        * @return string error message
+        */
+       public function getLdapError() {
+               return $this->error;
+       }
+
+       /**
+        * Validate LDAP connection parameters.
+        *
+        * @param array $params connection parameters
+        * @return false if any of parameters is invalid
+        */
+       public function validateConnectionParams(array $params) {
+               $valid = true;
+               for ($i = 0; $i < count($this->req_params); $i++) {
+                       if (!key_exists($this->req_params[$i], $params)) {
+                               $valid = false;
+                       }
+               }
+               return $valid;
+       }
+
+       /**
+        * Get simple key/value filter.
+        * Filters are used to search on LDAP server.
+        *
+        * @param string $key search key
+        * @param string $value search value
+        * @return formatted string in search filter
+        */
+       public function getFilter($key, $value) {
+               return sprintf('(%s=%s)', $key, $value);
+       }
+}
+?>
index 530edcc637c23d6283b7a0c813c716ef1e686c0f..73c64222f58a6f5a14083b0dcdd2d30948d0f8fd 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
  * Bacula(R) is a registered trademark of Kern Sibbald.
  */
  
+
+/**
+ * Module with miscellaneous tools.
+ * Targetly it is meant to remove after splitting into smaller modules.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Module
+ * @package Baculum Common
+ */
 class Miscellaneous extends TModule {
 
        const RPATH_PATTERN = '/^b2\d+$/';
@@ -289,75 +298,5 @@ class Miscellaneous extends TModule {
                }
                return $jobid;
        }
-
-       /**
-        * Get (pseudo)random string.
-        *
-        * Useful for log out user from HTTP Basic auth by providing random password.
-        *
-        * @access public
-        * @return string random 62 characters string from range [a-zA-Z0-9]
-        */
-       public function getRandomString($length = null) {
-               $characters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
-               $rand_string = str_shuffle($characters);
-               if (is_int($length) && $length <= 62) {
-                       $rand_string = substr($rand_string, 0, $length);
-               }
-               return $rand_string;
-       }
-
-       /**
-        * Get hashed password to use in web server auth.
-        *
-        * @access public
-        * @param string $password plain text password
-        * @return string hashed password
-        */
-       public function getHashedPassword($password) {
-               return $this->cryptApr1Md5($password);
-       }
-
-       public function cryptApr1Md5($password) {
-               $salt = $this->getRandomString(8);
-               $len = strlen($password);
-               $text = sprintf('%s$apr1$%s', $password, $salt);
-               $bin = pack('H32', md5($password . $salt . $password));
-               for ($i = $len; $i > 0; $i -= 16) {
-                       $text .= substr($bin, 0, min(16, $i));
-               }
-               for ($i = $len; $i > 0; $i >>= 1) {
-                       $text .= ($i & 1) ? chr(0) : $password[0];
-               }
-               $bin = pack('H32', md5($text));
-               for ($i = 0; $i < 1000; $i++) {
-                       $new = ($i & 1) ? $password : $bin;
-                       if ($i % 3) {
-                               $new .= $salt;
-                       }
-                       if ($i % 7) {
-                               $new .= $password;
-                       }
-                       $new .= ($i & 1) ? $bin : $password;
-                       $bin = pack('H32', md5($new));
-               }
-               $tmp = null;
-               for ($i = 0; $i < 5; $i++) {
-                       $k = $i + 6;
-                       $j = $i + 12;
-                       if ($j == 16) {
-                               $j = 5;
-                       }
-                       $tmp = $bin[$i] . $bin[$k] . $bin[$j] . $tmp;
-               }
-               $tmp = chr(0) . chr(0) . $bin[11] . $tmp;
-               $str = strrev(substr(base64_encode($tmp), 2));
-               $tmp = strtr(
-                       $str,
-                       'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
-                       './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
-               );
-               return sprintf('$apr1$%s$%s', $salt, $tmp);
-       }
 }
 ?>
index 468a9ce70806a11210429dac28eb8f8890e2d499..e3378b3a3120e9f3694d15ea534019621788b957 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -32,6 +32,8 @@ Prado::using('Application.Common.Class.Interfaces');
  */
 class SessionRecord extends CommonModule implements SessionItem {
 
+       const SESS_FILE_PERM = 0600;
+
        private static $lock = false;
        private static $queue = 0;
 
@@ -46,8 +48,18 @@ class SessionRecord extends CommonModule implements SessionItem {
        private static function store($wouldblock = true) {
                $c = get_called_class();
                $sessfile = $c::getSessionFile();
-               if (array_key_exists('sess', $GLOBALS)) {
+               if (key_exists('sess', $GLOBALS)) {
                        $content = serialize($GLOBALS['sess']);
+                       if (file_exists($sessfile)) {
+                               $perm = (fileperms($sessfile) & 0777);
+                               if ($perm !== self::SESS_FILE_PERM) {
+                                       // Correct permissions to more restrictive if needed
+                                       chmod($sessfile, self::SESS_FILE_PERM);
+                               }
+                       }
+                       $old_umask = umask(0);
+                       $new_umask = (~(self::SESS_FILE_PERM) & 0777);
+                       umask($new_umask);
                        $fp = fopen($sessfile, 'w');
                        if (flock($fp, LOCK_EX, $wouldblock)) {
                                fwrite($fp, $content);
@@ -65,6 +77,7 @@ class SessionRecord extends CommonModule implements SessionItem {
                                );
                        }
                        fclose($fp);
+                       umask($old_umask);
                }
        }
 
diff --git a/gui/baculum/protected/Common/Class/Sha1.php b/gui/baculum/protected/Common/Class/Sha1.php
new file mode 100644 (file)
index 0000000..1ce829e
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum   - Bacula web interface
+ *
+ * Copyright (C) 2013-2020 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.CommonModule');
+
+/**
+ * Cryptographic SHA-1 hashing function module
+ * Module is responsible for providing SHA-1 support.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Module
+ * @package Baculum Common
+ */
+class Sha1 extends CommonModule {
+
+       /**
+        * Get hashed password using SHA-1 algorithm.
+        *
+        * @param string $password plain text password
+        * @return string hashed password
+        */
+       public function crypt($password) {
+               $hash = sha1($password, true);
+               $bh = base64_encode($hash);
+               $ret = '{SHA}' . $bh;
+               return $ret;
+       }
+
+}
+?>
diff --git a/gui/baculum/protected/Common/Class/Sha256.php b/gui/baculum/protected/Common/Class/Sha256.php
new file mode 100644 (file)
index 0000000..6310405
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum   - Bacula web interface
+ *
+ * Copyright (C) 2013-2020 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.CommonModule');
+
+/**
+ * Cryptographic SHA-256 hashing function module
+ * Module is responsible for providing SHA-256 support.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Module
+ * @package Baculum Common
+ */
+class Sha256 extends CommonModule {
+
+       const SHA256_ROUNDS = 10000;
+
+       /**
+        * Get hashed password using SHA-256 algorithm and salt.
+        *
+        * @param string $password plain text password
+        * @return string hashed password
+        */
+       public function crypt($password) {
+               // Salt string  - 16 characters for SHA-256
+               $salt_str = $this->getModule('crypto')->getRandomString(16);
+
+               $salt = sprintf(
+                       '$5$rounds=%d$%s$',
+                       self::SHA256_ROUNDS,
+                       $salt_str
+               );
+               return crypt($password, $salt);
+       }
+}
+?>
diff --git a/gui/baculum/protected/Common/Class/Sha512.php b/gui/baculum/protected/Common/Class/Sha512.php
new file mode 100644 (file)
index 0000000..85fcfec
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum   - Bacula web interface
+ *
+ * Copyright (C) 2013-2020 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.CommonModule');
+
+/**
+ * Cryptographic SHA-512 hashing function module
+ * Module is responsible for providing SHA-512 support.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Module
+ * @package Baculum Common
+ */
+class Sha512 extends CommonModule {
+
+       const SHA512_ROUNDS = 10000;
+
+       /**
+        * Get hashed password using SHA-512 algorithm and salt.
+        *
+        * @param string $password plain text password
+        * @return string hashed password
+        */
+       public function crypt($password) {
+               // Salt string  - 16 characters for SHA-512
+               $salt_str = $this->getModule('crypto')->getRandomString(16);
+
+               $salt = sprintf(
+                       '$6$rounds=%d$%s$',
+                       self::SHA512_ROUNDS,
+                       $salt_str
+               );
+               return crypt($password, $salt);
+       }
+}
+?>
diff --git a/gui/baculum/protected/Common/Class/Ssha1.php b/gui/baculum/protected/Common/Class/Ssha1.php
new file mode 100644 (file)
index 0000000..3b617f9
--- /dev/null
@@ -0,0 +1,54 @@
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum   - Bacula web interface
+ *
+ * Copyright (C) 2013-2020 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.CommonModule');
+
+/**
+ * Cryptographic SHA-1 hashing function module.
+ * Module is responsible for providing SHA-1 support with
+ * using salt.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Module
+ * @package Baculum Common
+ */
+class Ssha1 extends CommonModule {
+
+       /**
+        * Get hashed password using SHA-1 algorithm and salt.
+        *
+        * @param string $password plain text password
+        * @param string $salt cryptographic salt
+        * @return string hashed password
+        */
+       public function crypt($password) {
+               // Salt string  - 16 characters for SHA-256
+               $salt = $this->getModule('crypto')->getRandomString(4);
+
+               $hash = sha1($password . $salt, true);
+               $bh = base64_encode($hash . $salt);
+               $ret = '{SSHA}' . $bh;
+               return $ret;
+       }
+
+}
+?>
index 6c1cb55ecb7af0585453b10e7ed674ce1b7245ab..212300ad3710873cd03ed9494c76c243c4ddabd6 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -51,7 +51,7 @@ class NewAuthClient extends PortletTemplate {
                $exists = false;
                $config = $this->getModule('api_config')->getConfig();
                if ($this->getAuthType() === 'basic') {
-                       $users = $this->getModule('basic_apiuser')->getAllUsers();
+                       $users = $this->getModule('basic_apiuser')->getUsers();
                        if (!key_exists($this->APIBasicLogin->Text, $users)) {
                                $result = $this->getModule('basic_apiuser')->setUsersConfig(
                                        $this->APIBasicLogin->Text,
index bddde48da8b43d4c12700f5267649630686bf17f..9501bb65e1c3912e9001f6a91ab56ab6288e79ea 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -291,11 +291,7 @@ class BaculumAPIClient extends WebModule {
                $cached = null;
                $ret = null;
                if (is_null($host)) {
-                       if (isset($_SESSION['api_host'])) {
-                               $host = $_SESSION['api_host'];
-                       } else {
-                               $host = HostConfig::MAIN_CATALOG_HOST;
-                       }
+                       $host = $this->User->getAPIHosts();
                }
                if ($use_cache === true) {
                        $cached = $this->getSessionCache($host, $params);
@@ -336,11 +332,7 @@ class BaculumAPIClient extends WebModule {
         */
        public function set(array $params, array $options = array(), $host = null, $show_error = true) {
                if (is_null($host)) {
-                       if (isset($_SESSION['api_host'])) {
-                               $host = $_SESSION['api_host'];
-                       } else {
-                               $host = HostConfig::MAIN_CATALOG_HOST;
-                       }
+                       $host = $this->User->getAPIHosts();
                }
                $host_cfg = $this->getHostParams($host);
                $uri = $this->getURIResource($host, $params);
@@ -377,11 +369,7 @@ class BaculumAPIClient extends WebModule {
         */
        public function create(array $params, array $options, $host = null, $show_error = true) {
                if (is_null($host)) {
-                       if (isset($_SESSION['api_host'])) {
-                               $host = $_SESSION['api_host'];
-                       } else {
-                               $host = HostConfig::MAIN_CATALOG_HOST;
-                       }
+                       $host = $this->User->getAPIHosts();
                }
                $host_cfg = $this->getHostParams($host);
                $uri = $this->getURIResource($host, $params);
@@ -413,11 +401,7 @@ class BaculumAPIClient extends WebModule {
         */
        public function remove(array $params, $host = null, $show_error = true) {
                if (is_null($host)) {
-                       if (isset($_SESSION['api_host'])) {
-                               $host = $_SESSION['api_host'];
-                       } else {
-                               $host = HostConfig::MAIN_CATALOG_HOST;
-                       }
+                       $host = $this->User->getAPIHosts();
                }
                $host_cfg = $this->getHostParams($host);
                $uri = $this->getURIResource($host, $params);
@@ -579,7 +563,7 @@ class BaculumAPIClient extends WebModule {
 
        private function authToHost($host, $host_cfg) {
                if (count($host_cfg) > 0 && $host_cfg['auth_type'] === 'oauth2') {
-                       $state = $this->getModule('misc')->getRandomString(16);
+                       $state = $this->getModule('crypto')->getRandomString(16);
                        $params = array(
                                'response_type' => 'code',
                                'client_id' => $host_cfg['client_id'],
index 75449e8ea953e76eadbc6494ee52c7ea7c3d334a..29121095fd5bb2d055782be9d74a531ff7668475 100644 (file)
  * Bacula(R) is a registered trademark of Kern Sibbald.
  */
 
-session_start();
-
 Prado::using('Application.Web.Pages.Requirements');
 Prado::using('Application.Common.Class.BaculumPage');
 Prado::using('Application.Web.Init');
 Prado::using('Application.Web.Class.WebConfig');
+Prado::using('Application.Web.Class.PageCategory');
 
 /**
  * Baculum Web page module.
@@ -47,50 +46,19 @@ class BaculumWebPage extends BaculumPage {
        public function onPreInit($param) {
                parent::onPreInit($param);
                $this->web_config = $this->getModule('web_config')->getConfig();
-               $this->Application->getGlobalization()->Culture = $this->getLanguage();
                if (count($this->web_config) === 0) {
-                       if (isset($_SERVER['PHP_AUTH_USER'])) {
-                               if ($this->Service->getRequestedPagePath() != 'WebConfigWizard') {
-                                       $this->goToPage('WebConfigWizard');
-                               }
-                               // without config there is no way to call api below
-                               return;
-                       } else {
-                               self::accessDenied();
+                       if ($this->Service->getRequestedPagePath() != 'WebConfigWizard') {
+                               $this->goToPage('WebConfigWizard');
                        }
+                       // without config there is no way to call api below
+                       return;
                }
                Logging::$debug_enabled = (isset($this->web_config['baculum']['debug']) && $this->web_config['baculum']['debug'] == 1);
                if (!$this->IsPostBack && !$this->IsCallBack) {
+                       $this->postInitActions();
                        $this->getModule('api')->initSessionCache(true);
                        $this->setSessionUserVars();
                }
-               $this->checkPrivileges();
-       }
-
-       /**
-        * Get curently set language short name (for example: en, pl).
-        * If language short name is not set in session then the language value
-        * is taken from Baculum config file, saved in session and returned.
-        * If the language setting is set in session, then the value from
-        * session is returned.
-        *
-        * @access public
-        * @return string currently set language short name
-        */
-       public function getLanguage() {
-               $language = null;
-               if (isset($_SESSION['language']) && !empty($_SESSION['language'])) {
-                       $language =  $_SESSION['language'];
-               } else {
-                       if (isset($this->web_config['baculum']) && key_exists('lang', $this->web_config['baculum'])) {
-                               $language = $this->web_config['baculum']['lang'];
-                       }
-                       if (is_null($language)) {
-                               $language = WebConfig::DEFAULT_LANGUAGE;
-                       }
-                       $_SESSION['language'] = $language;
-               }
-               return $language;
        }
 
        /**
@@ -99,24 +67,10 @@ class BaculumWebPage extends BaculumPage {
         * @return none
         */
        private function setSessionUserVars() {
-               // NOTE. For oauth2 callback, the PHP_AUTH_USER is empty because no user/pass.
-               if (count($this->web_config) > 0 && isset($_SERVER['PHP_AUTH_USER'])) {
-                       // Set administrator role
-                       $_SESSION['admin'] = ($_SERVER['PHP_AUTH_USER'] === $this->web_config['baculum']['login']);
-
-                       // Set api host for normal user
-                       if (!$_SESSION['admin'] && key_exists('users', $this->web_config) && array_key_exists($_SERVER['PHP_AUTH_USER'], $this->web_config['users'])) {
-                               $_SESSION['api_host'] = $this->web_config['users'][$_SERVER['PHP_AUTH_USER']];
-                       } elseif ($_SESSION['admin']) {
-                               $_SESSION['api_host'] = 'Main';
-                       }
-               } else {
-                       $_SESSION['admin'] = false;
-               }
-
                // Set director
                $directors = $this->getModule('api')->get(array('directors'), null, false);
-               if ($directors->error === 0 && count($directors->output) > 0 && (!key_exists('director', $_SESSION) || $directors->output[0] != $_SESSION['director'])) {
+               if ($directors->error === 0 && count($directors->output) > 0 &&
+                      (!key_exists('director', $_SESSION) || $directors->output[0] != $_SESSION['director'])) {
                        $_SESSION['director'] = $directors->output[0];
                }
 
@@ -133,14 +87,72 @@ class BaculumWebPage extends BaculumPage {
                }
        }
 
-       private function checkPrivileges() {
-               if (property_exists($this, 'admin') && $this->admin === true && !$_SESSION['admin']) {
-                       self::accessDenied();
+       /**
+        * Redirection to default page defined in application config.
+        *
+        * @access public
+        * @param array $params HTTP GET method parameters in associative array
+        * @return none
+        */
+       public function goToDefaultPage($params = null) {
+               $def_page = $this->Service->DefaultPage;
+               $manager = $this->getModule('users');
+               if (!$manager->isPageAllowed($this->User, $this->Service->DefaultPage)) {
+                       // User hasn't access to default service page. Get first allowed page.
+                       $def_page = $this->findDefaultPageForUser();
+
+                       /**
+                        * If page different than default for service, reset params because
+                        * they will not work with different page.
+                        */
+                       $params = null;
                }
+               if (!is_string($def_page)) {
+                       $def_page = $this->getModule('auth')->getLoginPage();
+               }
+               $this->goToPage($def_page, $params);
        }
 
-       public static function accessDenied() {
-               die('Access denied');
+       /**
+        * Find default page for an user.
+        * Useful to determine on which page direct user. It takes first one found
+        * that can be accessible by the user.
+        *
+        * @return mixed page path or null if no page for user found
+        */
+       private function findDefaultPageForUser() {
+               $manager = $this->getModule('users');
+               $user_role = $this->getModule('user_role');
+               $roles = $this->User->getRoles();
+               $pages = [];
+               for ($i = 0; $i < count($roles); $i++) {
+                       $rpages = $user_role->getPagesByRole($roles[$i]);
+                       for ($j = 0; $j < count($rpages); $j++) {
+                               if (!in_array($rpages[$i], $pages) && $manager->isPageAllowed($this->User, $rpages[$i])) {
+                                       $pages[] = $rpages[$i];
+                               }
+                       }
+               }
+               return array_shift($pages);
+       }
+
+       /**
+        * Common actions which has to be done for each web page just after
+        * page pre-loading.
+        *
+        * @return none
+        */
+       private function postInitActions() {
+               /**
+                * If users config file doesn't exist, create it and populate
+                * using basic users file.
+                * Basic auth method is the main Baculum Web auth method. Before introducing
+                * users.conf file, it was the only one supported method.
+                */
+               $result = $this->getModule('user_config')->importUsers();
+               if ($result) {
+                       $this->goToDefaultPage();
+               }
        }
 }
 ?>
index 2e622f65127a676b56235f199430662d9935d33b..52ff2d630a8038a59e94b4c8df02a9cc108586ef 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -37,7 +37,9 @@ class BasicWebUserConfig extends BasicUserConfig {
        const USERS_FILE_NAME = 'Application.Web.Config.baculum';
        const USERS_FILE_EXTENSION = '.users';
 
-       protected function getConfigPath() {
-               return Prado::getPathOfNamespace(self::USERS_FILE_NAME, self::USERS_FILE_EXTENSION);
+       public function getConfigPath() {
+               // First check if custom config path is set, if not, then use default users file
+               return parent::getConfigPath() ?: Prado::getPathOfNamespace(self::USERS_FILE_NAME, self::USERS_FILE_EXTENSION);
        }
 }
+?>
diff --git a/gui/baculum/protected/Web/Class/PageCategory.php b/gui/baculum/protected/Web/Class/PageCategory.php
new file mode 100644 (file)
index 0000000..6f3976f
--- /dev/null
@@ -0,0 +1,121 @@
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum   - Bacula web interface
+ *
+ * Copyright (C) 2013-2020 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.:Web.Class.HostConfig');
+Prado::using('Application.:Web.Class.WebModule');
+
+/**
+ * Page category module.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Module
+ * @package Baculum Web
+ */
+class PageCategory extends WebModule {
+
+       /**
+        * Pages allowed for user with native role 'normal'
+        */
+       const DASHBOARD = 'Dashboard';
+       const JOB_HISTORY_LIST = 'JobHistoryList';
+       const JOB_HISTORY_VIEW = 'JobHistoryView';
+       const JOB_LIST = 'JobList';
+       const JOB_VIEW = 'JobView';
+       const CLIENT_LIST = 'ClientList';
+       const CLIENT_VIEW = 'ClientView';
+       const RESTORE_WIZARD = 'RestoreWizard';
+       const GRAPHS = 'Graphs';
+
+       /**
+        * System pages - always allowed for authenticated users
+        */
+       const MONITOR = 'Monitor';
+       const BACULUM_ERROR = 'BaculumError';
+
+       /**
+        * Public pages - always allowed
+        */
+       const LOGIN_PAGE = 'LoginPage';
+       const OAUTH2_REDIRECT = 'OAuth2Redirect';
+
+       /**
+        * Pages available under conditions.
+        */
+       const WEB_CONFIG_WIZARD = 'WebConfigWizard';
+
+       /**
+        * Get page categories.
+        *
+        * @return array page categories
+        */
+       public function getCategories($with_sys_pub_pages = true) {
+               $pages = $this->getModule('url_manager')->getPages();
+               //some pages are double because they are defined for access by name or id, so do unique()
+               $pages = array_unique($pages);
+               if (!$with_sys_pub_pages) {
+                       $system_pages = $this->getSystemCategories();
+                       $public_pages = $this->getPublicCategories();
+                       $pages = array_diff($pages, $system_pages, $public_pages);
+               }
+               return $pages;
+       }
+
+       public function isCategorySystem($category) {
+               $system_cats = $this->getSystemCategories();
+               return in_array($category, $system_cats);
+       }
+
+       private function getSystemCategories() {
+               return [
+                       self::MONITOR,
+                       self::BACULUM_ERROR
+               ];
+       }
+
+       public function isCategoryPublic($category) {
+               $public_cats = $this->getPublicCategories();
+               return in_array($category, $public_cats);
+       }
+
+       private function getPublicCategories() {
+               return [
+                       self::OAUTH2_REDIRECT,
+                       self::LOGIN_PAGE
+               ];
+       }
+
+       public function isCategoryConditional($category) {
+               $cond_cats = $this->getConditionalCategories();
+               return in_array($category, $cond_cats);
+       }
+
+       private function getConditionalCategories() {
+               $cond_cats = [];
+               $host_config = $this->getModule('host_config')->getConfig();
+               $first_run = (count($host_config) == 0 || !key_exists(HostConfig::MAIN_CATALOG_HOST, $host_config));
+               if ($first_run) {
+                       $cond_cats[] = self::WEB_CONFIG_WIZARD;
+               }
+               return $cond_cats;
+       }
+}
+?>
diff --git a/gui/baculum/protected/Web/Class/WebBasicUserManager.php b/gui/baculum/protected/Web/Class/WebBasicUserManager.php
new file mode 100644 (file)
index 0000000..ea3a6db
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum   - Bacula web interface
+ *
+ * Copyright (C) 2013-2020 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.Web.Class.WebModule');
+
+/**
+ * Web HTTP Basic user manager module.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Module
+ * @package Baculum Web
+ */
+class WebBasicUserManager extends WebModule implements UserManager {
+
+       public function init($config) {
+       }
+
+       public function validateUser($username, $password) {
+               /**
+                * Basic auth is realized by web server, so validating
+                * user/pass is always true here.
+                */
+               return true;
+       }
+}
+?>
index 47732498ecb6b3c4130ba0de088060cf9b171fce..3fb1212ddfdf7babbdf31ae11034d54281e54951 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -21,6 +21,7 @@
  */
 
 Prado::using('Application.Common.Class.ConfigFileModule');
+Prado::using('Application.Common.Class.Crypto');
 
 /**
  * Manage webGUI configuration.
@@ -45,36 +46,87 @@ class WebConfig extends ConfigFileModule {
        /**
         * Default application language
         */
-       const DEFAULT_LANGUAGE = 'en';
+       const DEF_LANG = 'en';
+
+       /**
+        * Default number of jobs visible in tables/
+        */
+       const DEF_MAX_JOBS = 15000;
+
+       /**
+        * Default size values unit.
+        */
+       const DEF_SIZE_VAL_UNIT = 'decimal';
+
+       /**
+        * Default value for showing time in job log.
+        */
+       const DEF_TIME_IN_JOB_LOG = 0;
+
+       /**
+        * Supported authentication methods.
+        */
+       const AUTH_METHOD_BASIC = 'basic';
+       const AUTH_METHOD_LDAP = 'ldap';
+
+       /**
+        * Default access options.
+        */
+       const DEF_ACCESS_NO_ACCESS = 'no_access';
+       const DEF_ACCESS_DEFAULT_SETTINGS = 'default_settings';
+
+       /**
+        * Stores web config content.
+        */
+       private $config = null;
 
        /**
         * These options are obligatory for web config.
         */
        private $required_options = array(
-               'baculum' => array('login', 'debug', 'lang')
+               'baculum' => array('debug', 'lang')
        );
 
        /**
-        * Get (read) web config.
+        * Initialize module configuration.
+        *
+        * @param TXmlElement $config module configuration
+        * @return none
+        */
+       public function init($config) {
+               // add event handler to set page language
+               $this->Application->attachEventHandler(
+                       'onPreRunService',
+                       [$this, 'setCulture']
+               );
+       }
+
+       /**
+        * Get web config.
         *
         * @access public
         * @param string $section config section name
         * @return array config
         */
        public function getConfig($section = null) {
-               $config = $this->readConfig(self::CONFIG_FILE_PATH, self::CONFIG_FILE_FORMAT);
-               if ($this->validateConfig($config) === true) {
-                       if (!is_null($section)) {
-                               $config = array_key_exists($section, $this->required_options) ? $config[$section] : array();
+               $config = [];
+               $valid = true;
+               if (is_null($this->config)) {
+                       $this->config = $this->readConfig(self::CONFIG_FILE_PATH, self::CONFIG_FILE_FORMAT);
+                       $valid = $this->validateConfig($this->config);
+               }
+               if ($valid === true) {
+                       if (is_string($section)) {
+                               $config = key_exists($section, $this->config) ? $this->config[$section] : array();
+                       } else {
+                               $config = $this->config;
                        }
-               } else {
-                       $config = array();
                }
                return $config;
        }
 
        /**
-        * Set (save) web config.
+        * Set web config.
         *
         * @access public
         * @param array $config config
@@ -84,6 +136,9 @@ class WebConfig extends ConfigFileModule {
                $result = false;
                if ($this->validateConfig($config) === true) {
                        $result = $this->writeConfig($config, self::CONFIG_FILE_PATH, self::CONFIG_FILE_FORMAT);
+                       if ($result === true) {
+                               $this->config = null;
+                       }
                }
                return $result;
        }
@@ -103,34 +158,171 @@ class WebConfig extends ConfigFileModule {
        }
 
        /**
-        * Get web administrator name.
-        * Web interface supports one admin and many normal users.
+        * Set default options for new config.
         *
-        * @return string web admin name
+        * @param array $opts custom options to add to default options
+        * @return true on success, otherwise false
         */
-       public function getWebAdmin() {
+       public function setDefConfigOpts($opts = []) {
                $config = $this->getConfig();
-               $web_admin = $config['baculum']['login'];
-               return $web_admin;
+
+               $baculum = [
+                       'debug' => 0,
+                       'lang' => self::DEF_LANG,
+                       'max_jobs' => self::DEF_MAX_JOBS,
+                       'size_values_unit' => self::DEF_SIZE_VAL_UNIT,
+                       'time_in_job_log' => self::DEF_TIME_IN_JOB_LOG
+               ];
+               if (key_exists('baculum', $config)) {
+                       $config['baculum'] = array_merge($baculum, $config['baculum']);
+               } else {
+                       $config['baculum'] = $baculum;
+               }
+
+               $user_file = $this->getModule('basic_webuser')->getConfigPath();
+               // basic options
+               $auth_basic = [
+                       'allow_manage_users' => 1,
+                       'user_file' => $user_file,
+                       'hash_alg' => Crypto::HASH_ALG_APR1_MD5
+               ];
+               if (key_exists('auth_basic', $config)) {
+                       $config['auth_basic'] = array_merge($auth_basic, $config['auth_basic']);
+               } else {
+                       $config['auth_basic'] = $auth_basic;
+               }
+
+               // security options
+               $security = [
+                       'auth_method' => self::AUTH_METHOD_BASIC,
+                       'def_access' => self::DEF_ACCESS_DEFAULT_SETTINGS,
+                       'def_role' => WebUserRoles::NORMAL,
+                       'def_api_host' => HostConfig::MAIN_CATALOG_HOST
+               ];
+               if  (key_exists('security', $config)) {
+                       $config['security'] = array_merge($security, $config['security']);
+               } else {
+                       $config['security'] = $security;
+               }
+
+               if (count($opts) > 0) {
+                       $config = array_replace_recursive($config, $opts);
+               }
+
+               // set default properties
+               $ret = $this->setConfig($config);
+               if ($ret !== true) {
+                       $emsg = 'Error while saving auth basic config.';
+                       $this->getModule('logging')->log(
+                               __FUNCTION__,
+                               $emsg,
+                               Logging::CATEGORY_APPLICATION,
+                               __FILE__,
+                               __LINE__
+                       );
+               }
+               return $ret;
        }
 
        /**
-        * Get application language short name.
-        * If no language set then returned is default language.
+        * Get authentication method.
         *
-        * @access public
-        * @return string lanuage short name
+        * @return string authentication method
         */
-       public function getLanguage() {
-               $language = null;
+       public function getAuthMethod() {
                $config = $this->getConfig();
-               if (array_key_exists('baculum', $config) && array_key_exists('lang', $config['baculum'])) {
-                       $language = $config['baculum']['lang'];
+
+               $auth_method = self::AUTH_METHOD_BASIC; // Basic is default method
+               if (isset($config['security']['auth_method'])) {
+                       $auth_method = $config['security']['auth_method'];
                }
-               if (is_null($language)) {
-                       $language = self::DEFAULT_LANGUAGE;
+               return $auth_method;
+       }
+
+       /**
+        * Check if current authentication method is set to Basic.
+        *
+        * @return boolean true if is set Basic auth, otherwise false
+        */
+       public function isAuthMethodBasic() {
+               return ($this->getAuthMethod() === self::AUTH_METHOD_BASIC);
+       }
+
+       /**
+        * Check if current authentication method is set to LDAP.
+        *
+        * @return boolean true if is set LDAP auth, otherwise false
+        */
+       public function isAuthMethodLdap() {
+               return ($this->getAuthMethod() === self::AUTH_METHOD_LDAP);
+       }
+
+       /**
+        * Check if current default access method for not existing users
+        * in configuration file is set to no access.
+        *
+        * @return boolean true if is set no access, otherwise false
+        */
+       public function isDefAccessNoAccess() {
+               $config = $this->getConfig();
+               return (isset($config['security']['def_access']) && $config['security']['def_access'] === self::DEF_ACCESS_NO_ACCESS);
+       }
+
+       /**
+        * Check if current default access method for not existing users
+        * in configuration file is set to default settings.
+        *
+        * @return boolean true if is set default settings, otherwise false
+        */
+       public function isDefAccessDefaultSettings() {
+               $config = $this->getConfig();
+               return (isset($config['security']['def_access']) && $config['security']['def_access'] === self::DEF_ACCESS_DEFAULT_SETTINGS);
+       }
+
+       /**
+        * Set culture for whole page.
+        * Uses currently set language settings.
+        *
+        * @return none
+        */
+       public function setCulture() {
+               $this->Application->getGlobalization()->Culture = $this->getLanguage();
+       }
+
+       /**
+        * Get curently set language short name (for example: en, pl).
+        * If language short name is not set in session then the language value
+        * is taken from Baculum config file, saved in session and returned.
+        * If the language setting is set in session, then the value from
+        * session is returned.
+        *
+        * @return string currently set language short name
+        */
+       public function getLanguage() {
+               $language = null;
+               if (isset($_SESSION['language']) && !empty($_SESSION['language'])) {
+                       $language =  $_SESSION['language'];
+               } else {
+                       $config = $this->getConfig();
+                       if (isset($config['baculum']) && key_exists('lang', $config['baculum'])) {
+                               $language = $config['baculum']['lang'];
+                       }
+                       if (is_null($language)) {
+                               $language = self::DEF_LANG;
+                       }
+                       $_SESSION['language'] = $language;
                }
                return $language;
        }
+
+       /**
+        * Set language for current page.
+        * Note, it is done in session only.
+        *
+        * @return none
+        */
+       public function setLanguage($lang) {
+               $_SESSION['language'] = $lang;
+       }
 }
 ?>
diff --git a/gui/baculum/protected/Web/Class/WebLdapUserManager.php b/gui/baculum/protected/Web/Class/WebLdapUserManager.php
new file mode 100644 (file)
index 0000000..3f939de
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum   - Bacula web interface
+ *
+ * Copyright (C) 2013-2020 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.Web.Class.WebModule');
+
+/**
+ * Web LDAP user manager module.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Module
+ * @package Baculum Web
+ */
+class WebLdapUserManager extends WebModule implements UserManager {
+
+       private $ldap = null;
+
+       public function init($config) {
+               parent::init($config);
+               $web_config = $this->getModule('web_config')->getConfig();
+               if (key_exists('auth_ldap', $web_config)) {
+                       $this->ldap = $this->getModule('ldap');
+                       $this->ldap->setParams($web_config['auth_ldap']);
+               }
+       }
+
+       public function validateUser($username, $password) {
+               return $this->ldap->login($username, $password);
+       }
+}
+?>
diff --git a/gui/baculum/protected/Web/Class/WebRoleConfig.php b/gui/baculum/protected/Web/Class/WebRoleConfig.php
new file mode 100644 (file)
index 0000000..f6d49d3
--- /dev/null
@@ -0,0 +1,186 @@
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum   - Bacula web interface
+ *
+ * Copyright (C) 2013-2020 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 web role configuration.
+ * Module is responsible for get/set web role config data.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Module
+ * @package Baculum Web
+ */
+class WebRoleConfig extends ConfigFileModule {
+
+        /**
+         * Web role name allowed characters pattern
+         */
+       const ROLE_PATTERN = '[\w\@\-\.]+';
+
+       /**
+        * Web role config file path
+        */
+       const CONFIG_FILE_PATH = 'Application.Web.Config.roles';
+
+       /**
+        * Web role config file format
+        */
+       const CONFIG_FILE_FORMAT = 'ini';
+
+       /**
+        * These options are obligatory for web config.
+        */
+       private $role_required_options = [
+               'long_name',
+               'description',
+               'enabled',
+               'resources'
+       ];
+
+       /**
+        * Stores web roles config content.
+        */
+       private $config = null;
+
+       /**
+        * Get (read) web role config.
+        *
+        * @return array config
+        */
+       public function getConfig() {
+               if (is_null($this->config)) {
+                       $this->config = $this->getConfigInternal();
+               }
+               return $this->config;
+       }
+
+       private function getConfigInternal() {
+               $roles_config = [];
+               $config = $this->readConfig(self::CONFIG_FILE_PATH, self::CONFIG_FILE_FORMAT);
+               // Web role config validation per single role
+               foreach ($config as $role => $role_config) {
+                       if ($this->isRoleConfigValid([$role => $role_config]) === false) {
+                               /**
+                                * If config for one web role is invalid, don't add this role config
+                                * but continue checking next role config sections.
+                                * It is because during adding new role manually, admin can do a typo
+                                * in new role config section.  Validation errors are logged.
+                                */
+                               continue;
+                       }
+                       $role_config['role'] = $role;
+                       $roles_config[$role] = $role_config;
+               }
+               return $roles_config;
+       }
+
+       /**
+        * Set web role config.
+        * Method is private, because this method saves whole web role config.
+        * To add (or modify) role in config, use method to save single role in config.
+        * @see setRoleConfig()
+        *
+        * @param array $config config
+        * @return boolean true if config saved successfully, otherwise false
+        */
+       public function setConfig(array $config) {
+               $result = false;
+               if ($this->isRoleConfigValid($config) === true) {
+                       $result = $this->writeConfig($config, self::CONFIG_FILE_PATH, self::CONFIG_FILE_FORMAT);
+                       if ($result === true) {
+                               $this->config = null;;
+                       }
+               }
+               return $result;
+       }
+
+       /**
+        * Get single role config.
+        *
+        * @param $role role name
+        * @return array role config
+        */
+       public function getRoleConfig($role) {
+               $role_config = [];
+               $config = $this->getConfig();
+               if (key_exists($role, $config)) {
+                       $config[$role]['role'] = $role;
+                       $role_config = $config[$role];
+               }
+               return $role_config;
+       }
+
+       /**
+        * Set single role config.
+        *
+        * @param string $role role name
+        * @param array $role config
+        * @return boolean true if config saved successfully, otherwise false
+        */
+       public function setRoleConfig($role, array $role_config) {
+               $config = $this->getConfig();
+               $config[$role] = $role_config;
+               return $this->setConfig($config);
+       }
+
+       /**
+        * Validate role single role section in config.
+        *
+        * @param array $config role config section
+        * @return boolean true if config valid, otherwise false
+        */
+       private function isRoleConfigValid(array $config) {
+               $invalid = ['required' => []];
+               foreach ($config as $role => $role_config) {
+                       for ($i = 0; $i < count($this->role_required_options); $i++) {
+                               if (!key_exists($this->role_required_options[$i], $role_config)) {
+                                       $invalid['required'][] = [
+                                               'role' => $role,
+                                               'value' => $this->role_required_options[$i],
+                                               'type' => 'option'
+                                       ];
+                               }
+                       }
+               }
+
+               $valid = true;
+               $required_len = count($invalid['required']);
+               if ($required_len > 0) {
+                       $valid = false;
+                       $emsg = '';
+                       $path = $this->getConfigRealPath(self::CONFIG_FILE_PATH);
+                       for ($i = 0; $i < $required_len; $i++) {
+                               $emsg = "ERROR [$path] Required {$invalid['required'][$i]['type']} '{$invalid['required'][$i]['value']}' not found for role '{$invalid['required'][$i]['role']}.";
+                               $this->Application->getModule('logging')->log(
+                                       __FUNCTION__,
+                                       $emsg,
+                                       Logging::CATEGORY_APPLICATION,
+                                       __FILE__,
+                                       __LINE__
+                               );
+                       }
+               }
+               return $valid;
+       }
+}
+?>
diff --git a/gui/baculum/protected/Web/Class/WebUser.php b/gui/baculum/protected/Web/Class/WebUser.php
new file mode 100644 (file)
index 0000000..3b9e22b
--- /dev/null
@@ -0,0 +1,251 @@
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum   - Bacula web interface
+ *
+ * Copyright (C) 2013-2020 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.
+ */
+
+
+/**
+ * Web user module.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Module
+ * @package Baculum Web
+ */
+class WebUser extends TUser {
+
+       /**
+        * Saved in session user properties.
+        */
+       const LONG_NAME = 'LongName';
+       const EMAIL = 'Email';
+       const DESCRIPTION = 'Description';
+       const API_HOSTS = 'ApiHosts';
+       const IPS = 'Ips';
+       const ENABLED = 'Enabled';
+       const IN_CONFIG = 'InConfig';
+
+       /**
+        * Create single user instance.
+        * Used for authenticated users.
+        * If user doesn't exist in configuration then default access values can be taken into accout.
+        *
+        * @param string username user name
+        * @return WebUser user instance
+        */
+       public function createUser($username) {
+               $user = Prado::createComponent(__CLASS__, $this->getManager());
+               $user->setUsername($username);
+
+               $application = $this->getManager()->getApplication();
+               $user_config = $application->getModule('user_config')->getUserConfig($username);
+               $web_config = $application->getModule('web_config')->getConfig();
+
+               if (count($user_config) > 0) {
+                       // User exists in Baculum Web users database
+                       $user->setInConfig(true);
+                       $user->setDescription($user_config['description']);
+                       $user->setLongName($user_config['long_name']);
+                       $user->setEmail($user_config['email']);
+                       $user->setRoles($user_config['roles']);
+                       $user->setAPIHosts($user_config['api_hosts']);
+                       $user->setIps($user_config['ips']);
+                       $user->setEnabled($user_config['enabled']);
+               } elseif (isset($web_config['security']['def_access'])) {
+                       // User doesn't exist. Check if user can have access.
+                       $user->setInConfig(false);
+                       if ($web_config['security']['def_access'] === WebConfig::DEF_ACCESS_NO_ACCESS) {
+                               // no access, nothing to do
+                       } elseif ($web_config['security']['def_access'] === WebConfig::DEF_ACCESS_DEFAULT_SETTINGS) {
+                               if (isset($web_config['security']['def_role'])) {
+                                       $user->setRoles($web_config['security']['def_role']);
+                               }
+                               if (isset($web_config['security']['def_api_host'])) {
+                                       $user->setAPIHosts($web_config['security']['def_api_host']);
+                               }
+                       }
+               }
+               return $user;
+       }
+
+       /**
+        * Username setter.
+        *
+        * @param string $username user name
+        * @return none
+        */
+       public function setUsername($username) {
+               $this->setName($username);
+       }
+
+       /**
+        * Username getter.
+        *
+        * @return string user name
+        */
+       public function getUsername() {
+               return $this->getName();
+       }
+
+       /**
+        * Long name setter.
+        *
+        * @param string $long_name long name
+        * @return none
+        */
+       public function setLongName($long_name) {
+               $this->setState(self::LONG_NAME, $long_name);
+       }
+
+       /**
+        * Long name getter.
+        *
+        * @return string long name (default empty string)
+        */
+       public function getLongName() {
+               return $this->getState(self::LONG_NAME, '');
+       }
+
+       /**
+        * E-mail address setter.
+        *
+        * @param string $email e-mail address
+        * @return none
+        */
+       public function setEmail($email) {
+               $this->setState(self::EMAIL, $email);
+       }
+
+       /**
+        * E-mail address getter.
+        *
+        * @return string e-mail address
+        */
+       public function getEmail() {
+               return $this->getState(self::EMAIL, '');
+       }
+
+       /**
+        * Description setter.
+        *
+        * @param string $desc description
+        * @return none
+        */
+       public function setDescription($desc) {
+               $this->setState(self::DESCRIPTION, $desc);
+       }
+
+       /**
+        * Description getter.
+        *
+        * @return string description
+        */
+       public function getDescription() {
+               return $this->getState(self::DESCRIPTION, '');
+       }
+
+       /**
+        * Set API hosts.
+        * So far is supported only one API host per user.
+        * In the future this method can support more API hosts per user.
+        *
+        * @param string $api_hosts API hosts
+        * @return none
+        */
+       public function setAPIHosts($api_hosts) {
+               $this->setState(self::API_HOSTS, $api_hosts);
+       }
+
+       /**
+        * API hosts getter.
+        *
+        * @return string API host
+        */
+       public function getAPIHosts() {
+               $hosts = $this->getState(self::API_HOSTS);
+               $hosts = explode(',', $hosts);
+               if (count($hosts) == 1 && !empty($hosts[0])) {
+                       $api_hosts = $hosts[0];
+               } else {
+                       // default API host
+                       $api_hosts = HostConfig::MAIN_CATALOG_HOST;
+               }
+               return $api_hosts;
+       }
+
+       /**
+        * IP address restriction setter.
+        *
+        * @param string $ips comma separated IP addresses
+        * @return none
+        */
+       public function setIps($ips) {
+               $this->setState(self::IPS, $ips);
+       }
+
+       /**
+        * IP address restriction getter.
+        *
+        * @return string comma separated IP address list (default empty string)
+        */
+       public function getIps() {
+               return $this->getState(self::IPS, '');
+       }
+
+       /**
+        * Enabled setter
+        *
+        * @param boolean $enabled enabled flag state
+        * @return none
+        */
+       public function setEnabled($enabled) {
+               $enabled = TPropertyValue::ensureBoolean($enabled);
+               $this->setState(self::ENABLED, $enabled);
+       }
+
+       /**
+        * Enabled getter.
+        *
+        * @return string enabled flag state (default false)
+        */
+       public function getEnabled() {
+               return $this->getState(self::ENABLED, false);
+       }
+
+       /**
+        * Set if user exists in configuration file.
+        *
+        * @param boolean $in_config in config state value
+        * @return none
+        */
+       public function setInConfig($in_config) {
+               $in_config = TPropertyValue::ensureBoolean($in_config);
+               $this->setState(self::IN_CONFIG, $in_config);
+       }
+
+       /**
+        * In config getter.
+        *
+        * @return string in config state value (default false)
+        */
+       public function getInConfig() {
+               return $this->getState(self::IN_CONFIG, false);
+       }
+}
+?>
diff --git a/gui/baculum/protected/Web/Class/WebUserConfig.php b/gui/baculum/protected/Web/Class/WebUserConfig.php
new file mode 100644 (file)
index 0000000..256a8b0
--- /dev/null
@@ -0,0 +1,289 @@
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum   - Bacula web interface
+ *
+ * Copyright (C) 2013-2020 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');
+Prado::using('Application.Web.Class.WebUserRoles');
+
+/**
+ * Manage web user configuration.
+ * Module is responsible for get/set web user config data.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Module
+ * @package Baculum Web
+ */
+class WebUserConfig extends ConfigFileModule {
+
+        /**
+         * Web user name allowed characters pattern
+         */
+       const USER_PATTERN = '[\w\@\-\.]+';
+
+       /**
+        * Regular expression to validate e-mail address.
+        * Note, there exists TEmailAddressValidator control but it doesn't allow
+        * local e-mail address, for example root@localhost or gani@darkstar.
+        *
+        * @see http://www.regular-expressions.info/email.html
+        */
+       const EMAIL_ADDRESS_PATTERN = '[a-zA-Z0-9!#$%&\'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&\'*+/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.?)+';
+
+       /**
+        * Web user config file path
+        */
+       const CONFIG_FILE_PATH = 'Application.Web.Config.users';
+
+       /**
+        * Web user config file format
+        */
+       const CONFIG_FILE_FORMAT = 'ini';
+
+       /**
+        * Stores web user config content.
+        */
+       private $config = null;
+
+       /**
+        * These properties are obligatory for web config.
+        */
+       private $user_req_prop = [
+               'long_name',
+               'description',
+               'email',
+               'roles',
+               'api_hosts',
+               'enabled',
+               'ips'
+       ];
+
+       /**
+        * Get web user config.
+        *
+        * @return array config
+        */
+       public function getConfig() {
+               $config = [];
+               if (is_null($this->config)) {
+                       $this->config = $this->readConfig(self::CONFIG_FILE_PATH, self::CONFIG_FILE_FORMAT);
+               }
+               // Web user config validation per single user
+               foreach ($this->config as $username => $user_config) {
+                       if ($this->isUserConfigValid([$username => $user_config]) === false) {
+                               /**
+                                * If config for one web user is invalid, don't add this user config
+                                * but continue checking next user config sections.
+                                * It is because during adding new user manually, admin can do a typo
+                                * in new user config section.  Validation errors are logged.
+                                */
+                               continue;
+                       }
+                       $user_config['username'] = $username;
+                       $config[$username] = $user_config;
+               }
+               return $config;
+       }
+
+       /**
+        * Set web user config.
+        * Method is private, because this method saves whole web user config.
+        * To add (or modify) user in config, use method to save single user in config.
+        * @see setUserConfig()
+        *
+        * @param array $config config
+        * @return boolean true if config saved successfully, otherwise false
+        */
+       public function setConfig(array $config) {
+               $result = false;
+               if ($this->isUserConfigValid($config) === true) {
+                       $result = $this->writeConfig($config, self::CONFIG_FILE_PATH, self::CONFIG_FILE_FORMAT);
+                       if ($result === true) {
+                               $this->config = null;
+                       }
+               }
+               return $result;
+       }
+
+       public function configExists() {
+               return parent::isConfig(self::CONFIG_FILE_PATH);
+       }
+
+       /**
+        * Get single user config.
+        *
+        * @param $user user name
+        * @return array user config
+        */
+       public function getUserConfig($username) {
+               $user_config = [];
+               $config = $this->getConfig();
+               if (key_exists($username, $config)) {
+                       $config[$username]['username'] = $username;
+                       $user_config = $config[$username];
+               }
+               return $user_config;
+       }
+
+       /**
+        * Set single user config.
+        *
+        * @param string $username user name
+        * @param array $user_config user configuration
+        * @return boolean true if config saved successfully, otherwise false
+        */
+       public function setUserConfig($username, array $user_config) {
+               $config = $this->getConfig();
+               $config[$username] = $user_config;
+               if (key_exists('username', $user_config)) {
+                       unset($user_config['username']);
+               }
+               return $this->setConfig($config);
+       }
+
+       /**
+        * Get user config properties.
+        * If custom properties provided, they are merged with required properties.
+        *
+        * @param array $prop custom user properties
+        * @return array user config properties
+        */
+       public function getUserConfigProps($prop = []) {
+               $req_prop =  array_fill_keys($this->user_req_prop, '');
+               return array_merge($req_prop, $prop);
+       }
+
+
+       /**
+        * Import basic auth users to web user config.
+        * It can be done both if web user config hasn't been created yet
+        * and if web user config exists already with some users.
+        *
+        * @return boolean true if config with imported users saved successfully,
+        *                      otherwise false
+        */
+       public function importUsersToConfig() {
+               $basic_users = $this->getModule('basic_webuser')->getUsers();
+               $web_config = $this->getModule('web_config')->getConfig();
+               $users = array_keys($basic_users);
+               sort($users);
+               $users_list = [];
+               $is_users_config = (key_exists('users', $web_config) && is_array($web_config['users']));
+               $user_count = count($users);
+               for ($i = 0; $i < $user_count; $i++) {
+                       $host = '';
+                       if ($is_users_config && key_exists($users[$i], $web_config['users'])) {
+                               $host = $web_config['users'][$users[$i]];
+                       }
+                       $role = '';
+                       if ((isset($web_config['baculum']['login']) && $users[$i] === $web_config['baculum']['login']) || $user_count === 1) {
+                               $role = WebUserRoles::ADMIN;
+                       } else {
+                               $role = WebUserRoles::NORMAL;
+                       }
+                       $users_list[$users[$i]] = $this->getUserConfigProps([
+                               'roles' => $role,
+                               'api_hosts' => $host,
+                               'enabled' => 1
+                       ]);
+               }
+               /**
+                * Merge existing user configs if any.
+                * Already existing users overwrites basic user configs if the same users
+                * exists in basic user config and web user configuration.
+                */
+               $users_list = array_merge($users_list, $this->getConfig());
+               return $this->setConfig($users_list);
+       }
+
+       /**
+        * Import users config from basic auth file into users.conf file.
+        * If users config file doesn't exist, create it and populate
+        * using basic users file. It is one time operation.
+        * Basic auth method is the main Baculum Web auth method. Before introducing
+        * users.conf file, it was the only one supported method.
+        *
+        * @return boolean true on successfull import, otherwise false
+        */
+       public function importUsers() {
+               // import can take place only if user config file doesn't exist
+               if (parent::isConfig(self::CONFIG_FILE_PATH) === true) {
+                       return false;
+               }
+
+               // import users
+               $ret = $this->importUsersToConfig();
+               if ($ret === true) {
+                       $ret = $this->getModule('web_config')->setDefConfigOpts();
+               } else {
+                       $ret = false;
+                       $emsg = 'Error while importing basic users.';
+                       $this->getModule('logging')->log(
+                               __FUNCTION__,
+                               $emsg,
+                               Logging::CATEGORY_APPLICATION,
+                               __FILE__,
+                               __LINE__
+                       );
+               }
+               return $ret;
+       }
+
+       /**
+        * Validate user single user section in config.
+        *
+        * @param array $config user config section
+        * @return boolean true if config valid, otherwise false
+        */
+       private function isUserConfigValid(array $config) {
+               $invalid = ['required' => []];
+               foreach ($config as $username => $user_config) {
+                       for ($i = 0; $i < count($this->user_req_prop); $i++) {
+                               if (!key_exists($this->user_req_prop[$i], $user_config)) {
+                                       $invalid['required'][] = [
+                                               'username' => $username,
+                                               'value' => $this->user_req_prop[$i],
+                                               'type' => 'option'
+                                       ];
+                               }
+                       }
+               }
+
+               $valid = true;
+               $required_len = count($invalid['required']);
+               if ($required_len > 0) {
+                       $valid = false;
+                       $emsg = '';
+                       $path = $this->getConfigRealPath(self::CONFIG_FILE_PATH);
+                       for ($i = 0; $i < $required_len; $i++) {
+                               $emsg = "ERROR [$path] Required {$invalid['required'][$i]['type']} '{$invalid['required'][$i]['value']}' not found for user '{$invalid['required'][$i]['username']}.";
+                               $this->Application->getModule('logging')->log(
+                                       __FUNCTION__,
+                                       $emsg,
+                                       Logging::CATEGORY_APPLICATION,
+                                       __FILE__,
+                                       __LINE__
+                               );
+                       }
+               }
+               return $valid;
+       }
+}
+?>
diff --git a/gui/baculum/protected/Web/Class/WebUserManager.php b/gui/baculum/protected/Web/Class/WebUserManager.php
new file mode 100644 (file)
index 0000000..dc2416b
--- /dev/null
@@ -0,0 +1,407 @@
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum   - Bacula web interface
+ *
+ * Copyright (C) 2013-2020 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.Web.Class.WebUser');
+Prado::using('Application.Web.Class.WebModule');
+
+/**
+ * Web user manager module.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Module
+ * @package Baculum Web
+ */
+class WebUserManager extends WebModule implements IUserManager {
+
+       /**
+        * Authorized flag key.
+        * This flag is used to set authorization state.
+        */
+       const SET_AUTHROIZED_FLAG = 'AuthorizedFlag';
+
+       /**
+        * User class to represent single user instance
+        */
+       private $user_class = '';
+
+       /**
+        * Guest name.
+        */
+       private $guest_name = 'guest';
+
+       /**
+        * Stores object used to create users (factory)
+        */
+       private $user_factory;
+
+       /**
+        * Initialize module configuration.
+        *
+        * @param TXmlElement $config module configuration
+        * @return none
+        */
+       public function init($config) {
+               $this->user_factory = Prado::createComponent($this->user_class, $this);
+               $this->setAuthorizedFlag(null);
+               $this->getModule('auth')->attachEventHandler(
+                       'OnAuthenticate',
+                       [$this, 'doAuthentication']
+               );
+               $this->Application->attachEventHandler(
+                       'onAuthorization',
+                       [$this, 'doAuthorization']
+               );
+               $this->Application->attachEventHandler(
+                       'onAuthorizationComplete',
+                       [$this, 'doAuthorizationComplete']
+               );
+       }
+
+       /**
+        * Create and get new user object.
+        *
+        * @param mixed $username username string or null for guests
+        */
+       public function getUser($username = null) {
+               $user = null;
+               if (is_null($username)) {
+                       $user = Prado::createComponent($this->user_class, $this);
+                       $user->setIsGuest(true);
+               } else {
+                       $user = $this->user_factory->createUser($username);
+                       $user->setIsGuest(false);
+               }
+               return $user;
+       }
+
+       /**
+        * Used for authentication.
+        * It does login try.
+        *
+        * @param string $username username
+        * @param string $password password
+        * @return boolean true if user and password are valid, false otherwise
+        */
+       public function validateUser($username, $password) {
+               $valid = false;
+               $manager_cls = $this->getUserManagerClass();
+               if (!empty($manager_cls)) {
+                       $manager = Prado::createComponent($manager_cls);
+                       $manager->init(null);
+                       $valid = $manager->validateUser($username, $password);
+               }
+               return $valid;
+       }
+
+       /**
+        * User class getter.
+        *
+        * @return string user class name
+        */
+       public function getUserClass() {
+               return $this->user_class;
+       }
+
+       /**
+        * User class setter.
+        *
+        * @param string $cls user class name
+        * @return none
+        */
+       public function setUserClass($cls) {
+               $this->user_class = $cls;
+       }
+
+       /**
+        * Guest name getter.
+        *
+        * @return guest name
+        */
+       public function getGuestName() {
+               return $this->guest_name;
+       }
+
+       /**
+        * Guest name setter.
+        *
+        * @param string guest name
+        * @return none
+        */
+       public function setGuestName($name) {
+               $this->guest_name = $name;
+       }
+
+       /**
+        * Used for getting stored in cookie information about user.
+        * Useful to keep login between sessions.
+        *
+        * @param THttpCookie $cookie cookie object
+        * @return none
+        */
+       public function getUserFromCookie($cookie) {
+               // not implemented
+       }
+
+       /**
+        * Used for setting in cookie information about user.
+        * Useful to keep login between sessions.
+        *
+        * @param THttpCookie $cookie cookie object
+        * @return none
+        */
+       public function saveUserToCookie($cookie) {
+               // not implemented
+       }
+
+       /**
+        * Check if currently loading page is allowed for current user.
+        *
+        * @param WebUser $user user object
+        * @param string $page_path page path
+        */
+       public function isPageAllowed($user, $page_path) {
+               $allowed = false;
+               $page_roles = $this->getModule('user_role')->getRolesByPagePath($page_path);
+               $user_roles = $user->getRoles();
+               for ($i = 0; $i < count($user_roles); $i++) {
+                       if (in_array($user_roles[$i], $page_roles)) {
+                               $allowed = true;
+                               break;
+                       }
+               }
+               return $allowed;
+       }
+
+       /**
+        * Get user manager class.
+        * It is switcher between different authentication backends.
+        *
+        * @return string user manager class path
+        */
+       private function getUserManagerClass() {
+               $cls = null;
+               $auth_method = $this->getModule('web_config')->getAuthMethod();
+
+               switch ($auth_method) {
+                       case WebConfig::AUTH_METHOD_BASIC:
+                               $cls = 'Application.Web.Class.WebBasicUserManager';
+                               break;
+                       case WebConfig::AUTH_METHOD_LDAP:
+                               $cls = 'Application.Web.Class.WebLdapUserManager';
+                               break;
+                       default:
+                               $cls = 'Application.Web.Class.WebBasicUserManager';
+               }
+               return $cls;
+       }
+
+       /**
+        * Event handler attached to the application authentication event.
+        * It does auto-login for Basic authentication users and applies
+        * authorization rules before they are used in authorization phase.
+        *
+        * @param TApplication $application application object
+        * @return none
+        */
+       public function doAuthentication($application) {
+               if ($this->getModule('web_config')->isAuthMethodBasic() && $application->getUser()->IsGuest) {
+                       /**
+                        * For basic method the authentication is realized by web server.
+                        * From this reason do log in autmatically.
+                        */
+                       // For basic auth take username from web server.
+                       if (key_exists('PHP_AUTH_USER' , $_SERVER) && !empty($_SERVER['PHP_AUTH_USER'])) {
+                               $username = $_SERVER['PHP_AUTH_USER'];
+                               $password = '';
+                               $this->getModule('auth')->login($username, $password);
+                               $this->Response->redirect('/');
+                       } else {
+                               $emsg = 'Basic auth is enabled, but PHP_AUTH_USER is not set or is empty.';
+                               $this->Application->getModule('logging')->log(
+                                       __FUNCTION__,
+                                       $emsg,
+                                       Logging::CATEGORY_SECURITY,
+                                       __FILE__,
+                                       __LINE__
+                               );
+                       }
+               }
+
+               $this->applyAuthorizationRules($application);
+       }
+
+       /**
+        * Apply authorization rules.
+        * It is important place because here are defined authorization rules for currently
+        * logged in user and currently visited page.
+        *
+        * @param TApplication $application application object
+        */
+       private function applyAuthorizationRules($application) {
+               $page_path = $this->getService()->getRequestedPagePath();
+               $page_roles = $this->getModule('user_role')->getRolesByPagePath($page_path, false);
+               $auth_rules = $this->getApplication()->getAuthorizationRules();
+               $web_config = $this->getModule('web_config');
+               $roles = implode(',', $page_roles);
+               $users = '';
+               $ips = $application->User->getIps();
+               $pc = $this->getModule('page_category');
+               $add_role = false;
+               if ($pc->isCategorySystem($page_path) || $pc->isCategoryConditional($page_path)) {
+                       // allow system pages for all logged in users
+                       $roles = '';
+                       $users = '@';
+                       $add_role = true;
+               }
+
+               if ($pc->isCategoryPublic($page_path)) {
+                       // public pages are available for everybody
+                       $roles = $ips = '';
+                       $users = '*';
+                       $add_role = true;
+               }
+
+               // remove authorization rules if any
+               $auth_rules->clear();
+
+               /**
+                * Rules for current page.
+                * NOTE: items order has meaning.
+                */
+               $allow_rule = [
+                       'action' => 'allow',
+                       'roles' => $roles,
+                       'users' => $users,
+                       'verb' => '',
+                       'ips' => $ips
+               ];
+
+               $deny_rule = [
+                       'action' => 'deny',
+                       'roles' => '',
+                       'users' => '*',
+                       'verb' => '',
+                       'ips' => ''
+               ];
+               $rules = [];
+
+               /**
+                * Add allow rule for enabled users, for special pages (system and public)
+                * and if user doesn't exist in user config and access by default setting
+                * is enabled.
+                */
+               if ($application->User->Enabled === true || $add_role === true || (!$application->User->InConfig && $web_config->isDefAccessDefaultSettings())) {
+                       // Add allow rules for user with set enabled flag
+                       $rules[] = $allow_rule;
+               }
+               // Deny everything else
+               $rules[] = $deny_rule;
+
+               // Add authorization rules
+               for ($i = 0; $i < count($rules); $i++) {
+                       $rule = new TAuthorizationRule(
+                               $rules[$i]['action'],
+                               $rules[$i]['users'],
+                               $rules[$i]['roles'],
+                               $rules[$i]['verb'],
+                               $rules[$i]['ips']
+                       );
+                       $auth_rules->insertAt($i, $rule);
+               }
+       }
+
+       /**
+        * Set authorization flag.
+        * This flag is to know if user finished authorization process successfully or not.
+        * The flag is set in temporary on authorization process and it is set to null
+        * just after the authorization finishes with success. If authorization fails
+        * then onAuthorizationComplete event isn't fired because all process page loading
+        * is stopped on onAuthorization event.
+        * NOTE: This flag has only informational character. All authorization work is
+        * done by the framework.
+        *
+        * @param string|null $state flag state
+        * @return none
+        */
+       public function setAuthorizedFlag($state) {
+               $this->Application->getSession()->add(self::SET_AUTHROIZED_FLAG, $state);
+       }
+
+       /**
+        * Get authorization flag.
+        *
+        * @return string|null flag state
+        */
+       public function getAuthorizedFlag() {
+               return $this->Application->getSession()->itemAt(self::SET_AUTHROIZED_FLAG);
+       }
+
+       /**
+        * Event handler attached to the application authorization event.
+        * It sets the authorization flag.
+        *
+        * @param TApplication $param application object
+        */
+       public function doAuthorization($application) {
+               $service = $this->Application->getService();
+               $auth = $this->getModule('auth');
+               $page = $service->getRequestedPagePath();
+               if (($service instanceof TPageService) && $page !== $auth->getLoginPage()) {
+                       $this->setAuthorizedFlag($page);
+               }
+       }
+
+       /**
+        * Event handler attached to the application authorization event.
+        * It sets the authorization flag.
+        *
+        * @param TApplication $param application object
+        */
+       public function doAuthorizationComplete($application) {
+               $service = $this->Application->getService();
+               $auth = $this->getModule('auth');
+               $page = $service->getRequestedPagePath();
+               if (($service instanceof TPageService) && $page === $this->getAuthorizedFlag()) {
+                       $this->setAuthorizedFlag(null);
+               }
+       }
+
+       /**
+        * Returns authorization state for current page request.
+        * Because the framework doesn't provide any public method to check
+        * whether authorization finished successfully, this method can
+        * be used for that purpose.
+        * NOTE: Use it after onAuthorizationComplete application event,
+        * not before.
+        *
+        * @return boolean true if authorization finished successfully, otherwise false
+        */
+       public function isAuthorized() {
+               $web_config = $this->getModule('web_config');
+               $user = $this->Application->getUser();
+               return ($this->getAuthorizedFlag() === null &&
+                       (($user->InConfig && !$web_config->isDefAccessNoAccess()) ||
+                       (!$user->InConfig && $web_config->isDefAccessDefaultSettings()))
+               );
+       }
+}
+?>
diff --git a/gui/baculum/protected/Web/Class/WebUserRoles.php b/gui/baculum/protected/Web/Class/WebUserRoles.php
new file mode 100644 (file)
index 0000000..7af51e0
--- /dev/null
@@ -0,0 +1,160 @@
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum   - Bacula web interface
+ *
+ * Copyright (C) 2013-2020 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.Web.Class.WebModule');
+Prado::using('Application.Web.Class.PageCategory');
+
+/**
+ * Web user roles module.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Module
+ * @package Baculum Web
+ */
+class WebUserRoles extends WebModule {
+
+       /**
+        * Pre-defined roles.
+        */
+       const ADMIN = 'admin';
+       const NORMAL = 'normal';
+
+       /**
+        * Single role properties.
+        */
+       private $role_prop = [
+               'role',
+               'long_name',
+               'description',
+               'enabled',
+               'resources'
+       ];
+
+       /**
+        * Get pre-defined roles with available resources for them.
+        *
+        * @return array pre-defined roles
+        */
+       public function getPreDefinedRoles() {
+               $roles = [];
+
+               // Admin user resources
+               $res = $this->getModule('page_category')->getCategories(false);
+               $admin_res = array_values($res);
+               $roles[self::ADMIN] = array_combine($this->role_prop, [
+                       self::ADMIN,
+                       'Administrator',
+                       'Role with full access',
+                       '1',
+                       implode(',', $admin_res)
+               ]);
+
+               // Normal user resources
+               $res = [
+                       PageCategory::DASHBOARD,
+                       PageCategory::JOB_HISTORY_LIST,
+                       PageCategory::JOB_HISTORY_VIEW,
+                       PageCategory::JOB_LIST,
+                       PageCategory::JOB_VIEW,
+                       PageCategory::CLIENT_LIST,
+                       PageCategory::CLIENT_VIEW,
+                       PageCategory::RESTORE_WIZARD,
+                       PageCategory::GRAPHS
+               ];
+               $roles[self::NORMAL] = array_combine($this->role_prop, [
+                       self::NORMAL,
+                       'Normal user',
+                       'Role with limitted access',
+                       '1',
+                       implode(',', $res)
+               ]);
+               return $roles;
+       }
+
+       /**
+        * Check if a role is predefined.
+        *
+        * @param string $role nazwa roli
+        * @return boolean true if role is predefined, otherwise false
+        */
+       public function isRolePreDefined($role) {
+               $roles = $this->getPreDefinedRoles();
+               return key_exists($role, $roles);
+       }
+
+       /**
+        * Get all roles pre-defined and defined in config.
+        * Pre-defined are merged together with defined.
+        *
+        * @return array all available roles
+        */
+       public function getRoles() {
+               $roles = $this->getModule('role_config')->getConfig();
+               return array_merge($this->getPreDefinedRoles(), $roles);
+       }
+
+       /**
+        * Get single role config.
+        *
+        * @param string $role role name
+        * @return array role config or empty array if role doesn't exist
+        */
+       public function getRole($role) {
+               $ret = [];
+               $roles = $this->getRoles();
+               if (key_exists($role, $roles)) {
+                       $roles[$role]['role'] = $role;
+                       $ret = $roles[$role];
+               }
+               return $ret;
+       }
+
+       /**
+        * Get all roles by specific page path.
+        * They are roles that have page defined in allowed resources.
+        *
+        * @param string $page_path page path
+        * @return array roles defined for a page
+        */
+       public function getRolesByPagePath($page_path, $with_disabled = true) {
+               $roles = [];
+               $all_roles = $this->getRoles();
+               foreach ($all_roles as $role => $prop) {
+                       $rs = explode(',', $prop['resources']);
+                       $enabled = (boolean)$prop['enabled'];
+                       if (($enabled || $with_disabled) && in_array($page_path, $rs)) {
+                               $roles[] = $role;
+                       }
+               }
+               return $roles;
+       }
+
+       /**
+        * Get all pages for specific role.
+        */
+       public function getPagesByRole($role) {
+               $pages = [];
+               $role_cfg = $this->getRole($role);
+               return explode(',', $role_cfg['resources']);
+       }
+}
+?>
index aa55b8e9004262333e792264e485d83c54f00a81..b69fd558df3e57a028e6cab1e4ca9b5d1d37c4c0 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -33,6 +33,13 @@ if (!ini_get('date.timezone')) {
        date_default_timezone_set($timezone);
 }
 
+/**
+ * Set time limit to 60 seconds.
+ * Please note that async requests default times out after 30 seconds
+ * if not set other value.
+ */
+set_time_limit(60);
+
 /*
  * Support for web servers (for example Lighttpd) which do not provide direct
  * info about HTTP Basic auth to PHP superglobal $_SERVER array.
index 5c5342efe4d259e422682cc3ca7a1480eedd2fb7..b89b018fa9a007211b2d65bdc993dc242d4c3b22 100644 (file)
@@ -630,132 +630,6 @@ var Dashboard = {
        }
 }
 
-var Users = {
-       ids: {
-               create_user: {
-                       add_user: 'add_user',
-                       add_user_btn: 'add_user_btn',
-                       newuser: 'newuser',
-                       newpwd: 'newpwd'
-               },
-               change_pwd: {
-                       rel_chpwd: 'chpwd',
-                       rel_chpwd_btn: 'chpwd_btn'
-               },
-               set_host: {
-                       rel_user_host: 'user_host_img'
-               }
-       },
-       validators: {
-               user_pattern: null
-       },
-       current_action: null,
-       init: function() {
-               this.setEvents();
-       },
-       setEvents: function() {
-               document.getElementById(this.ids.create_user.add_user_btn).addEventListener('click', function(e) {
-                       $('#' + this.ids.create_user.add_user).show();
-                       $('#' + this.ids.create_user.newuser).focus();
-               }.bind(this));
-               document.getElementById(this.ids.create_user.newuser).addEventListener('keydown', function(e) {
-                       var target = e.target || e.srcElement;
-                       if (e.keyCode == 13) {
-                               this.addUser();
-                       } else if (e.keyCode == 27) {
-                               this.cancelAddUser();
-                       }
-                       return false;
-               }.bind(this));
-               document.getElementById(this.ids.create_user.newpwd).addEventListener('keydown', function(e) {
-                       var target = e.target || e.srcElement;
-                       if (e.keyCode == 13) {
-                               this.addUser();
-                       } else if (e.keyCode == 27) {
-                               this.cancelAddUser();
-                       }
-                       return false;
-               }.bind(this));
-       },
-       userValidator: function(user) {
-               user = user.replace(/\s/g, '');
-               if (user == '') {
-                       alert(this.txt.enter_login);
-                       return false;
-               }
-               var valid = this.validators.user_pattern.test(user);
-               if (valid === false) {
-                       alert(this.txt.invalid_login);
-                       return false;
-               }
-               return true;
-       },
-       pwdValidator: function(pwd) {
-               var valid = pwd.length > 4;
-               if (valid === false) {
-                       alert(this.txt.invalid_pwd);
-               }
-               return valid;
-       },
-       addUser: function() {
-               var user = document.getElementById(this.ids.create_user.newuser);
-               var pwd = document.getElementById(this.ids.create_user.newpwd);
-               if (this.userValidator(user.value) === false) {
-                       return false;
-               }
-               if (this.pwdValidator(pwd.value) === false) {
-                       return false;
-               }
-
-               $('#' + this.ids.create_user.add_user).hide();
-               this.action_callback('newuser', user.value, pwd.value);
-               user.value = '';
-               pwd.value = '';
-               return true;
-       },
-       rmUser: function(user) {
-               this.action_callback('rmuser', user);
-       },
-       showChangePwd: function(el) {
-               $('a[rel=\'' + this.ids.change_pwd.rel_chpwd_btn + '\']').show();
-               $('#' + el).hide();
-               $('span[rel=\'' + this.ids.change_pwd.rel_chpwd + '\']').hide();
-               $(el.nextElementSibling).show();
-               $(el.nextElementSibling).find('input')[0].focus();
-       },
-       changePwd: function(el, user) {
-               var pwd = el.value;
-
-               if (this.pwdValidator(pwd) === false) {
-                       return false;
-               }
-
-               $(el.parentNode).hide();
-               $(el.parentNode.previousElementSibling).show();
-               this.action_callback('chpwd', user, pwd);
-               return true;
-       },
-       set_host: function(user, select) {
-               select.nextElementSibling.style.visibility = 'visible';
-               this.action_callback('set_host', user, select.value);
-       },
-       hide_loader: function() {
-               setTimeout(function() {
-                       if (this.current_action === 'set_host') {
-                               $('I[rel=\'' + this.ids.set_host.rel_user_host + '\']').css({visibility: 'hidden'});
-                       }
-               }.bind(this), 300);
-
-       },
-       cancelAddUser: function() {
-               $('#' + this.ids.create_user.add_user).hide();
-       },
-       cancelChangePwd: function(el) {
-               $(el.parentNode.parentNode).hide();
-       }
-
-};
-
 var W3SideBar = {
        ids: {
                sidebar: 'sidebar',
@@ -1122,9 +996,26 @@ dtEscapeRegex = function(value) {
        return $.fn.dataTable.util.escapeRegex(value);
 };
 
+/**
+ * Do validation comma separated list basing on regular expression
+ * for particular values.
+ */
+function validate_comma_separated_list(value, regex) {
+       var valid = true;
+       var vals = value.split(',');
+       var val;
+       for (var i = 0; i < vals.length; i++) {
+               val = vals[i].trim();
+               if (!val || (regex && !regex.test(val))) {
+                       valid = false;
+                       break;
+               }
+       }
+       return valid;
+}
 
 function get_table_toolbar(table, actions, txt) {
-       var table_toolbar = document.querySelector('div.table_toolbar');
+       var table_toolbar = document.querySelector('#' + table.table().node().id + '_wrapper div.table_toolbar');
        table_toolbar.className += ' dt-buttons';
        table_toolbar.style.display = 'none';
        var title = document.createTextNode(txt.actions);
@@ -1141,6 +1032,9 @@ function get_table_toolbar(table, actions, txt) {
                select.appendChild(option);
                acts[actions[i].action] = actions[i];
        }
+       if (actions.length == 1) {
+               select.selectedIndex = 1;
+       }
        var btn = document.createElement('BUTTON');
        btn.type = 'button';
        btn.className = 'dt-button';
index 4fbd8d2ee4f9abdd70ec318ac802bd66d0dffe23..12a4ad79e34017d4d74068fb082e253f8ea017cb 100644 (file)
Binary files a/gui/baculum/protected/Web/Lang/en/messages.mo and b/gui/baculum/protected/Web/Lang/en/messages.mo differ
index e00e5187dd73ad1fa71bd8872a3e976025b610b3..af7471b1bd9798ee8255f0406bb10452f6a88a98 100644 (file)
@@ -1208,12 +1208,6 @@ msgstr "Supported"
 msgid "Not supported"
 msgstr "Not supported"
 
-msgid "Authorization to Baculum API"
-msgstr "Authorization to Baculum API"
-
-msgid "Authorization to Baculum Web"
-msgstr "Authorization to Baculum Web"
-
 msgid "Step 3 - authentication params to Baculum Web panel"
 msgstr "Step 3 - authentication params to Baculum Web panel"
 
@@ -1445,9 +1439,6 @@ msgstr "Volume"
 msgid "During restore there will be used following volumes:"
 msgstr "During restore there will be used following volumes:"
 
-msgid "Use Ctrl + Mouse Click to change selection"
-msgstr "Use Ctrl + Mouse Click to change selection"
-
 msgid "Go back"
 msgstr "Go back"
 
@@ -2629,3 +2620,306 @@ msgstr "Tip: Use left-click to select table row. Use CTRL + left-click to multip
 
 msgid "Show time in job log:"
 msgstr "Show time in job log:"
+
+msgid "Security"
+msgstr "Security"
+
+msgid "Roles"
+msgstr "Roles"
+
+msgid "Long name"
+msgstr "Long name"
+
+msgid "Edit user"
+msgstr "Edit user"
+
+msgid "Long name:"
+msgstr "Long name:"
+
+msgid "Description:"
+msgstr "Description:"
+
+msgid "E-mail:"
+msgstr "E-mail:"
+
+msgid "Roles:"
+msgstr "Roles:"
+
+msgid "Invalid e-mail address value."
+msgstr "Invalid e-mail address value."
+
+msgid "Invalid username value."
+msgstr "Invalid username value."
+
+msgid "Username"
+msgstr "Username"
+
+msgid "E-mail"
+msgstr "E-mail"
+
+msgid "Description"
+msgstr "Description"
+
+msgid "Add user"
+msgstr "Add user"
+
+msgid "Username field cannot be empty."
+msgstr "Username field cannot be empty."
+
+msgid "At least one role must be selected."
+msgstr "At least one role must be selected."
+
+msgid "Username with the given name already exists."
+msgstr "Username with the given name already exists."
+
+msgid "Edit role"
+msgstr "Edit role"
+
+msgid "Role with limitted access"
+msgstr "Role with limitted access"
+
+msgid "Role with full access"
+msgstr "Role with full access"
+
+msgid "Role:"
+msgstr "Role:"
+
+msgid "Resources:"
+msgstr "Resources:"
+
+msgid "Role name"
+msgstr "Role name"
+
+msgid "Resources"
+msgstr "Resources"
+
+msgid "Add new role"
+msgstr "Add new role"
+
+msgid "Role field cannot be empty."
+msgstr "Role field cannot be empty."
+
+msgid "At least one resource must be selected."
+msgstr "At least one resource must be selected."
+
+msgid "Role with the given name already exists."
+msgstr "Role with the given name already exists."
+
+msgid "This is native predefined role, that cannot be changed. For having custom roles please use the button to add new role."
+msgstr "This is native predefined role, that cannot be changed. For having custom roles please use the button to add new role."
+
+msgid "The following roles are predefined and cannot be removed: %predefined_roles"
+msgstr "The following roles are predefined and cannot be removed: %predefined_roles"
+
+msgid "The following roles are using by users and cannot be removed: %used_roles To remove them, please unassign all users from these roles."
+msgstr "The following roles are using by users and cannot be removed: %used_roles To remove them, please unassign all users from these roles."
+
+msgid "Add role"
+msgstr "Add role"
+
+msgid "Authentication method"
+msgstr "Authentication method"
+
+msgid "HTTP Basic authentication"
+msgstr "HTTP Basic authentication"
+
+msgid "LDAP authentication"
+msgstr "LDAP authentication"
+
+msgid "LDAP server options"
+msgstr "LDAP server options"
+
+msgid "SSL encryption (LDAPS):"
+msgstr "SSL encryption (LDAPS):"
+
+msgid "Protocol version:"
+msgstr "Protocol version:"
+
+msgid "LDAP version"
+msgstr "LDAP version"
+
+msgid "LDAP authentication method"
+msgstr "LDAP authentication method"
+
+msgid "Anonymous authentication"
+msgstr "Anonymous authentication"
+
+msgid "Simple authentication"
+msgstr "Simple authentication"
+
+msgid "Base DN:"
+msgstr "Base DN:"
+
+msgid "Attributes"
+msgstr "Attributes"
+
+msgid "Please enter port."
+msgstr "Please enter port."
+
+msgid "Please enter username."
+msgstr "Please enter username."
+
+msgid "Manager DN:"
+msgstr "Manager DN:"
+
+msgid "Please enter IP Address/Hostname."
+msgstr "Please enter IP Address/Hostname."
+
+msgid "Please enter username attribute."
+msgstr "Please enter username attribute."
+
+msgid "Test LDAP connection"
+msgstr "Test LDAP connection"
+
+msgid "Manage LDAP users"
+msgstr "Manage LDAP users"
+
+msgid "Please enter Base DN."
+msgstr "Please enter Base DN."
+
+msgid "LDAP user list"
+msgstr "LDAP user list"
+
+msgid "Import users"
+msgstr "Import users"
+
+msgid "Import all users"
+msgstr "Import all users"
+
+msgid "Import selected users"
+msgstr "Import selected users"
+
+msgid "Import users whose fulfill criteria"
+msgstr "Import users whose fulfill criteria"
+
+msgid "Import options:"
+msgstr "Import options:"
+
+msgid "Criteria filter:"
+msgstr "Criteria filter:"
+
+msgid "contains:"
+msgstr "contains:"
+
+msgid "Reload"
+msgstr "Reload"
+
+msgid "Please select users in table to import."
+msgstr "Please select users in table to import."
+
+msgid "Do not overwrite existing Baculum Web users, if they have the same username"
+msgstr "Do not overwrite existing Baculum Web users, if they have the same username"
+
+msgid "Default role for imported users:"
+msgstr "Default role for imported users:"
+
+msgid "Advanced options"
+msgstr "Advanced options"
+
+msgid "Default API host for imported users:"
+msgstr "Default API host for imported users:"
+
+msgid "Log in"
+msgstr "Log in"
+
+msgid "Invalid username or password"
+msgstr "Invalid username or password"
+
+msgid "IP address restrictions"
+msgstr "IP address restrictions"
+
+msgid "IP address restrictions:"
+msgstr "IP address restrictions:"
+
+msgid "Use CTRL + left-click to multiple item selection"
+msgstr "Use CTRL + left-click to multiple item selection"
+
+msgid "Comma separated IP addresses. Using asterisk character, there is also possible to provide subnet, for example: 192.168.1.*"
+msgstr "Comma separated IP addresses. Using asterisk character, there is also possible to provide subnet, for example: 192.168.1.*"
+
+msgid "Default IP address restrictions for imported users:"
+msgstr "Default IP address restrictions for imported users:"
+
+msgid "Set your IP address"
+msgstr "Set your IP address"
+
+msgid "Invalid IP address restrictions value. This field can have comma separated IP addresses only or subnet addresses like 192.168.1.*"
+msgstr "Invalid IP address restrictions value. This field can have comma separated IP addresses only or subnet addresses like 192.168.1.*"
+
+msgid "This type of authentication is realized by web server. To use it, the basic authentication has to be enabled in the Baculum Web web server configuration."
+msgstr "This type of authentication is realized by web server. To use it, the basic authentication has to be enabled in the Baculum Web web server configuration."
+
+msgid "General settings"
+msgstr "General settings"
+
+msgid "Default access setting for logged in users not defined in Baculum Web:"
+msgstr "Default access setting for logged in users not defined in Baculum Web:"
+
+msgid "No access"
+msgstr "No access"
+
+msgid "Access with default settings"
+msgstr "Access with default settings"
+
+msgid "Default role:"
+msgstr "Default role:"
+
+msgid "Default API host:"
+msgstr "Default API host:"
+
+msgid "Users file path:"
+msgstr "Users file path:"
+
+msgid "Hash algorithm:"
+msgstr "Hash algorithm:"
+
+msgid "Please enter users file path value."
+msgstr "Please enter users file path value."
+
+msgid "Test"
+msgstr "Test"
+
+msgid "The user file is not accessible."
+msgstr "The user file is not accessible."
+
+msgid "The user file is not readable by web server user."
+msgstr "The user file is not readable by web server user."
+
+msgid "The user file is readable but not writeable by web server user."
+msgstr "The user file is readable but not writeable by web server user."
+
+msgid "Allow Baculum Web to manage the Basic authentication users (add/remove users and change their passwords)"
+msgstr "Allow Baculum Web to manage the Basic authentication users (add/remove users and change their passwords)"
+
+msgid "Authorization failed. Please contact the Baculum administrator to grant permissions."
+msgstr "Authorization failed. Please contact the Baculum administrator to grant permissions."
+
+msgid "Try again"
+msgstr "Try again"
+
+msgid "Manage Basic users"
+msgstr "Manage Basic users"
+
+msgid "Empty user list"
+msgstr "Empty user list"
+
+msgid "Basic user list"
+msgstr "Basic user list"
+
+msgid "Access to Baculum API"
+msgstr "Access to Baculum API"
+
+msgid "Access to Baculum Web"
+msgstr "Access to Baculum Web"
+
+msgid "Requried support from web server side - e.g. %supported_web_server_list"
+msgstr "Requried support from web server side - e.g. %supported_web_server_list"
+
+msgid "Apache 2.4 with apr-util 1.5+"
+msgstr "Apache 2.4 with apr-util 1.5+"
+
+msgid "Apache, Lighttpd and Nginx on most UNIX platforms"
+msgstr "Apache, Lighttpd and Nginx on most UNIX platforms"
+
+msgid "Nginx on most UNIX platforms"
+msgstr "Nginx on most UNIX platforms"
index 67dbea08305da1b4046d9b57210f2d4d59ee92c8..cb0b4a519b3ce7ec8ce8afaf8912d5832ddd3485 100644 (file)
Binary files a/gui/baculum/protected/Web/Lang/ja/messages.mo and b/gui/baculum/protected/Web/Lang/ja/messages.mo differ
index 7445d08744c0ccff4ef2a58b3833b4d4e573313f..844d641d8a736d6e4d85e3fc7644550dd6baa3e0 100644 (file)
@@ -164,12 +164,6 @@ msgstr "Auth Type:"
 msgid "Authentication"
 msgstr "認証"
 
-msgid "Authorization to Baculum API"
-msgstr "Baculum API 設定"
-
-msgid "Authorization to Baculum Web"
-msgstr "Baculum Web 管理ユーザー設定"
-
 msgid "Authorization to Baculum panel"
 msgstr "Authorization to Baculum panel"
 
@@ -1638,9 +1632,6 @@ msgstr "Update slots without barcodes"
 msgid "Update volumes"
 msgstr "Update volumes"
 
-msgid "Use Ctrl + Mouse Click to change selection"
-msgstr "Use Ctrl + Mouse Click to change selection"
-
 msgid "Use HTTP Basic authentication"
 msgstr "ベーシック認証使用"
 
@@ -2715,3 +2706,306 @@ msgstr "Tip: Use left-click to select table row. Use CTRL + left-click to multip
 
 msgid "Show time in job log:"
 msgstr "Show time in job log:"
+
+msgid "Security"
+msgstr "Security"
+
+msgid "Roles"
+msgstr "Roles"
+
+msgid "Long name"
+msgstr "Long name"
+
+msgid "Edit user"
+msgstr "Edit user"
+
+msgid "Long name:"
+msgstr "Long name:"
+
+msgid "Description:"
+msgstr "Description:"
+
+msgid "E-mail:"
+msgstr "E-mail:"
+
+msgid "Roles:"
+msgstr "Roles:"
+
+msgid "Invalid e-mail address value."
+msgstr "Invalid e-mail address value."
+
+msgid "Invalid username value."
+msgstr "Invalid username value."
+
+msgid "Username"
+msgstr "Username"
+
+msgid "E-mail"
+msgstr "E-mail"
+
+msgid "Description"
+msgstr "Description"
+
+msgid "Add user"
+msgstr "Add user"
+
+msgid "Username field cannot be empty."
+msgstr "Username field cannot be empty."
+
+msgid "At least one role must be selected."
+msgstr "At least one role must be selected."
+
+msgid "Username with the given name already exists."
+msgstr "Username with the given name already exists."
+
+msgid "Edit role"
+msgstr "Edit role"
+
+msgid "Role with limitted access"
+msgstr "Role with limitted access"
+
+msgid "Role with full access"
+msgstr "Role with full access"
+
+msgid "Role:"
+msgstr "Role:"
+
+msgid "Resources:"
+msgstr "Resources:"
+
+msgid "Role name"
+msgstr "Role name"
+
+msgid "Resources"
+msgstr "Resources"
+
+msgid "Add new role"
+msgstr "Add new role"
+
+msgid "Role field cannot be empty."
+msgstr "Role field cannot be empty."
+
+msgid "At least one resource must be selected."
+msgstr "At least one resource must be selected."
+
+msgid "Role with the given name already exists."
+msgstr "Role with the given name already exists."
+
+msgid "This is native predefined role, that cannot be changed. For having custom roles please use the button to add new role."
+msgstr "This is native predefined role, that cannot be changed. For having custom roles please use the button to add new role."
+
+msgid "The following roles are predefined and cannot be removed: %predefined_roles"
+msgstr "The following roles are predefined and cannot be removed: %predefined_roles"
+
+msgid "The following roles are using by users and cannot be removed: %used_roles To remove them, please unassign all users from these roles."
+msgstr "The following roles are using by users and cannot be removed: %used_roles To remove them, please unassign all users from these roles."
+
+msgid "Add role"
+msgstr "Add role"
+
+msgid "Authentication method"
+msgstr "Authentication method"
+
+msgid "HTTP Basic authentication"
+msgstr "HTTP Basic authentication"
+
+msgid "LDAP authentication"
+msgstr "LDAP authentication"
+
+msgid "LDAP server options"
+msgstr "LDAP server options"
+
+msgid "SSL encryption (LDAPS):"
+msgstr "SSL encryption (LDAPS):"
+
+msgid "Protocol version:"
+msgstr "Protocol version:"
+
+msgid "LDAP version"
+msgstr "LDAP version"
+
+msgid "LDAP authentication method"
+msgstr "LDAP authentication method"
+
+msgid "Anonymous authentication"
+msgstr "Anonymous authentication"
+
+msgid "Simple authentication"
+msgstr "Simple authentication"
+
+msgid "Base DN:"
+msgstr "Base DN:"
+
+msgid "Attributes"
+msgstr "Attributes"
+
+msgid "Please enter port."
+msgstr "Please enter port."
+
+msgid "Please enter username."
+msgstr "Please enter username."
+
+msgid "Manager DN:"
+msgstr "Manager DN:"
+
+msgid "Please enter IP Address/Hostname."
+msgstr "Please enter IP Address/Hostname."
+
+msgid "Please enter username attribute."
+msgstr "Please enter username attribute."
+
+msgid "Test LDAP connection"
+msgstr "Test LDAP connection"
+
+msgid "Manage LDAP users"
+msgstr "Manage LDAP users"
+
+msgid "Please enter Base DN."
+msgstr "Please enter Base DN."
+
+msgid "LDAP user list"
+msgstr "LDAP user list"
+
+msgid "Import users"
+msgstr "Import users"
+
+msgid "Import all users"
+msgstr "Import all users"
+
+msgid "Import selected users"
+msgstr "Import selected users"
+
+msgid "Import users whose fulfill criteria"
+msgstr "Import users whose fulfill criteria"
+
+msgid "Import options:"
+msgstr "Import options:"
+
+msgid "Criteria filter:"
+msgstr "Criteria filter:"
+
+msgid "contains:"
+msgstr "contains:"
+
+msgid "Reload"
+msgstr "Reload"
+
+msgid "Please select users in table to import."
+msgstr "Please select users in table to import."
+
+msgid "Do not overwrite existing Baculum Web users, if they have the same username"
+msgstr "Do not overwrite existing Baculum Web users, if they have the same username"
+
+msgid "Default role for imported users:"
+msgstr "Default role for imported users:"
+
+msgid "Advanced options"
+msgstr "Advanced options"
+
+msgid "Default API host for imported users:"
+msgstr "Default API host for imported users:"
+
+msgid "Log in"
+msgstr "Log in"
+
+msgid "Invalid username or password"
+msgstr "Invalid username or password"
+
+msgid "IP address restrictions"
+msgstr "IP address restrictions"
+
+msgid "IP address restrictions:"
+msgstr "IP address restrictions:"
+
+msgid "Use CTRL + left-click to multiple item selection"
+msgstr "Use CTRL + left-click to multiple item selection"
+
+msgid "Comma separated IP addresses. Using asterisk character, there is also possible to provide subnet, for example: 192.168.1.*"
+msgstr "Comma separated IP addresses. Using asterisk character, there is also possible to provide subnet, for example: 192.168.1.*"
+
+msgid "Default IP address restrictions for imported users:"
+msgstr "Default IP address restrictions for imported users:"
+
+msgid "Set your IP address"
+msgstr "Set your IP address"
+
+msgid "Invalid IP address restrictions value. This field can have comma separated IP addresses only or subnet addresses like 192.168.1.*"
+msgstr "Invalid IP address restrictions value. This field can have comma separated IP addresses only or subnet addresses like 192.168.1.*"
+
+msgid "This type of authentication is realized by web server. To use it, the basic authentication has to be enabled in the Baculum Web web server configuration."
+msgstr "This type of authentication is realized by web server. To use it, the basic authentication has to be enabled in the Baculum Web web server configuration."
+
+msgid "General settings"
+msgstr "General settings"
+
+msgid "Default access setting for logged in users not defined in Baculum Web:"
+msgstr "Default access setting for logged in users not defined in Baculum Web:"
+
+msgid "No access"
+msgstr "No access"
+
+msgid "Access with default settings"
+msgstr "Access with default settings"
+
+msgid "Default role:"
+msgstr "Default role:"
+
+msgid "Default API host:"
+msgstr "Default API host:"
+
+msgid "Users file path:"
+msgstr "Users file path:"
+
+msgid "Hash algorithm:"
+msgstr "Hash algorithm:"
+
+msgid "Please enter users file path value."
+msgstr "Please enter users file path value."
+
+msgid "Test"
+msgstr "Test"
+
+msgid "The user file is not accessible."
+msgstr "The user file is not accessible."
+
+msgid "The user file is not readable by web server user."
+msgstr "The user file is not readable by web server user."
+
+msgid "The user file is readable but not writeable by web server user."
+msgstr "The user file is readable but not writeable by web server user."
+
+msgid "Allow Baculum Web to manage the Basic authentication users (add/remove users and change their passwords)"
+msgstr "Allow Baculum Web to manage the Basic authentication users (add/remove users and change their passwords)"
+
+msgid "Authorization failed. Please contact the Baculum administrator to grant permissions."
+msgstr "Authorization failed. Please contact the Baculum administrator to grant permissions."
+
+msgid "Try again"
+msgstr "Try again"
+
+msgid "Manage Basic users"
+msgstr "Manage Basic users"
+
+msgid "Empty user list"
+msgstr "Empty user list"
+
+msgid "Basic user list"
+msgstr "Basic user list"
+
+msgid "Access to Baculum API"
+msgstr "Access to Baculum API"
+
+msgid "Access to Baculum Web"
+msgstr "Access to Baculum Web"
+
+msgid "Requried support from web server side - e.g. %supported_web_server_list"
+msgstr "Requried support from web server side - e.g. %supported_web_server_list"
+
+msgid "Apache 2.4 with apr-util 1.5+"
+msgstr "Apache 2.4 with apr-util 1.5+"
+
+msgid "Apache, Lighttpd and Nginx on most UNIX platforms"
+msgstr "Apache, Lighttpd and Nginx on most UNIX platforms"
+
+msgid "Nginx on most UNIX platforms"
+msgstr "Nginx on most UNIX platforms"
index 50d30a7a87beab9ad6ae00813753e411b0f2442e..6490c924f82e909effc28588f73dc58f70ef55f2 100644 (file)
Binary files a/gui/baculum/protected/Web/Lang/pl/messages.mo and b/gui/baculum/protected/Web/Lang/pl/messages.mo differ
index 7b8e91a9c8b69c394ad2ae20c38b9b0294910c66..aa237d112f6976e80f4adfd649afcf2f315da526 100644 (file)
@@ -1216,12 +1216,6 @@ msgstr "Wspierane"
 msgid "Not supported"
 msgstr "Nie wspierane"
 
-msgid "Authorization to Baculum API"
-msgstr "Autoryzacja do Baculum API"
-
-msgid "Authorization to Baculum Web"
-msgstr "Autoryzacja do Baculum Web"
-
 msgid "Step 3 - authentication params to Baculum Web panel"
 msgstr "Step 3 - parametry autentykacji do panelu Baculum Web"
 
@@ -1453,9 +1447,6 @@ msgstr "Wolumen"
 msgid "During restore there will be used following volumes:"
 msgstr "Podczas zadania przywracania danych potrzebne będą następujące wolumeny:"
 
-msgid "Use Ctrl + Mouse Click to change selection"
-msgstr "Aby zmienić wybór, użyj Ctrl + klik myszy"
-
 msgid "Go back"
 msgstr "Powrót"
 
@@ -2637,3 +2628,306 @@ msgstr "Tip: Użyj lewego przycisku myszy, aby wybrać wiersz tabeli. Użyj CTRL
 
 msgid "Show time in job log:"
 msgstr "Pokaż czas w dzienniku zadania:"
+
+msgid "Security"
+msgstr "Bezpieczeństwo"
+
+msgid "Roles"
+msgstr "Role"
+
+msgid "Long name"
+msgstr "Długa nazwa"
+
+msgid "Edit user"
+msgstr "Edytuj użytkownika"
+
+msgid "Long name:"
+msgstr "Długa nazwa:"
+
+msgid "Description:"
+msgstr "Opis:"
+
+msgid "E-mail:"
+msgstr "E-mail:"
+
+msgid "Roles:"
+msgstr "Roles:"
+
+msgid "Invalid e-mail address value."
+msgstr "Niepoprawna wartość adresu e-mail."
+
+msgid "Invalid username value."
+msgstr "Niepoprawna nazwa użytkownika."
+
+msgid "Username"
+msgstr "Użytkownik"
+
+msgid "E-mail"
+msgstr "E-mail"
+
+msgid "Description"
+msgstr "Opis"
+
+msgid "Add user"
+msgstr "Dodaj użytkownika"
+
+msgid "Username field cannot be empty."
+msgstr "Pole użytkownika nie może być puste."
+
+msgid "At least one role must be selected."
+msgstr "Co najmniej jednak rola musi być wybrana."
+
+msgid "Username with the given name already exists."
+msgstr "Użytkownik z podaną nazwą już istnieje."
+
+msgid "Edit role"
+msgstr "Edytuj rolę"
+
+msgid "Role with limitted access"
+msgstr "Rola z ograniczonym dostępem"
+
+msgid "Role with full access"
+msgstr "Rola z pełnym dostępem"
+
+msgid "Role:"
+msgstr "Rola:"
+
+msgid "Resources:"
+msgstr "Zasoby:"
+
+msgid "Role name"
+msgstr "Rola"
+
+msgid "Resources"
+msgstr "Zasoby"
+
+msgid "Add new role"
+msgstr "Dodaj nową rolę"
+
+msgid "Role field cannot be empty."
+msgstr "Pole roli nie może być puste."
+
+msgid "At least one resource must be selected."
+msgstr "Musisz wybrać co najmniej jeden zasób."
+
+msgid "Role with the given name already exists."
+msgstr "Rola z podaną nazwą już istnieje."
+
+msgid "This is native predefined role, that cannot be changed. For having custom roles, please use the button to add new role."
+msgstr "To jest natywna predefiniowana rola, która nie może zostać zmieniona. Aby mieć własne niestandardowe role, proszę użyć przycisku do dodawania nowej roli."
+
+msgid "The following roles are predefined and cannot be removed: %predefined_roles"
+msgstr "Następujące role są natywne predefiniowane i nie mogą zostać usunięte: %predefined_roles"
+
+msgid "The following roles are using by users and cannot be removed: %used_roles To remove them, please unassign all users from these roles."
+msgstr "Następujące role są używane przez użytkowników i nie mogą zostać usunięte: %used_roles Aby je usunąć, wypisz wszystkich"
+
+msgid "Add role"
+msgstr "Add role"
+
+msgid "Authentication method"
+msgstr "Metoda uwierzytelniania"
+
+msgid "HTTP Basic authentication"
+msgstr "Uwierzytelnianie HTTP Basic"
+
+msgid "LDAP authentication"
+msgstr "Uwierzytelnianie LDAP"
+
+msgid "LDAP server options"
+msgstr "Opcje serwera LDAP"
+
+msgid "SSL encryption (LDAPS):"
+msgstr "Szyfrowanie SSL (LDAPS):"
+
+msgid "Protocol version:"
+msgstr "Wersja protokołu:"
+
+msgid "LDAP version"
+msgstr "LDAP wersja"
+
+msgid "LDAP authentication method"
+msgstr "Metoda uwierzytelniania LDAP"
+
+msgid "Anonymous authentication"
+msgstr "Uwierzytelnianie anonimowe"
+
+msgid "Simple authentication"
+msgstr "Proste uwierzytelnianie"
+
+msgid "Base DN:"
+msgstr "Bazowy DN:"
+
+msgid "Attributes"
+msgstr "Atrybuty"
+
+msgid "Please enter port."
+msgstr "Proszę wprowadzić port."
+
+msgid "Please enter username."
+msgstr "Proszę wprowadzić nazwę użytkownika."
+
+msgid "Manager DN:"
+msgstr "DN konta administratora:"
+
+msgid "Please enter IP Address/Hostname."
+msgstr "Proszę wprowadzić adres IP/nazwę hosta."
+
+msgid "Please enter username attribute."
+msgstr "Proszę wprowadzić atrybut użytkownika."
+
+msgid "Test LDAP connection"
+msgstr "Test połączenia LDAP"
+
+msgid "Manage LDAP users"
+msgstr "Zarządzaj użytkowników LDAP"
+
+msgid "Please enter Base DN."
+msgstr "Proszę wprowadzić Bazowy DN."
+
+msgid "LDAP user list"
+msgstr "Lista użytkowników LDAP"
+
+msgid "Import users"
+msgstr "Importuj użytkowników"
+
+msgid "Import all users"
+msgstr "Importuj wszystkich użytkowników"
+
+msgid "Import selected users"
+msgstr "Importuj wybranych użytkowników"
+
+msgid "Import users whose fulfill criteria"
+msgstr "Importuj użytkowników spełniających kryteria"
+
+msgid "Import options:"
+msgstr "Opcje importu:"
+
+msgid "Criteria filter:"
+msgstr "Filtr kryteriów:"
+
+msgid "contains:"
+msgstr "zawiera:"
+
+msgid "Reload"
+msgstr "Przeładuj"
+
+msgid "Please select users in table to import."
+msgstr "Proszę wybrać użytkowników w tabeli do zaimportowania."
+
+msgid "Do not overwrite existing Baculum Web users, if they have the same username"
+msgstr "Nie nadpisuj istniejących użytkowników Baculum Web, jeśli mają tą samą nazwę"
+
+msgid "Default role for imported users:"
+msgstr "Domyślna rola dla zaimportowanych użytkowników:"
+
+msgid "Advanced options"
+msgstr "Opcje zaawansowane"
+
+msgid "Default API host for imported users:"
+msgstr "Domyślny host API dla zaimportowanych użytkowników:"
+
+msgid "Log in"
+msgstr "Zaloguj"
+
+msgid "Invalid username or password"
+msgstr "Niepoprawna nazwa użytkownika lub hasło"
+
+msgid "IP address restrictions"
+msgstr "IP address restrictions"
+
+msgid "IP address restrictions:"
+msgstr "IP address restrictions:"
+
+msgid "Use CTRL + left-click to multiple item selection"
+msgstr "Aby zaznaczyć wiele elementów, użyj CTRL + lewy przycisk myszy"
+
+msgid "Comma separated IP addresses. Using asterisk character, there is also possible to provide subnet, for example: 192.168.1.*"
+msgstr "Adresy IP oddzielone przecinkami. Używając znaku gwiazdki, jest również możliwe d"
+
+msgid "Default IP address restrictions for imported users:"
+msgstr "Domyślne restrykcje adresu IP dla zaimportowanych użytkowników:"
+
+msgid "Set your IP address"
+msgstr "Ustaw twój adres IP"
+
+msgid "Invalid IP address restrictions value. This field can have comma separated IP addresses only or subnet addresses like 192.168.1.*"
+msgstr "Nieprawidłowa wartość restrykcji adresów IP. To pole może mieć tylko oddzielone przecinkami adresy IP lub adresy podsieci, takie jak 192.168.1.*"
+
+msgid "This type of authentication is realized by web server. To use it, the basic authentication has to be enabled in the Baculum Web web server configuration."
+msgstr "Ten typ uwierzytelniania jest realizowany przez serwer WWW. Aby z niego skorzystać, uwierzytelnianie 'basic' musi być włączone w konfiguracji serwera WWW."
+
+msgid "General settings"
+msgstr "Ustawienia ogólne"
+
+msgid "Default access setting for logged in users not defined in Baculum Web:"
+msgstr "Domyślne ustawienie dostępu dla zalogowanych użytkowników nie zdefiniowanych w Baculum Web:"
+
+msgid "No access"
+msgstr "Brak dostępu"
+
+msgid "Access with default settings"
+msgstr "Dostęp z domyślnymi ustawieniami"
+
+msgid "Default role:"
+msgstr "Domyślna rola:"
+
+msgid "Default API host:"
+msgstr "Domyślny host API:"
+
+msgid "Users file path:"
+msgstr "Lokalizacja pliku użytkowników:"
+
+msgid "Hash algorithm:"
+msgstr "Funkcja skrótu:"
+
+msgid "Please enter users file path value."
+msgstr "Proszę wprowadzić lokalizację pliku użytkowników."
+
+msgid "Test"
+msgstr "Test"
+
+msgid "The user file is not accessible."
+msgstr "Plik użytkownika nie jest dostępny."
+
+msgid "The user file is not readable by web server user."
+msgstr "Plik użytkowników nie jest możliwy do odczytania przez użytkownika serwera WWW."
+
+msgid "The user file is readable but not writeable by web server user."
+msgstr "Plik użytkowników jest możliwy do odczytania, ale nie jest możliwy do zapisania przez użytkownika serwera WWW."
+
+msgid "Allow Baculum Web to manage the Basic authentication users (add/remove users and change their passwords)"
+msgstr "Allow Baculum Web to manage the Basic authentication users (add/remove users and change their passwords)"
+
+msgid "Authorization failed. Please contact the Baculum administrator to grant permissions."
+msgstr "Błąd autoryzacji. Proszę skontaktować się administratorem Baculum w celu nadania uprawnień."
+
+msgid "Try again"
+msgstr "Spróbuj ponownie"
+
+msgid "Manage Basic users"
+msgstr "Zarządzaj użytkownikami Basic"
+
+msgid "Empty user list"
+msgstr "Pusta lista użytkowników"
+
+msgid "Basic user list"
+msgstr "Lista użytkowników Basic"
+
+msgid "Access to Baculum API"
+msgstr "Dostęp do Baculum API"
+
+msgid "Access to Baculum Web"
+msgstr "Dostęp do Baculum Web"
+
+msgid "Requried support from web server side - e.g. %supported_web_server_list"
+msgstr "Wymagane wsparcie ze strony serwera WWW - m.in. %supported_web_server_list"
+
+msgid "Apache 2.4 with apr-util 1.5+"
+msgstr "Apache 2.4 z apr-util 1.5+"
+
+msgid "Apache, Lighttpd and Nginx on most UNIX platforms"
+msgstr "Apache, Lighttpd i Nginx na większości platform UNIXowych"
+
+msgid "Nginx on most UNIX platforms"
+msgstr "Nginx na większości platform UNIXowych"
index 927ea42a31972ebc25ea915f570c918cfdf3970c..48f2718d38a0c26cffea21e69ee4ed34d7c7c465 100644 (file)
Binary files a/gui/baculum/protected/Web/Lang/pt/messages.mo and b/gui/baculum/protected/Web/Lang/pt/messages.mo differ
index 78cb1bf94d9ff52edec8fee50251d3dcc69f7743..8b31dd6e2a4299aaae9feb55a1e987b4c003ba69 100644 (file)
@@ -1216,12 +1216,6 @@ msgstr "Suportado"
 msgid "Not supported"
 msgstr "Não suportado"
 
-msgid "Authorization to Baculum API"
-msgstr "Autorização para a API do Baculum"
-
-msgid "Authorization to Baculum Web"
-msgstr "Autorização para o Baculum Web"
-
 msgid "Step 3 - authentication params to Baculum Web panel"
 msgstr "Etapa 3 - Parâmetros de autenticação para o painel Web do Baculum"
 
@@ -1453,9 +1447,6 @@ msgstr "Volume"
 msgid "During restore there will be used following volumes:"
 msgstr "Durante a restauração, serão utilizados os seguintes volumes:"
 
-msgid "Use Ctrl + Mouse Click to change selection"
-msgstr "Use Ctrl + Mouse Click para alterar a seleção"
-
 msgid "Go back"
 msgstr "Voltar"
 
@@ -2637,3 +2628,306 @@ msgstr "Tip: Use left-click to select table row. Use CTRL + left-click to multip
 
 msgid "Show time in job log:"
 msgstr "Show time in job log:"
+
+msgid "Security"
+msgstr "Security"
+
+msgid "Roles"
+msgstr "Roles"
+
+msgid "Long name"
+msgstr "Long name"
+
+msgid "Edit user"
+msgstr "Edit user"
+
+msgid "Long name:"
+msgstr "Long name:"
+
+msgid "Description:"
+msgstr "Description:"
+
+msgid "E-mail:"
+msgstr "E-mail:"
+
+msgid "Roles:"
+msgstr "Roles:"
+
+msgid "Invalid e-mail address value."
+msgstr "Invalid e-mail address value."
+
+msgid "Invalid username value."
+msgstr "Invalid username value."
+
+msgid "Username"
+msgstr "Username"
+
+msgid "E-mail"
+msgstr "E-mail"
+
+msgid "Description"
+msgstr "Description"
+
+msgid "Add user"
+msgstr "Add user"
+
+msgid "Username field cannot be empty."
+msgstr "Username field cannot be empty."
+
+msgid "At least one role must be selected."
+msgstr "At least one role must be selected."
+
+msgid "Username with the given name already exists."
+msgstr "Username with the given name already exists."
+
+msgid "Edit role"
+msgstr "Edit role"
+
+msgid "Role with limitted access"
+msgstr "Role with limitted access"
+
+msgid "Role with full access"
+msgstr "Role with full access"
+
+msgid "Role:"
+msgstr "Role:"
+
+msgid "Resources:"
+msgstr "Resources:"
+
+msgid "Role name"
+msgstr "Role name"
+
+msgid "Resources"
+msgstr "Resources"
+
+msgid "Add new role"
+msgstr "Add new role"
+
+msgid "Role field cannot be empty."
+msgstr "Role field cannot be empty."
+
+msgid "At least one resource must be selected."
+msgstr "At least one resource must be selected."
+
+msgid "Role with the given name already exists."
+msgstr "Role with the given name already exists."
+
+msgid "This is native predefined role, that cannot be changed. For having custom roles please use the button to add new role."
+msgstr "This is native predefined role, that cannot be changed. For having custom roles please use the button to add new role."
+
+msgid "The following roles are predefined and cannot be removed: %predefined_roles"
+msgstr "The following roles are predefined and cannot be removed: %predefined_roles"
+
+msgid "The following roles are using by users and cannot be removed: %used_roles To remove them, please unassign all users from these roles."
+msgstr "The following roles are using by users and cannot be removed: %used_roles To remove them, please unassign all users from these roles."
+
+msgid "Add role"
+msgstr "Add role"
+
+msgid "Authentication method"
+msgstr "Authentication method"
+
+msgid "HTTP Basic authentication"
+msgstr "HTTP Basic authentication"
+
+msgid "LDAP authentication"
+msgstr "LDAP authentication"
+
+msgid "LDAP server options"
+msgstr "LDAP server options"
+
+msgid "SSL encryption (LDAPS):"
+msgstr "SSL encryption (LDAPS):"
+
+msgid "Protocol version:"
+msgstr "Protocol version:"
+
+msgid "LDAP version"
+msgstr "LDAP version"
+
+msgid "LDAP authentication method"
+msgstr "LDAP authentication method"
+
+msgid "Anonymous authentication"
+msgstr "Anonymous authentication"
+
+msgid "Simple authentication"
+msgstr "Simple authentication"
+
+msgid "Base DN:"
+msgstr "Base DN:"
+
+msgid "Attributes"
+msgstr "Attributes"
+
+msgid "Please enter port."
+msgstr "Please enter port."
+
+msgid "Please enter username."
+msgstr "Please enter username."
+
+msgid "Manager DN:"
+msgstr "Manager DN:"
+
+msgid "Please enter IP Address/Hostname."
+msgstr "Please enter IP Address/Hostname."
+
+msgid "Please enter username attribute."
+msgstr "Please enter username attribute."
+
+msgid "Test LDAP connection"
+msgstr "Test LDAP connection"
+
+msgid "Manage LDAP users"
+msgstr "Manage LDAP users"
+
+msgid "Please enter Base DN."
+msgstr "Please enter Base DN."
+
+msgid "LDAP user list"
+msgstr "LDAP user list"
+
+msgid "Import users"
+msgstr "Import users"
+
+msgid "Import all users"
+msgstr "Import all users"
+
+msgid "Import selected users"
+msgstr "Import selected users"
+
+msgid "Import users whose fulfill criteria"
+msgstr "Import users whose fulfill criteria"
+
+msgid "Import options:"
+msgstr "Import options:"
+
+msgid "Criteria filter:"
+msgstr "Criteria filter:"
+
+msgid "contains:"
+msgstr "contains:"
+
+msgid "Reload"
+msgstr "Reload"
+
+msgid "Please select users in table to import."
+msgstr "Please select users in table to import."
+
+msgid "Do not overwrite existing Baculum Web users, if they have the same username"
+msgstr "Do not overwrite existing Baculum Web users, if they have the same username"
+
+msgid "Default role for imported users:"
+msgstr "Default role for imported users:"
+
+msgid "Advanced options"
+msgstr "Advanced options"
+
+msgid "Default API host for imported users:"
+msgstr "Default API host for imported users:"
+
+msgid "Log in"
+msgstr "Log in"
+
+msgid "Invalid username or password"
+msgstr "Invalid username or password"
+
+msgid "IP address restrictions"
+msgstr "IP address restrictions"
+
+msgid "IP address restrictions:"
+msgstr "IP address restrictions:"
+
+msgid "Use CTRL + left-click to multiple item selection"
+msgstr "Use CTRL + left-click to multiple item selection"
+
+msgid "Comma separated IP addresses. Using asterisk character, there is also possible to provide subnet, for example: 192.168.1.*"
+msgstr "Comma separated IP addresses. Using asterisk character, there is also possible to provide subnet, for example: 192.168.1.*"
+
+msgid "Default IP address restrictions for imported users:"
+msgstr "Default IP address restrictions for imported users:"
+
+msgid "Set your IP address"
+msgstr "Set your IP address"
+
+msgid "Invalid IP address restrictions value. This field can have comma separated IP addresses only or subnet addresses like 192.168.1.*"
+msgstr "Invalid IP address restrictions value. This field can have comma separated IP addresses only or subnet addresses like 192.168.1.*"
+
+msgid "This type of authentication is realized by web server. To use it, the basic authentication has to be enabled in the Baculum Web web server configuration."
+msgstr "This type of authentication is realized by web server. To use it, the basic authentication has to be enabled in the Baculum Web web server configuration."
+
+msgid "General settings"
+msgstr "General settings"
+
+msgid "Default access setting for logged in users not defined in Baculum Web:"
+msgstr "Default access setting for logged in users not defined in Baculum Web:"
+
+msgid "No access"
+msgstr "No access"
+
+msgid "Access with default settings"
+msgstr "Access with default settings"
+
+msgid "Default role:"
+msgstr "Default role:"
+
+msgid "Default API host:"
+msgstr "Default API host:"
+
+msgid "Users file path:"
+msgstr "Users file path:"
+
+msgid "Hash algorithm:"
+msgstr "Hash algorithm:"
+
+msgid "Please enter users file path value."
+msgstr "Please enter users file path value."
+
+msgid "Test"
+msgstr "Test"
+
+msgid "The user file is not accessible."
+msgstr "The user file is not accessible."
+
+msgid "The user file is not readable by web server user."
+msgstr "The user file is not readable by web server user."
+
+msgid "The user file is readable but not writeable by web server user."
+msgstr "The user file is readable but not writeable by web server user."
+
+msgid "Allow Baculum Web to manage the Basic authentication users (add/remove users and change their passwords)"
+msgstr "Allow Baculum Web to manage the Basic authentication users (add/remove users and change their passwords)"
+
+msgid "Authorization failed. Please contact the Baculum administrator to grant permissions."
+msgstr "Authorization failed. Please contact the Baculum administrator to grant permissions."
+
+msgid "Try again"
+msgstr "Try again"
+
+msgid "Manage Basic users"
+msgstr "Manage Basic users"
+
+msgid "Empty user list"
+msgstr "Empty user list"
+
+msgid "Basic user list"
+msgstr "Basic user list"
+
+msgid "Access to Baculum API"
+msgstr "Access to Baculum API"
+
+msgid "Access to Baculum Web"
+msgstr "Access to Baculum Web"
+
+msgid "Requried support from web server side - e.g. %supported_web_server_list"
+msgstr "Requried support from web server side - e.g. %supported_web_server_list"
+
+msgid "Apache 2.4 with apr-util 1.5+"
+msgstr "Apache 2.4 with apr-util 1.5+"
+
+msgid "Apache, Lighttpd and Nginx on most UNIX platforms"
+msgstr "Apache, Lighttpd and Nginx on most UNIX platforms"
+
+msgid "Nginx on most UNIX platforms"
+msgstr "Nginx on most UNIX platforms"
diff --git a/gui/baculum/protected/Web/Layouts/Simple.php b/gui/baculum/protected/Web/Layouts/Simple.php
new file mode 100644 (file)
index 0000000..f4eeb88
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum   - Bacula web interface
+ *
+ * Copyright (C) 2013-2020 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.Params');
+
+/**
+ * Simple layout class.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Layout
+ * @package Baculum Web
+ */
+class Simple extends TTemplateControl {
+}
+?>
diff --git a/gui/baculum/protected/Web/Layouts/Simple.tpl b/gui/baculum/protected/Web/Layouts/Simple.tpl
new file mode 100644 (file)
index 0000000..6ad4146
--- /dev/null
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html lang="en">
+       <com:THead Title="Baculum - Bacula Web Interface">
+       <meta charset="utf-8" />
+       <meta name="viewport" content="width=device-width, initial-scale=1" />
+       <link rel="icon" href="<%=$this->getPage()->getTheme()->getBaseUrl()%>/favicon.ico" type="image/x-icon" />
+       </com:THead>
+       <body  class="w3-light-grey">
+               <com:TForm>
+                       <com:BClientScript ScriptUrl=<%~ ../JavaScript/fontawesome.min.js %> />
+                       <!-- Top container -->
+                       <div class="w3-bar w3-top w3-black w3-large" style="z-index:4">
+                               <span class="w3-bar-item w3-right">
+                                       <img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/logo.png" alt="" />
+                               </span>
+                       </div>
+                       <!-- !PAGE CONTENT! -->
+                       <com:TContentPlaceHolder ID="Main" />
+               </com:TForm>
+       </body>
+</html>
index c32934eb5dc44350704d4dad474e82feead7ba3d..f3729bf79a441254b7fe2bca446b213fefdeeb0f 100644 (file)
@@ -33,8 +33,6 @@ Prado::using('Application.Web.Pages.Monitor');
  */
 class ApplicationSettings extends BaculumWebPage {
 
-       protected $admin = true;
-
        public function onInit($param) {
                parent::onInit($param);
                $this->DecimalBytes->Checked = true;
index eb53cf7b4cd989e63a1f9b098b75f598a6e29b50..720ae10977a388bde211a37960a27f42a3764f44 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -31,8 +31,6 @@ Prado::using('Application.Web.Class.BaculumWebPage');
  */
 class ConfigureHosts extends BaculumWebPage {
 
-       protected $admin = true;
-
        public function onInit($param) {
                parent::onInit($param);
                if ($this->IsPostBack || $this->IsCallBack) {
index 9f6362a11f395c1a080eb47b6fd96ffdf2e3fbe4..5ea186f886796d44bded05fa34a308133dd6d6d7 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -33,8 +33,6 @@ class FileSetList extends BaculumWebPage {
 
        const USE_CACHE = true;
 
-       protected $admin = true;
-
        public $filesets = array();
 
        public function onInit($param) {
index 49cb31106d0e145bad087932635e120e315369b1..cf657cc60a8ffbe3441f6823b238e284419e959a 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -33,8 +33,6 @@ class FileSetView extends BaculumWebPage {
 
        const USE_CACHE = true;
 
-       protected $admin = true;
-
        const FILESET_NAME = 'FileSetName';
 
        public function onInit($param) {
diff --git a/gui/baculum/protected/Web/Pages/LoginPage.page b/gui/baculum/protected/Web/Pages/LoginPage.page
new file mode 100644 (file)
index 0000000..e029821
--- /dev/null
@@ -0,0 +1,44 @@
+<%@ MasterClass="Application.Web.Layouts.Simple" Theme="Baculum-v2"%>
+<com:TContent ID="Main">
+       <div style="width: 100%; height: 100%;">
+               <com:TPanel ID="LoginForm" CssClass="w3-display-middle w3-center" Style="width: 100%; max-width: 440px" DefaultButton="LoginBtn">
+                       <img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/logo_xl.png" alt="Baculum - The Bacula web interface" class="w3-block" style="margin-bottom: 10px"/>
+                       <com:TLabel ID="Msg" CssClass="error" Display="Hidden"><%[ Invalid username or password ]%></com:TLabel>
+                       <div class="w3-section">
+                               <label for="<%=$this->Username->ClientID%>" class="w3-show-inline-block" style="width: 95px"><%[ Username: ]%></label> <com:TTextBox ID="Username" CssClass="w3-input w3-border w3-show-inline-block" Style="width: 335px" />
+                       </div>
+                       <div class="w3-section">
+                               <label for="<%=$this->Password->ClientID%>" class="w3-show-inline-block" style="width: 95px"><%[ Password: ]%></label> <com:TTextBox ID="Password" CssClass="w3-input w3-border w3-show-inline-block" TextMode="Password" Style="width: 335px" />
+                       </div>
+                       <div class="w3-section w3-center">
+                               <com:TLinkButton ID="LoginBtn" OnClick="login" CssClass="w3-button w3-green">
+                                       <i class="fas fa-sign-in-alt"></i> &nbsp;<%[ Log in ]%></i>
+                               </com:TLinkButton>
+                       </div>
+               </com:TPanel>
+               <com:TPanel ID="AuthorizationError" CssClass="w3-display-middle w3-center" Style="width: 100%; max-width: 440px" Display="None">
+                       <img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/logo_xl.png" alt="Baculum - The Bacula web interface" style="margin-bottom: 40px; width: 100%;"/>
+                       <p class="w3-text-red"><%[ Authorization failed. Please contact the Baculum administrator to grant permissions. ]%></p>
+                       <p class="w3-center">
+                               <script>var login_form_reload_url = '<%=$this->reload_url%>';</script>
+                               <com:TActiveLinkButton
+                                       OnClick="logout"
+                               >
+                                       <prop:ClientSide.OnComplete>
+                                               if (!window.chrome || window.navigator.webdriver)  {
+                                                       window.location.href = main_side_bar_reload_url;
+                                               } else if (window.chrome) {
+                                                       // For chrome this reload is required to show login Basic auth prompt
+                                                       window.location.reload();
+                                               }
+                                       </prop:ClientSide.OnComplete>
+                                       <%[ Logout ]%>
+                               </com:TActiveLinkButton>
+                                &nbsp;<a href="<%=$this->getModule('auth')->getReturnUrl() ?: '/'%>"><%[ Try again ]%></a>
+                       </p>
+               </com:TPanel>
+       </div>
+<script>
+       document.getElementById('<%=$this->Username->ClientID%>').focus();
+</script>
+</com:TContent>
diff --git a/gui/baculum/protected/Web/Pages/LoginPage.php b/gui/baculum/protected/Web/Pages/LoginPage.php
new file mode 100644 (file)
index 0000000..76132db
--- /dev/null
@@ -0,0 +1,125 @@
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum   - Bacula web interface
+ *
+ * Copyright (C) 2013-2020 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.Web.Class.BaculumWebPage');
+
+/**
+ * User login page.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Page
+ * @package Baculum Web
+ */
+class LoginPage extends BaculumWebPage {
+
+       /**
+        * Reload URL is used to refresh page after logout with Basic auth.
+        */
+       public $reload_url = '';
+
+       public function onInit($param) {
+               parent::onInit($param);
+               if ($this->getModule('web_config')->isAuthMethodBasic()) {
+                       $fake_pwd = $this->getModule('crypto')->getRandomString();
+                       $user = $_SERVER['PHP_AUTH_USER'] . '1'; // must be different than currently logged in Basic user
+
+                       // do a login try with different user and password to logout current user
+                       $this->reload_url = $this->getPage()->getFullLoginUrl($user, $fake_pwd);
+               }
+       }
+
+       public function onPreLoad($param) {
+               parent::onPreLoad($param);
+               $users = $this->getModule('users');
+
+               $authorized = $users->isAuthorized();
+
+               if ($this->User->getIsGuest() === false) {
+                       // for authenticated users
+
+                       $web_config = $this->getModule('web_config');
+                       if ($web_config->isAuthMethodBasic()) {
+                               /**
+                                * Using default page here is because it is required for case if user doesn't
+                                * have access to default service page. Then it is directed to first free page
+                                * available for him.
+                                */
+                               if ($authorized || ($this->User->Enabled && $users->getAuthorizedFlag() === $this->Service->DefaultPage)) {
+                                       // Basic user authenticated and authorized
+                                       $this->goToDefaultPage();
+                               } else {
+                                       // Basic - user authenticated but not authorized or no access by default
+                                       $this->LoginForm->Display = 'None';
+                                       $this->AuthorizationError->Display = 'Dynamic';
+                               }
+                       } else if ($web_config->isAuthMethodLdap() && !$authorized) {
+                               // Ldap - user authenticated but not authorized
+                               $this->LoginForm->Display = 'None';
+                               $this->AuthorizationError->Display = 'Dynamic';
+                       }
+               }
+       }
+
+       /**
+        * Login using login page form.
+        *
+        * @param TLinkButton $sender sender object
+        * @param mixed $param event parameter (in this case null)
+        */
+       public function login($sender, $param) {
+               $username = $this->Username->Text;
+               $password = $this->Password->Text;
+
+               if ($this->getModule('web_config')->isAuthMethodBasic() && !empty($_SERVER['PHP_AUTH_USER'])) {
+                       // For basic auth take username from web server.
+                       $username = $_SERVER['PHP_AUTH_USER'];
+               }
+
+               $success = $this->getModule('auth')->login($username, $password);
+               if ($success === true) {
+                       $this->goToDefaultPage();
+               } else {
+                       $this->Msg->Display = 'Fixed';
+               }
+       }
+
+       /**
+        * Logout button event handler.
+        * It is used for logout button visible after unsuccessfull authorization.
+        *
+        * @param TLinkButton $sender sender object
+        * @param mixed $param event parameter (in this case null)
+        */
+       public function logout($sender, $param) {
+               $this->getModule('auth')->logout();
+               if ($this->getModule('web_config')->isAuthMethodBasic()) {
+                       /**
+                        * This status code 401 is necessary to stop comming AJAX requests
+                        * and to bring the login prompt on.
+                        */
+                       $this->Response->setStatusCode(401);
+               } else {
+                       $this->goToDefaultPage();
+               }
+       }
+}
+?>
index a85c143da8e9d989d31d903757669ba5d11ebced..835e33f02b8ec6f0c82c12ddd4079b096aa7d542 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -119,7 +119,7 @@ class Monitor extends BaculumPage {
                                $error = $result;
                        }
                }
-               if (!$error && $_SESSION['admin'] && key_exists('dbsize', $params)) {
+               if (!$error && key_exists('dbsize', $params)) {
                        $result = $this->getModule('api')->get(array('dbsize'));
                        if ($result->error === 0) {
                                $monitor_data['dbsize'] = $result->output;
index 5e16b46585576a56c7071dcb9e08ab04b9bfa417..08b952ab504561cf1ebcad03776040784e85eac3 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -33,8 +33,6 @@ Prado::using('System.Web.UI.WebControls.TWizard');
  */
 class NewJobWizard extends BaculumWebPage {
 
-       protected $admin = true;
-
        const PREV_STEP = 'PrevStep';
        const JOBDEFS = 'JobDefs';
 
index 5ab843091d5bdbbaa787849d2ec8c240d1244910..933132e266aa630aa23351e66915ceac154d6cb2 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -60,10 +60,8 @@ class NewResource extends BaculumWebPage {
                        $this->setComponentType($component_type);
                        $this->setComponentName($component_name);
                        $this->setResourceType($resource_type);
-                       if (!$_SESSION['admin']) {
-                               // Non-admin can configure only host assigned to him
-                               $this->NewResource->setHost($_SESSION['api_host']);
-                       }
+                       // Non-admin can configure only host assigned to him
+                       $this->NewResource->setHost($this->User->getAPIHosts());
                        $this->NewResource->setComponentType($component_type);
                        $this->NewResource->setComponentName($component_name);
                        $this->NewResource->setResourceType($resource_type);
@@ -80,7 +78,7 @@ class NewResource extends BaculumWebPage {
                $config = $this->getModule('host_config')->getConfig();
                $hosts = array('' => Prado::localize('Please select host'));
                foreach ($config as $host => $vals) {
-                       if (!$_SESSION['admin'] && $host !== $_SESSION['api_host']) {
+                       if ($host !== $this->User->getAPIHosts()) {
                                continue;
                        }
                        $item = "Host: $host, Address: {$vals['address']}, Port: {$vals['port']}";
index 35f89c016d0363d7775ed110a4ea057e425196a0..733fec9fa2435509faf76945e825e650489a3be3 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -30,7 +30,5 @@ Prado::using('Application.Web.Class.BaculumWebPage');
  * @package Baculum Web
  */
 class PoolList extends BaculumWebPage {
-
-       protected $admin = true;
 }
 ?>
index a80d4174b78f9a949a107fa478dd626d82ae6025..96ecd8791c39433461325fd72ab0ad73df606acd 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -38,8 +38,6 @@ class PoolView extends BaculumWebPage {
        const POOLID = 'PoolId';
        const POOL_NAME = 'PoolName';
 
-       protected $admin = true;
-
        public $volumes_in_pool;
 
        public function onInit($param) {
index 9ae2cb9334c116bc7d4d164d2c47ef756f24e2b2..4325a73a122309b95d9a2732bcd6c93eebfc45f1 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -38,12 +38,16 @@ class Requirements extends GeneralRequirements {
         * translation engine initialization. From this reason all messages are not
         * translated.
         */
-       private $req_exts = array(
-               array(
+       private $req_exts = [
+               [
                        'ext' => 'curl',
                        'help_msg' => 'Please install <b>PHP cURL module</b>.'
-               )
-       );
+               ],
+               [
+                       'ext' => 'ldap',
+                       'help_msg' => 'Please install <b>PHP LDAP module</b>.'
+               ]
+       ];
 
        public function __construct($app_dir, $base_dir) {
                parent::__construct($app_dir, $base_dir);
index 678924221006ee132eef4f3a35fe18b84bed9dfc..f2463f884e48b65ad6075dfff0ef244d89d5f7ec 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -33,8 +33,6 @@ class ScheduleList extends BaculumWebPage {
 
        const USE_CACHE = true;
 
-       protected $admin = true;
-
        public $schedules = array();
 
        public function onInit($param) {
index 8de37cc9c3e92b094296bcf364dd161f0700645c..58c6e998c64d2af471165466713a90308f8032c0 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -35,8 +35,6 @@ class ScheduleView extends BaculumWebPage {
 
        const SCHEDULE_NAME = 'ScheduleName';
 
-       protected $admin = true;
-
        public function onInit($param) {
                parent::onInit($param);
                if ($this->IsPostBack || $this->IsCallBack) {
diff --git a/gui/baculum/protected/Web/Pages/Security.page b/gui/baculum/protected/Web/Pages/Security.page
new file mode 100644 (file)
index 0000000..5b2996a
--- /dev/null
@@ -0,0 +1,1844 @@
+<%@ MasterClass="Application.Web.Layouts.Main" Theme="Baculum-v2"%>
+<com:TContent ID="Main">
+       <!-- Header -->
+       <header class="w3-container">
+               <h5>
+                       <b><i class="fa fa-lock"></i> <%[ Security ]%></b>
+               </h5>
+       </header>
+       <div class="w3-bar w3-green w3-margin-bottom">
+               <button id="btn_user_security" type="button" class="w3-bar-item w3-button tab_btn w3-grey" onclick="W3Tabs.open(this.id, 'user_security');"><%[ Settings ]%></button>
+               <button id="btn_user_list" type="button" class="w3-bar-item w3-button tab_btn" onclick="W3Tabs.open(this.id, 'user_list'); oUserList.table.responsive.recalc();"><%[ Users ]%></button>
+               <button id="btn_role_list" type="button" class="w3-bar-item w3-button tab_btn" onclick="W3Tabs.open(this.id, 'role_list'); oRoleList.table.responsive.recalc();"><%[ Roles ]%></button>
+       </div>
+
+       <div class="w3-container tab_item" id="user_security">
+               <h4><%[ General settings ]%></h4>
+               <p><%[ Default access setting for logged in users not defined in Baculum Web: ]%></p>
+               <div class="w3-container w3-row w3-padding opt_row">
+                       <div class="w3-col" style="width: 40px">
+                               <com:TRadioButton
+                                       ID="GeneralDefaultNoAccess"
+                                       GroupName="GeneralDefault"
+                                       CssClass="w3-radio"
+                                       Attributes.onclick="document.getElementById('general_options_default_access').style.display = 'none';"
+                               />
+                       </div>
+                       <label for="<%=$this->GeneralDefaultNoAccess->ClientID%>"><%[ No access ]%></label>
+               </div>
+               <div class="w3-container w3-row w3-padding opt_row">
+                       <div class="w3-col" style="width: 40px">
+                               <com:TRadioButton
+                                       ID="GeneralDefaultAccess"
+                                       GroupName="GeneralDefault"
+                                       CssClass="w3-radio"
+                                       Attributes.onclick="document.getElementById('general_options_default_access').style.display = '';"
+                               />
+                       </div>
+                       <label for="<%=$this->GeneralDefaultAccess->ClientID%>"><%[ Access with default settings ]%></label>
+               </div>
+               <div id="general_options_default_access" class="w3-container w3-margin-left" style="display: <%=$this->GeneralDefaultAccess->Checked ? 'block' : 'none'%>">
+                       <div class="w3-half">
+                               <div class="w3-container w3-row w3-padding w3-block">
+                                       <div class="w3-third w3-col">
+                                               <%[ Default role: ]%>
+                                       </div>
+                                       <div class="w3-twothird w3-col">
+                                               <com:TActiveDropDownList
+                                                       ID="GeneralDefaultAccessRole"
+                                                       CssClass="w3-input w3-border"
+                                                       CausesValidation="false"
+                                                       Width="90%"
+                                               />
+                                       </div>
+                               </div>
+                               <div class="w3-container w3-row w3-padding">
+                                       <div class="w3-third w3-col">
+                                               <%[ Default API host: ]%>
+                                       </div>
+                                       <div class="w3-twothird w3-col">
+                                               <com:TActiveDropDownList
+                                                       ID="GeneralDefaultAccessAPIHost"
+                                                       CssClass="w3-input w3-border"
+                                                       CausesValidation="false"
+                                                       Width="90%"
+                                               />
+                                       </div>
+                               </div>
+                       </div>
+               </div>
+
+               <h4><%[ Authentication method ]%></h4>
+               <div class="w3-container w3-row w3-padding opt_row">
+                       <div class="w3-col" style="width: 40px">
+                               <com:TRadioButton
+                                       ID="BasicAuth"
+                                       GroupName="AuthMethod"
+                                       CssClass="w3-radio"
+                                       Attributes.onclick="oUserSecurity.select_auth_method('basic');"
+                               />
+                       </div>
+                       <label for="<%=$this->BasicAuth->ClientID%>"><%[ HTTP Basic authentication ]%></label>
+               </div>
+               <div id="authentication_method_basic" class="w3-container" style="display: none">
+                       <%[ This type of authentication is realized by web server. To use it, the basic authentication has to be enabled in the Baculum Web web server configuration. ]%>
+                       <div>
+                               <div class="w3-container w3-row w3-padding opt_row">
+                                       <div class="w3-col" style="width: 40px">
+                                               <com:TCheckBox
+                                                       ID="BasicAuthAllowManageUsers"
+                                                       CssClass="w3-check"
+                                               />
+                                       </div>
+                                       <label for="<%=$this->BasicAuthAllowManageUsers->ClientID%>"><%[ Allow Baculum Web to manage the Basic authentication users (add/remove users and change their passwords) ]%></label>
+                               </div>
+                               <div class="w3-container w3-row w3-padding w3-block">
+                                       <div class="w3-col opt_row" style="width: 220px">
+                                               <%[ Users file path: ]%>
+                                       </div>
+                                       <div class="w3-twothird w3-col">
+                                               <com:TActiveTextBox
+                                                       ID="BasicAuthUserFile"
+                                                       CssClass="w3-input w3-border w3-show-inline-block"
+                                                       CausesValidation="false"
+                                                       Width="400px"
+                                               /><com:TActiveLinkButton
+                                                       ValidationGroup="AuthMethodGroup"
+                                                       CausesValidation="true"
+                                                       CssClass="w3-button w3-green"
+                                                       OnCallback="doBasicUserFileTest"
+                                               >
+                                                       <i class="fas fa-play"></i> &nbsp;<%[ Test ]%>
+                                                       <prop:ClientSide.OnLoading>
+                                                               document.getElementById('basic_auth_user_file_test_ok').style.display = 'none';
+                                                               document.getElementById('basic_auth_user_file_test_error').style.display = 'none';
+                                                               document.getElementById('basic_auth_user_file_test_loading').style.visibility = 'visible';
+                                                               document.getElementById('<%=$this->BasicAuthUserFileMsg->ClientID%>').style.display = 'none';
+                                                       </prop:ClientSide.OnLoading>
+                                                       <prop:ClientSide.OnComplete>
+                                                               document.getElementById('basic_auth_user_file_test_loading').style.visibility = 'hidden';
+                                                               document.getElementById('<%=$this->BasicAuthUserFileMsg->ClientID%>').style.display = '';
+                                                       </prop:ClientSide.OnComplete>
+                                               </com:TActiveLinkButton>
+                                               &nbsp;<i id="basic_auth_user_file_test_loading" class="fas fa-sync w3-spin" style="visibility: hidden;"></i>
+                                               <span id="basic_auth_user_file_test_ok" class="w3-text-green" style="display: none;"><i class="fas fa-check w3-text-green"></i> &nbsp;<%[ OK ]%></span>
+                                               <div>
+                                                       <i id="basic_auth_user_file_test_error" class="fas fa-times-circle w3-text-red" style="display: none;"></i>
+                                                       <com:TActiveLabel
+                                                               ID="BasicAuthUserFileMsg"
+                                                               CssClass="error"
+                                                               ActiveControl.EnableUpdate="true"
+                                                       />
+                                                       <com:TRequiredFieldValidator
+                                                               ValidationGroup="AuthMethodGroup"
+                                                               CssClass="validator-block"
+                                                               ControlCssClass="field_invalid"
+                                                               Display="Dynamic"
+                                                               ControlToValidate="BasicAuthUserFile"
+                                                               Text="<%[ Please enter users file path value. ]%>"
+                                                       >
+                                                               <prop:ClientSide.OnValidate>
+                                                                       var basic_auth_opt = $('#<%=$this->BasicAuth->ClientID%>')[0];
+                                                                       var user_file_opt = $('#<%=$this->BasicAuthAllowManageUsers->ClientID%>')[0];
+                                                                       sender.enabled = (basic_auth_opt.checked && user_file_opt.checked);
+                                                               </prop:ClientSide.OnValidate>
+                                                       </com:TRequiredFieldValidator>
+                                               </div>
+                                       </div>
+                               </div>
+                               <div class="w3-container w3-row w3-padding w3-block">
+                                       <div class="w3-col opt_row" style="width: 220px">
+                                               <%[  Hash algorithm: ]%>
+                                       </div>
+                                       <div class="w3-twothird w3-col">
+                                               <com:TActiveDropDownList
+                                                       ID="BasicAuthHashAlgorithm"
+                                                       CssClass="w3-select w3-border"
+                                                       CausesValidation="false"
+                                                       Width="180px"
+                                                       Attributes.onchange="basic_auth_change_hash_alg(this.value);"
+                                               >
+                                                       <com:TListItem Value="apr1-md5" Text="APR1-MD5" />
+                                                       <com:TListItem Value="sha1" Text="SHA-1" />
+                                                       <com:TListItem Value="sha256" Text="SHA-256" />
+                                                       <com:TListItem Value="sha512" Text="SHA-512" />
+                                                       <com:TListItem Value="ssha1" Text="SSHA (salted SHA-1)" />
+                                                       <com:TListItem Value="bcrypt" Text="BCrypt" />
+                                               </com:TActiveDropDownList>
+                                               <span id="basic_auth_alg_supp_msg"></span>
+                                       </div>
+                                       <script>
+                                               function basic_auth_change_hash_alg(alg) {
+                                                       var msg = '<%[ Requried support from web server side - e.g. %supported_web_server_list ]%>'
+                                                       var hash_alg_supp = {
+                                                               'apr1-md5': [
+                                                                       'Apache',
+                                                                       'Lighttpd 1.4.13+',
+                                                                       'Nginx 1.0.3+'
+                                                               ],
+                                                               'sha1': [
+                                                                       'Apache',
+                                                                       'Lighttpd 1.4.33+',
+                                                                       'Nginx 1.3.13+'
+                                                               ],
+                                                               'sha256': [
+                                                                       '<%[ Apache, Lighttpd and Nginx on most UNIX platforms ]%>',
+                                                               ],
+                                                               'sha512': [
+                                                                       '<%[ Apache, Lighttpd and Nginx on most UNIX platforms ]%>',
+                                                               ],
+                                                               'ssha1': [
+                                                                       'Nginx 1.0.3+'
+                                                               ],
+                                                               'bcrypt': [
+                                                                       '<%[ Apache 2.4 with apr-util 1.5+ ]%>',
+                                                                       '<%[ Nginx on most UNIX platforms ]%>'
+                                                               ]
+                                                       };
+                                                       msg = msg.replace('%supported_web_server_list',  hash_alg_supp[alg].join(', '));
+                                                       document.getElementById('basic_auth_alg_supp_msg').textContent = '( ' + msg + ' )';
+                                               }
+                                               var basic_auth_hash_alg_sel_val = document.getElementById('<%=$this->BasicAuthHashAlgorithm->ClientID%>').value;
+                                               basic_auth_change_hash_alg(basic_auth_hash_alg_sel_val);
+                                       </script>
+                               </div>
+                       </div>
+                       <div class="w3-container">
+                               <com:TActiveLinkButton
+                                       ValidationGroup="AuthMethodGroup"
+                                       CausesValidation="true"
+                                       OnCommand="getBasicUsers"
+                                       CommandParameter="load"
+                                       CssClass="w3-button w3-section w3-green w3-padding"
+                               >
+                                       <i class="fa fa-users"></i> &nbsp;<%[ Manage Basic users ]%>
+                                       <prop:ClientSide.OnLoading>
+                                               document.getElementById('basic_get_users_ok').style.display = 'none';
+                                               document.getElementById('basic_get_users_error').style.display = 'none';
+                                               document.getElementById('basic_get_users_loading').style.visibility = 'visible';
+                                               oUserSecurity.show_user_selected_msg(false);
+                                       </prop:ClientSide.OnLoading>
+                                       <prop:ClientSide.OnComplete>
+                                               document.getElementById('basic_get_users_loading').style.visibility = 'hidden';
+                                       </prop:ClientSide.OnComplete>
+                               </com:TActiveLinkButton>
+                               &nbsp;<i id="basic_get_users_loading" class="fas fa-sync w3-spin" style="visibility: hidden;"></i>
+                               <span id="basic_get_users_ok" class="w3-text-green" style="display: none;"><i class="fas fa-check w3-text-green"></i> &nbsp;<%[ OK ]%></span>
+                               <i id="basic_get_users_error" class="fas fa-times-circle w3-text-red" style="display: none;"></i>
+                               <com:TActiveLabel ID="TestBasicGetUsersMsg" CssClass="w3-text-red" Display="None" />
+                       </div>
+               </div>
+               <div class="w3-container w3-row w3-padding opt_row">
+                       <div class="w3-col" style="width: 40px">
+                               <com:TRadioButton
+                                       ID="LdapAuth"
+                                       GroupName="AuthMethod"
+                                       CssClass="w3-radio"
+                                       Attributes.onclick="oUserSecurity.select_auth_method('ldap');"
+                               />
+                       </div>
+                       <label for="<%=$this->LdapAuth->ClientID%>"><%[ LDAP authentication ]%></label>
+               </div>
+               <div id="authentication_method_ldap" class="w3-container w3-half w3-margin-left" style="display: none">
+                       <h5><%[ LDAP server options ]%></h5>
+                       <div class="w3-container w3-row w3-padding">
+                               <div class="w3-third w3-col">
+                                       <%[ IP Address/Hostname: ]%>
+                               </div>
+                               <div class="w3-twothird w3-col">
+                                       <com:TActiveTextBox
+                                               ID="LdapAuthServerAddress"
+                                               CssClass="w3-input w3-border w3-show-inline-block"
+                                               CausesValidation="false"
+                                               Width="90%"
+                                       />
+                                       <i class="fas fa-asterisk w3-text-red opt_req"></i>
+                                       <com:TRequiredFieldValidator
+                                               ValidationGroup="AuthMethodGroup"
+                                               CssClass="validator-block"
+                                               Display="Dynamic"
+                                               ControlCssClass="field_invalid"
+                                               ControlToValidate="LdapAuthServerAddress"
+                                               Text="<%[ Please enter IP Address/Hostname. ]%>"
+                                       >
+                                               <prop:ClientSide.OnValidate>
+                                                       sender.enabled = $('#<%=$this->LdapAuth->ClientID%>')[0].checked;
+                                               </prop:ClientSide.OnValidate>
+                                       </com:TRequiredFieldValidator>
+                               </div>
+                       </div>
+                       <div class="w3-container w3-row w3-padding">
+                               <div class="w3-third w3-col">
+                                       <%[ Port: ]%>
+                               </div>
+                               <div class="w3-col" style="width: 245px;">
+                                       <com:TActiveTextBox
+                                               ID="LdapAuthServerPort"
+                                               CssClass="w3-input w3-border w3-show-inline-block"
+                                               CausesValidation="false"
+                                               Width="70px"
+                                               Text="389"
+                                       />
+                                       <i class="fas fa-asterisk w3-text-red opt_req"></i><br />
+                                       <com:TRequiredFieldValidator
+                                               ValidationGroup="AuthMethodGroup"
+                                               CssClass="validator-block"
+                                               Display="Dynamic"
+                                               ControlCssClass="field_invalid"
+                                               ControlToValidate="LdapAuthServerPort"
+                                               Text="<%[ Please enter port. ]%>"
+                                       >
+                                               <prop:ClientSide.OnValidate>
+                                                       sender.enabled = $('#<%=$this->LdapAuth->ClientID%>')[0].checked;
+                                               </prop:ClientSide.OnValidate>
+                                       </com:TRequiredFieldValidator>
+                               </div>
+                       </div>
+                       <div class="w3-container w3-row w3-padding">
+                               <div class="w3-third w3-col">
+                                       <%[ SSL encryption (LDAPS): ]%>
+                               </div>
+                               <div class="w3-col" style="width: 70px;">
+                                       <com:TActiveCheckBox
+                                               ID="LdapAuthServerLdaps"
+                                               CssClass="w3-check w3-border"
+                                               CausesValidation="false"
+                                               AutoPostBack="false"
+                                       />
+                               </div>
+                       </div>
+                       <div class="w3-container w3-row w3-padding">
+                               <div class="w3-third w3-col">
+                                       <%[ Protocol version: ]%>
+                               </div>
+                               <div class="w3-col" style="width: 160px">
+                                       <com:TActiveDropDownList
+                                               ID="LdapAuthServerProtocolVersion"
+                                               CssClass="w3-input w3-border"
+                                               CausesValidation="false"
+                                               AutoPostBack="false"
+                                       >
+                                               <com:TListItem Value="1" Text="LDAP version 1" />
+                                               <com:TListItem Value="2" Text="LDAP version 2" />
+                                               <com:TListItem Value="3" Text="LDAP version 3" Selected="true" />
+                                       </com:TActiveDropDownList>
+                               </div>
+                       </div>
+                       <h5><%[ LDAP authentication method ]%></h5>
+                       <div class="w3-container w3-row w3-padding opt_row">
+                               <div class="w3-col" style="width: 40px">
+                                       <com:TRadioButton
+                                               ID="LdapAuthMethodAnonymous"
+                                               GroupName="LdapAuthMethod"
+                                               CssClass="w3-radio"
+                                               Attributes.onclick="oLdapUserSecurity.show_ldap_auth('anon');"
+                                               Checked="true"
+                                       />
+                               </div>
+                               <label for="<%=$this->LdapAuthMethodAnonymous->ClientID%>"><%[ Anonymous authentication ]%></label>
+                       </div>
+                       <div class="w3-container w3-row w3-padding opt_row">
+                               <div class="w3-col" style="width: 40px">
+                                       <com:TRadioButton
+                                               ID="LdapAuthMethodSimple"
+                                               GroupName="LdapAuthMethod"
+                                               CssClass="w3-radio"
+                                               Attributes.onclick="oLdapUserSecurity.show_ldap_auth('simple');"
+                                       />
+                               </div>
+                               <label for="<%=$this->LdapAuthMethodSimple->ClientID%>"><%[ Simple authentication ]%></label>
+                       </div>
+                       <div id="authentication_method_ldap_auth_simple" class="w3-container w3-margin-left" style="display: <%=$this->LdapAuthMethodSimple->Checked ? 'block' : 'none'%>">
+                               <div class="w3-container w3-row w3-padding">
+                                       <div class="w3-third w3-col">
+                                               <%[ Manager DN: ]%>
+                                       </div>
+                                       <div class="w3-twothird w3-col">
+                                               <com:TActiveTextBox
+                                                       ID="LdapAuthMethodSimpleUsername"
+                                                       CssClass="w3-input w3-border w3-show-inline-block"
+                                                       CausesValidation="false"
+                                                       Width="90%"
+                                               />
+                                               <i class="fas fa-asterisk w3-text-red opt_req"></i>
+                                               <com:TRequiredFieldValidator
+                                                       ValidationGroup="AuthMethodGroup"
+                                                       CssClass="validator-block"
+                                                       Display="Dynamic"
+                                                       ControlCssClass="field_invalid"
+                                                       ControlToValidate="LdapAuthMethodSimpleUsername"
+                                                       Text="<%[ Please enter username. ]%>"
+                                               >
+                                                       <prop:ClientSide.OnValidate>
+                                                               var is_ldap_auth = $('#<%=$this->LdapAuth->ClientID%>')[0].checked;
+                                                               var is_auth_simple = $('#<%=$this->LdapAuthMethodSimple->ClientID%>')[0].checked;
+                                                               sender.enabled = (is_ldap_auth && is_auth_simple);
+                                                       </prop:ClientSide.OnValidate>
+                                               </com:TRequiredFieldValidator>
+                                       </div>
+                               </div>
+                               <div class="w3-container w3-row w3-padding">
+                                       <div class="w3-third w3-col">
+                                               <%[ Password: ]%>
+                                       </div>
+                                       <div class="w3-twothird w3-col">
+                                               <com:TActiveTextBox
+                                                       ID="LdapAuthMethodSimplePassword"
+                                                       CssClass="w3-input w3-border w3-show-inline-block"
+                                                       CausesValidation="false"
+                                                       TextMode="Password"
+                                                       PersistPassword="true"
+                                                       Width="90%"
+                                               />
+                                               <i class="fas fa-asterisk w3-text-red opt_req"></i>
+                                               <com:TRequiredFieldValidator
+                                                       ValidationGroup="AuthMethodGroup"
+                                                       CssClass="validator-block"
+                                                       Display="Dynamic"
+                                                       ControlCssClass="field_invalid"
+                                                       ControlToValidate="LdapAuthMethodSimplePassword"
+                                                       Text="<%[ Please enter password. ]%>"
+                                               >
+                                                       <prop:ClientSide.OnValidate>
+                                                               var is_ldap_auth = $('#<%=$this->LdapAuth->ClientID%>')[0].checked;
+                                                               var is_auth_simple = $('#<%=$this->LdapAuthMethodSimple->ClientID%>')[0].checked;
+                                                               sender.enabled = (is_ldap_auth && is_auth_simple);
+                                                       </prop:ClientSide.OnValidate>
+                                               </com:TRequiredFieldValidator>
+                                       </div>
+                               </div>
+                       </div>
+                       <div class="w3-container">
+                               <com:TActiveLinkButton
+                                       ID="LdapAuthServerConnectionTest"
+                                       ValidationGroup="AuthMethodGroup"
+                                       CausesValidation="true"
+                                       OnCallback="testLdapConnection"
+                                       CssClass="w3-button w3-section w3-green w3-padding"
+                               >
+                                       <i class="fa fa-plug"></i> &nbsp;<%[ Test LDAP connection ]%>
+                                       <prop:ClientSide.OnLoading>
+                                               document.getElementById('ldap_test_connection_ok').style.display = 'none';
+                                               document.getElementById('ldap_test_connection_error').style.display = 'none';
+                                               document.getElementById('ldap_test_connection_loading').style.visibility = 'visible';
+                                       </prop:ClientSide.OnLoading>
+                                       <prop:ClientSide.OnComplete>
+                                               document.getElementById('ldap_test_connection_loading').style.visibility = 'hidden';
+                                       </prop:ClientSide.OnComplete>
+                               </com:TActiveLinkButton>
+                               &nbsp;<i id="ldap_test_connection_loading" class="fas fa-sync w3-spin" style="visibility: hidden;"></i>
+                               <span id="ldap_test_connection_ok" class="w3-text-green" style="display: none;"><i class="fas fa-check w3-text-green"></i> &nbsp;<%[ OK ]%></span>
+                               <i id="ldap_test_connection_error" class="fas fa-times-circle w3-text-red" style="display: none;"></i>
+                               <com:TActiveLabel ID="TestLdapConnectionMsg" CssClass="w3-text-red" Display="None" />
+                       </div>
+                       <div class="w3-container w3-row w3-padding">
+                               <div class="w3-third w3-col">
+                                       <%[ Base DN: ]%>
+                               </div>
+                               <div class="w3-twothird w3-col">
+                                       <com:TActiveTextBox
+                                               ID="LdapAuthServerBaseDn"
+                                               CssClass="w3-input w3-border w3-show-inline-block"
+                                               CausesValidation="false"
+                                               Width="90%"
+                                       />
+                                       <i class="fas fa-asterisk w3-text-red opt_req"></i><br />
+                                       <com:TRequiredFieldValidator
+                                               ValidationGroup="AuthMethodGroup"
+                                               CssClass="validator-block"
+                                               Display="Dynamic"
+                                               ControlCssClass="field_invalid"
+                                               ControlToValidate="LdapAuthServerBaseDn"
+                                               Text="<%[ Please enter Base DN. ]%>"
+                                       >
+                                               <prop:ClientSide.OnValidate>
+                                                       var is_test_con = (parameter && parameter.options.ID == '<%=$this->LdapAuthServerConnectionTest->ClientID%>');
+                                                       var is_ldap_enabled = $('#<%=$this->LdapAuth->ClientID%>')[0].checked;
+                                                       sender.enabled = (!is_test_con && is_ldap_enabled);
+                                               </prop:ClientSide.OnValidate>
+                                       </com:TRequiredFieldValidator>
+                               </div>
+                       </div>
+                       <h5><%[ Attributes ]%></h5>
+                       <div class="w3-container w3-row w3-padding">
+                               <div class="w3-third w3-col">
+                                       <%[ Username: ]%>
+                               </div>
+                               <div class="w3-twothird w3-col">
+                                       <com:TActiveTextBox
+                                               ID="LdapAttributesUsername"
+                                               CssClass="w3-input w3-border w3-show-inline-block"
+                                               CausesValidation="false"
+                                               Text="uid"
+                                               Width="90%"
+                                       />
+                                       <i class="fas fa-asterisk w3-text-red opt_req"></i>
+                                       <com:TRequiredFieldValidator
+                                               ValidationGroup="AuthMethodGroup"
+                                               CssClass="validator-block"
+                                               Display="Dynamic"
+                                               ControlCssClass="field_invalid"
+                                               ControlToValidate="LdapAttributesUsername"
+                                               Text="<%[ Please enter username attribute. ]%>"
+                                       >
+                                               <prop:ClientSide.OnValidate>
+                                                       sender.enabled = $('#<%=$this->LdapAuth->ClientID%>')[0].checked;
+                                               </prop:ClientSide.OnValidate>
+                                       </com:TRequiredFieldValidator>
+                               </div>
+                       </div>
+                       <div class="w3-container w3-row w3-padding">
+                               <div class="w3-third w3-col">
+                                       <%[ Long name: ]%>
+                               </div>
+                               <div class="w3-twothird w3-col">
+                                       <com:TActiveTextBox
+                                               ID="LdapAttributesLongName"
+                                               CssClass="w3-input w3-border"
+                                               CausesValidation="false"
+                                               Text="sn"
+                                               Width="90%"
+                                       />
+                               </div>
+                       </div>
+                       <div class="w3-container w3-row w3-padding">
+                               <div class="w3-third w3-col">
+                                       <%[ E-mail: ]%>
+                               </div>
+                               <div class="w3-twothird w3-col">
+                                       <com:TActiveTextBox
+                                               ID="LdapAttributesEmail"
+                                               CssClass="w3-input w3-border"
+                                               CausesValidation="false"
+                                               Text="mail"
+                                               Width="90%"
+                                       />
+                               </div>
+                       </div>
+                       <div class="w3-container w3-row w3-padding">
+                               <div class="w3-third w3-col">
+                                       <%[ Description: ]%>
+                               </div>
+                               <div class="w3-twothird w3-col">
+                                       <com:TActiveTextBox
+                                               ID="LdapAttributesDescription"
+                                               CssClass="w3-input w3-border"
+                                               CausesValidation="false"
+                                               Width="90%"
+                                       />
+                               </div>
+                       </div>
+                       <div class="w3-container">
+                               <com:TActiveLinkButton
+                                       ValidationGroup="AuthMethodGroup"
+                                       CausesValidation="true"
+                                       OnCommand="getLdapUsers"
+                                       CommandParameter="load"
+                                       CssClass="w3-button w3-section w3-green w3-padding"
+                               >
+                                       <i class="fa fa-users"></i> &nbsp;<%[ Manage LDAP users ]%>
+                                       <prop:ClientSide.OnLoading>
+                                               document.getElementById('ldap_get_users_ok').style.display = 'none';
+                                               document.getElementById('ldap_get_users_error').style.display = 'none';
+                                               document.getElementById('ldap_get_users_loading').style.visibility = 'visible';
+                                               oUserSecurity.show_user_selected_msg(false);
+                                       </prop:ClientSide.OnLoading>
+                                       <prop:ClientSide.OnComplete>
+                                               document.getElementById('ldap_get_users_loading').style.visibility = 'hidden';
+                                       </prop:ClientSide.OnComplete>
+                               </com:TActiveLinkButton>
+                               &nbsp;<i id="ldap_get_users_loading" class="fas fa-sync w3-spin" style="visibility: hidden;"></i>
+                               <span id="ldap_get_users_ok" class="w3-text-green" style="display: none;"><i class="fas fa-check w3-text-green"></i> &nbsp;<%[ OK ]%></span>
+                               <i id="ldap_get_users_error" class="fas fa-times-circle w3-text-red" style="display: none;"></i>
+                               <com:TActiveLabel ID="TestLdapGetUsersMsg" CssClass="w3-text-red" Display="None" />
+                       </div>
+               </div>
+               <div class="w3-container w3-center">
+                       <com:TActiveLinkButton
+                               ID="AuthMethodSave"
+                               ValidationGroup="AuthMethodGroup"
+                               CausesValidation="true"
+                               OnCallback="saveSecurityConfig"
+                               CssClass="w3-button w3-section w3-green w3-padding"
+                       >
+                               <i class="fa fa-save"></i> &nbsp;<%[ Save ]%>
+                               <prop:ClientSide.OnLoading>
+                                       document.getElementById('auth_method_save_ok').style.display = 'none';
+                                       document.getElementById('auth_method_save_error').style.display = 'none';
+                                       document.getElementById('auth_method_save_loading').style.visibility = 'visible';
+                               </prop:ClientSide.OnLoading>
+                               <prop:ClientSide.OnComplete>
+                                       document.getElementById('auth_method_save_loading').style.visibility = 'hidden';
+                               </prop:ClientSide.OnComplete>
+                       </com:TActiveLinkButton>
+                       &nbsp;<i id="auth_method_save_loading" class="fas fa-sync w3-spin" style="visibility: hidden;"></i>
+                       <span id="auth_method_save_ok" class="w3-text-green" style="display: none;"><i class="fas fa-check w3-text-green"></i> &nbsp;<%[ OK ]%></span>
+                       <span id="auth_method_save_error" class="w3-text-red" style="display: none"><i class="fas fa-times-circle w3-text-red"></i><%[ Error ]%></span>
+               </div>
+
+               <div id="get_users_modal" class="w3-modal" style="display: none">
+                       <div class="w3-modal-content w3-card-4 w3-animate-zoom" style="width: 990px">
+                               <header class="w3-container w3-green">
+                                       <span onclick="oUserSecurity.show_user_modal(false);" class="w3-button w3-display-topright">×</span>
+                                       <h2 id="get_users_table_title"></h2>
+                               </header>
+                               <div class="w3-margin-left w3-margin-right" style="max-height: 645px; overflow-x: auto; margin: 10px auto;">
+                                       <table id="get_users_table" class="w3-table w3-striped w3-hoverable w3-white w3-margin-bottom" style="width: 100%;">
+                                               <thead>
+                                                       <tr>
+                                                               <th><%[ Username ]%></th>
+                                                               <th class="w3-center"><%[ Long name ]%></th>
+                                                               <th class="w3-center"><%[ Description ]%></th>
+                                                               <th class="w3-center"><%[ E-mail ]%></th>
+                                                       </tr>
+                                               </thead>
+                                               <tbody id="get_users_body"></tbody>
+                                               <tfoot>
+                                                       <tr>
+                                                               <th><%[ Username ]%></th>
+                                                               <th class="w3-center"><%[ Long name ]%></th>
+                                                               <th class="w3-center"><%[ Description ]%></th>
+                                                               <th class="w3-center"><%[ E-mail ]%></th>
+                                                       </tr>
+                                               </tfoot>
+                                       </table>
+                                       <p class="info w3-hide-medium w3-hide-small" style="margin: 4px 0;"><%[ Tip: Use left-click to select table row. Use CTRL + left-click to multiple row selection. Use SHIFT + left-click to add a range of rows to selection. ]%></p>
+                               </div>
+                               <footer class="w3-container w3-border-top">
+                                       <div style="padding-top: 10px">
+                                               <%[ Import options: ]%> <com:TActiveDropDownList
+                                                       ID="GetUsersImportOptions"
+                                                       CssClass="w3-select w3-border w3-show-inline-block"
+                                                       AutoPostBack="false"
+                                                       Width="250px"
+                                               >
+                                                       <com:TListItem Value="0" Text="<%[ Import all users ]%>" />
+                                                       <com:TListItem Value="1" Text="<%[ Import selected users ]%>" />
+                                                       <com:TListItem Value="2" Text="<%[ Import users whose fulfill criteria ]%>" />
+                                               </com:TActiveDropDownList>
+                                               <div id="get_users_criteria" style="display: none;">
+                                                       <%[ Criteria filter: ]%>
+                                                       <com:TActiveDropDownList
+                                                               ID="GetUsersCriteria"
+                                                               CssClass="w3-select w3-border w3-show-inline-block"
+                                                               AutoPostBack="false"
+                                                               Width="150px"
+                                                       >
+                                                               <com:TListItem Attributes.id="get_users_criteria_filter_username" Value="0" Text="<%[ Username ]%>" />
+                                                               <com:TListItem Attributes.id="get_users_criteria_filter_long_name" Value="1" Text="<%[ Long name ]%>" />
+                                                               <com:TListItem Attributes.id="get_users_criteria_filter_desc" Value="2" Text="<%[ Description ]%>" />
+                                                               <com:TListItem Attributes.id="get_users_criteria_filter_email" Value="3" Text="<%[ E-mail ]%>" />
+                                                       </com:TActiveDropDownList>
+                                                       <%[ contains: ]%>
+                                                       <com:TActiveTextBox
+                                                               ID="GetUsersCriteriaFilter"
+                                                               CssClass="w3-input w3-border w3-show-inline-block"
+                                                               Width="130px"
+                                                               Attributes.onkeypress="oUserSecurity.on_reload_user_list(event);"
+                                                       />
+                                                       <com:TActiveLinkButton
+                                                               ID="GetUsersCriteriaTest"
+                                                               CssClass="w3-button w3-green w3-show-inline-block"
+                                                               OnCallback="getUsers"
+                                                       >
+                                                               <i class="fa fa-sync-alt"></i> &nbsp;<%[ Reload ]%>
+                                                       </com:TActiveLinkButton>
+                                               </div>
+                                               <div id="get_users_selected_msg" style="display: none;">
+                                                       <%[ Please select users in table to import. ]%>
+                                               </div>
+                                       </div>
+                                       <i class="fas fa-wrench"></i> &nbsp;<a href="javascript:void(0)" onclick="$('#get_users_advanced_options').toggle('fast');"><%[ Advanced options ]%></a>
+                                       <div id="get_users_advanced_options" style="display: none">
+                                               <div>
+                                                       <com:TActiveCheckBox
+                                                               ID="GetUsersProtectOverwrite"
+                                                               CssClass="w3-check"
+                                                               Checked="true"
+                                                               AutoPostBack="false"
+                                                       />
+                                                       <label for="<%=$this->GetUsersProtectOverwrite->ClientID%>"><%[ Do not overwrite existing Baculum Web users, if they have the same username ]%></label>
+                                               </div>
+                                               <div class="w3-row w3-section">
+                                                       <div class="w3-col w3-third"><com:TLabel ForControl="GetUsersDefaultRole" Text="<%[ Default role for imported users: ]%>" /></div>
+                                                       <div class="w3-half">
+                                                               <com:TActiveListBox
+                                                                       ID="GetUsersDefaultRole"
+                                                                       SelectionMode="Multiple"
+                                                                       Rows="6"
+                                                                       CssClass="w3-select w3-border"
+                                                                       AutoPostBack="false"
+                                                               />
+                                                               <p class="w3-text-black" style="margin: 0 16px 0 0"><%[ Use CTRL + left-click to multiple item selection ]%></p>
+                                                               <com:TRequiredFieldValidator
+                                                                       ValidationGroup="GetUsersGroup"
+                                                                       ControlToValidate="GetUsersDefaultRole"
+                                                                       ErrorMessage="<%[ At least one role must be selected. ]%>"
+                                                                       ControlCssClass="field_invalid"
+                                                               />
+                                                       </div> &nbsp;<i class="fa fa-asterisk w3-text-red opt_req"></i>
+                                               </div>
+                                               <div class="w3-row w3-section">
+                                                       <div class="w3-col w3-third"><com:TLabel ForControl="GetUsersDefaultAPIHost" Text="<%[ Default API host for imported users: ]%>" /></div>
+                                                       <div class="w3-half">
+                                                               <com:TActiveDropDownList
+                                                                       ID="GetUsersDefaultAPIHost"
+                                                                       CssClass="w3-select w3-border"
+                                                                       AutoPostBack="false"
+                                                               />
+                                                       </div>
+                                               </div>
+                                               <div class="w3-row w3-section" title="<%[ Comma separated IP addresses. Using asterisk character, there is also possible to provide subnet, for example: 192.168.1.* ]%>">
+                                                       <div class="w3-col w3-third"><com:TLabel ForControl="UserIps" Text="<%[ Default IP address restrictions for imported users: ]%>" /></div>
+                                                       <div class="w3-half">
+                                                               <com:TActiveTextBox
+                                                                       ID="GetUsersDefaultIps"
+                                                                       AutoPostBack="false"
+                                                                       MaxLength="500"
+                                                                       CssClass="w3-input w3-border"
+                                                               />
+                                                               <p class="w3-text-black"><a href="javascript:void(0)" onclick="document.getElementById('<%=$this->GetUsersDefaultIps->ClientID%>').value = '<%=$_SERVER['REMOTE_ADDR']%>';"><%[ Set your IP address ]%></a></p>
+                                                               <com:TActiveCustomValidator
+                                                                       ID="GetUsersDefaultIpsValidator"
+                                                                       ValidationGroup="GetUsersGroup"
+                                                                       ControlToValidate="GetUsersDefaultIps"
+                                                                       OnServerValidate="validateIps"
+                                                                       Display="Dynamic"
+                                                                       ErrorMessage="<%[ Invalid IP address restrictions value. This field can have comma separated IP addresses only or subnet addresses like 192.168.1.* ]%>"
+                                                                       ControlCssClass="field_invalid"
+                                                               />
+                                                       </div>
+                                               </div>
+                                       </div>
+                                       <div class="w3-center w3-margin-top w3-margin-bottom w3-border-top" style="padding-top: 10px;">
+                                               <button type="button" class="w3-button w3-red" onclick="oUserSecurity.show_user_modal(false);"><i class="fa fa-times"></i> &nbsp;<%[ Close ]%></button>
+                                               <com:TActiveLinkButton
+                                                       ID="GetUsersImport"
+                                                       CssClass="w3-button w3-green"
+                                                       CausesValidation="true"
+                                                       ValidationGroup="GetUsersGroup"
+                                                       OnCallback="importUsers"
+                                                       Attributes.onclick="return oUserSecurity.prepare_import();"
+                                               >
+                                                       <prop:ClientSide.OnLoading>
+                                                               document.getElementById('get_users_loader').style.visibility = 'visible';
+                                                       </prop:ClientSide.OnLoading>
+                                                       <prop:ClientSide.OnComplete>
+                                                               document.getElementById('get_users_loader').style.visibility = 'hidden';
+                                                       </prop:ClientSide.OnComplete>
+                                                       <i class="fas fa-download"></i> &nbsp;<%[ Import users ]%>
+                                               </com:TActiveLinkButton>
+                                               <i id="get_users_loader" class="fa fa-sync w3-spin w3-margin-left" style="visibility: hidden"></i>
+                                       </div>
+                               </footer>
+                       </div>
+               </div>
+<com:TCallback ID="SelectedUsersImport" OnCallback="TemplateControl.importUsers" />
+<script>
+var oUserSecurity = {
+       ids: {
+               auth_method_basic: 'authentication_method_basic',
+               auth_method_ldap: 'authentication_method_ldap',
+               get_users: 'get_users_table',
+               get_users_body: 'get_users_body',
+               get_users_modal: 'get_users_modal',
+               get_users_selected_msg: 'get_users_selected_msg',
+               get_users_criteria: 'get_users_criteria',
+               get_users_criteria_test: '<%=$this->GetUsersCriteriaTest->ClientID%>',
+               get_users_import_opts: '<%=$this->GetUsersImportOptions->ClientID%>',
+               filter_long_name: 'get_users_criteria_filter_long_name',
+               filter_desc: 'get_users_criteria_filter_desc',
+               filter_email: 'get_users_criteria_filter_email',
+               table_title: 'get_users_table_title'
+       },
+       import_options: {
+               all_users: <%=Security::IMPORT_OPT_ALL_USERS%>,
+               selected_users: <%=Security::IMPORT_OPT_SELECTED_USERS%>,
+               criteria: <%=Security::IMPORT_OPT_CRITERIA%>
+       },
+       data: [],
+       table: null,
+       sel_users_import_cb: <%=$this->SelectedUsersImport->ActiveControl->Javascript%>,
+       user_obj: null,
+       init: function() {
+               this.set_events();
+       },
+       select_auth_method: function(auth) {
+               var auth_basic = document.getElementById(this.ids.auth_method_basic);
+               var auth_ldap = document.getElementById(this.ids.auth_method_ldap);
+
+               // hide all auth containers
+               [auth_basic, auth_ldap].forEach(function(el) {
+                       el.style.display = 'none';
+               });
+
+               switch (auth) {
+                       case 'basic': {
+                               auth_basic.style.display = 'block';
+                               this.user_obj = oBasicUserSecurity;
+                               break;
+                       }
+                       case 'ldap': {
+                               auth_ldap.style.display = 'block';
+                               this.user_obj = oLdapUserSecurity;
+                               break;
+                       }
+               }
+       },
+       set_events: function() {
+               document.getElementById(this.ids.get_users).addEventListener('click', function(e) {
+                       $(function() {
+                               if (import_opts.value == this.import_options.selected_users) {
+                                       var show = this.table.rows({selected: true}).data().length == 0;
+                                       this.show_user_selected_msg(show);
+                               }
+                       }.bind(this));
+               }.bind(this));
+               var import_opts = document.getElementById(this.ids.get_users_import_opts);
+               import_opts.addEventListener('change', function(e) {
+                       if (import_opts.value == this.import_options.criteria) {
+                               this.show_user_criteria(true);
+                       } else {
+                               this.show_user_criteria(false);
+                       }
+
+                       if (import_opts.value == this.import_options.selected_users && this.table.rows({selected: true}).data().length == 0) {
+                               this.show_user_selected_msg(true);
+                       } else {
+                               this.show_user_selected_msg(false);
+                       }
+               }.bind(this));
+       },
+       set_table: function() {
+               this.table = $('#' + this.ids.get_users).DataTable({
+                       data: this.data,
+                       deferRender: true,
+                       dom: 'lBfrtip',
+                       buttons: [
+                               'copy', 'csv', 'colvis'
+                       ],
+                       columns: [
+                               {data: 'username'},
+                               {
+                                       data: 'long_name',
+                                       visible: (this.user_obj.supported_fields.indexOf('long_name') !== -1)
+                               },
+                               {
+                                       data: 'description',
+                                       visible: (this.user_obj.supported_fields.indexOf('description') !== -1)
+                               },
+                               {
+                                       data: 'email',
+                                       visible: (this.user_obj.supported_fields.indexOf('email') !== -1)
+                               }
+                       ],
+                       responsive: {
+                               details: {
+                                       type: 'column'
+                               }
+                       },
+                       columnDefs: [{
+                               className: "dt-center",
+                               targets: [ 1, 2, 3 ]
+                       }],
+                       select: {
+                               style:    'os',
+                               selector: 'td',
+                               blurable: false
+                       },
+                       order: [1, 'asc']
+               });
+       },
+       destroy_table: function() {
+               if (this.table) {
+                       this.table.destroy();
+               }
+       },
+       prepare_import: function() {
+               ret = true;
+               var import_opts = document.getElementById(this.ids.get_users_import_opts);
+               if (import_opts.value == this.import_options.selected_users) {
+                       var users = this.table.rows({selected: true}).data().toArray();
+                       this.sel_users_import_cb.setCallbackParameter(users);
+                       this.sel_users_import_cb.dispatch();
+                       ret = false; // to stop sending click event in original button
+               }
+               return ret;
+       },
+       set_user_table_cb: function(data, obj) {
+               oUserSecurity.data = data;
+               oUserSecurity.destroy_table();
+               oUserSecurity.set_table();
+               oUserSecurity.show_user_modal(true);
+       },
+       show_user_modal: function(show) {
+               var modal = document.getElementById(oUserSecurity.ids.get_users_modal);
+               if (show) {
+                       modal.style.display = 'block';
+                       if (this.user_obj) {
+                               this.user_obj.show_user_modal();
+                       }
+               } else {
+                       modal.style.display = 'none';
+               }
+       },
+       show_user_criteria: function(show) {
+               document.getElementById(this.ids.get_users_criteria).style.display = (show ? 'inline-block' : 'none');
+       },
+       show_user_selected_msg: function(show) {
+               document.getElementById(this.ids.get_users_selected_msg).style.display = (show ? 'inline-block' : 'none');
+       },
+       on_reload_user_list: function(e) {
+               var x = e.which || e.keyCode;
+               if (x === 13) {
+                       $('#' + this.ids.get_users_criteria_test).click();
+               }
+       }
+};
+var oBasicUserSecurity = {
+       ids: {},
+       name: 'basic',
+       table_title: '<%[ Basic user list ]%>',
+       supported_fields: ['username'],
+       enabled: <%=$this->BasicAuth->Checked ? 'true' : 'false'%>,
+       init: function() {
+               jQuery.extend(this.ids, oUserSecurity.ids);
+               if (this.enabled) {
+                       oUserSecurity.select_auth_method(this.name);
+               }
+       },
+       show_user_modal: function() {
+               document.getElementById(this.ids.table_title).textContent = this.table_title;
+               document.getElementById(this.ids.filter_long_name).disabled = true;
+               document.getElementById(this.ids.filter_desc).disabled = true;
+               document.getElementById(this.ids.filter_email).disabled = true;
+       }
+};
+
+var oLdapUserSecurity = {
+       ids: {
+               auth_simple: 'authentication_method_ldap_auth_simple',
+       },
+       name: 'ldap',
+       table_title: '<%[ LDAP user list ]%>',
+       supported_fields: ['username', 'long_name', 'description', 'email'],
+       enabled: <%=$this->LdapAuth->Checked ? 'true' : 'false'%>,
+       init: function() {
+               jQuery.extend(this.ids, oUserSecurity.ids);
+               if (this.enabled) {
+                       oUserSecurity.select_auth_method(this.name);
+               }
+       },
+       show_ldap_auth: function(auth) {
+               var auth_simple = document.getElementById(this.ids.auth_simple);
+               auth_simple.style.display = (auth === 'simple') ? 'block' : 'none';
+       },
+       show_user_modal: function() {
+               document.getElementById(this.ids.table_title).textContent = this.table_title;
+               document.getElementById(this.ids.filter_long_name).disabled = false;
+               document.getElementById(this.ids.filter_desc).disabled = false;
+               document.getElementById(this.ids.filter_email).disabled = false;
+       }
+};
+
+function validate_ips(sender, parameter) {
+       return validate_comma_separated_list(parameter);
+}
+oUserSecurity.init();
+oBasicUserSecurity.init();
+oLdapUserSecurity.init();
+</script>
+       </div>
+       <div class="w3-container tab_item" id="user_list" style="display: none">
+               <div class="w3-panel">
+                       <button type="button" id="add_user_btn" class="w3-button w3-green" onclick="oUsers.load_user_window()"><i class="fa fa-plus"></i> &nbsp;<%[ Add new user ]%></a>
+               </div>
+               <table id="user_list_table" class="w3-table w3-striped w3-hoverable w3-white w3-margin-bottom" style="width: 100%">
+                       <thead>
+                               <tr>
+                                       <th></th>
+                                       <th><%[ Username ]%></th>
+                                       <th class="w3-center"><%[ Long name ]%></th>
+                                       <th class="w3-center"><%[ Description ]%></th>
+                                       <th class="w3-center"><%[ E-mail ]%></th>
+                                       <th class="w3-center"><%[ Roles ]%></th>
+                                       <th class="w3-center"><%[ API host ]%></th>
+                                       <th class="w3-center"><%[ IP address restrictions ]%></th>
+                                       <th class="w3-center"><%[ Enabled ]%></th>
+                                       <th class="w3-center"><%[ Action ]%></th>
+                               </tr>
+                       </thead>
+                       <tbody id="user_list_body"></tbody>
+                       <tfoot>
+                               <tr>
+                                       <th></th>
+                                       <th><%[ Username ]%></th>
+                                       <th class="w3-center"><%[ Long name ]%></th>
+                                       <th class="w3-center"><%[ Description ]%></th>
+                                       <th class="w3-center"><%[ E-mail ]%></th>
+                                       <th class="w3-center"><%[ Roles ]%></th>
+                                       <th class="w3-center"><%[ API host ]%></th>
+                                       <th class="w3-center"><%[ IP address restrictions ]%></th>
+                                       <th class="w3-center"><%[ Enabled ]%></th>
+                                       <th class="w3-center"><%[ Action ]%></th>
+                               </tr>
+                       </tfoot>
+               </table>
+               <p class="info w3-hide-medium w3-hide-small"><%[ Tip: Use left-click to select table row. Use CTRL + left-click to multiple row selection. Use SHIFT + left-click to add a range of rows to selection. ]%></p>
+<com:TCallback ID="UserList" OnCallback="TemplateControl.setUserList" />
+<com:TCallback ID="LoadUser" OnCallback="TemplateControl.loadUserWindow" />
+<com:TCallback ID="RemoveUsersAction" OnCallback="TemplateControl.removeUsers" />
+<script>
+var oUserList = {
+       ids: {
+               user_list: 'user_list_table',
+               user_list_body: 'user_list_body'
+       },
+       actions: [
+               {
+                       action: 'remove',
+                       label: '<%[ Remove ]%>',
+                       value: 'username',
+                       callback: <%=$this->RemoveUsersAction->ActiveControl->Javascript%>
+               }
+       ],
+       data: [],
+       table: null,
+       table_toolbar: null,
+       init: function() {
+               if (!this.table) {
+                       this.set_table();
+                       this.set_bulk_actions();
+                       this.set_events();
+               } else {
+                       var page = this.table.page();
+                       this.table.clear().rows.add(this.data).draw();
+                       this.table.page(page).draw(false);
+                       oUserList.set_filters(this.table);
+                       this.table_toolbar.style.display = 'none';
+               }
+       },
+       set_events: function() {
+               document.getElementById(this.ids.user_list).addEventListener('click', function(e) {
+                       $(function() {
+                               this.table_toolbar.style.display = this.table.rows({selected: true}).data().length > 0 ? '' : 'none';
+                       }.bind(this));
+               }.bind(this));
+       },
+       set_table: function() {
+               this.table = $('#' + this.ids.user_list).DataTable({
+                       data: this.data,
+                       deferRender: true,
+                       dom: 'lB<"table_toolbar">frtip',
+                       stateSave: true,
+                       buttons: [
+                               'copy', 'csv', 'colvis'
+                       ],
+                       columns: [
+                               {
+                                       className: 'details-control',
+                                       orderable: false,
+                                       data: null,
+                                       defaultContent: '<button type="button" class="w3-button w3-blue"><i class="fa fa-angle-down"></i></button>'
+                               },
+                               {data: 'username'},
+                               {data: 'long_name'},
+                               {
+                                       data: 'description',
+                                       visible: false
+                               },
+                               {
+                                       data: 'email',
+                                       visible: false
+                               },
+                               {data: 'roles'},
+                               {data: 'api_hosts'},
+                               {
+                                       data: 'ips',
+                                       visible: false
+                               },
+                               {
+                                       data: 'enabled',
+                                       render: function(data, type, row) {
+                                               var ret;
+                                               if (type == 'display') {
+                                                       ret = '';
+                                                       if (data == 1) {
+                                                               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) {
+                                               var btn_edit = document.createElement('BUTTON');
+                                               btn_edit.className = 'w3-button w3-green';
+                                               btn_edit.type = 'button';
+                                               var i_edit = document.createElement('I');
+                                               i_edit.className = 'fa fa-list-ul';
+                                               var label_edit = document.createTextNode(' <%[ Edit ]%>');
+                                               btn_edit.appendChild(i_edit);
+                                               btn_edit.innerHTML += '&nbsp';
+                                               btn_edit.style.marginRight = '8px';
+                                               btn_edit.appendChild(label_edit);
+                                               btn_edit.setAttribute('onclick', 'oUsers.load_user_window(\'' + data + '\')');
+                                               return btn_edit.outerHTML;
+                                       }
+                               }
+                       ],
+                       responsive: {
+                               details: {
+                                       type: 'column'
+                               }
+                       },
+                       columnDefs: [{
+                               className: 'control',
+                               orderable: false,
+                               targets: 0
+                       },
+                       {
+                               className: 'action_col',
+                               orderable: false,
+                               targets: [ 9 ]
+                       },
+                       {
+                               className: "dt-center",
+                               targets: [ 5, 6, 8, 9 ]
+                       }],
+                       select: {
+                               style:    'os',
+                               selector: 'td:not(:last-child):not(:first-child)',
+                               blurable: false
+                       },
+                       order: [1, 'asc'],
+                       initComplete: function () {
+                               oUserList.set_filters(this.api());
+                       }
+               });
+       },
+       set_filters: function(api) {
+               api.columns([5, 6, 8]).every(function () {
+                       var column = this;
+                       var select = $('<select><option value=""></option></select>')
+                       .appendTo($(column.footer()).empty())
+                       .on('change', function () {
+                               var val = dtEscapeRegex(
+                                       $(this).val()
+                               );
+                               column
+                               .search(val ? '^' + val + '$' : '', true, false)
+                               .draw();
+                       });
+                       if (column[0][0] == 8) { // Enabled column
+                               column.data().unique().sort().each(function (d, j) {
+                                       var ds = '';
+                                       if (d === '1') {
+                                               ds = '<%[ Enabled ]%>';
+                                       } else if (d === '0') {
+                                               ds = '<%[ Disabled ]%>';
+                                       }
+                                       if (column.search() == '^' + dtEscapeRegex(d) + '$') {
+                                               select.append('<option value="' + d + '" title="' + ds + '" selected>' + ds + '</option>');
+                                       } else {
+                                               select.append('<option value="' + d + '" title="' + ds + '">' + ds + '</option>');
+                                       }
+                               });
+                       } else {
+                               column.cells('', column[0]).render('display').unique().sort().each(function(d, j) {
+                                       if (column.search() == '^' + dtEscapeRegex(d) + '$') {
+                                               select.append('<option value="' + d + '" selected>' + d + '</option>');
+                                       } else if(d) {
+                                               select.append('<option value="' + d + '">' + d + '</option>');
+                                       }
+                               });
+                       }
+               });
+       },
+       set_bulk_actions: function() {
+               this.table_toolbar = get_table_toolbar(this.table, this.actions, {
+                       actions: '<%[ Actions ]%>',
+                       ok: '<%[ OK ]%>'
+               });
+       }
+};
+
+var oUsers = {
+       load_user_window: function(username) {
+               var title_add = document.getElementById('user_window_title_add');
+               var title_edit = document.getElementById('user_window_title_edit');
+               var user_field_name = document.getElementById('<%=$this->UserName->ClientID%>');
+               var user_field_req = document.getElementById('user_window_required');
+               var user_win_type = document.getElementById('<%=$this->UserWindowType->ClientID%>');
+               // callback is sent both for new and edit user because there is realized
+               // checking if password is allowed to set or not
+               var cb = <%=$this->LoadUser->ActiveControl->Javascript%>;
+               cb.setCallbackParameter(username);
+               cb.dispatch();
+               if (username) {
+                       title_add.style.display = 'none';
+                       title_edit.style.display = 'inline-block';
+                       user_field_name.setAttribute('readonly', '');
+                       user_field_req.style.display = 'none';
+                       user_win_type.value = 'edit';
+               } else {
+                       title_add.style.display = 'inline-block';
+                       title_edit.style.display = 'none';
+                       this.clear_user_window();
+                       if (user_field_name.hasAttribute('readonly')) {
+                               user_field_name.removeAttribute('readonly');
+                       }
+                       user_field_req.style.display = 'inline';
+                       user_win_type.value = 'add';
+               }
+               document.getElementById('user_window').style.display = 'block';
+               user_field_name.focus();
+       },
+       clear_user_window: function() {
+               [
+                       '<%=$this->UserName->ClientID%>',
+                       '<%=$this->UserLongName->ClientID%>',
+                       '<%=$this->UserDescription->ClientID%>',
+                       '<%=$this->UserEmail->ClientID%>',
+                       '<%=$this->UserPassword->ClientID%>',
+                       '<%=$this->UserRoles->ClientID%>',
+                       '<%=$this->UserAPIHost->ClientID%>',
+                       '<%=$this->UserIps->ClientID%>'
+               ].forEach(function(id) {
+                       document.getElementById(id).value = '';
+               });
+               document.getElementById('<%=$this->UserEnabled->ClientID%>').checked = true;
+       },
+       load_user_list: function() {
+               var cb = <%=$this->UserList->ActiveControl->Javascript%>;
+               cb.dispatch();
+       },
+       load_user_list_cb: function(list) {
+               oUserList.data = list;
+               oUserList.init();
+       },
+       save_user_cb: function() {
+               document.getElementById('user_window').style.display = 'none';
+       }
+}
+
+$(function() {
+       oUsers.load_user_list();
+});
+</script>
+               <div id="user_window" class="w3-modal">
+                       <div class="w3-modal-content w3-animate-top w3-card-4">
+                               <header class="w3-container w3-teal">
+                                       <span onclick="document.getElementById('user_window').style.display = 'none';" class="w3-button w3-display-topright">&times;</span>
+                                       <h2 id="user_window_title_add" style="display: none"><%[ Add user ]%></h2>
+                                       <h2 id="user_window_title_edit" style="display: none"><%[ Edit user ]%></h2>
+                               </header>
+                               <div class="w3-container w3-margin-left w3-margin-right w3-text-teal">
+                                       <com:TValidationSummary
+                                               CssClass="field_invalid-summary"
+                                               ValidationGroup="UserGroup"
+                                               AutoUpdate="true"
+                                               Display="Dynamic"
+                                               />
+                                       <span id="user_window_username_exists" class="error" style="display: none"><ul><li><%[ Username with the given name already exists. ]%></li></ul></span>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="UserName" Text="<%[ Username: ]%>" CssClass="w3-xlarge"/></div>
+                                               <div class="w3-half">
+                                                       <com:TActiveTextBox
+                                                               ID="UserName"
+                                                               AutoPostBack="false"
+                                                               MaxLength="100"
+                                                               CssClass="w3-input w3-border"
+                                                       />
+                                                       <com:TRequiredFieldValidator
+                                                               ValidationGroup="UserGroup"
+                                                               ControlToValidate="UserName"
+                                                               ErrorMessage="<%[ Username field cannot be empty. ]%>"
+                                                               ControlCssClass="field_invalid"
+                                                               Display="None"
+                                                       />
+                                                       <com:TRegularExpressionValidator
+                                                               ValidationGroup="UserGroup"
+                                                               RegularExpression="<%=WebUserConfig::USER_PATTERN%>"
+                                                               ControlToValidate="UserName"
+                                                               ErrorMessage="<%[ Invalid username value. ]%>"
+                                                               ControlCssClass="field_invalid"
+                                                               Display="None"
+                                                       />
+                                               </div> &nbsp;<i id="user_window_required" class="fa fa-asterisk w3-text-red opt_req" style="display none"></i>
+                                       </div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="UserLongName" Text="<%[ Long name: ]%>" CssClass="w3-xlarge"/></div>
+                                               <div class="w3-half">
+                                                       <com:TActiveTextBox
+                                                               ID="UserLongName"
+                                                               AutoPostBack="false"
+                                                               MaxLength="100"
+                                                               CssClass="w3-input w3-border"
+                                                       />
+                                               </div>
+                                       </div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="UserDescription" Text="<%[ Description: ]%>" CssClass="w3-xlarge"/></div>
+                                               <div class="w3-half">
+                                                       <com:TActiveTextBox
+                                                               ID="UserDescription"
+                                                               TextMode="MultiLine"
+                                                               Rows="3"
+                                                               AutoPostBack="false"
+                                                               MaxLength="500"
+                                                               CssClass="w3-input w3-border"
+                                                       />
+                                               </div>
+                                       </div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="UserEmail" Text="<%[ E-mail: ]%>" CssClass="w3-xlarge"/></div>
+                                               <div class="w3-half">
+                                                       <com:TActiveTextBox
+                                                               ID="UserEmail"
+                                                               AutoPostBack="false"
+                                                               MaxLength="500"
+                                                               CssClass="w3-input w3-border"
+                                                       />
+                                                       <com:TRegularExpressionValidator
+                                                               ValidationGroup="UserGroup"
+                                                               RegularExpression="<%=WebUserConfig::EMAIL_ADDRESS_PATTERN%>"
+                                                               ControlToValidate="UserEmail"
+                                                               ErrorMessage="<%[ Invalid e-mail address value. ]%>"
+                                                               ControlCssClass="field_invalid"
+                                                               Display="None"
+                                                       />
+                                               </div>
+                                       </div>
+                                       <div id="user_window_password" class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="UserPassword" Text="<%[ Password: ]%>" CssClass="w3-xlarge"/></div>
+                                               <div class="w3-half">
+                                                       <com:TActiveTextBox
+                                                               ID="UserPassword"
+                                                               TextMode="Password"
+                                                               AutoPostBack="false"
+                                                               MaxLength="1000"
+                                                               CssClass="w3-input w3-border"
+                                                       />
+                                               </div>
+                                       </div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="UserRoles" Text="<%[ Roles: ]%>" CssClass="w3-xlarge"/></div>
+                                               <div class="w3-half">
+                                                       <com:TActiveListBox
+                                                               ID="UserRoles"
+                                                               SelectionMode="Multiple"
+                                                               Rows="6"
+                                                               CssClass="w3-select w3-border"
+                                                               AutoPostBack="false"
+                                                       />
+                                                       <com:TRequiredFieldValidator
+                                                               ValidationGroup="UserGroup"
+                                                               ControlToValidate="UserRoles"
+                                                               ErrorMessage="<%[ At least one role must be selected. ]%>"
+                                                               ControlCssClass="field_invalid"
+                                                               Display="None"
+                                                       />
+                                                       <p class="w3-text-black" style="margin: 0 16px 0 0"><%[ Use CTRL + left-click to multiple item selection ]%></p>
+                                               </div> &nbsp;<i class="fa fa-asterisk w3-text-red opt_req"></i>
+                                       </div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="UserAPIHost" Text="<%[ API host: ]%>" CssClass="w3-xlarge"/></div>
+                                               <div class="w3-half">
+                                                       <com:TActiveDropDownList
+                                                               ID="UserAPIHost"
+                                                               CssClass="w3-select w3-border"
+                                                               AutoPostBack="false"
+                                                       />
+                                               </div>
+                                       </div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="UserEnabled" Text="<%[ Enabled: ]%>" CssClass="w3-xlarge"/></div>
+                                               <div class="w3-half">
+                                                       <com:TActiveCheckBox
+                                                               ID="UserEnabled"
+                                                               CssClass="w3-check"
+                                                               AutoPostBack="false"
+                                                       />
+                                               </div>
+                                       </div>
+                                       <i class="fas fa-wrench w3-text-black"></i> &nbsp;<a href="javascript:void(0)" onclick="$('#user_window_advanced_options').toggle('fast');" class="w3-text-black"><%[ Advanced options ]%></a>
+                                       <div id="user_window_advanced_options" style="display: none">
+                                               <div class="w3-row w3-section" title="<%[ Comma separated IP addresses. Using asterisk character, there is also possible to provide subnet, for example: 192.168.1.* ]%>">
+                                                       <div class="w3-col w3-third"><com:TLabel ForControl="UserIps" Text="<%[ IP address restrictions: ]%>" CssClass="w3-xlarge"/></div>
+                                                       <div class="w3-half">
+                                                               <com:TActiveTextBox
+                                                                       ID="UserIps"
+                                                                       AutoPostBack="false"
+                                                                       MaxLength="500"
+                                                                       CssClass="w3-input w3-border"
+                                                               />
+                                                               <p class="w3-text-black"><a href="javascript:void(0)" onclick="document.getElementById('<%=$this->UserIps->ClientID%>').value = '<%=$_SERVER['REMOTE_ADDR']%>';"><%[ Set your IP address ]%></a></p>
+                                                               <com:TActiveCustomValidator
+                                                                       ID="UserIpsValidator"
+                                                                       ValidationGroup="UserGroup"
+                                                                       ControlToValidate="UserIps"
+                                                                       OnServerValidate="validateIps"
+                                                                       Display="Dynamic"
+                                                                       ErrorMessage="<%[ Invalid IP address restrictions value. This field can have comma separated IP addresses only or subnet addresses like 192.168.1.* ]%>"
+                                                                       ControlCssClass="field_invalid"
+                                                               />
+                                                       </div>
+                                               </div>
+                                       </div>
+                               </div>
+                               <footer class="w3-container w3-center">
+                                       <button type="button" class="w3-button w3-red" onclick="document.getElementById('user_window').style.display = 'none';"><i class="fas fa-times"></i> &nbsp;<%[ Cancel ]%></button>
+                                       <com:TActiveLinkButton
+                                               ID="UserSave"
+                                               ValidationGroup="UserGroup"
+                                               CausesValidation="true"
+                                               OnCallback="saveUser"
+                                               CssClass="w3-button w3-section w3-teal w3-padding"
+                                       >
+                                               <i class="fa fa-save"></i> &nbsp;<%[ Save ]%>
+                                       </com:TActiveLinkButton>
+                                       <i id="status_command_loading" class="fa fa-sync w3-spin" style="visibility: hidden;"></i>
+                               </footer>
+                       </div>
+                       <com:TActiveHiddenField ID="UserWindowType" />
+               </div>
+       </div>
+       <div class="w3-container tab_item" id="role_list" style="display: none;">
+               <div class="w3-panel">
+                       <button type="button" id="add_role_btn" class="w3-button w3-green" onclick="oRoles.load_role_window()"><i class="fa fa-plus"></i> &nbsp;<%[ Add new role ]%></a>
+               </div>
+               <table id="role_list_table" class="w3-table w3-striped w3-hoverable w3-white w3-margin-bottom" style="width: 100%">
+                       <thead>
+                               <tr>
+                                       <th></th>
+                                       <th><%[ Role name ]%></th>
+                                       <th class="w3-center"><%[ Long name ]%></th>
+                                       <th class="w3-center"><%[ Description ]%></th>
+                                       <th class="w3-center"># <%[ Users ]%></th>
+                                       <th class="w3-center"><%[ Resources ]%></th>
+                                       <th class="w3-center"><%[ Enabled ]%></th>
+                                       <th class="w3-center"><%[ Action ]%></th>
+                               </tr>
+                       </thead>
+                       <tbody id="role_list_body"></tbody>
+                       <tfoot>
+                               <tr>
+                                       <th></th>
+                                       <th><%[ Role name ]%></th>
+                                       <th class="w3-center"><%[ Long name ]%></th>
+                                       <th class="w3-center"><%[ Description ]%></th>
+                                       <th class="w3-center"># <%[ Users ]%></th>
+                                       <th class="w3-center"><%[ Resources ]%></th>
+                                       <th class="w3-center"><%[ Enabled ]%></th>
+                                       <th class="w3-center"><%[ Action ]%></th>
+                               </tr>
+                       </tfoot>
+               </table>
+               <p class="info w3-hide-medium w3-hide-small"><%[ Tip: Use left-click to select table row. Use CTRL + left-click to multiple row selection. Use SHIFT + left-click to add a range of rows to selection. ]%></p>
+<com:TCallback ID="RoleList" OnCallback="TemplateControl.setRoleList" />
+<com:TCallback ID="LoadRole" OnCallback="TemplateControl.loadRoleWindow" />
+<script>
+var oRoleList = {
+       ids: {
+               role_list: 'role_list_table',
+               role_list_body: 'role_list_body'
+       },
+       actions: [
+               {
+                       action: 'remove',
+                       label: '<%[ Remove ]%>',
+                       value: 'role',
+                       callback: <%=$this->RemoveRolesAction->ActiveControl->Javascript%>,
+                       validate: function(selected) {
+                               var used_roles = [];
+                               var predefined_roles = <%=json_encode(array_keys($this->getModule('user_role')->getPreDefinedRoles()))%>;
+                               var predef_roles = [];
+                               selected.each(function(v, k) {
+                                       if (predefined_roles.indexOf(v.role) !== -1) {
+                                               predef_roles.push(' - ' + v.role);
+                                       } else if (v.user_count > 0) {
+                                               used_roles.push(' - ' + v.role);
+                                       }
+                               });
+                               var emsg = '', msg;
+                               if (predef_roles.length > 0) {
+                                       msg = '<%[ The following roles are predefined and cannot be removed: %predefined_roles ]%>';
+                                       emsg += msg.replace('%predefined_roles', '<hr />' + predef_roles.join('<br />') + '<hr />');
+                               }
+                               if (used_roles.length > 0) {
+                                       msg = '<%[ The following roles are using by users and cannot be removed: %used_roles To remove them, please unassign all users from these roles. ]%>';
+                                       emsg += msg.replace('%used_roles', '<hr />' + used_roles.join('<br />') + '<hr />');
+                               }
+                               if (emsg) {
+                                       oBulkActionsModal.set_error(emsg);
+                                       return false;
+                               }
+                               return true;
+                       }
+               }
+       ],
+       data: [],
+       table: null,
+       table_toolbar: null,
+       init: function() {
+               if (!this.table) {
+                       this.set_table();
+                       this.set_bulk_actions();
+                       this.set_events();
+               } else {
+                       var page = this.table.page();
+                       this.table.clear().rows.add(oRoleList.data).draw();
+                       this.table.page(page).draw(false);
+                       this.set_filters(this.table);
+                       this.table_toolbar.style.display = 'none';
+               }
+       },
+       set_events: function() {
+               document.getElementById(this.ids.role_list).addEventListener('click', function(e) {
+                       $(function() {
+                               this.table_toolbar.style.display = this.table.rows({selected: true}).data().length > 0 ? '' : 'none';
+                       }.bind(this));
+               }.bind(this));
+       },
+       set_table: function() {
+               this.table = $('#' + this.ids.role_list).DataTable({
+                       data: this.data,
+                       deferRender: true,
+                       dom: 'lB<"table_toolbar">frtip',
+                       stateSave: true,
+                       buttons: [
+                               'copy', 'csv', 'colvis'
+                       ],
+                       columns: [
+                               {
+                                       className: 'details-control',
+                                       orderable: false,
+                                       data: null,
+                                       defaultContent: '<button type="button" class="w3-button w3-blue"><i class="fa fa-angle-down"></i></button>'
+                               },
+                               {data: 'role'},
+                               {data: 'long_name'},
+                               {
+                                       data: 'description',
+                                       visible: false
+                               },
+                               {data: 'user_count'},
+                               {
+                                       data: 'resources',
+                                       render: function(data, type, row) {
+                                               ret = data;
+                                               if (type == 'display') {
+                                                       var span = document.createElement('SPAN');
+                                                       span.title = data;
+                                                       if (data.length > 40) {
+                                                               span.textContent = data.substring(0, 40) + '...';
+                                                       } else {
+                                                               span.textContent = data;
+                                                       }
+                                                       ret = span.outerHTML;
+                                               } else {
+                                                       ret = data;
+                                               }
+                                               return ret;
+                                       }
+                               },
+                               {
+                                       data: 'enabled',
+                                       render: function(data, type, row) {
+                                               var ret;
+                                               if (type == 'display') {
+                                                       ret = '';
+                                                       if (data == 1) {
+                                                               var check = document.createElement('I');
+                                                               check.className = 'fas fa-check';
+                                                               ret = check.outerHTML;
+                                                       }
+                                               } else {
+                                                       ret = data;
+                                               }
+                                               return ret;
+                                       }
+                               },
+                               {
+                                       data: 'role',
+                                       render: function (data, type, row) {
+                                               var btn_edit = document.createElement('BUTTON');
+                                               btn_edit.className = 'w3-button w3-green';
+                                               btn_edit.type = 'button';
+                                               var i_edit = document.createElement('I');
+                                               i_edit.className = 'fa fa-list-ul';
+                                               var label_edit = document.createTextNode(' <%[ Edit ]%>');
+                                               btn_edit.appendChild(i_edit);
+                                               btn_edit.innerHTML += '&nbsp';
+                                               btn_edit.style.marginRight = '8px';
+                                               btn_edit.appendChild(label_edit);
+                                               btn_edit.setAttribute('onclick', 'oRoles.load_role_window(\'' + data + '\')');
+                                               return btn_edit.outerHTML;
+                                       }
+                               }
+                       ],
+                       responsive: {
+                               details: {
+                                       type: 'column'
+                               }
+                       },
+                       columnDefs: [{
+                               className: 'control',
+                               orderable: false,
+                               targets: 0
+                       },
+                       {
+                               className: 'action_col',
+                               orderable: false,
+                               targets: [ 7 ]
+                       },
+                       {
+                               className: "dt-center",
+                               targets: [ 2, 3, 4, 5, 6, 7 ]
+                       }],
+                       select: {
+                               style:    'os',
+                               selector: 'td:not(:last-child):not(:first-child)',
+                               blurable: false
+                       },
+                       order: [1, 'asc'],
+                       initComplete: function () {
+                               oRoleList.set_filters(this.api());
+                       }
+               });
+       },
+       set_filters: function(api) {
+               api.columns([6]).every(function () {
+                       var column = this;
+                       var select = $('<select><option value=""></option></select>')
+                       .appendTo($(column.footer()).empty())
+                       .on('change', function () {
+                               var val = dtEscapeRegex(
+                                       $(this).val()
+                               );
+                               column
+                               .search(val ? '^' + val + '$' : '', true, false)
+                               .draw();
+                       });
+                       if (column[0][0] == 6) { // Enabled column
+                               column.data().unique().sort().each(function (d, j) {
+                                       var ds = '';
+                                       if (d === '1') {
+                                               ds = '<%[ Enabled ]%>';
+                                       } else if (d === '0') {
+                                               ds = '<%[ Disabled ]%>';
+                                       }
+                                       if (column.search() == '^' + dtEscapeRegex(d) + '$') {
+                                               select.append('<option value="' + d + '" title="' + ds + '" selected>' + ds + '</option>');
+                                       } else if (ds) {
+                                               select.append('<option value="' + d + '" title="' + ds + '">' + ds + '</option>');
+                                       }
+                               });
+                       }
+               });
+       },
+       set_bulk_actions: function() {
+               this.table_toolbar = get_table_toolbar(this.table, this.actions, {
+                       actions: '<%[ Actions ]%>',
+                       ok: '<%[ OK ]%>'
+               });
+       }
+};
+
+var oRoles = {
+       load_role_window: function(role) {
+               var title_add = document.getElementById('role_window_title_add');
+               var title_edit = document.getElementById('role_window_title_edit');
+               var role_field_name = document.getElementById('<%=$this->Role->ClientID%>');
+               var role_field_req = document.getElementById('role_window_required');
+               var role_win_type = document.getElementById('<%=$this->RoleWindowType->ClientID%>');
+               var cb = <%=$this->LoadRole->ActiveControl->Javascript%>;
+               cb.setCallbackParameter(role);
+               cb.dispatch();
+               if (role) {
+                       title_add.style.display = 'none';
+                       title_edit.style.display = 'inline-block';
+                       role_field_name.setAttribute('readonly', '');
+                       role_field_req.style.display = 'none';
+                       role_win_type.value = 'edit';
+               } else {
+                       title_add.style.display = 'inline-block';
+                       title_edit.style.display = 'none';
+                       this.clear_role_window();
+                       if (role_field_name.hasAttribute('readonly')) {
+                               role_field_name.removeAttribute('readonly');
+                       }
+                       role_field_req.style.display = 'inline';
+                       role_win_type.value = 'add';
+               }
+               document.getElementById('role_window_role_exists').style.display = 'none';
+               document.getElementById('role_window').style.display = 'block';
+               if (!role) {
+                       role_field_name.focus();
+               }
+       },
+       clear_role_window: function() {
+               [
+                       '<%=$this->Role->ClientID%>',
+                       '<%=$this->RoleLongName->ClientID%>',
+                       '<%=$this->RoleDescription->ClientID%>',
+                       '<%=$this->RoleResources->ClientID%>',
+               ].forEach(function(id) {
+                       document.getElementById(id).value = '';
+               });
+               document.getElementById('<%=$this->RoleEnabled->ClientID%>').checked = true;
+       },
+       load_role_list: function() {
+               var cb = <%=$this->RoleList->ActiveControl->Javascript%>;
+               cb.dispatch();
+       },
+       load_role_list_cb: function(list) {
+               oRoleList.data = list;
+               oRoleList.init();
+       },
+       save_role_cb: function() {
+               document.getElementById('role_window').style.display = 'none';
+       }
+}
+
+$(function() {
+       oRoles.load_role_list();
+});
+</script>
+               <div id="role_window" class="w3-modal">
+                       <div class="w3-modal-content w3-animate-top w3-card-4">
+                               <header class="w3-container w3-teal">
+                                       <span onclick="document.getElementById('role_window').style.display = 'none';" class="w3-button w3-display-topright">&times;</span>
+                                       <h2 id="role_window_title_add" style="display: none"><%[ Add role ]%></h2>
+                                       <h2 id="role_window_title_edit" style="display: none"><%[ Edit role ]%></h2>
+                               </header>
+                               <div class="w3-container w3-margin-left w3-margin-right w3-text-teal">
+                                       <com:TValidationSummary
+                                               CssClass="field_invalid-summary"
+                                               ValidationGroup="RoleGroup"
+                                               AutoUpdate="true"
+                                               Display="Dynamic"
+                                               />
+                                       <span id="role_window_role_exists" class="error" style="display: none"><ul><li><%[ Role with the given name already exists. ]%></li></ul></span>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="Role" Text="<%[ Role: ]%>" CssClass="w3-xlarge"/></div>
+                                               <div class="w3-half">
+                                                       <com:TActiveTextBox
+                                                               ID="Role"
+                                                               AutoPostBack="false"
+                                                               MaxLength="100"
+                                                               CssClass="w3-input w3-border"
+                                                       />
+                                                       <com:TRequiredFieldValidator
+                                                               ValidationGroup="RoleGroup"
+                                                               ControlToValidate="Role"
+                                                               ErrorMessage="<%[ Role field cannot be empty. ]%>"
+                                                               ControlCssClass="field_invalid"
+                                                               Display="None"
+                                                       />
+                                                       <com:TRegularExpressionValidator
+                                                               ValidationGroup="RoleGroup"
+                                                               RegularExpression="<%=WebRoleConfig::ROLE_PATTERN%>"
+                                                               ControlToValidate="Role"
+                                                               ErrorMessage="<%[ Invalid role value. ]%>"
+                                                               ControlCssClass="field_invalid"
+                                                               Display="None"
+                                                       />
+                                               </div> &nbsp;<i id="role_window_required" class="fa fa-asterisk w3-text-red opt_req" style="display none"></i>
+                                       </div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="RoleLongName" Text="<%[ Long name: ]%>" CssClass="w3-xlarge"/></div>
+                                               <div class="w3-half">
+                                                       <com:TActiveTextBox
+                                                               ID="RoleLongName"
+                                                               AutoPostBack="false"
+                                                               MaxLength="100"
+                                                               CssClass="w3-input w3-border"
+                                                       />
+                                               </div>
+                                       </div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="RoleDescription" Text="<%[ Description: ]%>" CssClass="w3-xlarge"/></div>
+                                               <div class="w3-half">
+                                                       <com:TActiveTextBox
+                                                               ID="RoleDescription"
+                                                               TextMode="MultiLine"
+                                                               Rows="3"
+                                                               AutoPostBack="false"
+                                                               MaxLength="500"
+                                                               CssClass="w3-input w3-border"
+                                                       />
+                                               </div>
+                                       </div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="RoleResources" Text="<%[ Resources: ]%>" CssClass="w3-xlarge"/></div>
+                                               <div class="w3-half">
+                                                       <com:TActiveListBox
+                                                               ID="RoleResources"
+                                                               SelectionMode="Multiple"
+                                                               Rows="6"
+                                                               CssClass="w3-select w3-border"
+                                                               AutoPostBack="false"
+                                                       />
+                                                       <com:TRequiredFieldValidator
+                                                               ValidationGroup="RoleGroup"
+                                                               ControlToValidate="RoleResources"
+                                                               ErrorMessage="<%[ At least one resource must be selected. ]%>"
+                                                               ControlCssClass="field_invalid"
+                                                               Display="None"
+                                                       />
+                                                       <p class="w3-text-black" style="margin: 0 16px 0 0"><%[ Use CTRL + left-click to multiple item selection ]%></p>
+                                               </div> &nbsp;<i class="fa fa-asterisk w3-text-red opt_req"></i>
+
+                                       </div>
+                                       <div class="w3-row w3-section">
+                                               <div class="w3-col w3-third"><com:TLabel ForControl="RoleEnabled" Text="<%[ Enabled: ]%>" CssClass="w3-xlarge"/></div>
+                                               <div class="w3-half">
+                                                       <com:TActiveCheckBox
+                                                               ID="RoleEnabled"
+                                                               CssClass="w3-check"
+                                                               AutoPostBack="false"
+                                                       />
+                                               </div>
+                                       </div>
+                               </div>
+                               <com:TActiveLabel
+                                       ID="PreDefinedRoleMsg"
+                                       CssClass="w3-margin-left"
+                                       Display="None"
+                               >
+                                       <%[ This is native predefined role, that cannot be changed. For having custom roles please use the button to add new role. ]%>
+                               </com:TActiveLabel>
+                               <footer class="w3-container w3-center w3-padding">
+                                       <button type="button" class="w3-button w3-red" onclick="document.getElementById('role_window').style.display = 'none';"><i class="fas fa-times"></i> &nbsp;<%[ Cancel ]%></button>
+                                       <com:TActiveLinkButton
+                                               ID="RoleSave"
+                                               ValidationGroup="RoleGroup"
+                                               CausesValidation="true"
+                                               OnCallback="saveRole"
+                                               CssClass="w3-button w3-section w3-teal"
+                                       >
+                                               <i class="fa fa-save"></i> &nbsp;<%[ Save ]%>
+                                       </com:TActiveLinkButton>
+                                       <i id="status_command_loading" class="fa fa-sync w3-spin" style="visibility: hidden;"></i>
+                               </footer>
+                       </div>
+                       <com:TActiveHiddenField ID="RoleWindowType" />
+               </div>
+       </div>
+<script>
+/**
+ * Defne bulk actions output id here because expression tags (< % = % >) cannot
+ * be defined in the TCallback ClientSide properties.
+ */
+var bulk_actions_output_id = '<%=$this->SourceTemplateControl->BulkActions->BulkActionsOutput->ClientID%>';
+</script>
+       <com:TCallback ID="RemoveRolesAction" OnCallback="TemplateControl.removeRoles" />
+       <com:Application.Web.Portlets.BulkActionsModal ID="BulkActions" />
+</com:TContent>
diff --git a/gui/baculum/protected/Web/Pages/Security.php b/gui/baculum/protected/Web/Pages/Security.php
new file mode 100644 (file)
index 0000000..319c916
--- /dev/null
@@ -0,0 +1,1200 @@
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum   - Bacula web interface
+ *
+ * Copyright (C) 2013-2020 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('System.Web.UI.ActiveControls.TActiveCheckBox');
+Prado::using('System.Web.UI.ActiveControls.TActiveCustomValidator');
+Prado::using('System.Web.UI.ActiveControls.TActiveDropDownList');
+Prado::using('System.Web.UI.ActiveControls.TActiveHiddenField');
+Prado::using('System.Web.UI.ActiveControls.TActiveLabel');
+Prado::using('System.Web.UI.ActiveControls.TActiveLinkButton');
+Prado::using('System.Web.UI.ActiveControls.TActiveListBox');
+Prado::using('System.Web.UI.ActiveControls.TActiveTextBox');
+Prado::using('System.Web.UI.ActiveControls.TCallback');
+Prado::using('System.Web.UI.WebControls.TCheckBox');
+Prado::using('System.Web.UI.WebControls.TLabel');
+Prado::using('System.Web.UI.WebControls.TListItem');
+Prado::using('System.Web.UI.WebControls.TRadioButton');
+Prado::using('System.Web.UI.WebControls.TRegularExpressionValidator');
+Prado::using('System.Web.UI.WebControls.TRequiredFieldValidator');
+Prado::using('System.Web.UI.WebControls.TValidationSummary');
+Prado::using('Application.Common.Class.Ldap');
+Prado::using('Application.Web.Class.BaculumWebPage');
+
+/**
+ * Security page (auth methods, users, roles...).
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Page
+ * @package Baculum Web
+ */
+class Security extends BaculumWebPage {
+
+       /**
+        * Modal window types for users and roles.
+        */
+       const TYPE_ADD_WINDOW = 'add';
+       const TYPE_EDIT_WINDOW = 'edit';
+
+       /**
+        * Options for import users.
+        */
+       const IMPORT_OPT_ALL_USERS = 0;
+       const IMPORT_OPT_SELECTED_USERS = 1;
+       const IMPORT_OPT_CRITERIA = 2;
+
+       /**
+        * Options for import criteria.
+        */
+       const IMPORT_CRIT_USERNAME = 0;
+       const IMPORT_CRIT_LONG_NAME = 1;
+       const IMPORT_CRIT_DESCRIPTION = 2;
+       const IMPORT_CRIT_EMAIL = 3;
+
+
+       /**
+        * Store web user config.
+        */
+       private $user_config = [];
+
+       /**
+        * Initialize page.
+        *
+        * @param mixed $param oninit event parameter
+        * @return none
+        */
+       public function onInit($param) {
+               parent::onInit($param);
+               if ($this->IsCallBack || $this->IsPostBack) {
+                       return;
+               }
+               $this->initDefAccessForm();
+               $this->initAuthForm();
+               $this->initUserWindow();
+               $this->initRoleWindow();
+               $this->setBasicAuthConfig();
+       }
+
+       /**
+        * Initialize form with default access settings.
+        *
+        * @return none
+        */
+       public function initDefAccessForm() {
+               $this->setRoles(
+                       $this->GeneralDefaultAccessRole,
+                       WebUserRoles::NORMAL
+               );
+
+               $this->setAPIHosts(
+                       $this->GeneralDefaultAccessAPIHost,
+                       HostConfig::MAIN_CATALOG_HOST
+               );
+               if (isset($this->web_config['security']['def_access'])) {
+                       if ($this->web_config['security']['def_access'] === WebConfig::DEF_ACCESS_NO_ACCESS) {
+                               $this->GeneralDefaultNoAccess->Checked = true;
+                       } elseif ($this->web_config['security']['def_access'] === WebConfig::DEF_ACCESS_DEFAULT_SETTINGS) {
+                               $this->GeneralDefaultAccess->Checked = true;
+                       }
+                       if (isset($this->web_config['security']['def_role'])) {
+                               $this->GeneralDefaultAccessRole->SelectedValue = $this->web_config['security']['def_role'];
+                       }
+                       if (isset($this->web_config['security']['def_api_host'])) {
+                               $this->GeneralDefaultAccessAPIHost->SelectedValue = $this->web_config['security']['def_api_host'];
+                       }
+               } else {
+                       $this->GeneralDefaultAccess->Checked = true;
+               }
+       }
+
+       /**
+        * Initialize form with authentication method settings.
+        *
+        * @return none
+        */
+       public function initAuthForm() {
+               if (isset($this->web_config['security']['auth_method'])) {
+                       if ($this->web_config['security']['auth_method'] ===  WebConfig::AUTH_METHOD_BASIC) {
+                               $this->BasicAuth->Checked = true;
+                       } elseif ($this->web_config['security']['auth_method'] ===  WebConfig::AUTH_METHOD_LDAP) {
+                               $this->LdapAuth->Checked = true;
+                       }
+
+                       // Fill LDAP auth fileds
+                       if (key_exists('auth_ldap', $this->web_config)) {
+                               $this->LdapAuthServerAddress->Text = $this->web_config['auth_ldap']['address'];
+                               $this->LdapAuthServerPort->Text = $this->web_config['auth_ldap']['port'];
+                               $this->LdapAuthServerLdaps->Checked = ($this->web_config['auth_ldap']['ldaps'] == 1);
+                               $this->LdapAuthServerProtocolVersion->Text = $this->web_config['auth_ldap']['protocol_ver'];
+                               $this->LdapAuthServerBaseDn->Text = $this->web_config['auth_ldap']['base_dn'];
+                               if ($this->web_config['auth_ldap']['auth_method'] === Ldap::AUTH_METHOD_ANON) {
+                                       $this->LdapAuthMethodAnonymous->Checked = true;
+                               } elseif ($this->web_config['auth_ldap']['auth_method'] === Ldap::AUTH_METHOD_SIMPLE) {
+                                       $this->LdapAuthMethodSimple->Checked = true;
+                               }
+                               $this->LdapAuthMethodSimpleUsername->Text = $this->web_config['auth_ldap']['bind_dn'];
+                               $this->LdapAuthMethodSimplePassword->Text = $this->web_config['auth_ldap']['bind_password'];
+                               $this->LdapAuthServerBaseDn->Text = $this->web_config['auth_ldap']['base_dn'];
+                               $this->LdapAttributesUsername->Text = $this->web_config['auth_ldap']['user_attr'];
+                               $this->LdapAttributesLongName->Text = $this->web_config['auth_ldap']['long_name_attr'];
+                               $this->LdapAttributesEmail->Text = $this->web_config['auth_ldap']['email_attr'];
+                               $this->LdapAttributesDescription->Text = $this->web_config['auth_ldap']['desc_attr'];
+                       }
+                       // Fill Basic auth fields
+                       if (key_exists('auth_basic', $this->web_config)) {
+                               $this->BasicAuthAllowManageUsers->Checked = ($this->web_config['auth_basic']['allow_manage_users'] == 1);
+                               $this->BasicAuthUserFile->Text = $this->web_config['auth_basic']['user_file'];
+                               $this->BasicAuthHashAlgorithm->SelectedValue = $this->web_config['auth_basic']['hash_alg'];
+                       }
+               } else {
+                       // Default set to Basic auth method
+                       $this->BasicAuth->Checked = true;
+               }
+       }
+
+       /**
+        * Initialize values in user modal window.
+        *
+        * @return none
+        */
+       public function initUserWindow() {
+               // set API hosts
+               $this->setAPIHosts($this->UserAPIHost);
+
+               // set roles
+               $this->setRoles($this->UserRoles);
+       }
+
+       /**
+        * Set role list control.
+        *
+        * @param object $control control which contains role list
+        * @param mixed $def_val default value or null if no default value to set
+        * @return none
+        */
+       private function setRoles($control, $def_val = null) {
+               // set roles
+               $roles = $this->getModule('user_role')->getRoles();
+               $role_items = [];
+               foreach ($roles as $role_name => $role) {
+                       $role_items[$role_name] = $role['long_name'] ?: $role_name;
+               }
+               $control->DataSource = $role_items;
+               if ($def_val) {
+                       $control->SelectedValue = $def_val;
+               }
+               $control->dataBind();
+       }
+
+       /**
+        * Set API host list control.
+        *
+        * @param object $control control which contains API host list
+        * @param mixed $def_val default value or null if no default value to set
+        * @return none
+        */
+       private function setAPIHosts($control, $def_val = null) {
+               $api_hosts = array_keys($this->getModule('host_config')->getConfig());
+               array_unshift($api_hosts, '');
+               $control->DataSource = array_combine($api_hosts, $api_hosts);
+               if ($def_val) {
+                       $control->SelectedValue = $def_val;
+               }
+               $control->dataBind();
+       }
+
+       /**
+        * Set and load user list.
+        *
+        * @param TCallback $sender sender object
+        * @param TCallbackEventParameter callback parameter
+        * @return none
+        */
+       public function setUserList($sender, $param) {
+               $config = $this->getModule('user_config')->getConfig();
+               $this->getCallbackClient()->callClientFunction('oUsers.load_user_list_cb', [
+                       array_values($config)
+               ]);
+               $this->user_config = $config;
+       }
+
+       /**
+        * Load data in user modal window.
+        *
+        * @param TCallback $sender sender object
+        * @param TCallbackEventParameter $param callback parameter
+        * @return none
+        */
+       public function loadUserWindow($sender, $param) {
+               //$this->getModule('user_config')->importBasicUsers();
+               $username = $param->getCallbackParameter();
+               $config = $this->getModule('user_config')->getUserConfig($username);
+               if (count($config) > 0) {
+                       // It is done only for existing users
+                       $this->UserName->Text = $config['username'];
+                       $this->UserLongName->Text = $config['long_name'];
+                       $this->UserDescription->Text = $config['description'];
+                       $this->UserEmail->Text = $config['email'];
+                       $this->UserPassword->Text = '';
+                       $selected_indices = [];
+                       $roles = explode(',', $config['roles']);
+                       for ($i = 0; $i < $this->UserRoles->getItemCount(); $i++) {
+                               if (in_array($this->UserRoles->Items[$i]->Value, $roles)) {
+                                       $selected_indices[] = $i;
+                               }
+                       }
+                       $this->UserRoles->setSelectedIndices($selected_indices);
+                       $this->UserAPIHost->SelectedValue = $config['api_hosts'];
+                       $this->UserIps->Text = $config['ips'];
+                       $this->UserEnabled->Checked = ($config['enabled'] == 1);
+               }
+
+               // It is done both for new user and for edit user
+               if ($this->isManageUsersAvail()) {
+                       $this->getCallbackClient()->show('user_window_password');
+               } else {
+                       $this->getCallbackClient()->hide('user_window_password');
+               }
+       }
+
+       /**
+        * Save user.
+        * It works both for new users and for edited users.
+        * Saves values from modal popup.
+        *
+        * @param TCallback $sender sender object
+        * @param TCallbackEventParameter $param callback parameter
+        * @return none
+        */
+       public function saveUser($sender, $param) {
+               if (!$this->UserIps->IsValid) {
+                       // invalid IP restriction value
+                       return;
+               }
+               $user_win_type = $this->UserWindowType->Value;
+               $username = $this->UserName->Text;
+               $this->getCallbackClient()->hide('user_window_username_exists');
+               if ($user_win_type === self::TYPE_ADD_WINDOW) {
+                       $config = $this->getModule('user_config')->getUserConfig($username);
+                       if (count($config) > 0) {
+                               $this->getCallbackClient()->show('user_window_username_exists');
+                               return;
+                       }
+               }
+
+               $config = [];
+               $config['long_name'] = $this->UserLongName->Text;
+               $config['description'] = $this->UserDescription->Text;
+               $config['email'] = $this->UserEmail->Text;
+
+               $selected_indices = $this->UserRoles->getSelectedIndices();
+               $roles = [];
+               foreach ($selected_indices as $indice) {
+                       for ($i = 0; $i < $this->UserRoles->getItemCount(); $i++) {
+                               if ($i === $indice) {
+                                       $roles[] = $this->UserRoles->Items[$i]->Value;
+                               }
+                       }
+               }
+               $config['roles'] = implode(',', $roles);
+               $config['api_hosts'] = $this->UserAPIHost->SelectedValue;
+               $config['ips'] = $this->trimIps($this->UserIps->Text);
+               $config['enabled'] = $this->UserEnabled->Checked ? 1 : 0;
+               $result = $this->getModule('user_config')->setUserConfig($username, $config);
+
+               // Set password if auth method supports it
+               if ($result === true && !empty($this->UserPassword->Text) && $this->isManageUsersAvail()) {
+                       // Set Basic auth users password
+                       if ($this->getModule('web_config')->isAuthMethodBasic() &&
+                               isset($this->web_config['auth_basic']['user_file'])) {
+
+                               $opts = [];
+                               if (isset($this->web_config['auth_basic']['hash_alg'])) {
+                                       $opts['hash_alg'] = $this->web_config['auth_basic']['hash_alg'];
+                               }
+
+                               // Setting basic users works both for adding and editing users
+                               $basic = $this->getModule('basic_webuser');
+                               $basic->setUsersConfig(
+                                       $username,
+                                       $this->UserPassword->Text,
+                                       false,
+                                       null,
+                                       $opts
+                               );
+                       }
+               }
+
+               $this->setUserList(null, null);
+               $this->setRoleList(null, null);
+               $this->getCallbackClient()->callClientFunction('oUsers.save_user_cb');
+       }
+
+       /**
+        * Remove users action.
+        * Here is possible to remove one user or many.
+        * This action is linked with table bulk actions.
+        *
+        * @param TCallback $sender sender object
+        * @param TCallbackEventParameter $param callback parameter
+        * @return none
+        */
+       public function removeUsers($sender, $param) {
+               $usernames = explode('|', $param->getCallbackParameter());
+               $config = $this->getModule('user_config')->getConfig();
+               for ($i = 0; $i < count($usernames); $i++) {
+                       if (key_exists($usernames[$i], $config)) {
+                               unset($config[$usernames[$i]]);
+                       }
+               }
+               $result = $this->getModule('user_config')->setConfig($config);
+
+               if ($result === true && $this->isManageUsersAvail() &&
+                       $this->getModule('web_config')->isAuthMethodBasic() &&
+                       isset($this->web_config['auth_basic']['user_file'])) {
+                       // Remove basic auth users too
+                       $basic = $this->getModule('basic_webuser');
+                       $basic->removeUsers($usernames);
+               }
+
+               // refresh user list
+               $this->setUserList(null, null);
+
+               // refresh role list
+               $this->setRoleList(null, null);
+       }
+
+       /**
+        * Initialize values in role modal window.
+        *
+        * @return none
+        */
+       public function initRoleWindow() {
+               // set role resources
+               $resources = $this->getModule('page_category')->getCategories(false);
+               $this->RoleResources->DataSource = array_combine($resources, $resources);
+               $this->RoleResources->dataBind();
+       }
+
+       /**
+        * Set and load role list.
+        *
+        * @param TCallback $sender sender object
+        * @param TCallbackEventParameter callback parameter
+        * @return none
+        */
+       public function setRoleList($sender, $param) {
+               $config = $this->getModule('user_role')->getRoles();
+               $this->addUserStatsToRoles($config);
+               $this->getCallbackClient()->callClientFunction('oRoles.load_role_list_cb', [
+                       array_values($config)
+               ]);
+       }
+
+       /**
+        * Load data in role modal window.
+        *
+        * @param TCallback $sender sender object
+        * @param TCallbackEventParameter $param callback parameter
+        * @return none
+        */
+       public function loadRoleWindow($sender, $param) {
+               $role = $param->getCallbackParameter();
+               $config = $this->getModule('user_role')->getRole($role);
+               if (count($config) > 0) {
+                       // Edit role window
+                       $this->Role->Text = $config['role'];
+                       $this->RoleLongName->Text = $config['long_name'];
+                       $this->RoleDescription->Text = $config['description'];
+                       $selected_indices = [];
+                       $resources = explode(',', $config['resources']);
+                       for ($i = 0; $i < $this->RoleResources->getItemCount(); $i++) {
+                               if (in_array($this->RoleResources->Items[$i]->Value, $resources)) {
+                                       $selected_indices[] = $i;
+                               }
+                       }
+                       $this->RoleResources->setSelectedIndices($selected_indices);
+                       $this->RoleEnabled->Checked = ($config['enabled'] == 1);
+                       if ($this->getModule('user_role')->isRolePreDefined($role)) {
+                               $this->RoleSave->Display = 'None';
+                               $this->PreDefinedRoleMsg->Display = 'Dynamic';
+                       } else {
+                               $this->RoleSave->Display = 'Dynamic';
+                               $this->PreDefinedRoleMsg->Display = 'None';
+                       }
+               } else {
+                       // New role window
+                       $this->RoleSave->Display = 'Dynamic';
+                       $this->PreDefinedRoleMsg->Display = 'None';
+               }
+       }
+
+       /**
+        * Save role.
+        * It works both for new roles and for edited roles.
+        * Saves values from modal popup.
+        *
+        * @param TCallback $sender sender object
+        * @param TCallbackEventParameter $param callback parameter
+        * @return none
+        */
+       public function saveRole($sender, $param) {
+               $role_win_type = $this->RoleWindowType->Value;
+               $role = $this->Role->Text;
+               $this->getCallbackClient()->hide('role_window_role_exists');
+               if ($role_win_type === self::TYPE_ADD_WINDOW) {
+                       $config = $this->getModule('user_role')->getRole($role);
+                       if (count($config) > 0) {
+                               $this->getCallbackClient()->show('role_window_role_exists');
+                               return;
+                       }
+               }
+               if ($this->getModule('user_role')->isRolePreDefined($role)) {
+                       // Predefined roles cannot be saved
+                       return;
+               }
+               $config = [];
+               $config['long_name'] = $this->RoleLongName->Text;
+               $config['description'] = $this->RoleDescription->Text;
+
+               $selected_indices = $this->RoleResources->getSelectedIndices();
+               $resources = [];
+               foreach ($selected_indices as $indice) {
+                       for ($i = 0; $i < $this->RoleResources->getItemCount(); $i++) {
+                               if ($i === $indice) {
+                                       $resources[] = $this->RoleResources->Items[$i]->Value;
+                               }
+                       }
+               }
+               $config['resources'] = implode(',', $resources);
+               $config['enabled'] = $this->RoleEnabled->Checked ? 1 : 0;
+               $this->getModule('role_config')->setRoleConfig($role, $config);
+               $this->setRoleList(null, null);
+               if ($role_win_type === self::TYPE_ADD_WINDOW) {
+                       // refresh user window for new role
+                       $this->initUserWindow();
+               }
+               $this->getCallbackClient()->callClientFunction('oRoles.save_role_cb');
+       }
+
+       /**
+        * Remove roles action.
+        * Here is possible to remove one role or many.
+        * This action is linked with table bulk actions.
+        *
+        * @param TCallback $sender sender object
+        * @param TCallbackEventParameter $param callback parameter
+        * @return none
+        */
+       public function removeRoles($sender, $param) {
+               $roles = explode('|', $param->getCallbackParameter());
+               $config = $this->getModule('role_config')->getConfig();
+               $user_role = $this->getModule('user_role');
+               for ($i = 0; $i < count($roles); $i++) {
+                       if (key_exists($roles[$i], $config)) {
+                               if ($user_role->isRolePreDefined($roles[$i])) {
+                                       // Predefined roles cannot be saved
+                                       continue;
+                               }
+                               unset($config[$roles[$i]]);
+                       }
+               }
+               $this->getModule('role_config')->setConfig($config);
+               $this->setRoleList(null, null);
+               // refresh user window to now show removed roles
+               $this->initUserWindow();
+       }
+
+       /**
+        * Add user statistics to roles.
+        * It adds user count to information about roles.
+        *
+        * @param array $role_config role config (note, passing by reference)
+        * @return none
+        */
+       private function addUserStatsToRoles(&$role_config) {
+               $config = [];
+               if (count($this->user_config) > 0) {
+                       $config = $this->user_config;
+               } else {
+                       $config = $this->getModule('user_config')->getConfig();
+               }
+               $user_roles = [];
+               foreach ($role_config as $role => $prop) {
+                       $user_roles[$role] = 0;
+               }
+               foreach ($config as $username => $prop) {
+                       $roles = explode(',', $prop['roles']);
+                       for ($i = 0; $i < count($roles); $i++) {
+                               $user_roles[$roles[$i]]++;
+                       }
+               }
+               foreach ($role_config as $role => $prop) {
+                       $role_config[$role]['user_count'] = $user_roles[$role];
+               }
+       }
+
+       /**
+        * Set basic authentication user file.
+        *
+        * @return none
+        */
+       private function setBasicAuthConfig() {
+               if ($this->isManageUsersAvail() && isset($this->web_config['auth_basic']['user_file'])) {
+                       $this->getModule('basic_webuser')->setConfigPath($this->web_config['auth_basic']['user_file']);
+               }
+       }
+
+       /**
+        * Get basic users and provide them to template.
+        *
+        * @param TActiveLinkButton $sender sender
+        * @param TCommandEventParameter $param event parameter object
+        * @return none
+        */
+       public function getBasicUsers($sender, $param) {
+               if ($param instanceof Prado\Web\UI\TCommandEventParameter && $param->getCommandParameter() === 'load') {
+                       // reset criteria filters when modal is open
+                       $this->GetUsersImportOptions->SelectedValue = self::IMPORT_OPT_ALL_USERS;
+                       $this->GetUsersCriteria->SelectedValue = self::IMPORT_CRIT_USERNAME;
+                       $this->GetUsersCriteriaFilter->Text = '';
+                       $this->getCallbackClient()->hide('get_users_criteria');
+                       $this->getCallbackClient()->hide('get_users_advanced_options');
+
+                       // set role resources
+                       $this->setRoles($this->GetUsersDefaultRole, WebUserRoles::NORMAL);
+
+                       // set API hosts
+                       $this->setAPIHosts($this->GetUsersDefaultAPIHost, HostConfig::MAIN_CATALOG_HOST);
+               }
+
+               $params = $this->getBasicParams();
+
+               // add additional parameters
+               $this->addBasicExtraParams($params);
+
+               $pattern = '';
+               if (!empty($params['filter_val'])) {
+                       $pattern = '*' . $params['filter_val'] . '*';
+               }
+
+               $basic = $this->getModule('basic_webuser');
+               // set path from input because user can have unsaved changes
+               $basic->setConfigPath($this->BasicAuthUserFile->Text);
+               $users = $basic->getUsers($pattern);
+               $users = array_keys($users);
+               $user_list = $this->convertBasicUsers($users);
+               $this->getCallbackClient()->callClientFunction('oUserSecurity.set_user_table_cb', [
+                       $user_list
+               ]);
+               if (count($users) > 0) {
+                       // Success
+                       $this->TestBasicGetUsersMsg->Text = '';
+                       $this->TestBasicGetUsersMsg->Display = 'None';
+                       $this->getCallbackClient()->hide('basic_get_users_error');
+                       $this->getCallbackClient()->show('basic_get_users_ok');
+               } else {
+                       // Error
+                       $this->getCallbackClient()->show('basic_get_users_error');
+                       $this->TestBasicGetUsersMsg->Text = Prado::localize('Empty user list');
+                       $this->TestBasicGetUsersMsg->Display = 'Dynamic';
+               }
+       }
+
+       /**
+        * Convert basic users from simple username list into full form.
+        * There is option to return user list in config file form or data table form.
+        *
+        * @param array $users simple user list
+        * @param boolean $config_form_result if true, sets the list in config file form
+        * @return array user list
+        */
+       private function convertBasicUsers(array $users, $config_form_result = false) {
+               $user_list = [];
+               for ($i = 0; $i < count($users); $i++) {
+                       $user = [
+                               'username' => $users[$i],
+                               'long_name' => '',
+                               'email' => '',
+                               'description' => ''
+                       ];
+                       if ($config_form_result) {
+                               $user_list[$users[$i]] = $user;
+                       } else {
+                               $user_list[] = $user;
+                       }
+               }
+               return $user_list;
+       }
+
+       /**
+        * Get basic auth specific parameters with form values.
+        *
+        * @return array array basic auth parameters
+        */
+       private function getBasicParams() {
+               $params = [];
+               $params['allow_manage_users'] = $this->BasicAuthAllowManageUsers->Checked ? 1 : 0;
+               $params['user_file'] = $this->BasicAuthUserFile->Text;
+               $params['hash_alg'] = $this->BasicAuthHashAlgorithm->SelectedValue;
+               return $params;
+       }
+
+       /**
+        * Add to basic auth params additional parameters.
+        * Note, extra parameters are not set in config.
+        *
+        * @param array $params basic auth parameters (passing by reference)
+        * @return none
+        */
+       private function addBasicExtraParams(&$params) {
+               if ($this->GetUsersImportOptions->SelectedValue == self::IMPORT_OPT_CRITERIA) {
+                       $params['filter_val'] = $this->GetUsersCriteriaFilter->Text;
+               }
+       }
+
+       /**
+        * Prepare basic users to import.
+        *
+        * @param TCallback $sender sender object
+        * @param TCallbackEventParameter $param event parameter object
+        * @return array web users list to import
+        */
+       public function prepareBasicUsers($sender, $param) {
+               $users_web = [];
+               $import_opt = (integer)$this->GetUsersImportOptions->SelectedValue;
+               $basic_webuser = $this->getModule('basic_webuser');
+               switch ($import_opt) {
+                       case self::IMPORT_OPT_ALL_USERS: {
+                               $users_web = $basic_webuser->getUsers();
+                               $users_web = array_keys($users_web);
+                               $users_web = $this->convertBasicUsers($users_web, true);
+                               break;
+                       }
+                       case self::IMPORT_OPT_SELECTED_USERS: {
+                               if ($param instanceof Prado\Web\UI\ActiveControls\TCallbackEventParameter) {
+                                       $cb_param = $param->getCallbackParameter();
+                                       if (is_array($cb_param)) {
+                                               for ($i = 0; $i < count($cb_param); $i++) {
+                                                       $val = (array)$cb_param[$i];
+                                                       $users_web[$val['username']] = $val;
+                                               }
+                                       }
+                               }
+                               break;
+                       }
+                       case self::IMPORT_OPT_CRITERIA: {
+                               $params = $this->getBasicParams();
+                               // add additional parameters
+                               $this->addBasicExtraParams($params);
+                               if (!empty($params['filter_val'])) {
+                                       $pattern = '*' . $params['filter_val'] . '*';
+                                       $users_web = $basic_webuser->getUsers($pattern);
+                                       $users_web = array_keys($users_web);
+                                       $users_web = $this->convertBasicUsers($users_web, true);
+
+                               }
+                               break;
+                       }
+               }
+               return $users_web;
+       }
+
+       /**
+        * Test basic user file.
+        *
+        * @param TActiveLinkButton $sender sender object
+        * @param TCallbackEventParameter $param event parameter object
+        * @return none
+        */
+       public function doBasicUserFileTest($sender, $param) {
+               $user_file = $this->BasicAuthUserFile->Text;
+               $msg = '';
+               $valid = true;
+               if (!file_exists($user_file)) {
+                       $valid = false;
+                       $msg = Prado::localize('The user file is not accessible.');
+               } else if (!is_readable($user_file)) {
+                       $valid = false;
+                       $msg = Prado::localize('The user file is not readable by web server user.');
+               } else if (!is_writeable($user_file)) {
+                       $valid = false;
+                       $msg = Prado::localize('The user file is readable but not writeable by web server user.');
+               }
+               $this->BasicAuthUserFileMsg->Text = $msg;
+               if ($valid) {
+                       $this->getCallbackClient()->show('basic_auth_user_file_test_ok');
+                       $this->BasicAuthUserFileMsg->Display = 'None';
+               } else {
+                       $this->getCallbackClient()->show('basic_auth_user_file_test_error');
+                       $this->BasicAuthUserFileMsg->Display = 'Dynamic';
+               }
+       }
+
+       /**
+        * Get LDAP users and provide them to template.
+        *
+        * @param TActiveLinkButton $sender sender
+        * @param TCommandEventParameter $param event parameter object
+        * @return none
+        */
+       public function getLdapUsers($sender, $param) {
+               if ($param instanceof Prado\Web\UI\TCommandEventParameter && $param->getCommandParameter() === 'load') {
+                       // reset criteria filters when modal is open
+                       $this->GetUsersImportOptions->SelectedValue = self::IMPORT_OPT_ALL_USERS;
+                       $this->GetUsersCriteria->SelectedValue = self::IMPORT_CRIT_USERNAME;
+                       $this->GetUsersCriteriaFilter->Text = '';
+                       $this->getCallbackClient()->hide('get_users_criteria');
+                       $this->getCallbackClient()->hide('get_users_advanced_options');
+
+                       // set role resources
+                       $this->setRoles($this->GetUsersDefaultRole, WebUserRoles::NORMAL);
+
+                       // set API hosts
+                       $this->setAPIHosts($this->GetUsersDefaultAPIHost, HostConfig::MAIN_CATALOG_HOST);
+               }
+
+               $ldap = $this->getModule('ldap');
+               $params = $this->getLdapParams();
+               $ldap->setParams($params);
+
+               // add additional parameters
+               $this->addLdapExtraParams($params);
+
+               $filter = $ldap->getFilter($params['user_attr'], '*');
+               if (!empty($params['filter_attr']) && !empty($params['filter_val'])) {
+                       $filter = $ldap->getFilter(
+                               $params['filter_attr'],
+                               '*' . $params['filter_val'] . '*'
+                       );
+               }
+
+               $users = $ldap->findUserAttr($filter, $params['attrs']);
+               $user_list = $this->convertLdapUsers($users, $params);
+               $this->getCallbackClient()->callClientFunction('oUserSecurity.set_user_table_cb', [
+                       $user_list
+               ]);
+
+               if (key_exists('count', $users)) {
+                       // Success
+                       $this->TestLdapGetUsersMsg->Text = '';
+                       $this->TestLdapGetUsersMsg->Display = 'None';
+                       $this->getCallbackClient()->show('ldap_get_users_ok');
+               } else {
+                       // Error
+                       $this->getCallbackClient()->show('ldap_get_users_error');
+                       $this->TestLdapGetUsersMsg->Text = $ldap->getLdapError();
+                       $this->TestLdapGetUsersMsg->Display = 'Dynamic';
+               }
+       }
+
+       /**
+        * Convert LDAP users from simple username list into full form.
+        * There is option to return user list in config file form or data table form.
+        *
+        * @param array $users simple user list
+        * @param array $params LDAP specific parameters (@see getLdapParams)
+        * @param boolean $config_form_result if true, sets the list in config file form
+        * @return array user list
+        */
+       private function convertLdapUsers(array $users, array $params, $config_form_result = false) {
+               $user_list = [];
+               for ($i = 0; $i < $users['count']; $i++) {
+                       if (!key_exists($params['user_attr'], $users[$i])) {
+                               $emsg = "User attribute '{$params['user_attr']}' doesn't exist in LDAP response.";
+                               $this->getModule('logging')->log(
+                                       __FUNCTION__,
+                                       $emsg,
+                                       Logging::CATEGORY_EXTERNAL,
+                                       __FILE__,
+                                       __LINE__
+                               );
+                               continue;
+                       }
+                       $username = $long_name = $email = $desc = '';
+                       if ($params['user_attr'] !== Ldap::DN_ATTR && $users[$i][$params['user_attr']]['count'] != 1) {
+                               $emsg = "Invalid user attribute count for '{$params['user_attr']}'. Is {$users[$i][$params['user_attr']]['count']}, should be 1.";
+                               $this->getModule('logging')->log(
+                                       __FUNCTION__,
+                                       $emsg,
+                                       Logging::CATEGORY_EXTERNAL,
+                                       __FILE__,
+                                       __LINE__
+                               );
+                               continue;
+
+                       }
+                       $username = $users[$i][$params['user_attr']];
+                       if ($params['user_attr'] !== Ldap::DN_ATTR) {
+                               $username = $users[$i][$params['user_attr']][0];
+                       }
+
+                       if (key_exists($params['long_name_attr'], $users[$i])) {
+                               if ($params['long_name_attr'] === Ldap::DN_ATTR) {
+                                       $long_name = $users[$i][$params['long_name_attr']];
+                               } else if($users[$i][$params['long_name_attr']]['count'] === 1) {
+                                       $long_name = $users[$i][$params['long_name_attr']][0];
+                               }
+                       }
+
+                       if (key_exists($params['email_attr'], $users[$i])) {
+                               if ($params['email_attr'] === Ldap::DN_ATTR) {
+                                       $email = $users[$i][$params['email_attr']];
+                               } else if ($users[$i][$params['email_attr']]['count'] === 1) {
+                                       $email = $users[$i][$params['email_attr']][0];
+                               }
+                       }
+
+                       if (key_exists($params['desc_attr'], $users[$i])) {
+                               if ($params['desc_attr'] === Ldap::DN_ATTR) {
+                                       $desc = $users[$i][$params['desc_attr']];
+                               } else if ($users[$i][$params['desc_attr']]['count'] === 1) {
+                                       $desc = $users[$i][$params['desc_attr']][0];
+                               }
+                       }
+
+                       if ($config_form_result) {
+                               $user_list[$username] = [
+                                       'long_name' => $long_name,
+                                       'email' => $email,
+                                       'description' => $desc
+                               ];
+                       } else {
+                               $user_list[] = [
+                                       'username' => $username,
+                                       'long_name' => $long_name,
+                                       'email' => $email,
+                                       'description' => $desc
+                               ];
+                       }
+               }
+               return $user_list;
+       }
+
+
+       /**
+        * Get LDAP auth specific parameters with form values.
+        *
+        * @return array array LDAP auth parameters
+        */
+       private function getLdapParams() {
+               $params = [];
+               $params['address'] = $this->LdapAuthServerAddress->Text;
+               $params['port'] = $this->LdapAuthServerPort->Text;
+               $params['ldaps'] = $this->LdapAuthServerLdaps->Checked ?  1 : 0;
+               $params['protocol_ver'] = $this->LdapAuthServerProtocolVersion->SelectedValue;
+               $params['base_dn'] = $this->LdapAuthServerBaseDn->Text;
+               if ($this->LdapAuthMethodAnonymous->Checked) {
+                       $params['auth_method'] = Ldap::AUTH_METHOD_ANON;
+               } elseif ($this->LdapAuthMethodSimple->Checked) {
+                       $params['auth_method'] = Ldap::AUTH_METHOD_SIMPLE;
+               }
+               $params['bind_dn'] = $this->LdapAuthMethodSimpleUsername->Text;
+               $params['bind_password'] = $this->LdapAuthMethodSimplePassword->Text;
+               $params['user_attr'] = $this->LdapAttributesUsername->Text;
+               $params['long_name_attr'] = $this->LdapAttributesLongName->Text;
+               $params['desc_attr'] = $this->LdapAttributesDescription->Text;
+               $params['email_attr'] = $this->LdapAttributesEmail->Text;
+               return $params;
+       }
+
+       /**
+        * Add to LDAP auth params additional parameters.
+        * Note, extra parameters are not set in config.
+        *
+        * @param array $params LDAP auth parameters (passing by reference)
+        * @return none
+        */
+       private function addLdapExtraParams(&$params) {
+               $params['attrs'] = [$params['user_attr']]; // user attribute is obligatory
+               if (key_exists('long_name_attr', $params) && !empty($params['long_name_attr'])) {
+                       $params['attrs'][] = $params['long_name_attr'];
+               }
+               if (key_exists('email_attr', $params) && !empty($params['email_attr'])) {
+                       $params['attrs'][] = $params['email_attr'];
+               }
+               if (key_exists('desc_attr', $params) && !empty($params['desc_attr'])) {
+                       $params['attrs'][] = $params['desc_attr'];
+               }
+               if ($this->GetUsersImportOptions->SelectedValue == self::IMPORT_OPT_CRITERIA) {
+                       $crit = intval($this->GetUsersCriteria->SelectedValue);
+                       switch ($crit) {
+                               case self::IMPORT_CRIT_USERNAME: $params['filter_attr'] = $params['user_attr']; break;
+                               case self::IMPORT_CRIT_LONG_NAME: $params['filter_attr'] = $params['long_name_attr']; break;
+                               case self::IMPORT_CRIT_DESCRIPTION: $params['filter_attr'] = $params['desc_attr']; break;
+                               case self::IMPORT_CRIT_EMAIL: $params['filter_attr'] = $params['email_attr']; break;
+                       }
+                       $params['filter_val'] = $this->GetUsersCriteriaFilter->Text;
+               }
+       }
+
+
+       /**
+        * Prepare LDAP users to import.
+        *
+        * @param TCallback $sender sender object
+        * @param TCallbackEventParameter $param event parameter object
+        * @return array web users list to import
+        */
+       private function prepareLdapUsers($sender, $param) {
+               $ldap = $this->getModule('ldap');
+               $params = $this->getLdapParams();
+               $ldap->setParams($params);
+
+               // add additional parameters
+               $this->addLdapExtraParams($params);
+
+               $import_opt = (integer)$this->GetUsersImportOptions->SelectedValue;
+
+               $users_web = [];
+               switch ($import_opt) {
+                       case self::IMPORT_OPT_ALL_USERS: {
+                               $filter = $ldap->getFilter($params['user_attr'], '*');
+                               $users_ldap = $ldap->findUserAttr($filter, $params['attrs']);
+                               $users_web = $this->convertLdapUsers($users_ldap, $params, true);
+                               break;
+                       }
+                       case self::IMPORT_OPT_SELECTED_USERS: {
+                               if ($param instanceof Prado\Web\UI\ActiveControls\TCallbackEventParameter) {
+                                       $cb_param = $param->getCallbackParameter();
+                                       if (is_array($cb_param)) {
+                                               for ($i = 0; $i < count($cb_param); $i++) {
+                                                       $val = (array)$cb_param[$i];
+                                                       $users_web[$val['username']] = $val;
+                                                       unset($users_web[$val['username']]['username']);
+                                               }
+                                       }
+                               }
+                               break;
+                       }
+                       case self::IMPORT_OPT_CRITERIA: {
+                               if (!empty($params['filter_attr']) && !empty($params['filter_val'])) {
+                                       $filter = $ldap->getFilter(
+                                               $params['filter_attr'],
+                                               '*' . $params['filter_val'] . '*'
+                                       );
+                                       $users_ldap = $ldap->findUserAttr($filter, $params['attrs']);
+                                       $users_web = $this->convertLdapUsers($users_ldap, $params, true);
+
+                               }
+                               break;
+                       }
+               }
+               return $users_web;
+       }
+
+       /**
+        * Test LDAP connection.
+        *
+        * @param TActiveLinkButton $sender sender object
+        * @param TCallbackEventParameter $param event object parameter
+        * @return none
+        */
+       public function testLdapConnection($sender, $param) {
+               $ldap = $this->getModule('ldap');
+               $params = $this->getLdapParams();
+               $ldap->setParams($params);
+
+               if ($ldap->adminBind()) {
+                       $this->TestLdapConnectionMsg->Text = '';
+                       $this->TestLdapConnectionMsg->Display = 'None';
+                       $this->getCallbackClient()->show('ldap_test_connection_ok');
+               } else {
+                       $this->getCallbackClient()->show('ldap_test_connection_error');
+                       $this->TestLdapConnectionMsg->Text = $ldap->getLdapError();
+                       $this->TestLdapConnectionMsg->Display = 'Dynamic';
+               }
+       }
+
+       /**
+        * Main method to import users.
+        * Supported are basic auth and LDAP auth user imports.
+        *
+        * @param TActiveLinkButton $sender sender object
+        * @param TCallbackEventParameter $param event object parameter
+        * @return none
+        */
+       public function importUsers($sender, $param) {
+               if (!$this->GetUsersDefaultIps->IsValid) {
+                       // invalid IP restriction value
+                       return;
+               }
+
+               $users_web = [];
+               if ($this->BasicAuth->Checked) {
+                       $users_web = $this->prepareBasicUsers($sender, $param);
+               } elseif ($this->LdapAuth->Checked) {
+                       $users_web = $this->prepareLdapUsers($sender, $param);
+               }
+
+               // Get default roles for imported users
+               $def_roles = $this->GetUsersDefaultRole->getSelectedIndices();
+               $role_list = [];
+               foreach ($def_roles as $indice) {
+                       for ($i = 0; $i < $this->GetUsersDefaultRole->getItemCount(); $i++) {
+                               if ($i === $indice) {
+                                       $role_list[] = $this->GetUsersDefaultRole->Items[$i]->Value;
+                               }
+                       }
+               }
+               $roles = implode(',', $role_list);
+
+               // Get default API hosts for imported users
+               $api_hosts = $this->GetUsersDefaultAPIHost->SelectedValue;
+
+               // Get default IP address restrictions for imported users
+               $ips = $this->trimIps($this->GetUsersDefaultIps->Text);
+
+               // fill missing default values
+               $add_def_user_params = function (&$user, $idx) use ($roles, $api_hosts, $ips) {
+                       $user['roles'] = $roles;
+                       $user['api_hosts'] = $api_hosts;
+                       $user['ips'] = $ips;
+                       $user['enabled'] = '1';
+               };
+               array_walk($users_web, $add_def_user_params);
+
+               $user_mod = $this->getModule('user_config');
+               $users = $user_mod->getConfig();
+
+               $users_cfg = [];
+               if ($this->GetUsersProtectOverwrite->Checked) {
+                       $users_cfg = array_merge($users_web, $users);
+               } else {
+                       $users_cfg = array_merge($users, $users_web);
+               }
+               $user_mod->setConfig($users_cfg);
+
+               // refresh user list
+               $this->setUserList(null, null);
+
+               // refresh role list
+               $this->setRoleList(null, null);
+
+               $this->getCallbackClient()->callClientFunction('oUserSecurity.show_user_modal', [
+                       false
+               ]);
+       }
+
+       /**
+        * Get users and provide them to template.
+        *
+        * @param TActiveLinkButton $sender sender object
+        * @param TCallbackEventParameter $param event object parameter
+        * @return none
+        */
+       public function getUsers($sender, $param) {
+               if ($this->BasicAuth->Checked) {
+                       $this->getBasicUsers($sender, $param);
+               } elseif ($this->LdapAuth->Checked) {
+                       $this->getLdapUsers($sender, $param);
+               }
+       }
+
+       /**
+        * Save security config.
+        *
+        * @param TActiveLinkButton $sender sender object
+        * @param TCallbackEventParameter $param event object parameter
+        * @return none
+        */
+       public function saveSecurityConfig($sender, $param) {
+               $config = $this->web_config;
+               if (!key_exists('security', $config)) {
+                       $config['security'] = [];
+               }
+               if ($this->GeneralDefaultNoAccess->Checked) {
+                       $config['security']['def_access'] = WebConfig::DEF_ACCESS_NO_ACCESS;
+               } elseif ($this->GeneralDefaultAccess->Checked) {
+                       $config['security']['def_access'] = WebConfig::DEF_ACCESS_DEFAULT_SETTINGS;
+                       $config['security']['def_role'] = $this->GeneralDefaultAccessRole->SelectedValue;
+                       $config['security']['def_api_host'] = $this->GeneralDefaultAccessAPIHost->SelectedValue;
+               }
+               if ($this->BasicAuth->Checked) {
+                       $config['security']['auth_method'] = WebConfig::AUTH_METHOD_BASIC;
+                       $config['auth_basic'] = $this->getBasicParams();
+               } else if ($this->LdapAuth->Checked) {
+                       $config['security']['auth_method'] = WebConfig::AUTH_METHOD_LDAP;
+                       $config['auth_ldap'] = $this->getLdapParams();
+               }
+               $ret = $this->getModule('web_config')->setConfig($config);
+               if ($ret === true) {
+                       $this->getCallbackClient()->hide('auth_method_save_error');
+                       $this->getCallbackClient()->show('auth_method_save_ok');
+               } else {
+                       $this->getCallbackClient()->hide('auth_method_save_ok');
+                       $this->getCallbackClient()->show('auth_method_save_error');
+               }
+       }
+
+       /**
+        * Determines if user management is enabled.
+        * This checking bases on selected auth method and permission to manage users.
+        *
+        * @return boolean true if managing users is enabled, otherwise false
+        */
+       private function isManageUsersAvail() {
+               $is_basic = $this->getModule('web_config')->isAuthMethodBasic();
+               $allow_manage_users = (isset($this->web_config['auth_basic']['allow_manage_users']) &&
+                       $this->web_config['auth_basic']['allow_manage_users'] == 1);
+               return ($is_basic && $allow_manage_users);
+       }
+
+       /**
+        * Validate IP restriction address value.
+        *
+        * @param TActiveCustomValidator $sender sender object
+        * @param TServerValidateEventParameter $param event object parameter
+        * @return none
+        */
+       public function validateIps($sender, $param) {
+               $valid = true;
+               $val = trim($param->Value);
+               if (!empty($val)) {
+                       $ips = explode(',', $val);
+                       for ($i = 0; $i < count($ips); $i++) {
+                               $ip = trim($ips[$i]);
+                               if (!filter_var($ip, FILTER_VALIDATE_IP) && !(strpos($ip, '*') !== false && preg_match('/^[\da-f:.*]+$/i', $ip) === 1)) {
+                                       $valid = false;
+                                       break;
+                               }
+                       }
+               }
+               $param->IsValid = $valid;
+       }
+
+       /**
+        * Simple helper that trims IP restriction address values.
+        *
+        * @param string $ips IP restriction address values
+        * @return string trimmed addresses
+        */
+       public function trimIps($ips) {
+               $ips = trim($ips);
+               if (!empty($ips)) {
+                       $ips = explode(',', $ips);
+                       $ips = array_map('trim', $ips);
+                       $ips = implode(',', $ips);
+               }
+               return $ips;
+       }
+}
+?>
index e36e82408f60c481bf5e3a6d4ee02c6faeba6ff3..a6ee4ef78e63573725d15f54497e259663004aaa 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -33,8 +33,6 @@ class StatisticsList extends BaculumWebPage {
 
        const USE_CACHE = true;
 
-       protected $admin = true;
-
        public $statistics = array();
 
        public function onInit($param) {
index 51abd690c669551a8c4ac44b898c6a14f7fe409c..a8234a77e097a7894457a8ad0799bf37bdf13cbd 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -34,8 +34,6 @@ class StatisticsView extends BaculumWebPage {
        const COMPONENT_TYPE = 'ComponentType';
        const STATISTICS_NAME = 'StatisticsName';
 
-       protected $admin = true;
-
        public function onInit($param) {
                parent::onInit($param);
                if ($this->IsPostBack || $this->IsCallBack) {
index 3aa29e31705e42eb1c15e0aca8c618b1ea85b583..67bae0fb9e70875a3be04d3949f65f9a0d9f29e8 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -33,8 +33,6 @@ class StorageList extends BaculumWebPage {
 
        const USE_CACHE = true;
 
-       protected $admin = true;
-
        public $storages;
 
        public function onInit($param) {
index 3613f39cd22e558b59410e23d50f4e462619027a..45bb7c71f61d6d3f0ac4d0230237c2865effa6cc 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -43,8 +43,6 @@ class StorageView extends BaculumWebPage {
 
        const USE_CACHE = true;
 
-       protected $admin = true;
-
        public function onInit($param) {
                parent::onInit($param);
                if ($this->IsPostBack || $this->IsCallBack) {
diff --git a/gui/baculum/protected/Web/Pages/Users.page b/gui/baculum/protected/Web/Pages/Users.page
deleted file mode 100644 (file)
index 13f85fa..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-<%@ MasterClass="Application.Web.Layouts.Main" Theme="Baculum-v2"%>
-<com:TContent ID="Main">
-       <!-- Header -->
-       <header class="w3-container">
-               <h5>
-                       <b><i class="fa fa-users"></i> <%[ Users ]%></b>
-               </h5>
-       </header>
-       <div class="w3-panel">
-               <a class="w3-button w3-green" href="javascript:void(0)" id="add_user_btn"><i class="fa fa-plus"></i> &nbsp;<%[ Add new user ]%></a>
-       </div>
-<div id="add_user" style="display: none" class="w3-margin">
-       <div class="w3-third">
-               <div class="w3-quarter">
-                       <p><%[ Username: ]%></p>
-               </div>
-               <div class="w3-rest w3-margin-right">
-                       <input id="newuser" type="text" class="w3-input w3-border" />
-               </div>
-       </div>
-       <div class="w3-third">
-               <div class="w3-quarter">
-                       <p><%[ Password: ]%></p>
-               </div>
-               <div class="w3-rest w3-margin-right">
-                       <input id="newpwd" type="password" class="w3-input w3-border w3-margin-right" />
-               </div>
-       </div>
-       <div class="w3-third">
-               <a href="javascript:void(0)" onclick="Users.addUser()" class="w3-button w3-green">
-                       <i class="fa fa-save"></i> &nbsp;<%[ Save ]%>
-               </a>
-               <a href="javascript:void(0)" onclick="Users.cancelAddUser()" class="w3-button w3-red">
-                       <i class="fa fa-times"></i> &nbsp;<%[ Close ]%>
-               </a>
-       </div>
-</div>
-<com:TActiveRepeater ID="UsersList" OnLoad="setUsers" ActiveControl.EnableUpdate="false">
-       <prop:HeaderTemplate>
-       <table id="users_list" class="w3-table w3-striped w3-hoverable w3-white w3-margin-bottom">
-               <thead>
-                       <tr>
-                               <th><%[ User name ]%></th>
-                               <th><%[ Role ]%></th>
-                               <th><%[ API host ]%></th>
-                               <th><%[ Actions ]%></th>
-                       </tr>
-               </thead>
-       </prop:HeaderTemplate>
-       <prop:ItemTemplate>
-               <tr class="slide-window-element">
-                       <td><%=$this->Data['user']%></td>
-                       <td><%=$this->Data['admin'] ? Prado::localize('Administrator') :  Prado::localize('Normal user')%></td>
-                       <td>
-                               <com:TPanel Visible="<%=$this->Data['admin']%>" Style="line-height: 29px">
-                                       Main
-                               </com:TPanel>
-                               <com:TPanel Visible="<%=!$this->Data['admin']%>">
-                                               <select rel="user_host" onchange="Users.set_host('<%=$this->Data['user']%>', this);" class="w3-select w3-border" style="width: 85%; float: left;">
-                                                       <com:TRepeater OnInit="SourceTemplateControl.initHosts">
-                                                               <prop:HeaderTemplate>
-                                                       <option value=""><%[ Select host ]%></option>
-                                                               </prop:HeaderTemplate>
-                                                               <prop:ItemTemplate>
-                                                       <option value="<%=$this->Data%>" <%=$this->SourceTemplateControl->isSelectedHost($this->Parent->Parent->Parent->Data['user'], $this->Data) ? 'selected' : ''%>><%=$this->Data%></option>
-                                                               </prop:ItemTemplate>
-                                                       </com:TRepeater>
-                                               </select>
-                                               &nbsp;<i class="fa fa-sync w3-spin" rel="user_host_img" style="visibility: hidden" />
-                               </com:TPanel>
-                       </td>
-                       <td>
-                               <a href="javascript:void(0)" class="w3-button w3-green"  <%=$this->Data['admin'] ? 'style="visibility: hidden"' : ''%> onclick="Users.rmUser('<%=$this->Data['user']%>')">
-                                       <i class="fa fa-trash-alt"></i> &nbsp;<%[ Remove user ]%>
-                               </a>
-                               <a href="javascript:void(0)" class="w3-button w3-green" onclick="Users.showChangePwd(this)" rel="chpwd_btn">
-                                       <i class="fa fa-key"></i> &nbsp;<%[ Change password ]%>
-                               </a>
-                               <span style="display: none;" rel="chpwd">
-                                       <div class="w3-threequarter w3-section">
-                                               <%[ Password: ]%>
-                                               <input type="password" onkeydown="event.keyCode == 13 ? Users.changePwd(this, '<%=$this->Data['user']%>') : (event.keyCode == 27 ? Users.cancelChangePwd(this.nextElementSibling.nextElementSibling) : '');" class="w3-input w3-border" />
-                                               <a href="javascript:void(0)" onclick="Users.changePwd(this.previousElementSibling, '<%=$this->Data['user']%>')" class="w3-button w3-green w3-margin-top">
-                                                       <i class="fa fa-save"></i> &nbsp;<%[ Save ]%>
-                                               </a>
-                                               <a href="javascript:void(0)" onclick="Users.cancelChangePwd(this)" class="w3-button w3-red w3-margin-top">
-                                                       <i class="fa fa-times"></i> &nbsp;<%[ Close ]%>
-                                               </a>
-                                       </div>
-                               </span>
-                       </td>
-               </tr>
-       </prop:ItemTemplate>
-       <prop:FooterTemplate>
-               </table>
-       </prop:FooterTemplate>
-</com:TActiveRepeater>
-<com:TCallback ID="UserAction" OnCallback="TemplateControl.userAction" ClientSide.OnComplete="Users.hide_loader();" />
-<script type="text/javascript">
-       var send_user_action = function(action, param, value) {
-               Users.current_action = action;
-               if (!value) {
-                       value = '';
-               }
-               var user_action_callback = <%=$this->UserAction->ActiveControl->Javascript%>;
-               user_action_callback.setCallbackParameter([action, param, value].join(';'));
-               user_action_callback.dispatch();
-       };
-       Users.txt = {
-               enter_login: '<%[ Please enter login. ]%>',
-               invalid_login: '<%[ Invalid login value. Login may contain a-z A-Z 0-9 characters. ]%>',
-               invalid_pwd: '<%[ Password must be longer than 4 chars. ]%>'
-       };
-       Users.action_callback = send_user_action;
-       Users.validators = { user_pattern: new RegExp('^<%=BasicUserConfig::USER_PATTERN%>$') };
-       Users.init();
-</script>
-
-       <div id="debug_confirm" class="w3-modal" style="display: none">
-               <div class="w3-modal-content w3-card-4 w3-animate-zoom w3-padding" style="max-width: 600px">
-                       <span onclick="document.getElementById('debug_confirm').style.display='none'" class="w3-button w3-xlarge w3-hover-red w3-display-topright">&times;</span>
-                       <h4><%[ Enable debug ]%></h4>
-                       <div class="w3-center">
-                               <button class="w3-button w3-red" type="button" onclick="document.getElementById('debug_confirm').style.display='none'"><i class="fa fa-times"></i> &nbsp;<%[ No ]%></button>
-                               <button class="w3-button w3-green" type="button" onclick="document.getElementById('debug_confirm').style.display='none'"><i class="fa fa-check"></i> &nbsp;<%[ Yes ]%></button>
-                       </div>
-               </div>
-       </div>
-</com:TContent>
diff --git a/gui/baculum/protected/Web/Pages/Users.php b/gui/baculum/protected/Web/Pages/Users.php
deleted file mode 100644 (file)
index 2b73b34..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-<?php
-/*
- * Bacula(R) - The Network Backup Solution
- * Baculum   - Bacula web interface
- *
- * Copyright (C) 2013-2020 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('System.Web.UI.ActiveControls.TActiveLinkButton');
-Prado::using('System.Web.UI.ActiveControls.TActiveRepeater');
-Prado::using('System.Web.UI.ActiveControls.TCallback');
-Prado::using('Application.Web.Class.BaculumWebPage'); 
-
-/**
- * Users page.
- *
- * @author Marcin Haba <marcin.haba@bacula.pl>
- * @category Page
- * @package Baculum Web
- */
-class Users extends BaculumWebPage {
-
-       protected $admin = true;
-
-       public function setUsers() {
-               $all_users = $this->getModule('basic_webuser')->getAllUsers();
-               $users = array_keys($all_users);
-               sort($users);
-               $users_list = array();
-               $users_feature = (array_key_exists('users', $this->web_config) && is_array($this->web_config['users']));
-               for ($i = 0; $i < count($users); $i++) {
-                       $host = null;
-                       if ($users_feature && array_key_exists($users[$i], $this->web_config['users'])) {
-                               $host = $this->web_config['users'][$users[$i]];
-                       }
-                       $users_list[] = array(
-                               'user' => $users[$i],
-                               'host' => $host,
-                               'admin' => ($users[$i] === $this->web_config['baculum']['login'])
-                       );
-               }
-               $this->UsersList->dataSource = $users_list;
-               $this->UsersList->dataBind();
-       }
-
-       public function initHosts($sender, $param) {
-               $api_hosts = array_keys($this->getModule('host_config')->getConfig());
-               $sender->DataSource = array_combine($api_hosts, $api_hosts);
-               $sender->dataBind();
-       }
-
-       public function userAction($sender, $param) {
-               $this->UsersList->ActiveControl->EnableUpdate = true;
-               list($action, $user, $value) = explode(';', $param->CallbackParameter, 3);
-               switch($action) {
-                       case 'newuser':
-                       case 'chpwd': {
-                                       $this->getModule('basic_webuser')->setUsersConfig($user, $value);
-                                       if ($user === $this->web_config['baculum']['login']) {
-                                               // if admin password changed then try to auto-login by async request
-                                               $http_protocol = isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) ? 'https' : 'http';
-                                               $this->switchToUser($user, $value);
-                                               exit();
-                                       } else {
-                                               // if normal user's password changed then update users grid
-                                               $this->setUsers();
-                                       }
-                               }
-                               break;
-                       case 'rmuser': {
-                                       if ($user != $_SERVER['PHP_AUTH_USER']) {
-                                               $this->getModule('basic_webuser')->removeUser($user);
-                                               if (array_key_exists('users', $this->web_config) && array_key_exists($user, $this->web_config['users'])) {
-                                                       unset($this->web_config['users'][$user]);
-                                               }
-                                               $this->getModule('web_config')->setConfig($this->web_config);
-                                               $this->setUsers();
-                                       }
-                               break;
-                               }
-                       case 'set_host': {
-                                       if (empty($value) && array_key_exists($user, $this->web_config['users'])) {
-                                               unset($this->web_config['users'][$user]);
-                                       } else {
-                                               $this->web_config['users'][$user] = $value;
-                                       }
-                                       $this->getModule('web_config')->setConfig($this->web_config);
-                               break;
-                               }
-               }
-       }
-
-       public function isSelectedHost($user, $host) {
-               if (!key_exists('users', $this->web_config)) {
-                       return;
-               }
-               return (key_exists($user, $this->web_config['users']) && $this->web_config['users'][$user] === $host);
-       }
-}
-?>
index f0b731e09a0188ab21c38edb1d33018a1e43071c..a1b93fddddd4d1f82d587199722034c57c3c1f7f 100644 (file)
@@ -33,8 +33,6 @@ class VolumeList extends BaculumWebPage {
 
        const USE_CACHE = true;
 
-       protected $admin = true;
-
        public $volumes;
 
        public function onInit($param) {
@@ -42,9 +40,7 @@ class VolumeList extends BaculumWebPage {
                if ($this->IsPostBack || $this->IsCallBack) {
                        return;
                }
-               if ($_SESSION['admin']) {
-                       $this->setVolumes();
-               }
+               $this->setVolumes();
        }
 
        public function setVolumes() {
index 54793255461d050223e7f74c1d2d602623ce56a3..a6c87d50ef9f5417717b70b0892191b876d7df8b 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -41,8 +41,6 @@ class VolumeView extends BaculumWebPage {
        const MEDIAID = 'MediaId';
        const VOLUME_NAME = 'VolumeName';
 
-       protected $admin = true;
-
        public $jobs_on_volume;
 
        private $volstatus_by_dir = array('Recycle', 'Purged', 'Error', 'Busy');
index 71b3cdb49c9cbcd6ff165ea3295b799dbe0a2017..dc0c729c6f6a2bfa4760ff56d6f5cc58185c10fb 100644 (file)
@@ -7,32 +7,34 @@
                NavigationStyle.CssClass="navigation"
                UseDefaultLayout="false"
                ShowSideBar="false"
+               OnPreviousButtonClick="previousStep"
+               OnNextButtonClick="nextStep"
                OnCancelButtonClick="wizardStop"
                OnCompleteButtonClick="wizardCompleted"
                >
                <prop:HeaderTemplate>
-                       <div class="w3-quarter w3-padding-16">
+                       <div class="<%=$this->SourceTemplateControl->first_run ? 'w3-quarter' : 'w3-third'%> w3-padding-16">
                                <div class="step w3-padding w3-text-white w3-margin-right <%=($this->Parent->ActiveStepIndex === 0 ? 'w3-light-green' : 'w3-green')%>">
                                        <div class="w3-left"><i class="fa fa-language w3-xxxlarge"></i></div>
                                        <div class="w3-clear"></div>
                                        <h4><com:TTranslate Text="Language" /></h4>
                                </div>
                        </div>
-                       <div class="w3-quarter w3-padding-16">
+                       <div class="<%=$this->SourceTemplateControl->first_run ? 'w3-quarter' : 'w3-third'%> w3-padding-16">
                                <div class="step w3-padding w3-text-white w3-margin-right <%=($this->Parent->ActiveStepIndex === 1 ? 'w3-light-green' : 'w3-green')%>">
                                        <div class="w3-left"><i class="fa fa-plus-square w3-xxxlarge"></i></div>
                                        <div class="w3-clear"></div>
                                        <h4><com:TTranslate Text="Add APIs" /></h4>
                                </div>
                        </div>
-                       <div class="w3-quarter w3-padding-16">
+                       <div class="<%=$this->SourceTemplateControl->first_run ? 'w3-quarter' : 'w3-third'%> w3-padding-16"<%=!$this->SourceTemplateControl->first_run ? ' style="display: none"' : ''%>>
                                <div class="step w3-padding w3-text-white w3-margin-right <%=($this->Parent->ActiveStepIndex === 2 ? 'w3-light-green' : 'w3-green')%>">
                                        <div class="w3-left"><i class="fa fa-key w3-xxxlarge"></i></div>
                                        <div class="w3-clear"></div>
                                        <h4><com:TTranslate Text="Authentication" /></h4>
                                </div>
                        </div>
-                       <div class="w3-quarter w3-padding-16">
+                       <div class="<%=$this->SourceTemplateControl->first_run ? 'w3-quarter' : 'w3-third'%> w3-padding-16">
                                <div class="step w3-padding w3-text-white w3-margin-right <%=($this->Parent->ActiveStepIndex === 3 ? 'w3-light-green' : 'w3-green')%>">
                                        <div class="w3-left"><i class="fa fa-check-square w3-xxxlarge"></i></div>
                                        <div class="w3-clear"></div>
                </com:TWizardStep>
                <com:TWizardStep ID="Step4" Title="<%[ Step 4 - Finish ]%>" StepType="Finish">
                        <fieldset>
-                               <legend><%[ Authorization to Baculum API ]%></legend>
+                               <legend><%[ Access to Baculum API ]%></legend>
                                <div class="w3-container w3-section">
                                        <div class="w3-third"><%[ Protocol: ]%></div>
                                        <div class="w3-third bold"><%=$this->AddNewHost->APIProtocol->SelectedValue%></div>
                                        </div>
                                </div>
                        </fieldset>
-                       <fieldset>
-                               <legend><%[ Authorization to Baculum Web ]%></legend>
+                       <fieldset<%=!$this->SourceTemplateControl->first_run ? ' style="display: none"' : ''%>>
+                               <legend><%[ Access to Baculum Web ]%></legend>
                                <div class="w3-container w3-section">
                                        <div class="w3-third"><%[ Administration login: ]%></div>
                                        <div class="w3-third bold"><%=$this->WebLogin->Text%></div>
index 5384f2f1ea44f9986f8f659b6c9c5cf3f913beda..56c2a84dfb8e7d6efd5b068c4b2204750a50b275 100644 (file)
@@ -34,21 +34,14 @@ Prado::using('System.Web.UI.ActiveControls.TActiveDropDownList');
  */
 class WebConfigWizard extends BaculumWebPage
 {
-
-       protected $admin = false;
-
        public $first_run;
        public $host_config;
 
        public function onInit($param) {
                parent::onInit($param);
-               $this->Lang->SelectedValue = $this->getLanguage();
                $this->host_config = $this->getModule('host_config')->getConfig();
                $this->first_run = (count($this->host_config) == 0 || !key_exists(HostConfig::MAIN_CATALOG_HOST, $this->host_config));
                Logging::$debug_enabled = Logging::$debug_enabled ?: $this->first_run;
-               if($this->first_run === false && !$_SESSION['admin']) {
-                       parent::accessDenied();
-               }
        }
 
        public function onLoad($param) {
@@ -75,7 +68,6 @@ class WebConfigWizard extends BaculumWebPage
                                $this->AddNewHost->APIOAuth2RedirectURI->Text = $this->host_config[$host]['redirect_uri'];
                                $this->AddNewHost->APIOAuth2Scope->Text = $this->host_config[$host]['scope'];
                        }
-                       $this->WebLogin->Text = $this->web_config['baculum']['login'];
                } else {
                        $this->AddNewHost->APIProtocol->SelectedValue = 'http';
                        $this->AddNewHost->APIAddress->Text = 'localhost';
@@ -84,10 +76,21 @@ class WebConfigWizard extends BaculumWebPage
                }
        }
 
-       public function NextStep($sender, $param) {
+       public function onPreRender($param) {
+               parent::onPreRender($param);
+               if($this->IsPostBack || $this->IsCallBack) {
+                       return;
+               }
+               $this->Lang->SelectedValue = $this->getModule('web_config')->getLanguage();
+       }
+
+       public function nextStep($sender, $param) {
+               if ($param->CurrentStepIndex === 1 && !$this->first_run) {
+                       $this->InstallWizard->ActiveStepIndex = 3;
+               }
        }
        
-       public function PreviousStep($sender, $param) {
+       public function previousStep($sender, $param) {
        }
 
        public function wizardStop($sender, $param) {
@@ -124,28 +127,70 @@ class WebConfigWizard extends BaculumWebPage
                $host_config[$host] = $cfg_host;
                $ret = $this->getModule('host_config')->setConfig($host_config);
                if($ret === true) {
-                       $cfg_web = array('baculum' => array(), 'users' => array());
-                       if (count($this->web_config) > 0) {
-                               $cfg_web = $this->web_config;
-                       }
-                       $cfg_web['baculum']['login'] = $this->WebLogin->Text;
-                       $cfg_web['baculum']['debug'] = 0;
-                       $cfg_web['baculum']['lang'] = $this->Lang->SelectedValue;
-                       if (array_key_exists('users', $cfg_web) && array_key_exists($this->WebLogin->Text, $cfg_web)) {
-                               // Admin shoudn't be added to users section, only regular users
-                               unset($cfg_web['users'][$this->WebLogin->Text]);
-                       }
-                       $ret = $this->getModule('web_config')->setConfig($cfg_web);
-                       if($ret && $this->getModule('basic_webuser')->isUsersConfig() === true) {
-                               $previous_user = $this->first_run ? parent::DEFAULT_AUTH_USER : $this->web_config['baculum']['login'];
-                               $this->getModule('basic_webuser')->setUsersConfig(
-                                       $cfg_web['baculum']['login'],
+                       // complete new Baculum main settings
+                       $web_config = $this->getModule('web_config');
+                       $ret = $web_config->setDefConfigOpts([
+                               'baculum' => [
+                                       'lang' => $this->Lang->SelectedValue
+                               ]
+                       ]);
+
+                       $basic_webuser = $this->getModule('basic_webuser');
+                       if($this->first_run && $ret && $web_config->isAuthMethodBasic()) {
+                               // set new user on first wizard run
+                               $previous_user = parent::DEFAULT_AUTH_USER;
+                               $ret = $basic_webuser->setUsersConfig(
+                                       $this->WebLogin->Text,
                                        $this->WebPassword->Text,
                                        false,
                                        $previous_user
                                );
+                       } else {
+                               $emsg = 'Error while saving basic user config.';
+                               $this->getModule('logging')->log(
+                                       __FUNCTION__,
+                                       $emsg,
+                                       Logging::CATEGORY_APPLICATION,
+                                       __FILE__,
+                                       __LINE__
+                               );
                        }
+
+                       if ($this->first_run && $ret) {
+                               // create new Baculum user on first wizard run
+                               $user_config = $this->getModule('user_config');
+                               $new_user_prop = $user_config->getUserConfigProps([
+                                       'username' => $this->WebLogin->Text,
+                                       'roles' => WebUserRoles::ADMIN,
+                                       'enabled' => 1
+                               ]);
+                               $ret = $user_config->setUserConfig($this->WebLogin->Text, $new_user_prop);
+                               if (!$ret) {
+                                       $emsg = 'Error while saving user config.';
+                                       $this->getModule('logging')->log(
+                                               __FUNCTION__,
+                                               $emsg,
+                                               Logging::CATEGORY_APPLICATION,
+                                               __FILE__,
+                                               __LINE__
+                                       );
+                               }
+
+                               // Login user with new parameters
+                               $this->getModule('auth')->login($this->WebLogin->Text, $this->WebPassword->Text);
+                       }
+
+                       // Go to default user page
                        $this->goToDefaultPage();
+               } else {
+                       $emsg = 'Error while saving auth host config.';
+                       $this->getModule('logging')->log(
+                               __FUNCTION__,
+                               $emsg,
+                               Logging::CATEGORY_APPLICATION,
+                               __FILE__,
+                               __LINE__
+                       );
                }
        }
 
@@ -158,7 +203,7 @@ class WebConfigWizard extends BaculumWebPage
        }
 
        public function setLang($sender, $param) {
-               $_SESSION['language'] = $sender->SelectedValue;
+               $this->getModule('web_config')->setLanguage($sender->SelectedValue);
        }
 
        public function validateAdministratorPassword($sender, $param) {
index 21babe7b757f8f4bc00e2cb0ea6e4e08d30285f9..5a9b90eb6ea888a92d7b9164c1f416f31276670d 100644 (file)
@@ -6,6 +6,8 @@
                <!-- config modules -->
                <module id="web_config" class="Application.Web.Class.WebConfig" />
                <module id="host_config" class="Application.Web.Class.HostConfig" />
+               <module id="user_config" class="Application.Web.Class.WebUserConfig" />
+               <module id="role_config" class="Application.Web.Class.WebRoleConfig" />
                <!-- data modules -->
                <module id="api" class="Application.Web.Class.BaculumAPIClient" />
                <module id="data_desc" class="Application.Web.Class.DataDescription" />
@@ -19,5 +21,9 @@
                <module id="log_parser" class="Application.Web.Class.LogParser" />
                <!-- auth modules -->
                <module id="basic_webuser" class="Application.Web.Class.BasicWebUserConfig" />
+               <module id="page_category" class="Application.Web.Class.PageCategory" />
+               <module id="user_role" class="Application.Web.Class.WebUserRoles" />
+               <module id="auth" class="System.Security.TAuthManager" UserManager="users" LoginPage="LoginPage" />
+               <module id="users" class="Application.Web.Class.WebUserManager" UserClass="Application.Web.Class.WebUser" />
        </modules>
 </configuration>
index 41e58b003dc8cc8364d29775dc7ad0e4866cad19..54456831d1c0595409fe57ff1139d286b298daf6 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -39,9 +39,6 @@ class BaculaHosts extends HostListTemplate {
        public $config;
 
        public function loadConfig($sender, $param) {
-               if(!$_SESSION['admin']) {
-                       return;
-               }
                $this->config = $this->getModule('host_config')->getConfig();
                $hosts = array_keys($this->config);
                $this->RepeaterHosts->DataSource = $hosts;
@@ -73,9 +70,6 @@ class BaculaHosts extends HostListTemplate {
        }
 
        public function removeHost($sender, $param) {
-               if(!$_SESSION['admin']) {
-                       return;
-               }
                $host = $param->getCommandParameter();
                if (!empty($host)) {
                        $host_config = $this->getModule('host_config');
index 5172e38325a3d573634962fd69faddb89f10d5cc..7e8307d59ef4ebc8f0d6f156827882f54c08d594 100644 (file)
@@ -20,6 +20,6 @@
                        Text="Field required."
                        Enabled="<%=$this->getRequired() && $this->getShow()%>"
                />
-               <p class="w3-row w3-padding"><%[ Use Ctrl + Mouse Click to change selection ]%></p>
+               <p class="w3-row w3-padding"><%[ Use CTRL + left-click to multiple item selection ]%></p>
        </div>
 </div>
index 8128f7bd8efc99f04ccd90113ab2cc2be19a9aaa..ac43a1a17646c0a906f4a4e401c5d87211245930 100644 (file)
@@ -3,7 +3,7 @@
  * Bacula(R) - The Network Backup Solution
  * Baculum   - Bacula web interface
  *
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
  *
  * The main author of Baculum is Marcin Haba.
  * The original author of Bacula is Kern Sibbald, with contributions
@@ -32,10 +32,33 @@ Prado::using('Application.Web.Portlets.Portlets');
  */
 class MainSideBar extends Portlets {
 
+       /**
+        * Reload URL is used to refresh page after logout with Basic auth.
+        */
+       public $reload_url = '';
+
+       public function onInit($param) {
+               parent::onInit($param);
+               if ($this->getModule('web_config')->isAuthMethodBasic()) {
+                       $fake_pwd = $this->getModule('crypto')->getRandomString();
+                       $user = $_SERVER['PHP_AUTH_USER'] . '1'; // must be different than currently logged in Basic user
+
+                       // do a login try with different user and password to logout current user
+                       $this->reload_url = $this->getPage()->getFullLoginUrl($user, $fake_pwd);
+               }
+       }
+
        public function logout($sender, $param) {
-               $fake_pwd = $this->getModule('misc')->getRandomString();
-               $this->getPage()->switchToUser($_SERVER['PHP_AUTH_USER'], $fake_pwd);
-               exit();
+               $this->getModule('auth')->logout();
+               if ($this->getModule('web_config')->isAuthMethodBasic()) {
+                       /**
+                        * This status code 401 is necessary to stop comming AJAX requests
+                        * and to bring the login prompt on.
+                        */
+                       $this->Response->setStatusCode(401);
+               } else {
+                       $this->getPage()->goToDefaultPage();
+               }
        }
 }
 ?>
index 0ac1d898768e64bff66a53f937e0f10548de334c..9393060a4646990298650eac8bb75e3fdf3ea38a 100644 (file)
@@ -5,40 +5,50 @@
                        <img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/avatar2.png" class="w3-circle w3-margin-right" style="width:46px" />
                </div>
                <div class="w3-col s8 w3-bar">
-                       <span><%[ Welcome ]%>, <strong><%=$_SERVER['PHP_AUTH_USER']%></strong></span><br>
+                       <span><%[ Welcome ]%>, <strong><%=$this->User->getUsername()%></strong></span><br>
+                       <script>var main_side_bar_reload_url = '<%=$this->reload_url%>';</script>
                        <com:TActiveLinkButton
                                ID="Logout"
-                               OnCommand="logout"
+                               OnClick="logout"
                                CssClass="w3-bar-item w3-button"
                                ToolTip="<%[ Logout ]%>"
                        >
+                               <prop:ClientSide.OnComplete>
+                                       if (!window.chrome || window.navigator.webdriver)  {
+                                               window.location.href = main_side_bar_reload_url;
+                                       } else if (window.chrome) {
+                                               // For chrome this reload is required to show login Basic auth prompt
+                                               window.location.reload();
+                                       }
+                               </prop:ClientSide.OnComplete>
                                <i class="fa fa-power-off"></i>
                        </com:TActiveLinkButton>
-                       <a href="<%=$this->Service->constructUrl('Console')%>" class="w3-bar-item w3-button" title="<%[ Console ]%>"><i class="fa fa-terminal"></i></a>
-                       <a href="<%=$this->Service->constructUrl('ApplicationSettings')%>" class="w3-bar-item w3-button<%=!$_SESSION['admin'] ? ' hide' : ''%>" title="<%[ Application settings ]%>"><i class="fa fa-cog"></i></a>
+                       <a href="<%=$this->Service->constructUrl('Console')%>" class="w3-bar-item w3-button<%=$this->getModule('users')->isPageAllowed($this->User, 'Console') ? '' : ' hide'%>" title="<%[ Console ]%>"><i class="fa fa-terminal"></i></a>
+                       <a href="<%=$this->Service->constructUrl('ApplicationSettings')%>" class="w3-bar-item w3-button<%=$this->getModule('users')->isPageAllowed($this->User, 'ApplicationSettings') ? '' : ' hide'%>" title="<%[ Application settings ]%>"><i class="fa fa-cog"></i></a>
                </div>
        </div>
        <hr />
-       <div class="w3-container">
+       <div class="w3-container w3-black">
                <h5>Baculum Menu</h5>
        </div>
        <div class="w3-bar-block" style="margin-bottom: 45px;">
-               <a href="#" class="w3-bar-item w3-button w3-padding-16 w3-black w3-hover-black" onclick="W3SideBar.close(); return false;" title="close menu">  <%[ Close Menu ]%> <i class="fa fa-window-close fa-fw w3-right w3-xlarge"></i></a>
-               <a href="<%=$this->Service->constructUrl('Dashboard')%>" class="w3-bar-item w3-button w3-padding<%=$this->Service->getRequestedPagePath() == 'Dashboard' ? ' w3-blue': ''%>"><i class="fa fa-tachometer-alt fa-fw"></i>  <%[ Dashboard ]%></a>
-               <a href="<%=$this->Service->constructUrl('JobHistoryList')%>" class="w3-bar-item w3-button w3-padding<%=in_array($this->Service->getRequestedPagePath(), array('JobHistoryList', 'JobHistoryView')) ? ' w3-blue': ''%>"><i class="fa fa-history fa-fw"></i>  <%[ Job history ]%></a>
-               <a href="<%=$this->Service->constructUrl('JobList')%>" class="w3-bar-item w3-button w3-padding<%=in_array($this->Service->getRequestedPagePath(), array('JobList', 'JobView')) ? ' w3-blue': ''%>"><i class="fa fa-tasks fa-fw"></i>  <%[ Jobs ]%></a>
-               <a href="<%=$this->Service->constructUrl('ClientList')%>" class="w3-bar-item w3-button w3-padding<%=in_array($this->Service->getRequestedPagePath(), array('ClientList', 'ClientView')) ? ' w3-blue': ''%>"><i class="fa fa-desktop fa-fw"></i>  <%[ Clients ]%></a>
-               <a href="<%=$this->Service->constructUrl('StorageList')%>" class="w3-bar-item w3-button w3-padding<%=in_array($this->Service->getRequestedPagePath(), array('StorageList', 'StorageView', 'DeviceView')) ? ' w3-blue': ''%><%=!$_SESSION['admin'] ? ' hide' : ''%>"><i class="fa fa-database fa-fw"></i>  <%[ Storages ]%></a>
-               <a href="<%=$this->Service->constructUrl('PoolList')%>" class="w3-bar-item w3-button w3-padding<%=in_array($this->Service->getRequestedPagePath(), array('PoolList', 'PoolView')) ? ' w3-blue': ''%><%=!$_SESSION['admin'] ? ' hide' : ''%>"><i class="fa fa-tape fa-fw"></i>  <%[ Pools ]%></a>
-               <a href="<%=$this->Service->constructUrl('VolumeList')%>" class="w3-bar-item w3-button w3-padding<%=in_array($this->Service->getRequestedPagePath(), array('VolumeList', 'VolumeView')) ? ' w3-blue': ''%><%=!$_SESSION['admin'] ? ' hide' : ''%>"><i class="fa fa-hdd fa-fw"></i>  <%[ Volumes ]%></a>
-               <a href="<%=$this->Service->constructUrl('FileSetList')%>" class="w3-bar-item w3-button w3-padding<%=in_array($this->Service->getRequestedPagePath(), array('FileSetList', 'FileSetView')) ? ' w3-blue': ''%><%=!$_SESSION['admin'] ? ' hide' : ''%>"><i class="fa fa-copy fa-fw"></i>  <%[ FileSets ]%></a>
-               <a href="<%=$this->Service->constructUrl('ScheduleList')%>" class="w3-bar-item w3-button w3-padding<%=in_array($this->Service->getRequestedPagePath(), array('ScheduleList', 'ScheduleView')) ? ' w3-blue': ''%><%=!$_SESSION['admin'] ? ' hide' : ''%>"><i class="fa fa-clock fa-fw"></i>  <%[ Schedules ]%></a>
-               <a href="<%=$this->Service->constructUrl('ConfigureHosts')%>" class="w3-bar-item w3-button w3-padding<%=$this->Service->getRequestedPagePath() == 'ConfigureHosts' ? ' w3-blue': ''%><%=!$_SESSION['admin'] ? ' hide' : ''%>"><i class="fa fa-cog fa-fw"></i>  <%[ Configure ]%></a>
-               <a href="<%=$this->Service->constructUrl('RestoreWizard')%>" class="w3-bar-item w3-button w3-padding<%=$this->Service->getRequestedPagePath() == 'RestoreWizard' ? ' w3-blue': ''%>"><i class="fa fa-reply fa-fw"></i>  <%[ Restore wizard ]%></a>
-               <a href="<%=$this->Service->constructUrl('Graphs')%>" class="w3-bar-item w3-button w3-padding<%=$this->Service->getRequestedPagePath() == 'Graphs' ? ' w3-blue': ''%>"><i class="fa fa-chart-pie fa-fw"></i>  <%[ Graphs ]%></a>
-               <a href="<%=$this->Service->constructUrl('StatisticsList')%>" class="w3-bar-item w3-button w3-padding<%=in_array($this->Service->getRequestedPagePath(), array('StatisticsList', 'StatisticsView')) ? ' w3-blue': ''%><%=!$_SESSION['admin'] ? ' hide' : ''%>"><i class="fas fa-chart-line fa-fw"></i>  <%[ Statistics ]%></a>
-               <a href="<%=$this->Service->constructUrl('WebConfigWizard')%>" class="w3-bar-item w3-button w3-padding<%=$this->Service->getRequestedPagePath() == 'WebConfigWizard' ? ' w3-blue': ''%><%=!$_SESSION['admin'] ? ' hide' : ''%>"><i class="fa fa-wrench fa-fw"></i>  <%[ Settings ]%></a>
-               <a href="<%=$this->Service->constructUrl('Users')%>" class="w3-bar-item w3-button w3-padding<%=$this->Service->getRequestedPagePath() == 'Users' ? ' w3-blue': ''%><%=!$_SESSION['admin'] ? ' hide' : ''%>"><i class="fa fa-users fa-fw"></i>  <%[ Users ]%></a>
+               <!--a href="#" class="w3-bar-item w3-button w3-padding-16 w3-black w3-hover-black w3-hide-large" onclick="W3SideBar.close(); return false;" title="close menu">  <%[ Close Menu ]%> <i class="fa fa-window-close fa-fw w3-right w3-xlarge"></i></a-->
+               <div class="w3-black" style="height: 3px"></div>
+               <a href="<%=$this->Service->constructUrl('Dashboard')%>" class="w3-bar-item w3-button w3-padding<%=$this->Service->getRequestedPagePath() == 'Dashboard' ? ' w3-blue': ''%><%=$this->getModule('users')->isPageAllowed($this->User, 'Dashboard') ? '' : ' hide'%>"><i class="fa fa-tachometer-alt fa-fw"></i>  <%[ Dashboard ]%></a>
+               <a href="<%=$this->Service->constructUrl('JobHistoryList')%>" class="w3-bar-item w3-button w3-padding<%=in_array($this->Service->getRequestedPagePath(), array('JobHistoryList', 'JobHistoryView')) ? ' w3-blue': ''%><%=$this->getModule('users')->isPageAllowed($this->User, 'JobHistoryList') ? '' : ' hide'%>"><i class="fa fa-history fa-fw"></i>  <%[ Job history ]%></a>
+               <a href="<%=$this->Service->constructUrl('JobList')%>" class="w3-bar-item w3-button w3-padding<%=in_array($this->Service->getRequestedPagePath(), array('JobList', 'JobView')) ? ' w3-blue': ''%><%=$this->getModule('users')->isPageAllowed($this->User, 'JobList') ? '' : ' hide'%>"><i class="fa fa-tasks fa-fw"></i>  <%[ Jobs ]%></a>
+               <a href="<%=$this->Service->constructUrl('ClientList')%>" class="w3-bar-item w3-button w3-padding<%=in_array($this->Service->getRequestedPagePath(), array('ClientList', 'ClientView')) ? ' w3-blue': ''%><%=$this->getModule('users')->isPageAllowed($this->User, 'ClientList') ? '' : ' hide'%>"><i class="fa fa-desktop fa-fw"></i>  <%[ Clients ]%></a>
+               <a href="<%=$this->Service->constructUrl('StorageList')%>" class="w3-bar-item w3-button w3-padding<%=in_array($this->Service->getRequestedPagePath(), array('StorageList', 'StorageView', 'DeviceView')) ? ' w3-blue': ''%><%=$this->getModule('users')->isPageAllowed($this->User, 'StorageList') ? '' : ' hide'%>"><i class="fa fa-database fa-fw"></i>  <%[ Storages ]%></a>
+               <a href="<%=$this->Service->constructUrl('PoolList')%>" class="w3-bar-item w3-button w3-padding<%=in_array($this->Service->getRequestedPagePath(), array('PoolList', 'PoolView')) ? ' w3-blue': ''%><%=$this->getModule('users')->isPageAllowed($this->User, 'PoolList') ? '' : ' hide'%>"><i class="fa fa-tape fa-fw"></i>  <%[ Pools ]%></a>
+               <a href="<%=$this->Service->constructUrl('VolumeList')%>" class="w3-bar-item w3-button w3-padding<%=in_array($this->Service->getRequestedPagePath(), array('VolumeList', 'VolumeView')) ? ' w3-blue': ''%><%=$this->getModule('users')->isPageAllowed($this->User, 'VolumeList') ? '' : ' hide'%>"><i class="fa fa-hdd fa-fw"></i>  <%[ Volumes ]%></a>
+               <a href="<%=$this->Service->constructUrl('FileSetList')%>" class="w3-bar-item w3-button w3-padding<%=in_array($this->Service->getRequestedPagePath(), array('FileSetList', 'FileSetView')) ? ' w3-blue': ''%><%=$this->getModule('users')->isPageAllowed($this->User, 'FileSetList') ? '' : ' hide'%>"><i class="fa fa-copy fa-fw"></i>  <%[ FileSets ]%></a>
+               <a href="<%=$this->Service->constructUrl('ScheduleList')%>" class="w3-bar-item w3-button w3-padding<%=in_array($this->Service->getRequestedPagePath(), array('ScheduleList', 'ScheduleView')) ? ' w3-blue': ''%><%=$this->getModule('users')->isPageAllowed($this->User, 'ScheduleList') ? '' : ' hide'%>"><i class="fa fa-clock fa-fw"></i>  <%[ Schedules ]%></a>
+               <a href="<%=$this->Service->constructUrl('ConfigureHosts')%>" class="w3-bar-item w3-button w3-padding<%=$this->Service->getRequestedPagePath() == 'ConfigureHosts' ? ' w3-blue': ''%><%=$this->getModule('users')->isPageAllowed($this->User, 'ConfigureHosts') ? '' : ' hide'%>"><i class="fa fa-cog fa-fw"></i>  <%[ Configure ]%></a>
+               <a href="<%=$this->Service->constructUrl('RestoreWizard')%>" class="w3-bar-item w3-button w3-padding<%=$this->Service->getRequestedPagePath() == 'RestoreWizard' ? ' w3-blue': ''%><%=$this->getModule('users')->isPageAllowed($this->User, 'RestoreWizard') ? '' : ' hide'%>"><i class="fa fa-reply fa-fw"></i>  <%[ Restore wizard ]%></a>
+               <a href="<%=$this->Service->constructUrl('Graphs')%>" class="w3-bar-item w3-button w3-padding<%=$this->Service->getRequestedPagePath() == 'Graphs' ? ' w3-blue': ''%><%=$this->getModule('users')->isPageAllowed($this->User, 'Graphs') ? '' : ' hide'%>"><i class="fa fa-chart-pie fa-fw"></i>  <%[ Graphs ]%></a>
+               <a href="<%=$this->Service->constructUrl('StatisticsList')%>" class="w3-bar-item w3-button w3-padding<%=in_array($this->Service->getRequestedPagePath(), array('StatisticsList', 'StatisticsView')) ? ' w3-blue': ''%><%=$this->getModule('users')->isPageAllowed($this->User, 'StatisticsList') ? '' : ' hide'%>"><i class="fas fa-chart-line fa-fw"></i>  <%[ Statistics ]%></a>
+               <a href="<%=$this->Service->constructUrl('WebConfigWizard')%>" class="w3-bar-item w3-button w3-padding<%=$this->Service->getRequestedPagePath() == 'WebConfigWizard' ? ' w3-blue': ''%><%=$this->getModule('users')->isPageAllowed($this->User, 'WebConfigWizard') ? '' : ' hide'%>"><i class="fa fa-wrench fa-fw"></i>  <%[ Settings ]%></a>
+               <a href="<%=$this->Service->constructUrl('Security')%>" class="w3-bar-item w3-button w3-padding<%=$this->Service->getRequestedPagePath() == 'Security' ? ' w3-blue': ''%><%=$this->getModule('users')->isPageAllowed($this->User, 'Security') ? '' : ' hide'%>"><i class="fa fa-lock fa-fw"></i>  <%[ Security ]%></a>
        </div>
 </nav>
 
index 3bff836dac6f6b1e8077395abb8eb3f7a2f9d5eb..006e255942506314c06a0e095e8ba71e9efffca4 100644 (file)
@@ -213,6 +213,7 @@ class RunJob extends Portlets {
                        $priority = $jobdata->priorjobid;
                }
                $this->Priority->Text = $priority;
+               $this->Estimate->Enabled = false;
        }
 
        public function selectJobValues($sender, $param) {
diff --git a/gui/baculum/protected/Web/Portlets/Users.php b/gui/baculum/protected/Web/Portlets/Users.php
deleted file mode 100644 (file)
index 04e1627..0000000
+++ /dev/null
@@ -1,124 +0,0 @@
-<?php
-/*
- * Bacula(R) - The Network Backup Solution
- * Baculum   - Bacula web interface
- *
- * Copyright (C) 2013-2019 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('System.Web.UI.ActiveControls.TActiveRepeater');
-Prado::using('Application.Web.Portlets.Portlets');
-
-/**
- * Users control.
- *
- * @author Marcin Haba <marcin.haba@bacula.pl>
- * @category Control
- * @package Baculum Web
- */
-class Users extends Portlets {
-
-       public $web_config;
-
-       public function __construct() {
-               parent::__construct();
-               $this->web_config = $this->getModule('web_config')->getConfig();
-       }
-
-       public function setUsers() {
-               if(!$_SESSION['admin']) {
-                       return;
-               }
-               $all_users = $this->getModule('basic_webuser')->getAllUsers();
-               $users = array_keys($all_users);
-               sort($users);
-               $users_list = array();
-               $users_feature = (array_key_exists('users', $this->web_config) && is_array($this->web_config['users']));
-               for ($i = 0; $i < count($users); $i++) {
-                       $host = null;
-                       if ($users_feature && array_key_exists($users[$i], $this->web_config['users'])) {
-                               $host = $this->web_config['users'][$users[$i]];
-                       }
-                       $users_list[] = array(
-                               'user' => $users[$i],
-                               'host' => $host,
-                               'admin' => ($users[$i] === $this->web_config['baculum']['login'])
-                       );
-               }
-               $this->UsersList->dataSource = $users_list;
-               $this->UsersList->dataBind();
-       }
-
-       public function initHosts($sender, $param) {
-               $api_hosts = array_keys($this->getModule('host_config')->getConfig());
-               $sender->DataSource = array_combine($api_hosts, $api_hosts);
-               $sender->dataBind();
-       }
-
-       public function userAction($sender, $param) {
-               if(!$_SESSION['admin']) {
-                       return;
-               }
-               $this->UsersList->ActiveControl->EnableUpdate = true;
-               list($action, $user, $value) = explode(';', $param->CallbackParameter, 3);
-               switch($action) {
-                       case 'newuser':
-                       case 'chpwd': {
-                                       $admin = false;
-                                       $valid = true;
-                                       if ($user === $this->web_config['baculum']['login']) {
-                                               $this->web_config['baculum']['password'] = $value;
-                                               $valid = $this->getModule('web_config')->setConfig($this->web_config);
-                                               $admin = true;
-                                       }
-                                       if ($valid === true) {
-                                               $this->getModule('basic_webuser')->setUsersConfig($user, $value);
-                                       }
-                                       if ($admin === true) {
-                                               // if admin password changed then try to auto-login by async request
-                                               $http_protocol = isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) ? 'https' : 'http';
-                                               $this->switchToUser($user, $value);
-                                               exit();
-                                       } else {
-                                               // if normal user's password changed then update users grid
-                                               $this->setUsers();
-                                       }
-                               }
-                               break;
-                       case 'rmuser': {
-                                       if ($user != $_SERVER['PHP_AUTH_USER']) {
-                                               $this->getModule('basic_webuser')->removeUser($user);
-                                               if (array_key_exists('users', $this->web_config) && array_key_exists($user, $this->web_config['users'])) {
-                                                       unset($this->web_config['users'][$user]);
-                                               }
-                                               $this->getModule('web_config')->setConfig($this->web_config);
-                                               $this->setUsers();
-                                       }
-                               break;
-                               }
-                       case 'set_host': {
-                                       if (empty($value) && array_key_exists($user, $this->web_config['users'])) {
-                                               unset($this->web_config['users'][$user]);
-                                       } else {
-                                               $this->web_config['users'][$user] = $value;
-                                       }
-                                       $this->getModule('web_config')->setConfig($this->web_config);
-                               break;
-                               }
-               }
-       }
-}
diff --git a/gui/baculum/protected/Web/Portlets/Users.tpl b/gui/baculum/protected/Web/Portlets/Users.tpl
deleted file mode 100644 (file)
index 9833b26..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-<a class="big" href="javascript:void(0)" id="add_user_btn"><img src="/themes/Baculum-v1/add.png" alt="Add"><%[ Add new user ]%></a>
-<div id="add_user" style="display: none">
-       <p><%[ Username: ]%><input id="newuser" type="text" /><%[ Password: ]%><input id="newpwd" type="password" />
-       <a href="javascript:void(0)" onclick="Users.addUser()">
-               <img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_ok.png" alt="<%[ Save ]%>" title="<%[ Save ]%>"/>
-       </a>
-       <a href="javascript:void(0)" onclick="Users.cancelAddUser()">
-               <img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_err.png" alt="<%[ Close ]%>" title="<%[ Close ]%>" />
-       </a></p>
-</div>
-<com:TActiveRepeater ID="UsersList" OnLoad="setUsers" ActiveControl.EnableUpdate="false">
-       <prop:HeaderTemplate>
-       <table id="users_list" class="window-section-detail-smallrow">
-               <tr>
-                       <th><%[ User name ]%></th>
-                       <th><%[ Role ]%></th>
-                       <th><%[ API host ]%></th>
-                       <th><%[ Actions ]%></th>
-               </tr>
-       </prop:HeaderTemplate>
-       <prop:ItemTemplate>
-               <tr class="slide-window-element">
-                       <td><%=$this->Data['user']%></td>
-                       <td><%=$this->Data['admin'] ? Prado::localize('Administrator') :  Prado::localize('Normal user')%></td>
-                       <td>
-                               <com:TPanel Visible="<%=$this->Data['admin']%>" Style="line-height: 29px">
-                                       Main
-                               </com:TPanel>
-                               <com:TPanel Visible="<%=!$this->Data['admin']%>">
-                                               <select rel="user_host" onchange="Users.set_host('<%=$this->Data['user']%>', this);">
-                                                       <com:TRepeater OnInit="SourceTemplateControl.initHosts">
-                                                               <prop:HeaderTemplate>
-                                                       <option value=""><%[ Select host ]%></option>
-                                                               </prop:HeaderTemplate>
-                                                               <prop:ItemTemplate>
-                                                       <option value="<%=$this->Data%>" <%=(array_key_exists('users', $this->SourceTemplateControl->web_config) && array_key_exists($this->Parent->Parent->Parent->Data['user'], $this->SourceTemplateControl->web_config['users']) && $this->SourceTemplateControl->web_config['users'][$this->Parent->Parent->Parent->Data['user']] === $this->Data) ? 'selected' : ''%>><%=$this->Data%></option>
-                                                               </prop:ItemTemplate>
-                                                       </com:TRepeater>
-                                               </select>
-                                               <img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/ajax-loader-arrows.gif" rel="user_host_img" alt="" style="visibility: hidden" />
-                               </com:TPanel>
-                       </td>
-                       <td>
-                               <a href="javascript:void(0)" <%=$this->Data['admin'] ? 'style="visibility: hidden"' : ''%> onclick="Users.rmUser('<%=$this->Data['user']%>')"><img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/user-del.png"> <%[ Remove user ]%></a>
-                               <a href="javascript:void(0)" onclick="Users.showChangePwd(this)" rel="chpwd_btn">
-                                       <img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/key.png" alt="" />
-                                       <%[ Change password ]%>
-                               </a>
-                               <span style="display: none;" rel="chpwd">
-                                       <input type="password" onkeydown="event.keyCode == 13 ? Users.changePwd(this, '<%=$this->Data['user']%>') : (event.keyCode == 27 ? Users.cancelChangePwd(this.nextElementSibling.nextElementSibling) : '');" />
-                                       <a href="javascript:void(0)" onclick="Users.changePwd(this.prevousElementSibling, '<%=$this->Data['user']%>')">
-                                               <img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_ok.png" alt="<%[ Save ]%>" title="<%[ Save ]%>"/>
-                                       </a>
-                                       <a href="javascript:void(0)" onclick="Users.cancelChangePwd(this)">
-                                               <img src="<%=$this->getPage()->getTheme()->getBaseUrl()%>/icon_err.png" alt="<%[ Close ]%>" title="<%[ Close ]%>" />
-                                       </a>
-                               </span>
-                       </td>
-               </tr>
-       </prop:ItemTemplate>
-       <prop:FooterTemplate>
-               </table>
-       </prop:FooterTemplate>
-</com:TActiveRepeater>
-<com:TCallback ID="UserAction" OnCallback="TemplateControl.userAction" ClientSide.OnComplete="Users.hide_loader();" />
-<script type="text/javascript">
-       var send_user_action = function(action, param, value) {
-               Users.current_action = action;
-               if (!value) {
-                       value = '';
-               }
-               var user_action_callback = <%=$this->UserAction->ActiveControl->Javascript%>;
-               user_action_callback.setCallbackParameter([action, param, value].join(';'));
-               user_action_callback.dispatch();
-       };
-       Users.txt = {
-               enter_login: '<%[ Please enter login. ]%>',
-               invalid_login: '<%[ Invalid login value. Login may contain a-z A-Z 0-9 characters. ]%>',
-               invalid_pwd: '<%[ Password must be longer than 4 chars. ]%>'
-       };
-       Users.action_callback = send_user_action;
-       Users.validators = { user_pattern: new RegExp('^<%=BasicUserConfig::USER_PATTERN%>$') };
-       Users.init();
-</script>
index 41d2d64be30dadb94b6eac672c4aa2947caa18bb..9673c8e1964bf5cad1163b32918aad91aad42c9b 100644 (file)
@@ -1,6 +1,7 @@
 <urls>
        <!-- webGUI endpoints -->
        <url ServiceParameter="Dashboard" pattern="web/" />
+       <url ServiceParameter="LoginPage" pattern="web/login/" />
        <url ServiceParameter="JobHistoryList" pattern="web/job/history/" />
        <url ServiceParameter="JobHistoryView" pattern="web/job/history/{jobid}/" parameters.jobid="\d+" />
        <url ServiceParameter="JobList" pattern="web/job/" />
@@ -29,7 +30,7 @@
        <url ServiceParameter="Graphs" pattern="web/graphs/" />
        <url ServiceParameter="Console" pattern="web/console/" />
        <url ServiceParameter="ApplicationSettings" pattern="web/settings/" />
-       <url ServiceParameter="Users" pattern="web/users/" />
+       <url ServiceParameter="Security" pattern="web/security/" />
        <url ServiceParameter="ConfigureHosts" pattern="web/configure/" />
        <url ServiceParameter="NewResource" pattern="web/new/{component_type}/{component_name}/{resource_type}/" parameters.component_type="\w+" parameters.component_name="[a-zA-Z0-9:.\-_ ]+" parameters.resource_type="\w+" />
        <url ServiceParameter="NewResource" pattern="web/new/{host}/{component_type}/{component_name}/{resource_type}/" parameters.host="[a-zA-Z0-9:.\-_ ]+" parameters.component_type="\w+" parameters.component_name="[a-zA-Z0-9:.\-_ ]+" parameters.resource_type="\w+" />
index 221eb22079b504647ceb4ded03809dce5fff8163..ff55430f3c235623ff72db00049383f0b4a1d475 100644 (file)
                <!-- authentication and authorization modules -->
                <module id="auth_basic" class="Application.Common.Class.AuthBasic" />
                <module id="auth_oauth2" class="Application.Common.Class.AuthOAuth2" />
+               <module id="ldap" class="Application.Common.Class.Ldap" />
+               <!-- cryptographic modules -->
+               <module id="crypto" class="Application.Common.Class.Crypto" />
+               <module id="apr1md5" class="Application.Common.Class.Apr1Md5" />
+               <module id="bcrypt" class="Application.Common.Class.BCrypt" />
+               <module id="sha1" class="Application.Common.Class.Sha1" />
+               <module id="ssha1" class="Application.Common.Class.Ssha1" />
+               <module id="sha256" class="Application.Common.Class.Sha256" />
+               <module id="sha512" class="Application.Common.Class.Sha512" />
                <!-- communication modules -->
                <module id="request" class="THttpRequest" UrlManager="url_manager" UrlFormat="HiddenPath" />
                <module id="url_manager" class="Application.Common.Class.BaculumUrlMapping" EnableCustomUrl="true" />
index 430d44436985aae75f03afb9920b5c81ad80d1d0..f944d9a2d04062468875e7f5fac766eeb44dc45a 100644 (file)
@@ -345,3 +345,23 @@ table.component td:nth-of-type(1) {
 .info {
        color: #333;
 }
+
+.error {
+       color: red;
+}
+
+/* Option row */
+.opt_row {
+       line-height: 36px;
+}
+
+/* Required option */
+.opt_req {
+       line-height: 40px;
+       display: inline;
+       margin-left: 2px;
+}
+
+.field_invalid {
+       border: 1px solid red !important;
+}
diff --git a/gui/baculum/themes/Baculum-v2/logo_xl.png b/gui/baculum/themes/Baculum-v2/logo_xl.png
new file mode 100644 (file)
index 0000000..fc75c2e
Binary files /dev/null and b/gui/baculum/themes/Baculum-v2/logo_xl.png differ