From 5dc4624ced469adb6103049ca82341cf6f4990a7 Mon Sep 17 00:00:00 2001 From: Eric Merrill Date: Thu, 17 Mar 2016 01:13:15 -0400 Subject: [PATCH] MDL-53412 search: Correctly handle Solr over SSL --- search/engine/solr/classes/engine.php | 76 +++++++++++++++++++++- search/engine/solr/classes/schema.php | 38 ++++------- search/engine/solr/lang/en/search_solr.php | 8 +-- search/engine/solr/settings.php | 1 - search/engine/solr/tests/engine_test.php | 26 +++++++- 5 files changed, 112 insertions(+), 37 deletions(-) diff --git a/search/engine/solr/classes/engine.php b/search/engine/solr/classes/engine.php index 1e45829519d..94c7fd92ea5 100644 --- a/search/engine/solr/classes/engine.php +++ b/search/engine/solr/classes/engine.php @@ -55,6 +55,11 @@ class engine extends \core_search\engine { */ protected $client = null; + /** + * @var \curl Direct curl object. + */ + protected $curl = null; + /** * @var array Fields that can be highlighted. */ @@ -417,11 +422,10 @@ class engine extends \core_search\engine { 'login' => !empty($this->config->server_username) ? $this->config->server_username : '', 'password' => !empty($this->config->server_password) ? $this->config->server_password : '', 'port' => !empty($this->config->server_port) ? $this->config->server_port : '', - 'issecure' => !empty($this->config->secure) ? $this->config->secure : '', + 'secure' => !empty($this->config->secure) ? true : false, 'ssl_cert' => !empty($this->config->ssl_cert) ? $this->config->ssl_cert : '', - 'ssl_cert_only' => !empty($this->config->ssl_cert_only) ? $this->config->ssl_cert_only : '', 'ssl_key' => !empty($this->config->ssl_key) ? $this->config->ssl_key : '', - 'ssl_password' => !empty($this->config->ssl_keypassword) ? $this->config->ssl_keypassword : '', + 'ssl_keypassword' => !empty($this->config->ssl_keypassword) ? $this->config->ssl_keypassword : '', 'ssl_cainfo' => !empty($this->config->ssl_cainfo) ? $this->config->ssl_cainfo : '', 'ssl_capath' => !empty($this->config->ssl_capath) ? $this->config->ssl_capath : '', ); @@ -434,4 +438,70 @@ class engine extends \core_search\engine { return $this->client; } + + /** + * Returns a curl object for conntecting to solr. + * + * @return \curl + */ + public function get_curl_object() { + if (!is_null($this->curl)) { + return $this->curl; + } + + $this->curl = new \curl(); + + $options = array(); + // Build the SSL options. Based on pecl-solr and general testing. + if (!empty($this->config->secure)) { + if (!empty($this->config->ssl_cert)) { + $options['CURLOPT_SSLCERT'] = $this->config->ssl_cert; + $options['CURLOPT_SSLCERTTYPE'] = 'PEM'; + } + + if (!empty($this->config->ssl_key)) { + $options['CURLOPT_SSLKEY'] = $this->config->ssl_key; + $options['CURLOPT_SSLKEYTYPE'] = 'PEM'; + } + + if (!empty($this->config->ssl_keypassword)) { + $options['CURLOPT_KEYPASSWD'] = $this->config->ssl_keypassword; + } + + if (!empty($this->config->ssl_cainfo)) { + $options['CURLOPT_CAINFO'] = $this->config->ssl_cainfo; + } + + if (!empty($this->config->ssl_capath)) { + $options['CURLOPT_CAPATH'] = $this->config->ssl_capath; + } + } + + $this->curl->setopt($options); + + if (!empty($this->config->server_username) && !empty($this->config->server_password)) { + $authorization = $this->config->server_username . ':' . $this->config->server_password; + $this->curl->setHeader('Authorization', 'Basic ' . base64_encode($authorization)); + } + + return $this->curl; + } + + /** + * Return a Moodle url object for the server connection. + * + * @param string $path The solr path to append. + * @return \moodle_url + */ + public function get_connection_url($path) { + // Must use the proper protocol, or SSL will fail. + $protocol = !empty($this->config->secure) ? 'https' : 'http'; + $url = $protocol . '://' . rtrim($this->config->server_hostname, '/'); + if (!empty($this->config->server_port)) { + $url .= ':' . $this->config->server_port; + } + $url .= '/solr/' . $this->config->indexname . '/' . ltrim($path, '/'); + + return new \moodle_url($url); + } } diff --git a/search/engine/solr/classes/schema.php b/search/engine/solr/classes/schema.php index 5adff6df3f4..c68268fa902 100644 --- a/search/engine/solr/classes/schema.php +++ b/search/engine/solr/classes/schema.php @@ -52,16 +52,10 @@ class schema { protected $curl = null; /** - * The URL. - * @var string + * An engine instance. + * @var engine */ - protected $url = null; - - /** - * The schema URL. - * @var string - */ - protected $schemaurl = null; + protected $engine = null; /** * Constructor. @@ -78,23 +72,11 @@ class schema { throw new \moodle_exception('missingconfig', 'search_solr'); } - $this->curl = new \curl(); + $this->engine = new engine(); + $this->curl = $this->engine->get_curl_object(); // HTTP headers. $this->curl->setHeader('Content-type: application/json'); - if (!empty($this->config->server_username) && !empty($this->config->server_password)) { - $authorization = $this->config->server_username . ':' . $this->config->server_password; - $this->curl->setHeader('Authorization', 'Basic ' . base64_encode($authorization)); - } - - $this->url = rtrim($this->config->server_hostname, '/'); - if (!empty($this->config->server_port)) { - $this->url .= ':' . $this->config->server_port; - } - $this->url .= '/solr/' . $this->config->indexname; - $this->schemaurl = $this->url . '/schema'; - - } /** @@ -139,7 +121,8 @@ class schema { protected function check_index() { // Check that the server is available and the index exists. - $result = $this->curl->get($this->url . '/select?wt=json'); + $url = $this->engine->get_connection_url('/select?wt=json'); + $result = $this->curl->get($url); if ($this->curl->error) { throw new \moodle_exception('connectionerror', 'search_solr'); } @@ -167,6 +150,8 @@ class schema { $this->validate_fields($fields, false); } + $url = $this->engine->get_connection_url('/schema'); + // Add all fields. foreach ($fields as $fieldname => $data) { @@ -183,7 +168,7 @@ class schema { 'indexed' => $data['indexed'] ) ); - $results = $this->curl->post($this->schemaurl, json_encode($params)); + $results = $this->curl->post($url, json_encode($params)); // We only validate if we are interested on it. if ($checkexisting) { @@ -209,7 +194,8 @@ class schema { global $CFG; foreach ($fields as $fieldname => $data) { - $results = $this->curl->get($this->schemaurl . '/fields/' . $fieldname); + $url = $this->engine->get_connection_url('/schema/fields/' . $fieldname); + $results = $this->curl->get($url); if ($this->curl->error) { throw new \moodle_exception('errorcreatingschema', 'search_solr', '', $this->curl->error); diff --git a/search/engine/solr/lang/en/search_solr.php b/search/engine/solr/lang/en/search_solr.php index 9869aa5d72f..5633931e6b0 100644 --- a/search/engine/solr/lang/en/search_solr.php +++ b/search/engine/solr/lang/en/search_solr.php @@ -63,11 +63,9 @@ $string['solrsslcainfo'] = 'SSL CA certificates name'; $string['solrsslcainfo_desc'] = 'File name holding one or more CA certificates to verify peer with'; $string['solrsslcapath'] = 'SSL CA certificates path'; $string['solrsslcapath_desc'] = 'Directory path holding multiple CA certificates to verify peer with'; -$string['solrsslcert'] = 'SSL key & certificate'; -$string['solrsslcert_desc'] = 'File name to a PEM-formatted private key + private certificate (concatenated in that order)'; -$string['solrsslcertonly'] = 'SSL certificate'; -$string['solrsslcertonly_desc'] = 'File name to a PEM-formatted private certificate only'; +$string['solrsslcert'] = 'SSL certificate'; +$string['solrsslcert_desc'] = 'File name to a PEM-formatted private certificate'; $string['solrsslkey'] = 'SSL key'; $string['solrsslkey_desc'] = 'File name to a PEM-formatted private key'; -$string['solrsslkeypassword'] = 'SSL Key password'; +$string['solrsslkeypassword'] = 'SSL key password'; $string['solrsslkeypassword_desc'] = 'Password for PEM-formatted private key file'; diff --git a/search/engine/solr/settings.php b/search/engine/solr/settings.php index 995005f0e6b..32b409e7add 100644 --- a/search/engine/solr/settings.php +++ b/search/engine/solr/settings.php @@ -42,7 +42,6 @@ if ($ADMIN->fulltree) { $settings->add(new admin_setting_configtext('search_solr/server_password', new lang_string('solrauthpassword', 'search_solr'), '', '', PARAM_RAW)); $settings->add(new admin_setting_configtext('search_solr/server_timeout', new lang_string('solrhttpconnectiontimeout', 'search_solr'), new lang_string('solrhttpconnectiontimeout_desc', 'search_solr'), 30, PARAM_INT)); $settings->add(new admin_setting_configtext('search_solr/ssl_cert', new lang_string('solrsslcert', 'search_solr'), new lang_string('solrsslcert_desc', 'search_solr'), '', PARAM_RAW)); - $settings->add(new admin_setting_configtext('search_solr/ssl_cert_only', new lang_string('solrsslcertonly', 'search_solr'), new lang_string('solrsslcertonly_desc', 'search_solr'), '', PARAM_RAW)); $settings->add(new admin_setting_configtext('search_solr/ssl_key', new lang_string('solrsslkey', 'search_solr'), new lang_string('solrsslkey_desc', 'search_solr'), '', PARAM_RAW)); $settings->add(new admin_setting_configtext('search_solr/ssl_keypassword', new lang_string('solrsslkeypassword', 'search_solr'), new lang_string('solrsslkeypassword_desc', 'search_solr'), '', PARAM_RAW)); $settings->add(new admin_setting_configtext('search_solr/ssl_cainfo', new lang_string('solrsslcainfo', 'search_solr'), new lang_string('solrsslcainfo_desc', 'search_solr'), '', PARAM_RAW)); diff --git a/search/engine/solr/tests/engine_test.php b/search/engine/solr/tests/engine_test.php index da9234db71e..ebd5721525f 100644 --- a/search/engine/solr/tests/engine_test.php +++ b/search/engine/solr/tests/engine_test.php @@ -25,6 +25,10 @@ * Optional params: * - define('TEST_SEARCH_SOLR_USERNAME', ''); * - define('TEST_SEARCH_SOLR_PASSWORD', ''); + * - define('TEST_SEARCH_SOLR_SSLCERT', ''); + * - define('TEST_SEARCH_SOLR_SSLKEY', ''); + * - define('TEST_SEARCH_SOLR_KEYPASSWORD', ''); + * - define('TEST_SEARCH_SOLR_CAINFOCERT', ''); * * @package core_search * @category phpunit @@ -71,13 +75,31 @@ class search_solr_engine_testcase extends advanced_testcase { set_config('indexname', TEST_SEARCH_SOLR_INDEXNAME, 'search_solr'); if (defined('TEST_SEARCH_SOLR_USERNAME')) { - set_config('server_username', TEST_SEARCH_SOLR_USERNAME); + set_config('server_username', TEST_SEARCH_SOLR_USERNAME, 'search_solr'); } if (defined('TEST_SEARCH_SOLR_PASSWORD')) { - set_config('server_password', TEST_SEARCH_SOLR_PASSWORD); + set_config('server_password', TEST_SEARCH_SOLR_PASSWORD, 'search_solr'); } + if (defined('TEST_SEARCH_SOLR_SSLCERT')) { + set_config('secure', true, 'search_solr'); + set_config('ssl_cert', TEST_SEARCH_SOLR_SSLCERT, 'search_solr'); + } + + if (defined('TEST_SEARCH_SOLR_SSLKEY')) { + set_config('ssl_key', TEST_SEARCH_SOLR_SSLKEY, 'search_solr'); + } + + if (defined('TEST_SEARCH_SOLR_KEYPASSWORD')) { + set_config('ssl_keypassword', TEST_SEARCH_SOLR_KEYPASSWORD, 'search_solr'); + } + + if (defined('TEST_SEARCH_SOLR_CAINFOCERT')) { + set_config('ssl_cainfo', TEST_SEARCH_SOLR_CAINFOCERT, 'search_solr'); + } + + // Inject search solr engine into the testable core search as we need to add the mock // search component to it. $searchengine = new \search_solr\engine();