From: Marcin Haba Date: Tue, 29 Sep 2020 04:16:33 +0000 (+0200) Subject: baculum: New features and improvements to multi-user interface and restricted access X-Git-Tag: Release-9.6.7~52 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5cbc82f847132f15495e665b6de5d214c606dc38;p=thirdparty%2Fbacula.git baculum: New features and improvements to multi-user interface and restricted access Implement the following functions on security page: - Console ACLs - OAuth2 clients - API hosts --- diff --git a/gui/baculum/protected/API/Class/BaculaConfig.php b/gui/baculum/protected/API/Class/BaculaConfig.php index 49278af5b..b083469a1 100644 --- a/gui/baculum/protected/API/Class/BaculaConfig.php +++ b/gui/baculum/protected/API/Class/BaculaConfig.php @@ -59,21 +59,21 @@ class BaculaConfig extends ConfigFileModule { * @access public * @param string $component_type Bacula component type * @param array $config config + * @param string $file config file path * @return array validation result, validation output and write to config result */ - public function setConfig($component_type, array $config) { + public function setConfig($component_type, array $config, $file = null) { $result = array('is_valid' => false, 'save_result' => false, 'output' => null); $config_content = $this->prepareConfig($config, self::CONFIG_FILE_FORMAT); $validation = $this->validateConfig($component_type, $config_content); $result['is_valid'] = $validation['is_valid']; $result['result'] = $validation['result']; if ($result['is_valid'] === true) { - $tool_config = $this->getModule('api_config')->getJSONToolConfig($component_type); - /** - * @TODO: Add option to save config in a specific directory. Users may want - * to see config and put it manually to production. - */ - $result['save_result'] = $this->writeConfig($config, $tool_config['cfg'], self::CONFIG_FILE_FORMAT); + if (is_null($file)) { + $tool_config = $this->getModule('api_config')->getJSONToolConfig($component_type); + $file = $tool_config['cfg']; + } + $result['save_result'] = $this->writeConfig($config, $file, self::CONFIG_FILE_FORMAT); } return $result; } diff --git a/gui/baculum/protected/API/Class/BaculumAPIServer.php b/gui/baculum/protected/API/Class/BaculumAPIServer.php index bf567931f..568c2a1dd 100644 --- a/gui/baculum/protected/API/Class/BaculumAPIServer.php +++ b/gui/baculum/protected/API/Class/BaculumAPIServer.php @@ -340,7 +340,7 @@ abstract class BaculumAPIServer extends TPage { private function delete() { $id = null; if ($this->Request->contains('id')) { - $id = intval($this->Request['id']); + $id = $this->Request['id']; } $this->remove($id); } diff --git a/gui/baculum/protected/API/Class/Bconsole.php b/gui/baculum/protected/API/Class/Bconsole.php index cd1575014..3eccd2fe7 100644 --- a/gui/baculum/protected/API/Class/Bconsole.php +++ b/gui/baculum/protected/API/Class/Bconsole.php @@ -44,17 +44,17 @@ class Bconsole extends APIModule { const PTYPE_CONFIRM_YES_CMD = 3; const PTYPE_CONFIRM_YES_BG_CMD = 4; - const BCONSOLE_COMMAND_PATTERN = "%s%s -c %s %s 2>&1 <&1 <%s 2>&1 &"; - const BCONSOLE_CONFIRM_YES_COMMAND_PATTERN = "%s%s -c %s %s 2>&1 <&1 <%s 2>&1 &"; - const BCONSOLE_API_COMMAND_PATTERN = "%s%s -c %s %s 2>&1 <&1 <&1"; const OUTPUT_FILE_PREFIX = 'output_'; diff --git a/gui/baculum/protected/API/JavaScript/misc.js b/gui/baculum/protected/API/JavaScript/misc.js index 22c9aa5d3..0b6a33963 100644 --- a/gui/baculum/protected/API/JavaScript/misc.js +++ b/gui/baculum/protected/API/JavaScript/misc.js @@ -20,7 +20,8 @@ var OAuth2Scopes = [ 'schedules', 'config', 'status', - 'actions' + 'actions', + 'oauth2' ]; var set_scopes = function(field_id) { document.getElementById(field_id).value = OAuth2Scopes.join(' '); diff --git a/gui/baculum/protected/API/Pages/API/OAuth2Client.php b/gui/baculum/protected/API/Pages/API/OAuth2Client.php new file mode 100644 index 000000000..52fbbf4dc --- /dev/null +++ b/gui/baculum/protected/API/Pages/API/OAuth2Client.php @@ -0,0 +1,322 @@ + + * @category API + * @package Baculum API + */ +class OAuth2Client extends BaculumAPIServer { + + public function get() { + $oauth2_client_id = $this->Request->contains('id') ? $this->Request['id'] : 0; + $client_id = $this->getModule('oauth2')->validateClientId($oauth2_client_id) ? $oauth2_client_id : null; + if (is_string($client_id)) { + $oauth2_cfg = $this->getModule('oauth2_config')->getConfig($client_id); + if (count($oauth2_cfg) > 0) { + $this->output = $oauth2_cfg; + $this->error = OAuth2Error::ERROR_NO_ERRORS; + } else { + $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_DOES_NOT_EXIST; + $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_DOES_NOT_EXIST; + } + } else { + $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_DOES_NOT_EXIST; + $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_DOES_NOT_EXIST; + } + } + + public function create($params) { + $oauth2 = $this->getModule('oauth2'); + $oauth2_config = $this->getModule('oauth2_config'); + $misc = $this->getModule('misc'); + $oauth2_cfg = $oauth2_config->getConfig(); + + if (property_exists($params, 'client_id') && key_exists($params->client_id, $oauth2_cfg)) { + $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_ALREADY_EXISTS; + $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_ALREADY_EXISTS; + return; + } + + if (property_exists($params, 'client_id') && $oauth2->validateClientId($params->client_id)) { + $oauth2_cfg[$params->client_id]['client_id'] = $params->client_id; + } else { + $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_INVALID_CLIENT_ID; + $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_INVALID_CLIENT_ID; + return; + } + + if (property_exists($params, 'client_secret') && $oauth2->validateClientSecret($params->client_secret)) { + $oauth2_cfg[$params->client_id]['client_secret'] = $params->client_secret; + } else { + $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_INVALID_CLIENT_SECRET; + $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_INVALID_CLIENT_SECRET; + return; + } + + if (property_exists($params, 'redirect_uri') && $oauth2->validateRedirectUri($params->redirect_uri)) { + $oauth2_cfg[$params->client_id]['redirect_uri'] = $params->redirect_uri; + } else { + $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_INVALID_REDIRECT_URI; + $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_INVALID_REDIRECT_URI; + return; + } + + if (property_exists($params, 'scope') && $oauth2->validateScopes($params->scope)) { + $oauth2_cfg[$params->client_id]['scope'] = $params->scope; + } else { + $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_INVALID_SCOPE; + $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_INVALID_SCOPE; + return; + } + + if (property_exists($params, 'bconsole_cfg_path')) { + if ($misc->isValidPath($params->bconsole_cfg_path)) { + $oauth2_cfg[$params->client_id]['bconsole_cfg_path'] = $params->bconsole_cfg_path; + } else { + $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_INVALID_BCONSOLE_CFG_PATH; + $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_INVALID_BCONSOLE_CFG_PATH; + return; + } + } else { + $oauth2_cfg[$params->client_id]['bconsole_cfg_path'] = ''; + } + + if (property_exists($params, 'name') && !empty($params->name)) { + if ($misc->isValidName($params->name)) { + $oauth2_cfg[$params->client_id]['name'] = $params->name; + } else { + $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_INVALID_NAME; + $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_INVALID_NAME; + return; + } + } else { + $oauth2_cfg[$params->client_id]['name'] = ''; + } + + if (property_exists($params, 'console') && property_exists($params, 'director')) { + if (!$misc->isValidName($params->console)) { + $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_INVALID_CONSOLE; + $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_INVALID_CONSOLE; + return; + } + if (!$misc->isValidName($params->director)) { + $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_INVALID_DIRECTOR; + $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_INVALID_DIRECTOR; + return; + } + $bs = $this->getModule('bacula_setting'); + + $dir_cfg = $bs->getConfig('bcons', 'Director', $params->director); + if ($dir_cfg['exitcode'] != 0) { + $this->output = $dir_cfg->output; + $this->error = OAuth2Error::ERROR_INTERNAL_ERROR; + return; + } + + $console_cfg = $bs->getConfig('dir', 'Console', $params->console); + if ($console_cfg['exitcode'] != 0) { + $this->output = $console_cfg->output; + $this->error = OAuth2Error::ERROR_INTERNAL_ERROR; + return; + } + + $cfg = [ + [ + 'Director' => [ + 'Name' => '"' . $dir_cfg['output']['Name'] . '"', + 'DirPort' => $dir_cfg['output']['DirPort'], + 'Address' => $dir_cfg['output']['Address'], + 'Password' => 'XXXX' + ], + 'Console' => [ + 'Name' => '"' . $console_cfg['output']['Name'] . '"', + 'Password' => '"' . $console_cfg['output']['Password'] . '"' + ] + ] + ]; + $json_tools = $this->getModule('api_config')->getConfig('jsontools'); + $dir = $json_tools['bconfig_dir']; + $file = sprintf('%s/bconsole-%s.cfg', $dir, $console_cfg['output']['Name']); + $this->getModule('bacula_config')->setConfig('bcons', $cfg, $file); + $oauth2_cfg[$params->client_id]['bconsole_cfg_path'] = $file; + } + + // save config + $result = $oauth2_config->setConfig($oauth2_cfg); + + if ($result) { + $this->output = $oauth2_cfg; + $this->error = OAuth2Error::ERROR_NO_ERRORS; + } else { + $this->output = OAuth2Error::MSG_ERROR_INTERNAL_ERROR; + $this->error = OAuth2Error::ERROR_INTERNAL_ERROR; + } + } + + public function set($id, $params) { + $oauth2_client_id = property_exists($params, 'client_id') ? $params->client_id : 0; + $client_id = $this->getModule('oauth2')->validateClientId($oauth2_client_id) ? $oauth2_client_id : null; + if (!is_string($client_id)) { + $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_DOES_NOT_EXIST; + $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_DOES_NOT_EXIST; + return; + } + + $oauth2 = $this->getModule('oauth2'); + $oauth2_config = $this->getModule('oauth2_config'); + $misc = $this->getModule('misc'); + $oauth2_cfg = $oauth2_config->getConfig(); + + if (!key_exists($client_id, $oauth2_cfg)) { + $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_DOES_NOT_EXIST; + $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_DOES_NOT_EXIST; + return; + } + + if (property_exists($params, 'client_secret')) { + if ($oauth2->validateClientSecret($params->client_secret)) { + $oauth2_cfg[$client_id]['client_secret'] = $params->client_secret; + } else { + $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_INVALID_CLIENT_SECRET; + $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_INVALID_CLIENT_SECRET; + return; + } + } + if (property_exists($params, 'redirect_uri')) { + if ($oauth2->validateRedirectUri($params->redirect_uri)) { + $oauth2_cfg[$client_id]['redirect_uri'] = $params->redirect_uri; + } else { + $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_INVALID_REDIRECT_URI; + $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_INVALID_REDIRECT_URI; + return; + } + } + if (property_exists($params, 'scope')) { + if ($oauth2->validateScopes($params->scope)) { + $oauth2_cfg[$client_id]['scope'] = $params->scope; + } else { + $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_INVALID_SCOPE; + $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_INVALID_SCOPE; + return; + } + } + if (property_exists($params, 'bconsole_cfg_path')) { + if ($misc->isValidPath($params->bconsole_cfg_path)) { + $oauth2_cfg[$client_id]['bconsole_cfg_path'] = $params->bconsole_cfg_path; + } else { + $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_INVALID_BCONSOLE_CFG_PATH; + $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_INVALID_BCONSOLE_CFG_PATH; + return; + } + } + if (property_exists($params, 'name') && !empty($params->name)) { + if ($misc->isValidName($params->name)) { + $oauth2_cfg[$client_id]['name'] = $params->name; + } else { + $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_INVALID_NAME; + $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_INVALID_NAME; + return; + } + } else { + $oauth2_cfg[$client_id]['name'] = ''; + } + if (property_exists($params, 'console') && property_exists($params, 'director')) { + if (!$misc->isValidName($params->console)) { + $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_INVALID_CONSOLE; + $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_INVALID_CONSOLE; + return; + } + if (!$misc->isValidName($params->director)) { + $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_INVALID_DIRECTOR; + $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_INVALID_DIRECTOR; + return; + } + $bs = $this->getModule('bacula_setting'); + + $dir_cfg = $bs->getConfig('bcons', 'Director', $params->director); + if ($dir_cfg['exitcode'] != 0) { + $this->output = $dir_cfg->output; + $this->error = OAuth2Error::ERROR_INTERNAL_ERROR; + return; + } + + $console_cfg = $bs->getConfig('dir', 'Console', $params->console); + if ($console_cfg['exitcode'] != 0) { + $this->output = $console_cfg->output; + $this->error = OAuth2Error::ERROR_INTERNAL_ERROR; + return; + } + + $cfg = [ + [ + 'Director' => [ + 'Name' => '"' . $dir_cfg['output']['Name'] . '"', + 'DirPort' => $dir_cfg['output']['DirPort'], + 'Address' => $dir_cfg['output']['Address'], + 'Password' => 'XXXX' + ], + 'Console' => [ + 'Name' => '"' . $console_cfg['output']['Name'] . '"', + 'Password' => '"' . $console_cfg['output']['Password'] . '"' + ] + ] + ]; + $json_tools = $this->getModule('api_config')->getConfig('jsontools'); + $dir = $json_tools['bconfig_dir']; + $file = sprintf('%s/bconsole-%s.cfg', $dir, $console_cfg['output']['Name']); + $this->getModule('bacula_config')->setConfig('bcons', $cfg, $file); + $oauth2_cfg[$params->client_id]['bconsole_cfg_path'] = $file; + } + + $result = $oauth2_config->setConfig($oauth2_cfg); + if ($result) { + $this->output = $oauth2_cfg; + $this->error = OAuth2Error::ERROR_NO_ERRORS; + } else { + $this->output = OAuth2Error::MSG_ERROR_INTERNAL_ERROR; + $this->error = OAuth2Error::ERROR_INTERNAL_ERROR; + } + } + + public function remove($id) { + $oauth2 = $this->getModule('oauth2_config'); + $oauth2_cfg = $oauth2->getConfig(); + if (key_exists($id, $oauth2_cfg)) { + unset($oauth2_cfg[$id]); + $result = $oauth2->setConfig($oauth2_cfg); + if ($result) { + $this->output = []; + $this->error = OAuth2Error::ERROR_NO_ERRORS; + } else { + $this->output = OAuth2Error::MSG_ERROR_INTERNAL_ERROR; + $this->error = OAuth2Error::ERROR_INTERNAL_ERROR; + } + } else { + $this->output = OAuth2Error::MSG_ERROR_OAUTH2_CLIENT_DOES_NOT_EXIST; + $this->error = OAuth2Error::ERROR_OAUTH2_CLIENT_DOES_NOT_EXIST; + } + } +} +?> diff --git a/gui/baculum/protected/API/Pages/API/OAuth2Clients.php b/gui/baculum/protected/API/Pages/API/OAuth2Clients.php new file mode 100644 index 000000000..08d07c03b --- /dev/null +++ b/gui/baculum/protected/API/Pages/API/OAuth2Clients.php @@ -0,0 +1,38 @@ + + * @category API + * @package Baculum API + */ +class OAuth2Clients extends BaculumAPIServer { + + public function get() { + $oauth2_cfg = $this->getModule('oauth2_config')->getConfig(); + $this->output = array_values($oauth2_cfg); + $this->error = ClientError::ERROR_NO_ERRORS; + } +} +?> diff --git a/gui/baculum/protected/API/Pages/API/endpoints.xml b/gui/baculum/protected/API/Pages/API/endpoints.xml index e40e59047..2dfb161b9 100644 --- a/gui/baculum/protected/API/Pages/API/endpoints.xml +++ b/gui/baculum/protected/API/Pages/API/endpoints.xml @@ -93,4 +93,7 @@ + + + diff --git a/gui/baculum/protected/API/openapi_baculum.json b/gui/baculum/protected/API/openapi_baculum.json index c9b8f88a3..f29491277 100644 --- a/gui/baculum/protected/API/openapi_baculum.json +++ b/gui/baculum/protected/API/openapi_baculum.json @@ -98,6 +98,15 @@ }, "Schedule": { "$ref": "#/definitions/Schedule" + }, + "OAuth2Clients": { + "type": "array", + "items": { + "$ref": "#/definitions/OAuth2Client" + } + }, + "OAuth2Client": { + "$ref": "#/definitions/OAuth2Client" } }, "parameters": { @@ -4260,7 +4269,7 @@ }] } }, - "/api/v1/status/director/": { + "/api/v1/status/director": { "get": { "tags": ["status"], "summary": "Get director status", @@ -4553,7 +4562,7 @@ ] } }, - "/api/v1/status/storage/": { + "/api/v1/status/storage": { "get": { "tags": ["status"], "summary": "Get storage status", @@ -4931,7 +4940,7 @@ ] } }, - "/api/v1/status/client/": { + "/api/v1/status/client": { "get": { "tags": ["status"], "summary": "Get client status", @@ -5187,6 +5196,285 @@ } ] } + }, + "/api/v1/oauth2/clients": { + "get": { + "tags": ["oauth2"], + "summary": "OAuth2 client account list", + "description": "Get OAuth2 client account list.", + "responses": { + "200": { + "description": "List of OAuth2 clients properties", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "output": { + "$ref": "#/components/schemas/OAuth2Clients" + }, + "error": { + "type": "integer", + "description": "Error code", + "enum": [0, 6, 7, 1000] + } + } + } + } + } + } + } + } + }, + "/api/v1/oauth2/clients/{client_id}": { + "get": { + "tags": ["oauth2"], + "summary": "Specific OAuth2 client account config", + "description": "Get specific OAuth2 client account config", + "responses": { + "200": { + "description": "Specific OAuth2 client account config", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "output": { + "$ref": "#/components/schemas/OAuth2Client" + }, + "error": { + "type": "integer", + "description": "Error code", + "enum": [0, 6, 7, 120, 1000] + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "client_id", + "in": "path", + "required": true, + "description": "Client identifier (OAuth2 Client ID)", + "schema": { + "type": "string" + } + } + ] + }, + "put": { + "tags": ["oauth2"], + "summary": "Set OAuth2 client settings", + "description": "Set specific OAuth2 client settings", + "consumes": [ "application/x-www-form-urlencoded" ], + "responses": { + "200": { + "description": "Set OAuth2 client settings", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "output": { + "type": "object", + "description": "Updated OAuth2 client settings" + }, + "error": { + "type": "integer", + "description": "Error code", + "enum": [0, 6, 7, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 1000] + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "client_id", + "in": "body", + "required": true, + "description": "Client identifier (OAuth2 Client ID)", + "schema": { + "type": "string" + } + }, + { + "name": "client_secret", + "in": "body", + "required": true, + "description": "OAuth2 client secret", + "schema": { + "type": "string" + } + }, + { + "name": "redirect_uri", + "in": "body", + "required": true, + "description": "OAuth2 redirect URI (OAuth2 callback)", + "schema": { + "type": "string" + } + }, + { + "name": "scope", + "in": "body", + "required": true, + "description": "Comma separated OAuth2 scopes", + "schema": { + "type": "string" + } + }, + { + "name": "bconsole_cfg_path", + "in": "body", + "required": true, + "description": "Bconsole config file path", + "schema": { + "type": "string" + } + }, + { + "name": "name", + "in": "body", + "required": false, + "description": "OAuth2 client account name", + "schema": { + "type": "string" + } + } + ] + }, + "post": { + "tags": ["oauth2"], + "summary": "Create OAuth2 client settings", + "description": "Create specific OAuth2 client settings", + "consumes": [ "application/x-www-form-urlencoded" ], + "responses": { + "200": { + "description": "Create OAuth2 client settings", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "output": { + "type": "object", + "description": "New OAuth2 client settings" + }, + "error": { + "type": "integer", + "description": "Error code", + "enum": [0, 6, 7, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 1000] + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "client_id", + "in": "body", + "required": true, + "description": "Client identifier (OAuth2 Client ID)", + "schema": { + "type": "string" + } + }, + { + "name": "client_secret", + "in": "body", + "required": true, + "description": "OAuth2 client secret", + "schema": { + "type": "string" + } + }, + { + "name": "redirect_uri", + "in": "body", + "required": true, + "description": "OAuth2 redirect URI (OAuth2 callback)", + "schema": { + "type": "string" + } + }, + { + "name": "scope", + "in": "body", + "required": true, + "description": "Comma separated OAuth2 scopes", + "schema": { + "type": "string" + } + }, + { + "name": "bconsole_cfg_path", + "in": "body", + "required": true, + "description": "Bconsole config file path", + "schema": { + "type": "string" + } + }, + { + "name": "name", + "in": "body", + "required": false, + "description": "OAuth2 client account name", + "schema": { + "type": "string" + } + } + ] + }, + "delete": { + "tags": ["oauth2"], + "summary": "Delete OAuth2 client account", + "description": "Delete OAuth2 client account.", + "responses": { + "200": { + "description": "Delete OAuth2 client account", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "output": { + "type": "array", + "items": { + } + }, + "error": { + "type": "integer", + "description": "Error code", + "enum": [0, 6, 7, 120, 1000] + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "client_id", + "in": "path", + "required": true, + "description": "Client identifier (OAuth2 Client ID)", + "schema": { + "type": "string" + } + } + ] + } } }, "definitions": { @@ -5819,6 +6107,37 @@ "pattern": "[a-zA-Z0-9:.-_ ]+" } } + }, + "OAuth2Client": { + "type": "object", + "properties": { + "client_id": { + "description": "OAuth2 client identifier (Client ID)", + "type": "string", + "pattern": "[a-zA-Z0-9-_]{32}" + }, + "client_secret": { + "description": "OAuth2 client secret", + "type": "string", + "pattern": "\\S{6,50}" + }, + "redirect_uri": { + "description": "Redirect URI (OAuth2 callback)", + "type": "string" + }, + "scope": { + "description": "Comma separated OAuth2 scopes", + "type": "string" + }, + "bconsole_cfg_path": { + "description": "Dedicated Bconsole configuration file", + "type": "string" + }, + "name": { + "description": "OAuth2 client name (optional)", + "type": "string" + } + } } } } diff --git a/gui/baculum/protected/Common/Class/Errors.php b/gui/baculum/protected/Common/Class/Errors.php index 5e4c3e3cf..e08a29143 100644 --- a/gui/baculum/protected/Common/Class/Errors.php +++ b/gui/baculum/protected/Common/Class/Errors.php @@ -160,7 +160,6 @@ class JSONToolsError extends GenericError { const ERROR_JSON_TOOLS_UNABLE_TO_PARSE_OUTPUT = 83; const ERROR_JSON_TOOL_NOT_CONFIGURED = 84; - const MSG_ERROR_JSON_TOOLS_DISABLED = 'JSON tools support is disabled.'; const MSG_ERROR_JSON_TOOLS_CONNECTION_PROBLEM = 'Problem with connection to a JSON tool.'; const MSG_ERROR_JSON_TOOLS_WRONG_EXITCODE = 'JSON tool returned wrong exitcode.'; @@ -197,10 +196,34 @@ class ActionsError extends GenericError { const ERROR_ACTIONS_WRONG_EXITCODE = 112; const ERROR_ACTIONS_NOT_CONFIGURED = 113; - const MSG_ERROR_ACTIONS_ACTION_DOES_NOT_EXIST = 'Action does not exist.'; const MSG_ERROR_ACTIONS_DISABLED = 'Actions support is disabled.'; const MSG_ERROR_ACTIONS_WRONG_EXITCODE = 'Action command returned wrong exitcode.'; const MSG_ERROR_ACTIONS_NOT_CONFIGURED = 'Action is not configured.'; } + +class OAuth2Error extends GenericError { + + const ERROR_OAUTH2_CLIENT_DOES_NOT_EXIST = 120; + const ERROR_OAUTH2_CLIENT_ALREADY_EXISTS = 121; + const ERROR_OAUTH2_CLIENT_INVALID_CLIENT_ID = 122; + const ERROR_OAUTH2_CLIENT_INVALID_CLIENT_SECRET = 123; + const ERROR_OAUTH2_CLIENT_INVALID_REDIRECT_URI = 124; + const ERROR_OAUTH2_CLIENT_INVALID_SCOPE = 125; + const ERROR_OAUTH2_CLIENT_INVALID_BCONSOLE_CFG_PATH = 126; + const ERROR_OAUTH2_CLIENT_INVALID_NAME = 127; + const ERROR_OAUTH2_CLIENT_INVALID_CONSOLE = 128; + const ERROR_OAUTH2_CLIENT_INVALID_DIRECTOR = 129; + + const MSG_ERROR_OAUTH2_CLIENT_DOES_NOT_EXIST = 'OAuth2 client does not exist.'; + const MSG_ERROR_OAUTH2_CLIENT_ALREADY_EXISTS = 'OAuth2 client already exists.'; + const MSG_ERROR_OAUTH2_CLIENT_INVALID_CLIENT_ID = 'Invalid OAuth2 client ID.'; + const MSG_ERROR_OAUTH2_CLIENT_INVALID_CLIENT_SECRET = 'Invalid OAuth2 client secret.'; + const MSG_ERROR_OAUTH2_CLIENT_INVALID_REDIRECT_URI = 'Invalid OAuth2 redirect URI.'; + const MSG_ERROR_OAUTH2_CLIENT_INVALID_SCOPE = 'Invalid OAuth2 scope.'; + const MSG_ERROR_OAUTH2_CLIENT_INVALID_BCONSOLE_CFG_PATH = 'Invalid Bconsole config path.'; + const MSG_ERROR_OAUTH2_CLIENT_INVALID_NAME = 'Invalid OAuth2 client name.'; + const MSG_ERROR_OAUTH2_CLIENT_INVALID_CONSOLE = 'Invalid Console name.'; + const MSG_ERROR_OAUTH2_CLIENT_INVALID_DIRECTOR = 'Invalid Director name.'; +} ?> diff --git a/gui/baculum/protected/Common/Class/OAuth2.php b/gui/baculum/protected/Common/Class/OAuth2.php index 7685eb773..364c9cefc 100644 --- a/gui/baculum/protected/Common/Class/OAuth2.php +++ b/gui/baculum/protected/Common/Class/OAuth2.php @@ -47,6 +47,13 @@ abstract class OAuth2 extends CommonModule { */ const CLIENT_SECRET_PATTERN = '^\S{6,50}$'; + /** + * Very basic redirect URI validation pattern. + * + * @see https://tools.ietf.org/html/rfc3986#appendix-B + */ + const REDIRECT_URI_PATTERN = '^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$'; + /** * Authorization ID (known also as 'authorization_code') regular expression pattern * allow to set hexadecimal value of the authorization ID with length equal 40 chars. @@ -85,7 +92,7 @@ abstract class OAuth2 extends CommonModule { /** * Scope pattern. */ - const SCOPE_PATTERN = '^[a-zA-Z:]+$'; + const SCOPE_PATTERN = '^[a-zA-Z0-9:]+$'; /** * State pattern. @@ -138,6 +145,17 @@ abstract class OAuth2 extends CommonModule { return (preg_match('/' . self::CLIENT_SECRET_PATTERN . '/', $secret) === 1); } + /** + * Validate redirect URI syntax. + * + * @access public + * @param string $redirect_uri redirect URI value + * @return boolean true if redirect URI is valid, otherwise false + */ + final public function validateRedirectUri($redirect_uri) { + return (preg_match('/' . self::REDIRECT_URI_PATTERN . '/', $redirect_uri) === 1); + } + /** * Generate (pseudo-)random authorization identifier. * diff --git a/gui/baculum/protected/Web/Class/BaculumAPIClient.php b/gui/baculum/protected/Web/Class/BaculumAPIClient.php index 9501bb65e..0f83be61e 100644 --- a/gui/baculum/protected/Web/Class/BaculumAPIClient.php +++ b/gui/baculum/protected/Web/Class/BaculumAPIClient.php @@ -129,7 +129,7 @@ class BaculumAPIClient extends WebModule { $this->authToHost($host, $host_cfg); $auth = OAuth2Record::findByPk($host); } - if (is_array($auth) && array_key_exists('tokens', $auth)) { + if (is_array($auth) && key_exists('tokens', $auth) && is_array($auth['tokens'])) { $headers[] = "Authorization: {$auth['tokens']['token_type']} {$auth['tokens']['access_token']}"; } } diff --git a/gui/baculum/protected/Web/JavaScript/misc.js b/gui/baculum/protected/Web/JavaScript/misc.js index 5b9702fb8..e1a1e631d 100644 --- a/gui/baculum/protected/Web/JavaScript/misc.js +++ b/gui/baculum/protected/Web/JavaScript/misc.js @@ -369,6 +369,23 @@ function render_time_period(data, type, row) { return ret; } +function render_string_short(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; +} + function set_formatters() { Formatters.set_formatters(); } @@ -891,6 +908,26 @@ W3SubTabs = { W3TabsCommon.open.call(this, btn_id, item_id); } }; +var OAuth2Scopes = [ + 'console', + 'jobs', + 'directors', + 'clients', + 'storages', + 'volumes', + 'pools', + 'bvfs', + 'joblog', + 'filesets', + 'schedules', + 'config', + 'status', + 'actions', + 'oauth2' +]; +var set_scopes = function(field_id) { + document.getElementById(field_id).value = OAuth2Scopes.join(' '); +} function estimate_job(jobs, job, level) { var bytes = 0; diff --git a/gui/baculum/protected/Web/Lang/en/messages.mo b/gui/baculum/protected/Web/Lang/en/messages.mo index 397ad936f..368f5a97d 100644 Binary files a/gui/baculum/protected/Web/Lang/en/messages.mo and b/gui/baculum/protected/Web/Lang/en/messages.mo differ diff --git a/gui/baculum/protected/Web/Lang/en/messages.po b/gui/baculum/protected/Web/Lang/en/messages.po index f85f69e03..c4e676d70 100644 --- a/gui/baculum/protected/Web/Lang/en/messages.po +++ b/gui/baculum/protected/Web/Lang/en/messages.po @@ -2992,3 +2992,72 @@ msgstr "This type of authentication is fully realized by Baculum Web. To authent msgid "This type of authentication is realized by an external directory service. To authenticate it uses the Baculum Web login form. The web server basic authentication can be disabled in this method." msgstr "This type of authentication is realized by an external directory service. To authenticate it uses the Baculum Web login form. The web server basic authentication can be disabled in this method." + +msgid "Console ACLs" +msgstr "Console ACLs" + +msgid "Add new console" +msgstr "Add new console" + +msgid "Edit console" +msgstr "Edit console" + +msgid "Add console" +msgstr "Add console" + +msgid "OAuth2 clients" +msgstr "OAuth2 clients" + +msgid "Short name" +msgstr "Short name" + +msgid "Add new OAuth2 client" +msgstr "Add new OAuth2 client" + +msgid "Short name:" +msgstr "Short name:" + +msgid "Add OAuth2 client account" +msgstr "Add OAuth2 client account" + +msgid "set all scopes" +msgstr "set all scopes" + +msgid "Edit OAuth2 client account" +msgstr "Edit OAuth2 client account" + +msgid "Add new API host" +msgstr "Add new API host" + +msgid "Get existing API host settings:" +msgstr "Get existing API host settings:" + +msgid "Get existing OAuth2 client settings:" +msgstr "Get existing OAuth2 client settings:" + +msgid "API hosts" +msgstr "API hosts" + +msgid "Edit API host" +msgstr "Edit API host" + +msgid "Create dedicated Bconsole config file:" +msgstr "Create dedicated Bconsole config file:" + +msgid "Console ACL to use in new Bconsole config file:" +msgstr "Console ACL to use in new Bconsole config file:" + +msgid "Director for Bconsole config:" +msgstr "Director for Bconsole config:" + +msgid "The console ACLs enable to define available resources for users. The consoles are used in the Bacula configuration on the API host side. There is possible to assign the consoles to the OAuth2 clients that can be used in API hosts. The assign relation is: Console ACL -> OAuth2 client -> API host -> User account." +msgstr "The console ACLs enable to define available resources for users. The consoles are used in the Bacula configuration on the API host side. There is possible to assign the consoles to the OAuth2 clients that can be used in API hosts. The assign relation is: Console ACL -> OAuth2 client -> API host -> User account." + +msgid "The OAuth2 clients are configured on the API hosts. To create the OAuth2 clients from this page, you need to have an 'oauth2' scope in OAuth2 account related to your current admin user account. The OAuth2 client accounts are also possible to create directly in the panel of the Baculum API." +msgstr "The OAuth2 clients are configured on the API hosts. To create the OAuth2 clients from this page, you need to have an 'oauth2' scope in OAuth2 account related to your current admin user account. The OAuth2 client accounts are also possible to create directly in the panel of the Baculum API." + +msgid "The API hosts define connection parameters to hosts with the Baculum API instances. You can create the API host connections dedicated for specific users by assigning API hosts to them on the Users tab. There is possible to create many API host connections to the same Baculum API instance." +msgstr "The API hosts define connection parameters to hosts with the Baculum API instances. You can create the API host connections dedicated for specific users by assigning API hosts to them on the Users tab. There is possible to create many API host connections to the same Baculum API instance." + +msgid "Set all CommandAcls used by Baculum Web" +msgstr "Set all CommandAcls used by Baculum Web" diff --git a/gui/baculum/protected/Web/Lang/ja/messages.mo b/gui/baculum/protected/Web/Lang/ja/messages.mo index 41fbdd7bc..51c51b8f4 100644 Binary files a/gui/baculum/protected/Web/Lang/ja/messages.mo and b/gui/baculum/protected/Web/Lang/ja/messages.mo differ diff --git a/gui/baculum/protected/Web/Lang/ja/messages.po b/gui/baculum/protected/Web/Lang/ja/messages.po index e3b8c9797..51cb5ae7e 100644 --- a/gui/baculum/protected/Web/Lang/ja/messages.po +++ b/gui/baculum/protected/Web/Lang/ja/messages.po @@ -3078,3 +3078,72 @@ msgstr "This type of authentication is fully realized by Baculum Web. To authent msgid "This type of authentication is realized by an external directory service. To authenticate it uses the Baculum Web login form. The web server basic authentication can be disabled in this method." msgstr "This type of authentication is realized by an external directory service. To authenticate it uses the Baculum Web login form. The web server basic authentication can be disabled in this method." + +msgid "Console ACLs" +msgstr "Console ACLs" + +msgid "Add new console" +msgstr "Add new console" + +msgid "Edit console" +msgstr "Edit console" + +msgid "Add console" +msgstr "Add console" + +msgid "OAuth2 clients" +msgstr "OAuth2 clients" + +msgid "Short name" +msgstr "Short name" + +msgid "Add new OAuth2 client" +msgstr "Add new OAuth2 client" + +msgid "Short name:" +msgstr "Short name:" + +msgid "Add OAuth2 client account" +msgstr "Add OAuth2 client account" + +msgid "set all scopes" +msgstr "set all scopes" + +msgid "Edit OAuth2 client account" +msgstr "Edit OAuth2 client account" + +msgid "Add new API host" +msgstr "Add new API host" + +msgid "Get existing API host settings:" +msgstr "Get existing API host settings:" + +msgid "Get existing OAuth2 client settings:" +msgstr "Get existing OAuth2 client settings:" + +msgid "API hosts" +msgstr "API hosts" + +msgid "Edit API host" +msgstr "Edit API host" + +msgid "Create dedicated Bconsole config file:" +msgstr "Create dedicated Bconsole config file:" + +msgid "Console ACL to use in new Bconsole config file:" +msgstr "Console ACL to use in new Bconsole config file:" + +msgid "Director for Bconsole config:" +msgstr "Director for Bconsole config:" + +msgid "The console ACLs enable to define available resources for users. The consoles are used in the Bacula configuration on the API host side. There is possible to assign the consoles to the OAuth2 clients that can be used in API hosts. The assign relation is: Console ACL -> OAuth2 client -> API host -> User account." +msgstr "The console ACLs enable to define available resources for users. The consoles are used in the Bacula configuration on the API host side. There is possible to assign the consoles to the OAuth2 clients that can be used in API hosts. The assign relation is: Console ACL -> OAuth2 client -> API host -> User account." + +msgid "The OAuth2 clients are configured on the API hosts. To create the OAuth2 clients from this page, you need to have an 'oauth2' scope in OAuth2 account related to your current admin user account. The OAuth2 client accounts are also possible to create directly in the panel of the Baculum API." +msgstr "The OAuth2 clients are configured on the API hosts. To create the OAuth2 clients from this page, you need to have an 'oauth2' scope in OAuth2 account related to your current admin user account. The OAuth2 client accounts are also possible to create directly in the panel of the Baculum API." + +msgid "The API hosts define connection parameters to hosts with the Baculum API instances. You can create the API host connections dedicated for specific users by assigning API hosts to them on the Users tab. There is possible to create many API host connections to the same Baculum API instance." +msgstr "The API hosts define connection parameters to hosts with the Baculum API instances. You can create the API host connections dedicated for specific users by assigning API hosts to them on the Users tab. There is possible to create many API host connections to the same Baculum API instance." + +msgid "Set all CommandAcls used by Baculum Web" +msgstr "Set all CommandAcls used by Baculum Web" diff --git a/gui/baculum/protected/Web/Lang/pl/messages.mo b/gui/baculum/protected/Web/Lang/pl/messages.mo index 818f9e7d1..bbf162e61 100644 Binary files a/gui/baculum/protected/Web/Lang/pl/messages.mo and b/gui/baculum/protected/Web/Lang/pl/messages.mo differ diff --git a/gui/baculum/protected/Web/Lang/pl/messages.po b/gui/baculum/protected/Web/Lang/pl/messages.po index 1d48068d4..8f4aef09b 100644 --- a/gui/baculum/protected/Web/Lang/pl/messages.po +++ b/gui/baculum/protected/Web/Lang/pl/messages.po @@ -3003,3 +3003,72 @@ msgstr "Ten typ uwierzytelniania jest w pełni realizowany Baculum Web. Do uwier msgid "This type of authentication is realized by an external directory service. To authenticate it uses the Baculum Web login form. The web server basic authentication can be disabled in this method." msgstr "Ten typ uwierzytelniania jest realizowany przez zewnętrzną usługę katalogową. Do uwierzytelniania używa formularza logowania Baculum Web. Uwierzytelnianie basic serwera WWW może być wyłączone w tej metodzie." +msgid "Console ACLs" +msgstr "Konsole ACLs" + +msgid "Add new console" +msgstr "Dodaj nową konsolę" + +msgid "Edit console" +msgstr "Edytuj konsolę" + +msgid "Add console" +msgstr "Dodaj konsolę" + +msgid "OAuth2 clients" +msgstr "Klienci OAuth2" + +msgid "Short name" +msgstr "Nazwa" + +msgid "Add new OAuth2 client" +msgstr "Dodaj nowego klienta OAuth2" + +msgid "Short name:" +msgstr "Nazwa:" + +msgid "Add OAuth2 client account" +msgstr "Dodaj konto klienta OAuth2" + +msgid "set all scopes" +msgstr "ustaw wszystkie zakresy" + +msgid "Edit OAuth2 client account" +msgstr "Edytuj konto klienta OAuth2" + +msgid "Add new API host" +msgstr "Dodaj nowy host API" + +msgid "Get existing API host settings:" +msgstr "Pobierz konfigurację istniejącego hosta API:" + +msgid "Get existing OAuth2 client settings:" +msgstr "Pobierz konfigurację istniejącego klienta OAuth2:" + +msgid "API hosts" +msgstr "Hosty API" + +msgid "Edit API host" +msgstr "Edytuj host API" + +msgid "Create dedicated Bconsole config file:" +msgstr "Stwórz dedykowany plik konfiguracyjny Bconsole:" + +msgid "Console ACL to use in new Bconsole config file:" +msgstr "Konsole ACL do użycia w nowym pliku konfiguracyjnym:" + +msgid "Director for Bconsole config:" +msgstr "Zarządca dla pliku konfiguracyjnego Bconsole:" + +msgid "The console ACLs enable to define available resources for users. The consoles are used in the Bacula configuration on the API host side. There is possible to assign the consoles to the OAuth2 clients that can be used in API hosts. The assign relation is: Console ACL -> OAuth2 client -> API host -> User account." +msgstr "Konsole ACL umożliwiają zdefiniowanie dostępnych zasobów dla użytkowników. Konsole są używane w konfiguracji Bacula po stronie hosta API. Jest możliwe przypisanie konsoli do klientów OAuth2, którzy mogą być użyci w hostach API. Relacja przypisania jest: Console ACL -> Klient OAuth2 -> Host API -> Konto użytkownika." + + +msgid "The OAuth2 clients are configured on the API hosts. To create the OAuth2 clients from this page, you need to have an 'oauth2' scope in OAuth2 account related to your current admin user account. The OAuth2 client accounts are also possible to create directly in the panel of the Baculum API." +msgstr "Klienci OAuth2 są konfigurowani na hostach API. Aby stworzyć klienta OAuth2 z tej strony, potrzebujesz przypisać zakres 'oauth2' na koncie OAuth2 powiązanym z twoim obecnym kontem administratora." + +msgid "The API hosts define connection parameters to hosts with the Baculum API instances. You can create the API host connections dedicated for specific users by assigning API hosts to them on the Users tab. There is possible to create many API host connections to the same Baculum API instance." +msgstr "Hosty API definiują parametry połączenia do hostów z instancjami Baculum API. Możesz stworzyć połączenia do hostów API dedykowane dla określonych użytkowników poprzez przypisanie hostów API do nich na zakładce Użytkownicy. Jest możliwe utworzenie wielu połączeń do hostów API dla tej samej instancji Baculum API." + +msgid "Set all CommandAcls used by Baculum Web" +msgstr "Ustaw wszystkie wartości CommandAcl używane przez Baculum Web" diff --git a/gui/baculum/protected/Web/Lang/pt/messages.mo b/gui/baculum/protected/Web/Lang/pt/messages.mo index 3dd17eeb1..82d293853 100644 Binary files a/gui/baculum/protected/Web/Lang/pt/messages.mo and b/gui/baculum/protected/Web/Lang/pt/messages.mo differ diff --git a/gui/baculum/protected/Web/Lang/pt/messages.po b/gui/baculum/protected/Web/Lang/pt/messages.po index 70cf4b9d8..dbf0f5a01 100644 --- a/gui/baculum/protected/Web/Lang/pt/messages.po +++ b/gui/baculum/protected/Web/Lang/pt/messages.po @@ -3003,3 +3003,71 @@ msgstr "Esse tipo de autenticação é totalmente realizado pelo Baculum Web. Pa msgid "This type of authentication is realized by an external directory service. To authenticate it uses the Baculum Web login form. The web server basic authentication can be disabled in this method." msgstr "Esse tipo de autenticação é realizado por um serviço de diretório externo. Para autenticar, use o formulário de login do Baculum. A autenticação básica do servidor da web pode ser desativada nesse método." +msgid "Console ACLs" +msgstr "Console ACLs" + +msgid "Add new console" +msgstr "Add new console" + +msgid "Edit console" +msgstr "Edit console" + +msgid "Add console" +msgstr "Add console" + +msgid "OAuth2 clients" +msgstr "OAuth2 clients" + +msgid "Short name" +msgstr "Short name" + +msgid "Add new OAuth2 client" +msgstr "Add new OAuth2 client" + +msgid "Short name:" +msgstr "Short name:" + +msgid "Add OAuth2 client account" +msgstr "Add OAuth2 client account" + +msgid "set all scopes" +msgstr "set all scopes" + +msgid "Edit OAuth2 client account" +msgstr "Edit OAuth2 client account" + +msgid "Add new API host" +msgstr "Add new API host" + +msgid "Get existing API host settings:" +msgstr "Get existing API host settings:" + +msgid "Get existing OAuth2 client settings:" +msgstr "Get existing OAuth2 client settings:" + +msgid "API hosts" +msgstr "API hosts" + +msgid "Edit API host" +msgstr "Edit API host" + +msgid "Create dedicated Bconsole config file:" +msgstr "Create dedicated Bconsole config file:" + +msgid "Console ACL to use in new Bconsole config file:" +msgstr "Console ACL to use in new Bconsole config file:" + +msgid "Director for Bconsole config:" +msgstr "Director for Bconsole config:" + +msgid "The console ACLs enable to define available resources for users. The consoles are used in the Bacula configuration on the API host side. There is possible to assign the consoles to the OAuth2 clients that can be used in API hosts. The assign relation is: Console ACL -> OAuth2 client -> API host -> User account." +msgstr "The console ACLs enable to define available resources for users. The consoles are used in the Bacula configuration on the API host side. There is possible to assign the consoles to the OAuth2 clients that can be used in API hosts. The assign relation is: Console ACL -> OAuth2 client -> API host -> User account." + +msgid "The OAuth2 clients are configured on the API hosts. To create the OAuth2 clients from this page, you need to have an 'oauth2' scope in OAuth2 account related to your current admin user account. The OAuth2 client accounts are also possible to create directly in the panel of the Baculum API." +msgstr "The OAuth2 clients are configured on the API hosts. To create the OAuth2 clients from this page, you need to have an 'oauth2' scope in OAuth2 account related to your current admin user account. The OAuth2 client accounts are also possible to create directly in the panel of the Baculum API." + +msgid "The API hosts define connection parameters to hosts with the Baculum API instances. You can create the API host connections dedicated for specific users by assigning API hosts to them on the Users tab. There is possible to create many API host connections to the same Baculum API instance." +msgstr "The API hosts define connection parameters to hosts with the Baculum API instances. You can create the API host connections dedicated for specific users by assigning API hosts to them on the Users tab. There is possible to create many API host connections to the same Baculum API instance." + +msgid "Set all CommandAcls used by Baculum Web" +msgstr "Set all CommandAcls used by Baculum Web" diff --git a/gui/baculum/protected/Web/Pages/Security.page b/gui/baculum/protected/Web/Pages/Security.page index 3b8d2d233..c774ef722 100644 --- a/gui/baculum/protected/Web/Pages/Security.page +++ b/gui/baculum/protected/Web/Pages/Security.page @@ -10,6 +10,9 @@ + + +
@@ -1443,7 +1446,6 @@ $(function() { >  <%[ Save ]%> -
@@ -1866,7 +1868,6 @@ $(function() { >  <%[ Save ]%> - @@ -1881,4 +1882,1326 @@ var bulk_actions_output_id = '<%=$this->SourceTemplateControl->BulkActions->Bulk + +
+
+
+ × + + +
+
+ +  <%[ Set all CommandAcls used by Baculum Web ]%> + + +
+
+ +
+ +
+
+
+ × + + +
+
+
+
+
+ + + + + +
  +
+
+
+
+ + + + <%[ generate ]%> +
  +
+
+
+
+ + +
  +
+
+
+   +
+
+
+
+ +
+
+ +
+
+
+ +
+
+
+
+
+ +
+
+
+
+ + +  <%[ Save ]%> + + +
+
+ +
+ +
+
+
+ × + + +
+
+
+
+
+
+ + + document.getElementById('api_host_settings_loading').style.visibility = 'visible'; + + + document.getElementById('api_host_settings_loading').style.visibility = 'hidden'; + + +
  +
+
+
+
+
+ + + +   +
+
+
+
+
+ + +
+   +
+
+
+
+ +   + +
+
+
+
+ + +
+
+ + +
+
+
+
+
+
+ + +
+   +
+
+
+
+ + +
+   +
+
+
+
+
+
+ + + document.getElementById('api_host_oauth2_client_settings_loading').style.visibility = 'visible'; + + + document.getElementById('api_host_oauth2_client_settings_loading').style.visibility = 'hidden'; + + +
  +
+
+
+
+ + + +
+   +
+
+
+
+ + + +
+   +
+
+
+
+ + +
+   +
+
+
+
+ + +
+   +
+
+
+
+
+ + + + + + + + + + + + + + + + + +
+ + + $('#<%=$this->APIHostTestResultOk->ClientID%>').hide(); + $('#<%=$this->APIHostTestResultErr->ClientID%>').hide(); + $('#<%=$this->APIHostCatalogSupportYes->ClientID%>').hide(); + $('#<%=$this->APIHostCatalogSupportNo->ClientID%>').hide(); + $('#<%=$this->APIHostConsoleSupportYes->ClientID%>').hide(); + $('#<%=$this->APIHostConsoleSupportNo->ClientID%>').hide(); + $('#<%=$this->APIHostConfigSupportYes->ClientID%>').hide(); + $('#<%=$this->APIHostConfigSupportNo->ClientID%>').hide(); + $('#<%=$this->APIHostTestLoader->ClientID%>').show(); + + + $('#<%=$this->APIHostTestLoader->ClientID%>').hide(); + +  <%[ test ]%> + + + +  <%[ OK ]%> +  <%[ Connection error ]%> +
<%[ Catalog support ]%> +  <%[ Supported ]%> +  <%[ Not supported ]%> +
<%[ Console support ]%> +  <%[ Supported ]%> +  <%[ Not supported ]%> +
<%[ Config support ]%> +  <%[ Supported ]%> +  <%[ Not supported ]%> +
+
+
+
+
+
+ +
+
+
+
+ + +  <%[ Save ]%> + +
+
+ + +
diff --git a/gui/baculum/protected/Web/Pages/Security.php b/gui/baculum/protected/Web/Pages/Security.php index a9d8decff..23983e968 100644 --- a/gui/baculum/protected/Web/Pages/Security.php +++ b/gui/baculum/protected/Web/Pages/Security.php @@ -27,6 +27,7 @@ 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.TActiveRadioButton'); Prado::using('System.Web.UI.ActiveControls.TActiveTextBox'); Prado::using('System.Web.UI.ActiveControls.TCallback'); Prado::using('System.Web.UI.WebControls.TCheckBox'); @@ -38,7 +39,9 @@ Prado::using('System.Web.UI.WebControls.TRequiredFieldValidator'); Prado::using('System.Web.UI.WebControls.TValidationSummary'); Prado::using('Application.Common.Class.Crypto'); Prado::using('Application.Common.Class.Ldap'); +Prado::using('Application.Common.Class.OAuth2'); Prado::using('Application.Web.Class.BaculumWebPage'); +Prado::using('Application.Web.Portlets.BaculaConfigResources'); /** * Security page (auth methods, users, roles...). @@ -76,6 +79,11 @@ class Security extends BaculumWebPage { */ private $user_config = []; + /** + * Store console ACL config. + */ + private $console_config = []; + /** * Initialize page. * @@ -1170,6 +1178,546 @@ class Security extends BaculumWebPage { return (($is_basic && $allow_manage_users) || $is_local); } + /** + * Set and load console ACL list. + * + * @param TCallback $sender sender object + * @param TCallbackEventParameter callback parameter + * @return none + */ + public function setConsoleList($sender, $param) { + $config = $this->getModule('api')->get(['config', 'dir', 'Console']); + $console_directives = [ + 'Description' => '', + 'JobAcl' => '', + 'ClientAcl' => '', + 'StorageAcl' => '', + 'ScheduleAcl' => '', + 'RunAcl' => '', + 'PoolAcl' => '', + 'CommandAcl' => '', + 'FilesetAcl' => '', + 'CatalogAcl' => '', + 'WhereAcl' => '', + 'PluginOptionsAcl' => '', + 'BackupClientAcl' => '', + 'RestoreClientAcl' => '', + 'DirectoryAcl' => '' + ]; + $consoles = []; + function join_cons($item) { + if (is_array($item)) { + $item = implode(',', $item); + } + return $item; + } + if ($config->error == 0) { + for ($i = 0; $i < count($config->output); $i++) { + $cons = (array)$config->output[$i]->Console; + $cons = array_map('join_cons', $cons); + $consoles[] = array_merge($console_directives, $cons); + } + } + $this->getCallbackClient()->callClientFunction('oConsoles.load_console_list_cb', [ + $consoles + ]); + $this->console_config = $consoles; + } + + /** + * Load data in console modal window. + * + * @param TCallback $sender sender object + * @param TCallbackEventParameter $param callback parameter + * @return none + */ + public function loadConsoleWindow($sender, $param) { + $name = $param->getCallbackParameter(); + if (!empty($name)) { + // edit existing console + $this->ConsoleConfig->setResourceName($name); + $this->ConsoleConfig->setLoadValues(true); + } else { + // add new console + $this->ConsoleConfig->setLoadValues(false); + $this->getCallbackClient()->callClientFunction('oBaculaConfigSection.show_sections', [true]); + } + $this->ConsoleConfig->setHost($this->User->getAPIHosts()); + $this->ConsoleConfig->setComponentName($_SESSION['dir']); + $this->ConsoleConfig->raiseEvent('OnDirectiveListLoad', $this, null); + } + + /** + * Remove consoles action. + * Here is possible to remove one console or many. + * This action is linked with table bulk actions. + * + * @param TCallback $sender sender object + * @param TCallbackEventParameter $param callback parameter + * @return none + */ + public function removeConsoles($sender, $param) { + $consoles = explode('|', $param->getCallbackParameter()); + $res = new BaculaConfigResources(); + $config = $res->getConfigData($this->User->getAPIHosts(), 'dir'); + for ($i = 0; $i < count($consoles); $i++) { + $res->removeResourceFromConfig( + $config, + 'Console', + $consoles[$i] + ); + } + $this->getModule('api')->set( + array('config', 'dir'), + array('config' => json_encode($config)), + $this->User->getAPIHosts(), + false + ); + + // refresh console list + $this->setConsoleList(null, null); + + // refresh OAuth2 client console combobox + $this->loadOAuth2ClientConsole(null, null); + } + + public function setAllCommandAcls($sender, $param) { + $config = (object)[ + "CommandAcl" => [ + 'gui', + '.api', + '.jobs', + '.ls', + '.client', + '.fileset', + '.pool', + '.status', + '.storage', + '.bvfs_get_jobids', + '.bvfs_update', + '.bvfs_lsdirs', + '.bvfs_lsfiles', + '.bvfs_versions', + '.bvfs_restore', + '.bvfs_cleanup', + 'restore', + 'show', + 'estimate', + 'run', + 'delete', + 'cancel' + ] + ]; + $this->ConsoleConfig->setData($config); + $this->ConsoleConfig->raiseEvent('OnDirectiveListLoad', $this, null); + $this->getCallbackClient()->callClientFunction('oBaculaConfigSection.show_sections', [true]); + } + + /** + * Set and load OAuth2 client list. + * + * @param TCallback $sender sender object + * @param TCallbackEventParameter callback parameter + * @return none + */ + public function setOAuth2ClientList($sender, $param) { + $oauth2_clients = $this->getModule('api')->get(['oauth2', 'clients']); + $this->getCallbackClient()->callClientFunction('oOAuth2Clients.load_oauth2_client_list_cb', [ + $oauth2_clients->output, + $oauth2_clients->error + ]); + } + + public function loadOAuth2ClientConsole($sender, $param) { + $cons = $this->getModule('api')->get(['config', 'dir', 'Console']); + $console = ['' => '']; + if ($cons->error == 0) { + for ($i = 0; $i < count($cons->output); $i++) { + $console[$cons->output[$i]->Console->Name] = $cons->output[$i]->Console->Name; + } + } + $this->OAuth2ClientConsole->DataSource = $console; + $this->OAuth2ClientConsole->dataBind(); + } + + /** + * Load data in OAuth2 client modal window. + * + * @param TCallback $sender sender object + * @param TCallbackEventParameter $param callback parameter + * @return none + */ + public function loadOAuth2ClientWindow($sender, $param) { + $client_id = $param->getCallbackParameter(); + if (!empty($client_id)) { + $oauth2_cfg = $this->getModule('api')->get(['oauth2', 'clients', $client_id]); + if ($oauth2_cfg->error === 0 && is_object($oauth2_cfg->output)) { + // It is done only for existing OAuth2 client accounts + $this->OAuth2ClientClientId->Text = $oauth2_cfg->output->client_id; + $this->OAuth2ClientClientSecret->Text = $oauth2_cfg->output->client_secret; + $this->OAuth2ClientRedirectURI->Text = $oauth2_cfg->output->redirect_uri; + $this->OAuth2ClientScope->Text = $oauth2_cfg->output->scope; + $this->OAuth2ClientBconsoleCfgPath->Text = $oauth2_cfg->output->bconsole_cfg_path; + $this->OAuth2ClientName->Text = $oauth2_cfg->output->name; + } + } + $this->loadOAuth2ClientConsole(null, null); + + $dirs = $this->getModule('api')->get(['config', 'bcons', 'Director']); + $dir_names = []; + if ($dirs->error == 0) { + for ($i = 0; $i < count($dirs->output); $i++) { + $dir_names[$dirs->output[$i]->Director->Name] = $dirs->output[$i]->Director->Name; + } + } + $this->OAuth2ClientDirector->DataSource = $dir_names; + $this->OAuth2ClientDirector->dataBind(); + } + + /** + * Save OAuth2 client. + * It works both for new OAuth2 client and for edited OAuth2 client. + * Saves values from modal popup. + * + * @param TCallback $sender sender object + * @param TCallbackEventParameter $param callback parameter + * @return none + */ + public function saveOAuth2Client($sender, $param) { + $client_id = $this->OAuth2ClientClientId->Text; + $cfg = []; + $cfg['client_id'] = $client_id; + $cfg['client_secret'] = $this->OAuth2ClientClientSecret->Text; + $cfg['redirect_uri'] = $this->OAuth2ClientRedirectURI->Text; + $cfg['scope'] = $this->OAuth2ClientScope->Text; + $cfg['bconsole_cfg_path'] = $this->OAuth2ClientBconsoleCfgPath->Text; + if ($this->OAuth2ClientBconsoleCreate->Checked) { + $cfg['console'] = $this->OAuth2ClientConsole->SelectedValue; + $cfg['director'] = $this->OAuth2ClientDirector->SelectedValue; + } + $cfg['name'] = $this->OAuth2ClientName->Text; + + $win_type = $this->OAuth2ClientWindowType->Value; + $result = (object)['error' => -1]; + if ($win_type === self::TYPE_ADD_WINDOW) { + $result = $this->getModule('api')->create(['oauth2', 'clients', $client_id], $cfg); + } elseif ($win_type === self::TYPE_EDIT_WINDOW) { + $result = $this->getModule('api')->set(['oauth2', 'clients', $client_id], $cfg); + } + + if ($result->error === 0) { + // Refresh OAuth2 client list + $this->setOAuth2ClientList(null, null); + } + $this->getCallbackClient()->callClientFunction('oOAuth2Clients.save_oauth2_client_cb', [ + $result + ]); + } + + /** + * Remove OAuth2 client action. + * Here is possible to remove one OAuth2 client or many. + * This action is linked with table bulk actions. + * + * @param TCallback $sender sender object + * @param TCallbackEventParameter $param callback parameter + * @return none + */ + public function removeOAuth2Clients($sender, $param) { + $client_ids = explode('|', $param->getCallbackParameter()); + for ($i = 0; $i < count($client_ids); $i++) { + $result = $this->getModule('api')->remove(['oauth2', 'clients', $client_ids[$i]]); + if ($result->error !== 0) { + break; + } + } + + if (count($client_ids) > 0) { + // Refresh OAuth2 client list + $this->setOAuth2ClientList(null, null); + } + } + + /** + * Set and load OAuth2 client settings to API host modal window. + * + * @param TActiveDropDownList $sender sender object + * @param TCallbackEventParameter callback parameter + * @return none + */ + public function loadOAuth2ClientSettings($sender, $param) { + $client_id = $this->APIHostOAuth2ClientSettings->SelectedValue; + if (!empty($client_id)) { + $host = $this->APIHostSettings->SelectedValue ?: null; + $oauth2_cfg = $this->getModule('api')->get(['oauth2', 'clients', $client_id], $host); + if ($oauth2_cfg->error === 0 && is_object($oauth2_cfg->output)) { + $this->APIHostOAuth2ClientId->Text = $oauth2_cfg->output->client_id; + $this->APIHostOAuth2ClientSecret->Text = $oauth2_cfg->output->client_secret; + $this->APIHostOAuth2RedirectURI->Text = $oauth2_cfg->output->redirect_uri; + $this->APIHostOAuth2Scope->Text = $oauth2_cfg->output->scope; + } + } + } + + /** + * Load OAuth2 client list to get OAuth2 client settings. + * + * @return none + */ + private function loadOAuth2ClientList() { + $host = $this->APIHostSettings->SelectedValue ?: null; + $oauth2_clients = $this->getModule('api')->get(['oauth2', 'clients'], $host); + $oauth2_client_list = ['' => '']; + if ($oauth2_clients->error == 0 && is_array($oauth2_clients->output)) { + for ($i = 0; $i < count($oauth2_clients->output); $i++) { + $name = $oauth2_clients->output[$i]->name ?: $oauth2_clients->output[$i]->client_id; + $oauth2_client_list[$oauth2_clients->output[$i]->client_id] = $name; + } + } + $this->APIHostOAuth2ClientSettings->DataSource = $oauth2_client_list; + $this->APIHostOAuth2ClientSettings->dataBind(); + } + + /** + * Set and load API hosts list. + * + * @param TCallback $sender sender object + * @param TCallbackEventParameter callback parameter + * @return none + */ + public function setAPIHostList($sender, $param) { + $api_hosts = $this->getModule('host_config')->getConfig(); + $shortnames = array_keys($api_hosts); + $attributes = array_values($api_hosts); + for ($i = 0; $i < count($attributes); $i++) { + $attributes[$i]['name'] = $shortnames[$i]; + } + + $this->getCallbackClient()->callClientFunction('oAPIHosts.load_api_host_list_cb', [ + $attributes + ]); + } + + /** + * Load data in API host modal window. + * + * @param TCallback $sender sender object + * @param TCallbackEventParameter $param callback parameter + * @return none + */ + public function loadAPIHostWindow($sender, $param) { + $name = $param->getCallbackParameter(); + + // prepare API host combobox + $api_hosts = $this->getModule('host_config')->getConfig(); + + if (!empty($name) && key_exists($name, $api_hosts)) { + $this->APIHostAddress->Text = $api_hosts[$name]['address']; + $this->APIHostProtocol->SelectedValue = $api_hosts[$name]['protocol']; + $this->APIHostPort->Text = $api_hosts[$name]['port']; + $this->APIHostOAuth2ClientId->Text = $api_hosts[$name]['client_id']; + $this->APIHostOAuth2ClientSecret->Text = $api_hosts[$name]['client_secret']; + $this->APIHostOAuth2RedirectURI->Text = $api_hosts[$name]['redirect_uri']; + $this->APIHostOAuth2Scope->Text = $api_hosts[$name]['scope']; + $this->APIHostName->Text = $name; + $this->APIHostBasicLogin->Text = $api_hosts[$name]['login']; + $this->APIHostBasicPassword->Text = $api_hosts[$name]['password']; + if ($api_hosts[$name]['auth_type'] == 'basic') { + $this->APIHostAuthBasic->Checked = true; + $this->getCallbackClient()->hide('configure_oauth2_auth'); + $this->getCallbackClient()->show('configure_basic_auth'); + } elseif ($api_hosts[$name]['auth_type'] == 'oauth2') { + $this->APIHostAuthOAuth2->Checked = true; + $this->getCallbackClient()->hide('configure_basic_auth'); + $this->getCallbackClient()->show('configure_oauth2_auth'); + } + } + + $shortnames = array_keys($api_hosts); + + $api_host_names = array_combine($shortnames, $shortnames); + $this->APIHostSettings->DataSource = array_merge(['' => ''], $api_host_names); + $this->APIHostSettings->dataBind(); + + // prepare OAuth2 client combobox + $this->loadOAuth2ClientList(); + } + + /** + * Load API host settings to API host modal window. + * + * @param TActiveDropDownList $sender sender object + * @param TCallbackEventParameter callback parameter + * @return none + */ + public function loadAPIHostSettings($sender, $param) { + $api_host = $this->APIHostSettings->SelectedValue; + if (!empty($api_host)) { + $config = $this->getModule('host_config')->getConfig(); + if (key_exists($api_host, $config)) { + // load OAuth2 clients to combobox from selected API host + $this->loadOAuth2ClientList(); + + $this->APIHostProtocol->SelectedValue = $config[$api_host]['protocol']; + $this->APIHostAddress->Text = $config[$api_host]['address']; + $this->APIHostPort->Text = $config[$api_host]['port']; + if ($config[$api_host]['auth_type'] == 'basic') { + $this->APIHostAuthBasic->Checked = true; + $this->getCallbackClient()->hide('configure_oauth2_auth'); + $this->getCallbackClient()->show('configure_basic_auth'); + } elseif ($config[$api_host]['auth_type'] == 'oauth2') { + $this->APIHostAuthOAuth2->Checked = true; + $this->getCallbackClient()->hide('configure_basic_auth'); + $this->getCallbackClient()->show('configure_oauth2_auth'); + } + } + } + } + + public function connectionAPITest($sender, $param) { + $host = $this->APIHostAddress->Text; + if (empty($host)) { + $host = false; + } + $host_params = array( + 'protocol' => $this->APIHostProtocol->SelectedValue, + 'address' => $this->APIHostAddress->Text, + 'port' => $this->APIHostPort->Text, + 'url_prefix' => '' + ); + + if ($this->APIHostAuthBasic->Checked) { + $host_params['auth_type'] = 'basic'; + $host_params['login'] = $this->APIHostBasicLogin->Text; + $host_params['password'] = $this->APIHostBasicPassword->Text; + } elseif ($this->APIHostAuthOAuth2->Checked) { + $host_params['auth_type'] = 'oauth2'; + $host_params['client_id'] = $this->APIHostOAuth2ClientId->Text; + $host_params['client_secret'] = $this->APIHostOAuth2ClientSecret->Text; + $host_params['redirect_uri'] = $this->APIHostOAuth2RedirectURI->Text; + $host_params['scope'] = $this->APIHostOAuth2Scope->Text; + } + $api = $this->getModule('api'); + + // Catalog test + OAuth2Record::deleteByPk($host); + $api->setHostParams($host, $host_params); + $catalog = $api->get(array('catalog'), $host, false); + + // Console test + OAuth2Record::deleteByPk($host); + $api->setHostParams($host, $host_params); + $director = null; + if (array_key_exists('director', $_SESSION)) { + // Current director can't be passed to new remote host. + $director = $_SESSION['director']; + unset($_SESSION['director']); + } + + $console = $api->set(array('console'), array('version'), $host, false); + if (!is_null($director)) { + // Revert director setting if any + $_SESSION['director'] = $director; + } + + // Config test + OAuth2Record::deleteByPk($host); + $api->setHostParams($host, $host_params); + $config = $api->get(array('config'), $host, false); + + $is_catalog = (is_object($catalog) && $catalog->error === 0); + $is_console = (is_object($console) && $console->error === 0); + $is_config = (is_object($config) && $config->error === 0); + + $status_ok = $is_catalog; + if ($status_ok) { + $status_ok = $is_console; + } + + if (!$is_catalog) { + $this->APIHostTestResultErr->Text .= $catalog->output . '
'; + } + if (!$is_console) { + $this->APIHostTestResultErr->Text .= $console->output . '
'; + } + if (!$is_config) { + $this->APIHostTestResultErr->Text .= $config->output . '
'; + } + + $this->APIHostTestResultOk->Display = ($status_ok === true) ? 'Dynamic' : 'None'; + $this->APIHostTestResultErr->Display = ($status_ok === false) ? 'Dynamic' : 'None'; + $this->APIHostCatalogSupportYes->Display = ($is_catalog === true) ? 'Dynamic' : 'None'; + $this->APIHostCatalogSupportNo->Display = ($is_catalog === false) ? 'Dynamic' : 'None'; + $this->APIHostConsoleSupportYes->Display = ($is_console === true) ? 'Dynamic' : 'None'; + $this->APIHostConsoleSupportNo->Display = ($is_console === false) ? 'Dynamic' : 'None'; + $this->APIHostConfigSupportYes->Display = ($is_config === true) ? 'Dynamic' : 'None'; + $this->APIHostConfigSupportNo->Display = ($is_config === false) ? 'Dynamic' : 'None'; + } + + public function saveAPIHost($sender, $param) { + $cfg_host = array( + 'auth_type' => '', + 'login' => '', + 'password' => '', + 'client_id' => '', + 'client_secret' => '', + 'redirect_uri' => '', + 'scope' => '' + ); + $cfg_host['protocol'] = $this->APIHostProtocol->Text; + $cfg_host['address'] = $this->APIHostAddress->Text; + $cfg_host['port'] = $this->APIHostPort->Text; + $cfg_host['url_prefix'] = ''; + if ($this->APIHostAuthBasic->Checked == true) { + $cfg_host['auth_type'] = 'basic'; + $cfg_host['login'] = $this->APIHostBasicLogin->Text; + $cfg_host['password'] = $this->APIHostBasicPassword->Text; + } elseif($this->APIHostAuthOAuth2->Checked == true) { + $cfg_host['auth_type'] = 'oauth2'; + $cfg_host['client_id'] = $this->APIHostOAuth2ClientId->Text; + $cfg_host['client_secret'] = $this->APIHostOAuth2ClientSecret->Text; + $cfg_host['redirect_uri'] = $this->APIHostOAuth2RedirectURI->Text; + $cfg_host['scope'] = $this->APIHostOAuth2Scope->Text; + } + $hc = $this->getModule('host_config'); + $config = $hc->getConfig(); + $host_name = trim($this->APIHostName->Text); + if (empty($host_name)) { + $host_name = $cfg_host['address']; + } + $config[$host_name] = $cfg_host; + $hc->setConfig($config); + $this->setAPIHostList(null, null); + $this->getCallbackClient()->hide('api_host_window'); + + // refresh user window + $this->initUserWindow(); + } + + /** + * Remove API host action. + * Here is possible to remove one API host or many. + * This action is linked with table bulk actions. + * + * @param TCallback $sender sender object + * @param TCallbackEventParameter $param callback parameter + * @return none + */ + public function removeAPIHosts($sender, $param) { + $names = explode('|', $param->getCallbackParameter()); + $hc = $this->getModule('host_config'); + $config = $hc->getConfig(); + $cfg = []; + foreach ($config as $host => $opts) { + if (in_array($host, $names)) { + continue; + } + $cfg[$host] = $opts; + } + $hc->setConfig($cfg); + $this->setAPIHostList(null, null); + } + /** * Validate IP restriction address value. * diff --git a/gui/baculum/protected/Web/Portlets/BaculaConfigDirectives.php b/gui/baculum/protected/Web/Portlets/BaculaConfigDirectives.php index 3a5214686..39e118f3e 100644 --- a/gui/baculum/protected/Web/Portlets/BaculaConfigDirectives.php +++ b/gui/baculum/protected/Web/Portlets/BaculaConfigDirectives.php @@ -51,6 +51,7 @@ class BaculaConfigDirectives extends DirectiveListTemplate { const SHOW_REMOVE_BUTTON = 'ShowRemoveButton'; const SHOW_CANCEL_BUTTON = 'ShowCancelButton'; const SHOW_ALL_DIRECTIVES = 'ShowAllDirectives'; + const SHOW_BOTTOM_BUTTONS = 'ShowBottomButtons'; const SAVE_DIRECTIVE_ACTION_OK = 'SaveDirectiveActionOk'; private $show_all_directives = false; @@ -122,6 +123,7 @@ class BaculaConfigDirectives extends DirectiveListTemplate { $directives = array(); $parent_directives = array(); $config = new stdClass; + $predefined = false; if ($load_values === true) { $config = $this->getConfigData($host, array( $component_type, @@ -139,6 +141,13 @@ class BaculaConfigDirectives extends DirectiveListTemplate { $config->JobDefs )); } + } else { + // Pre-defined config for new resource can be provided in Data property. + $data = $this->getData(); + if (!empty($data)) { + $config = $data; + $predefined = true; + } } $data_desc = $this->Application->getModule('data_desc'); @@ -150,7 +159,7 @@ class BaculaConfigDirectives extends DirectiveListTemplate { } $directive_value = null; - if ($in_config === true && $load_values === true) { + if (($in_config === true && $load_values === true) || ($predefined && property_exists($config, $directive_name))) { $directive_value = $config->{$directive_name}; } @@ -163,7 +172,7 @@ class BaculaConfigDirectives extends DirectiveListTemplate { if (is_object($directive_desc)) { if (property_exists($directive_desc, 'Required')) { $required = $directive_desc->Required; - if ($load_values === true && array_key_exists($directive_name, $parent_directives)) { + if ($load_values === true && key_exists($directive_name, $parent_directives)) { // values can be taken from JobDefs $required = false; } @@ -377,6 +386,7 @@ class BaculaConfigDirectives extends DirectiveListTemplate { $this->SaveDirectiveErrMsg->Display = 'Dynamic'; $this->SaveDirectiveErrMsg->Text = "Error {$result->error}: {$result->output}"; } + $this->onSave(null); } public function setShowAllDirectives($show_all_directives) { @@ -574,5 +584,28 @@ class BaculaConfigDirectives extends DirectiveListTemplate { public function getShowCancelButton() { return $this->getViewState(self::SHOW_CANCEL_BUTTON, true); } + + /** + * Set if buttons should be flexible and available at the bottom of the page. + * + * @return none; + */ + public function setShowBottomButtons($show) { + $show = TPropertyValue::ensureBoolean($show); + $this->setViewState(self::SHOW_BOTTOM_BUTTONS, $show); + } + + /** + * Get if buttons should be flexible and available at the bottom of the page. + * + * @return bool true if buttons are available at the bottom of the page, otherwise false + */ + public function getShowBottomButtons() { + return $this->getViewState(self::SHOW_BOTTOM_BUTTONS, true); + } + + public function onSave($param) { + $this->raiseEvent('OnSave', $this, $param); + } } ?> diff --git a/gui/baculum/protected/Web/Portlets/BaculaConfigDirectives.tpl b/gui/baculum/protected/Web/Portlets/BaculaConfigDirectives.tpl index e5a89d645..f54ee41f5 100644 --- a/gui/baculum/protected/Web/Portlets/BaculaConfigDirectives.tpl +++ b/gui/baculum/protected/Web/Portlets/BaculaConfigDirectives.tpl @@ -37,7 +37,7 @@ ItemRenderer="Application.Web.Portlets.DirectiveRenderer" > -
+
ShowBottomButtons ? ' style="margin-left: 250px"' : ''%>> Application->getModule('api')->get($params, $host, false); $config = array(); @@ -222,7 +222,7 @@ class BaculaConfigResources extends ResourceListTemplate { * @param string $resource_name resource name to remove * @return none */ - private function removeResourceFromConfig(&$config, $resource_type, $resource_name) { + public function removeResourceFromConfig(&$config, $resource_type, $resource_name) { for ($i = 0; $i < count($config); $i++) { foreach ($config[$i] as $rtype => $resource) { if (!property_exists($resource, 'Name')) { @@ -231,7 +231,7 @@ class BaculaConfigResources extends ResourceListTemplate { if ($rtype === $resource_type && $resource->Name === $resource_name) { // remove resource array_splice($config, $i, 1); - break; + break 2; } } }