From 79e662d848c122709c1d4fb98ce09b8ddbc98cf9 Mon Sep 17 00:00:00 2001 From: Benn Oshrin Date: Sat, 12 Apr 2025 19:23:10 -0400 Subject: [PATCH] Initial commit of HttpServer (CFM-101) and MatchServer (CFM-104) --- app/config/schema/schema.json | 3 + .../resources/locales/en_US/core_server.po | 44 ++--- .../src/Controller/HttpServersController.php | 40 +++++ .../src/Controller/MatchServersController.php | 59 ++++++ .../src/Lib/Enum/HttpAuthTypeEnum.php | 38 ++++ .../src/Model/Entity/HttpServer.php | 49 +++++ .../src/Model/Entity/MatchServer.php | 49 +++++ .../src/Model/Table/HttpServersTable.php | 170 ++++++++++++++++++ .../src/Model/Table/MatchServersTable.php | 95 ++++++++++ app/plugins/CoreServer/src/config/plugin.json | 40 ++++- .../templates/HttpServers/fields.inc | 42 +++++ .../templates/MatchServers/fields.inc | 40 +++++ app/resources/locales/en_US/command.po | 24 ++- app/resources/locales/en_US/field.po | 9 + app/src/Command/TestCommand.php | 38 +++- 15 files changed, 705 insertions(+), 35 deletions(-) create mode 100644 app/plugins/CoreServer/src/Controller/HttpServersController.php create mode 100644 app/plugins/CoreServer/src/Controller/MatchServersController.php create mode 100644 app/plugins/CoreServer/src/Lib/Enum/HttpAuthTypeEnum.php create mode 100644 app/plugins/CoreServer/src/Model/Entity/HttpServer.php create mode 100644 app/plugins/CoreServer/src/Model/Entity/MatchServer.php create mode 100644 app/plugins/CoreServer/src/Model/Table/HttpServersTable.php create mode 100644 app/plugins/CoreServer/src/Model/Table/MatchServersTable.php create mode 100644 app/plugins/CoreServer/templates/HttpServers/fields.inc create mode 100644 app/plugins/CoreServer/templates/MatchServers/fields.inc diff --git a/app/config/schema/schema.json b/app/config/schema/schema.json index bcbd0fcfe..dc3080a8a 100644 --- a/app/config/schema/schema.json +++ b/app/config/schema/schema.json @@ -29,6 +29,7 @@ "message_template_id": { "type": "integer", "foreignkey": { "table": "message_templates", "column": "id" } }, "name": { "type": "string", "size": 128, "notnull": true }, "ordr": { "type": "integer" }, + "password": { "type": "string", "size": 400 }, "person_id": { "type": "integer", "foreignkey": { "table": "people", "column": "id" } }, "person_role_id": { "type": "integer", "foreignkey": { "table": "person_roles", "column": "id" } }, "petition_id": { "type": "integer", "foreignkey": { "table": "petitions", "column": "id" } }, @@ -36,11 +37,13 @@ "provisioning_target_id": { "type": "integer", "foreignkey": { "table": "provisioning_targets", "column": "id" }, "notnull": true }, "reference_identifier": { "type": "string", "size": 40 }, "report_id": { "type": "integer", "foreignkey": { "table": "reports", "column": "id" }, "notnull": true }, + "required": { "type": "string", "size": 2 }, "server_id": { "type": "integer", "foreignkey": { "table": "servers", "column": "id" }, "notnull": true }, "sor_label": { "type": "string", "size": 40 }, "status": { "type": "string", "size": 2 }, "traffic_detour_id": { "type": "integer", "foreignkey": { "table": "traffic_detours", "column": "id" }, "notnull": true }, "type_id": { "type": "integer", "foreignkey": { "table": "types", "column": "id" }, "notnull": true }, + "username": { "type": "string", "size": 128 }, "valid_from": { "type": "datetime" }, "valid_through": { "type": "datetime" } } diff --git a/app/plugins/CoreServer/resources/locales/en_US/core_server.po b/app/plugins/CoreServer/resources/locales/en_US/core_server.po index ff8cf722f..f16da9a10 100644 --- a/app/plugins/CoreServer/resources/locales/en_US/core_server.po +++ b/app/plugins/CoreServer/resources/locales/en_US/core_server.po @@ -22,12 +22,27 @@ # @since COmanage Registry v5.0.0 # @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) +msgid "controller.HttpServers" +msgstr "{0,plural,=1{HTTP Server} other{HTTP Servers}}" + +msgid "controller.MatchServers" +msgstr "{0,plural,=1{Match Server} other{Match Servers}}" + msgid "controller.SmtpServers" msgstr "{0,plural,=1{SMTP Server} other{SMTP Servers}}" msgid "controller.SqlServers" msgstr "{0,plural,=1{SQL Server} other{SQL Servers}}" +msgid "enumeration.HttpAuthTypeEnum.BA" +msgstr "Basic" + +msgid "enumeration.HttpAuthTypeEnum.BE" +msgstr "Bearer" + +msgid "enumeration.HttpAuthTypeEnum.X" +msgstr "None" + msgid "enumeration.RdbmsTypeEnum.LT" msgstr "SQLite" @@ -46,28 +61,27 @@ msgstr "Oracle" msgid "enumeration.RdbmsTypeEnum.PG" msgstr "Postgres" +msgid "field.auth_type" +msgstr "Authentication Type" + +msgid "field.skip_ssl_verification" +msgstr "Skip SSL Verification" + +msgid "field.MatchServers.api_endpoint" +msgstr "API Endpoint" + msgid "field.SmtpServers.default_from" msgstr "Default From Address" msgid "field.SmtpServers.default_reply_to" msgstr "Default Reply-To Address" -msgid "field.SmtpServers.hostname" -msgstr "Hostname" - msgid "field.SmtpServers.override_to" msgstr "Delivery Override" msgid "field.SmtpServers.override_to.desc" msgstr "If set, all outgoing email will only be sent to this address" -# XXX Temporary? -msgid "field.SmtpServers.password" -msgstr "Password" - -msgid "field.SmtpServers.port" -msgstr "Port" - msgid "field.SmtpServers.use_tls" msgstr "Use TLS" @@ -80,16 +94,6 @@ msgstr "OracleClient plugin is not loaded" msgid "field.SqlServers.databas" msgstr "Database Name" -msgid "field.SqlServers.hostname" -msgstr "Hostname" - -# XXX Temporary? -msgid "field.SqlServers.password" -msgstr "Password" - -msgid "field.SqlServers.port" -msgstr "Port" - msgid "field.SqlServers.port.desc" msgstr "Specify the port only if a non-standard port number is in use" diff --git a/app/plugins/CoreServer/src/Controller/HttpServersController.php b/app/plugins/CoreServer/src/Controller/HttpServersController.php new file mode 100644 index 000000000..06bbb5fd5 --- /dev/null +++ b/app/plugins/CoreServer/src/Controller/HttpServersController.php @@ -0,0 +1,40 @@ + [ + 'HttpServers.url' => 'asc' + ] + ]; +} diff --git a/app/plugins/CoreServer/src/Controller/MatchServersController.php b/app/plugins/CoreServer/src/Controller/MatchServersController.php new file mode 100644 index 000000000..4b7bc526a --- /dev/null +++ b/app/plugins/CoreServer/src/Controller/MatchServersController.php @@ -0,0 +1,59 @@ + [ + 'MatchServers.url' => 'asc' + ] + ]; + + /** + * Callback run prior to the request render. + * + * @param EventInterface $event Cake Event + * + * @return Response|void + * @since COmanage Registry v5.2.0 + */ + + public function beforeRender(EventInterface $event) { + // Generate the callback URL + +// XXX this needs to be updated for whereever the new API lands + $this->set('vv_api_endpoint', \Cake\Routing\Router::url('/', true) . 'api/co/' . $this->getCOID() . '/core/v1/resolution'); + + return parent::beforeRender($event); + } +} diff --git a/app/plugins/CoreServer/src/Lib/Enum/HttpAuthTypeEnum.php b/app/plugins/CoreServer/src/Lib/Enum/HttpAuthTypeEnum.php new file mode 100644 index 000000000..47f73c0e0 --- /dev/null +++ b/app/plugins/CoreServer/src/Lib/Enum/HttpAuthTypeEnum.php @@ -0,0 +1,38 @@ + + */ + protected $_accessible = [ + '*' => true, + 'id' => false, + 'slug' => false, + ]; +} diff --git a/app/plugins/CoreServer/src/Model/Entity/MatchServer.php b/app/plugins/CoreServer/src/Model/Entity/MatchServer.php new file mode 100644 index 000000000..ceab5bf4f --- /dev/null +++ b/app/plugins/CoreServer/src/Model/Entity/MatchServer.php @@ -0,0 +1,49 @@ + + */ + protected $_accessible = [ + '*' => true, + 'id' => false, + 'slug' => false, + ]; +} diff --git a/app/plugins/CoreServer/src/Model/Table/HttpServersTable.php b/app/plugins/CoreServer/src/Model/Table/HttpServersTable.php new file mode 100644 index 000000000..c92c92d04 --- /dev/null +++ b/app/plugins/CoreServer/src/Model/Table/HttpServersTable.php @@ -0,0 +1,170 @@ +addBehavior('Changelog'); + $this->addBehavior('Log'); + // Timestamp behavior handles created/modified updates + $this->addBehavior('Timestamp'); + + $this->setTableType(\App\Lib\Enum\TableTypeEnum::Configuration); + + // Define associations + $this->belongsTo('Servers'); + + $this->setDisplayField('hostname'); + + $this->setPrimaryLink('server_id'); + $this->setRequiresCO(true); + + $this->setAutoViewVars([ + 'authTypes' => [ + 'type' => 'enum', + 'class' => 'CoreServer.HttpAuthTypeEnum' + ] + ]); + + $this->setPermissions([ + // Actions that operate over an entity (ie: require an $id) + 'entity' => [ + 'delete' => false, // Delete the pluggable object instead + 'edit' => ['platformAdmin', 'coAdmin'], + 'view' => ['platformAdmin', 'coAdmin'] + ], + // Actions that operate over a table (ie: do not require an $id) + 'table' => [ + 'add' => ['platformAdmin', 'coAdmin'], + 'index' => ['platformAdmin', 'coAdmin'] + ] + ]); + } + + /** + * Create an HTTP Client from the HttpServer configuration. + * + * @since COmanage Registry v5.2.0 + * @param int $id HttpServer ID + * @return Client Cake Http Client + */ + + public function createHttpClient(int $id): Client { + // Pull the requested configuration + + $httpServer = $this->get($id); + + // In order to simplify our code (ie: to avoiding parsing the HttpServer URL) + // we create the client via URL, then update the configuration for additional options. + + $Client = Client::createFromUrl($httpServer->url); + + if($httpServer->auth_type == HttpAuthTypeEnum::Basic) { + if(!empty($httpServer->username)) { + $Client->setConfig('username', $httpServer->username); + } + + if(!empty($httpServer->password)) { + $Client->setConfig('password', $httpServer->password); + } + } + + if(!empty($httpServer->skip_ssl_verification) && $httpServer->skip_ssl_verification) { + $Client->setConfig('ssl_verify_peer', false); + } + + return $Client; + } + + /** + * Set validation rules. + * + * @since COmanage Registry v5.2.0 + * @param Validator $validator Validator + * @return Validator Validator + */ + + public function validationDefault(Validator $validator): Validator { + $schema = $this->getSchema(); + + $validator->add('server_id', [ + 'content' => ['rule' => 'isInteger'] + ]); + $validator->notEmptyString('server_id'); + + $validator->add('url', [ + 'content' => ['rule' => ['url'], + 'message' => __d('error', 'input.invalid.url')] + ]); + + $this->registerStringValidation($validator, $schema, 'url', true); + + $this->registerStringValidation($validator, $schema, 'username', false); + + $this->registerStringValidation($validator, $schema, 'password', false); + + $validator->add('auth_type', [ + 'content' => ['rule' => ['inList', HttpAuthTypeEnum::getConstValues()]] + ]); + $validator->allowEmptyString('auth_type'); + + $validator->add('skip_ssl_verification', [ + 'content' => ['rule' => ['boolean']] + ]); + $validator->allowEmptyString('skip_ssl_verification'); + + return $validator; + } +} diff --git a/app/plugins/CoreServer/src/Model/Table/MatchServersTable.php b/app/plugins/CoreServer/src/Model/Table/MatchServersTable.php new file mode 100644 index 000000000..833cca9e7 --- /dev/null +++ b/app/plugins/CoreServer/src/Model/Table/MatchServersTable.php @@ -0,0 +1,95 @@ +addBehavior('Changelog'); + $this->addBehavior('Log'); + // Timestamp behavior handles created/modified updates + $this->addBehavior('Timestamp'); + + $this->setTableType(\App\Lib\Enum\TableTypeEnum::Configuration); + + // Define associations + $this->belongsTo('Servers'); + + $this->setDisplayField('hostname'); + + $this->setPrimaryLink('server_id'); + $this->setRequiresCO(true); + + $this->setPermissions([ + // Actions that operate over an entity (ie: require an $id) + 'entity' => [ + 'delete' => false, // Delete the pluggable object instead + 'edit' => ['platformAdmin', 'coAdmin'], + 'view' => ['platformAdmin', 'coAdmin'] + ], + // Actions that operate over a table (ie: do not require an $id) + 'table' => [ + 'add' => ['platformAdmin', 'coAdmin'], + 'index' => ['platformAdmin', 'coAdmin'] + ] + ]); + } + + /** + * Set validation rules. + * + * @since COmanage Registry v5.2.0 + * @param Validator $validator Validator + * @return Validator Validator + */ + +/* + public function validationDefault(Validator $validator): Validator { + // We don't have any MatchServer specific configurations over the default HttpServer + // configurations, so we can just rely on the parent table. If we ever add any, then + // just call parrent::validationDefault() before adding the MatchServer specific rules. + } + */ +} diff --git a/app/plugins/CoreServer/src/config/plugin.json b/app/plugins/CoreServer/src/config/plugin.json index f01cea921..a1901134c 100644 --- a/app/plugins/CoreServer/src/config/plugin.json +++ b/app/plugins/CoreServer/src/config/plugin.json @@ -1,27 +1,57 @@ { "types": { "server": [ + "HttpServers", + "MatchServers", "SmtpServers", "SqlServers" ] }, "schema": { "tables": { + "http_servers": { + "columns": { + "id": {}, + "server_id": {}, + "url": { "type": "url" }, + "username": {}, + "password": {}, + "auth_type": { "type": "string", "size": 2 }, + "skip_ssl_verification": { "type": "boolean" } + }, + "indexes": { + "http_servers_i1": { "columns": [ "server_id" ]} + } + }, + "match_servers": { + "columns": { + "id": {}, + "server_id": {}, + "url": { "type": "url" }, + "username": {}, + "password": {}, + "auth_type": { "type": "string", "size": 2 }, + "skip_ssl_verification": { "type": "boolean" } + }, + "indexes": { + "match_servers_i1": { "columns": [ "server_id" ] } + } + }, "smtp_servers": { "columns": { "id": {}, "server_id": {}, "hostname": { "type": "string", "size": 128 }, "port": { "type": "integer" }, - "username": { "type": "string", "size": 128 }, - "password": { "type": "string", "size": 80 }, + "username": {}, + "password": {}, "use_tls": { "type": "boolean" }, "default_from": { "type": "string", "size": 256 }, "default_reply_to": { "type": "string", "size": 256 }, "override_to": { "type": "string", "size": 256 } }, "indexes": { - "smtp_servers_i1": { "columns": [ "server_id"] } + "smtp_servers_i1": { "columns": [ "server_id" ] } } }, "sql_servers": { @@ -32,8 +62,8 @@ "hostname": { "type": "string", "size": 128 }, "port": { "type": "integer" }, "databas": { "type": "string", "size": 128 }, - "username": { "type": "string", "size": 128 }, - "password": { "type": "string", "size": 80 } + "username": {}, + "password": {} }, "indexes": { "sql_servers_i1": { "columns": [ "server_id" ]} diff --git a/app/plugins/CoreServer/templates/HttpServers/fields.inc b/app/plugins/CoreServer/templates/HttpServers/fields.inc new file mode 100644 index 000000000..d643222b2 --- /dev/null +++ b/app/plugins/CoreServer/templates/HttpServers/fields.inc @@ -0,0 +1,42 @@ +element('form/listItem', [ + 'arguments' => [ + 'fieldName' => $field + ]]); + } +} diff --git a/app/plugins/CoreServer/templates/MatchServers/fields.inc b/app/plugins/CoreServer/templates/MatchServers/fields.inc new file mode 100644 index 000000000..550143398 --- /dev/null +++ b/app/plugins/CoreServer/templates/MatchServers/fields.inc @@ -0,0 +1,40 @@ +element('form/listItem', [ + 'arguments' => [ + 'fieldName' => 'api_endpoint', + 'fieldOptions' => [ + 'readOnly' => true, + 'default' => $vv_api_endpoint + ] + ]]); \ No newline at end of file diff --git a/app/resources/locales/en_US/command.po b/app/resources/locales/en_US/command.po index c4e0296fb..8fe923810 100644 --- a/app/resources/locales/en_US/command.po +++ b/app/resources/locales/en_US/command.po @@ -135,6 +135,21 @@ msgstr "Message Template ID" msgid "opt.notify.type" msgstr "Type (label) for provided Identifier" +msgid "opt.test.database.source" +msgstr "For database test, the datasource to use" + +msgid "opt.test.http.http_server_id" +msgstr "For http test, the HTTP Server configuration id to use" + +msgid "opt.test.http.url" +msgstr "For http test, the relative URL to retrieve" + +msgid "opt.test.mail.recipient" +msgstr "For email test, the Person ID (NOT email address) to try sending to" + +msgid "opt.test.test" +msgstr "Test to perform" + msgid "opt.upgrade.forcecurrent" msgstr "Force the specified current version -- ADVANCED USERS ONLY" @@ -177,15 +192,6 @@ msgstr "COmanage Platform Administrator" msgid "se.salt" msgstr "Generating salt file {0}" -msgid "opt.test.database.source" -msgstr "For database test, the datasource to use" - -msgid "opt.test.mail.recipient" -msgstr "For email test, the Person ID (NOT email address) to try sending to" - -msgid "opt.test.test" -msgstr "Test to perform" - msgid "tm.epilog" msgstr "An optional, space separated list of tables to transmogrify may be specified" diff --git a/app/resources/locales/en_US/field.po b/app/resources/locales/en_US/field.po index e39ff2fe7..d844d7f8c 100644 --- a/app/resources/locales/en_US/field.po +++ b/app/resources/locales/en_US/field.po @@ -164,6 +164,9 @@ msgstr "format" msgid "full_name" msgstr "Full Name" +msgid "hostname" +msgstr "Hostname" + msgid "language" msgstr "Language" @@ -210,9 +213,15 @@ msgstr "Parameters" msgid "parent_id" msgstr "Parent" +msgid "password" +msgstr "Password" + msgid "plugin" msgstr "Plugin" +msgid "port" +msgstr "Port" + msgid "postal_code" msgstr "Postal Code" diff --git a/app/src/Command/TestCommand.php b/app/src/Command/TestCommand.php index 6040f1001..be346fa8a 100644 --- a/app/src/Command/TestCommand.php +++ b/app/src/Command/TestCommand.php @@ -53,12 +53,16 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar $parser->addOption('test', [ 'help' => __d('command', 'opt.test.test'), 'short' => 't', - 'choices' => ['database', 'mail', 'setup'] + 'choices' => ['database', 'http', 'mail', 'setup'] ])->addOption('datasource', [ 'help' => __d('command', 'opt.test.database.source'), 'default' => 'default' + ])->addOption('http_server_id', [ + 'help' => __d('command', 'opt.test.http.http_server_id') ])->addOption('recipient', [ 'help' => __d('command', 'opt.test.mail.recipient') + ])->addOption('url', [ + 'help' => __d('command', 'opt.test.http.url') ]); return $parser; @@ -85,6 +89,9 @@ public function execute(Arguments $args, ConsoleIo $io) case 'database': $this->testDatabase($args->getOption('datasource')); break; + case 'http': + $this->testHttp((int)$args->getOption('http_server_id'), $args->getOption('url')); + break; case 'mail': $this->testMail((int)$args->getOption('recipient')); break; @@ -118,6 +125,35 @@ protected function testDatabase(string $source): int { return static::CODE_SUCCESS; } + /** + * Test HTTP connectivity via a configured HttpServer and request URL. + * + * @since COmanage Registry v5.2.0 + * @param int $httpServerId HttpServer ID + * @param string $url URL to request (relative to HttpServer configured URL) + * @return int Return Code (CODE_SUCCESS or CODE_ERROR) + */ + + protected function testHttp(int $httpServerId, string $url): int { + try { + $HttpServers = $this->getTableLocator()->get('CoreServer.HttpServers'); + + $Client = $HttpServers->createHttpClient($httpServerId); + + $response = $Client->get($url); + + if(!$response->isOk() || $response->isRedirect()) { + throw new \RuntimeException($response->getReasonPhrase()); + } + } + catch(\Exception $e) { + $this->io->error($e->getMessage()); + $this->abort(static::CODE_ERROR); + } + + return static::CODE_SUCCESS; + } + /** * Test mail delivery to the specified address. *