From d08cfd2d9da1305a7d8f2afe297d4acb3b997159 Mon Sep 17 00:00:00 2001 From: Mike Alhayek Date: Fri, 19 Jan 2024 15:04:50 -0800 Subject: [PATCH 1/2] Eliminate the anti-discovery pattern in Elasticsearch Fix #15133 --- .../Controllers/AdminController.cs | 68 ++++++++- ...tPickerFieldElasticEditorSettingsDriver.cs | 3 +- .../Drivers/ElasticSettingsDisplayDriver.cs | 10 ++ .../Services/ElasticSearchService.cs | 15 +- .../Startup.cs | 106 +------------- .../Views/Admin/NotConfigured.cshtml | 3 + .../Views/ElasticSettings.Edit.cshtml | 11 ++ .../Models/ElasticConnectionOptions.cs | 20 ++- .../ElasticContentPickerResultProvider.cs | 11 +- .../ElasticConnectionOptionsConfigurations.cs | 135 ++++++++++++++++++ .../Services/ElasticIndexingService.cs | 9 ++ .../Services/IndexingBackgroundTask.cs | 47 +++--- 12 files changed, 302 insertions(+), 136 deletions(-) create mode 100644 src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Views/Admin/NotConfigured.cshtml create mode 100644 src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticConnectionOptionsConfigurations.cs diff --git a/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Controllers/AdminController.cs b/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Controllers/AdminController.cs index 5b9cf0194e4..a9c4997dc5f 100644 --- a/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Controllers/AdminController.cs +++ b/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Controllers/AdminController.cs @@ -50,6 +50,7 @@ public class AdminController : Controller private readonly INotifier _notifier; private readonly ILogger _logger; private readonly IOptions _templateOptions; + private readonly ElasticConnectionOptions _elasticConnectionOptions; private readonly IShapeFactory _shapeFactory; private readonly ILocalizationService _localizationService; @@ -71,6 +72,7 @@ public AdminController( INotifier notifier, ILogger logger, IOptions templateOptions, + IOptions elasticConnectionOptions, IShapeFactory shapeFactory, ILocalizationService localizationService, IStringLocalizer stringLocalizer, @@ -90,6 +92,7 @@ public AdminController( _notifier = notifier; _logger = logger; _templateOptions = templateOptions; + _elasticConnectionOptions = elasticConnectionOptions.Value; _shapeFactory = shapeFactory; _localizationService = localizationService; S = stringLocalizer; @@ -103,6 +106,11 @@ public async Task Index(ContentOptions options, PagerParameters p return Forbid(); } + if (!_elasticConnectionOptions.IsFileConfigurationExists()) + { + return NotConfigured(); + } + var indexes = (await _elasticIndexSettingsService.GetSettingsAsync()) .Select(i => new IndexViewModel { Name = i.IndexName }) .ToList(); @@ -150,13 +158,13 @@ public async Task Index(ContentOptions options, PagerParameters p [HttpPost, ActionName(nameof(Index))] [FormValueRequired("submit.Filter")] - public ActionResult IndexFilterPOST(AdminIndexViewModel model) + public IActionResult IndexFilterPOST(AdminIndexViewModel model) => RedirectToAction(nameof(Index), new RouteValueDictionary { { _optionsSearch, model.Options.Search } }); - public async Task Edit(string indexName = null) + public async Task Edit(string indexName = null) { var IsCreate = string.IsNullOrWhiteSpace(indexName); var settings = new ElasticIndexSettings(); @@ -166,6 +174,11 @@ public async Task Edit(string indexName = null) return Forbid(); } + if (!_elasticConnectionOptions.IsFileConfigurationExists()) + { + return NotConfigured(); + } + if (!IsCreate) { settings = await _elasticIndexSettingsService.GetSettingsAsync(indexName); @@ -200,6 +213,11 @@ public async Task EditPost(ElasticIndexSettingsViewModel model, st return Forbid(); } + if (!_elasticConnectionOptions.IsFileConfigurationExists()) + { + return BadRequest(); + } + ValidateModel(model); if (model.IsCreate) @@ -295,6 +313,11 @@ public async Task Reset(string id) return Forbid(); } + if (!_elasticConnectionOptions.IsFileConfigurationExists()) + { + return BadRequest(); + } + if (!await _elasticIndexManager.ExistsAsync(id)) { return NotFound(); @@ -316,6 +339,11 @@ public async Task Rebuild(string id) return Forbid(); } + if (!_elasticConnectionOptions.IsFileConfigurationExists()) + { + return BadRequest(); + } + if (!await _elasticIndexManager.ExistsAsync(id)) { return NotFound(); @@ -349,6 +377,11 @@ public async Task Delete(ElasticIndexSettingsViewModel model) return Forbid(); } + if (!_elasticConnectionOptions.IsFileConfigurationExists()) + { + return BadRequest(); + } + if (!await _elasticIndexManager.ExistsAsync(model.IndexName)) { await _notifier.SuccessAsync(H["Index not found on Elasticsearch server.", model.IndexName]); @@ -378,6 +411,11 @@ public async Task ForceDelete(ElasticIndexSettingsViewModel model) return Forbid(); } + if (!_elasticConnectionOptions.IsFileConfigurationExists()) + { + return BadRequest(); + } + try { await _elasticIndexingService.DeleteIndexAsync(model.IndexName); @@ -416,12 +454,19 @@ public async Task SyncSettings() return RedirectToAction(nameof(Index)); } - public Task Query(string indexName, string query) - => Query(new AdminQueryViewModel + public async Task Query(string indexName, string query) + { + if (!_elasticConnectionOptions.IsFileConfigurationExists()) + { + return NotConfigured(); + } + + return await Query(new AdminQueryViewModel { IndexName = indexName, DecodedQuery = string.IsNullOrWhiteSpace(query) ? string.Empty : System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(query)) }); + } [HttpPost] public async Task Query(AdminQueryViewModel model) @@ -431,6 +476,11 @@ public async Task Query(AdminQueryViewModel model) return Forbid(); } + if (!_elasticConnectionOptions.IsFileConfigurationExists()) + { + return BadRequest(); + } + model.Indices = (await _elasticIndexSettingsService.GetSettingsAsync()).Select(x => x.IndexName).ToArray(); // Can't query if there are no indices. @@ -496,6 +546,11 @@ public async Task IndexPost(ContentOptions options, IEnumerable 0) { var elasticIndexSettings = await _elasticIndexSettingsService.GetSettingsAsync(); @@ -540,7 +595,7 @@ public async Task IndexPost(ContentOptions options, IEnumerable new SelectListItem { Text = x.Key, Value = x.Key }); } + + private IActionResult NotConfigured() + => View("NotConfigured"); } } diff --git a/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Drivers/ContentPickerFieldElasticEditorSettingsDriver.cs b/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Drivers/ContentPickerFieldElasticEditorSettingsDriver.cs index db6e8adb42c..bb702c4ba3d 100644 --- a/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Drivers/ContentPickerFieldElasticEditorSettingsDriver.cs +++ b/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Drivers/ContentPickerFieldElasticEditorSettingsDriver.cs @@ -1,4 +1,3 @@ -using System; using System.Linq; using System.Threading.Tasks; using OrchardCore.ContentManagement.Metadata.Models; @@ -22,7 +21,7 @@ public override IDisplayResult Edit(ContentPartFieldDefinition partFieldDefiniti { return Initialize("ContentPickerFieldElasticEditorSettings_Edit", async model => { - partFieldDefinition.PopulateSettings(model); + partFieldDefinition.PopulateSettings(model); model.Indices = (await _elasticIndexSettingsService.GetSettingsAsync()).Select(x => x.IndexName).ToArray(); }).Location("Editor"); } diff --git a/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Drivers/ElasticSettingsDisplayDriver.cs b/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Drivers/ElasticSettingsDisplayDriver.cs index 1bd65044e39..f511aa06715 100644 --- a/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Drivers/ElasticSettingsDisplayDriver.cs +++ b/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Drivers/ElasticSettingsDisplayDriver.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Options; using Nest; using OrchardCore.DisplayManagement.Entities; using OrchardCore.DisplayManagement.Handlers; @@ -30,13 +31,16 @@ public class ElasticSettingsDisplayDriver : SectionDisplayDriver elasticConnectionOptions, IElasticClient elasticClient, IStringLocalizer stringLocalizer ) @@ -44,6 +48,7 @@ IStringLocalizer stringLocalizer _elasticIndexSettingsService = elasticIndexSettingsService; _httpContextAccessor = httpContextAccessor; _authorizationService = authorizationService; + _elasticConnectionOptions = elasticConnectionOptions.Value; _elasticClient = elasticClient; S = stringLocalizer; } @@ -73,6 +78,11 @@ public override async Task UpdateAsync(ElasticSettings section, return null; } + if (!_elasticConnectionOptions.IsFileConfigurationExists()) + { + return null; + } + if (!await _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext?.User, Permissions.ManageElasticIndexes)) { return null; diff --git a/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Services/ElasticSearchService.cs b/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Services/ElasticSearchService.cs index 3fb3aaa136a..509175dcba9 100644 --- a/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Services/ElasticSearchService.cs +++ b/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Services/ElasticSearchService.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Fluid.Values; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Nest; using OrchardCore.Liquid; using OrchardCore.Search.Abstractions; @@ -25,6 +26,7 @@ public class ElasticsearchService : ISearchService private readonly IElasticSearchQueryService _elasticsearchQueryService; private readonly IElasticClient _elasticClient; private readonly JavaScriptEncoder _javaScriptEncoder; + private readonly ElasticConnectionOptions _elasticConnectionOptions; private readonly ILiquidTemplateManager _liquidTemplateManager; private readonly ILogger _logger; @@ -35,6 +37,7 @@ public ElasticsearchService( IElasticSearchQueryService elasticsearchQueryService, IElasticClient elasticClient, JavaScriptEncoder javaScriptEncoder, + IOptions elasticConnectionOptions, ILiquidTemplateManager liquidTemplateManager, ILogger logger ) @@ -45,6 +48,7 @@ ILogger logger _elasticsearchQueryService = elasticsearchQueryService; _elasticClient = elasticClient; _javaScriptEncoder = javaScriptEncoder; + _elasticConnectionOptions = elasticConnectionOptions.Value; _liquidTemplateManager = liquidTemplateManager; _logger = logger; } @@ -53,13 +57,20 @@ ILogger logger public async Task SearchAsync(string indexName, string term, int start, int pageSize) { + var result = new SearchResult(); + + if (!_elasticConnectionOptions.IsFileConfigurationExists()) + { + _logger.LogWarning("Elasticsearch: Couldn't execute search. The Elasticsearch has not yet been configured."); + + return result; + } + var siteSettings = await _siteService.GetSiteSettingsAsync(); var searchSettings = siteSettings.As(); var index = !string.IsNullOrWhiteSpace(indexName) ? indexName.Trim() : searchSettings.SearchIndex; - var result = new SearchResult(); - if (index == null || !await _elasticIndexManager.ExistsAsync(index)) { _logger.LogWarning("Elasticsearch: Couldn't execute search. The search index doesn't exist."); diff --git a/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Startup.cs index d928a152f0b..aac126eb5df 100644 --- a/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Startup.cs @@ -1,8 +1,6 @@ using System; -using System.Linq; using System.Text.Json; using System.Text.Json.Nodes; -using Elasticsearch.Net; using GraphQL; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; @@ -39,7 +37,6 @@ namespace OrchardCore.Search.Elasticsearch { public class Startup : StartupBase { - private const string ConfigSectionName = "OrchardCore_Elasticsearch"; private readonly AdminOptions _adminOptions; private readonly IShellConfiguration _shellConfiguration; private readonly ILogger _logger; @@ -55,21 +52,19 @@ public Startup(IOptions adminOptions, public override void ConfigureServices(IServiceCollection services) { - var configuration = _shellConfiguration.GetSection(ConfigSectionName); - var elasticConfiguration = configuration.Get(); + services.AddTransient, ElasticConnectionOptionsConfigurations>(); - if (!CheckOptions(elasticConfiguration, _logger)) + services.AddSingleton((sp) => { - return; - } + var options = sp.GetRequiredService>().Value; - services.Configure(o => o.ConfigurationExists = true); - var settings = GetConnectionSettings(elasticConfiguration); - - services.AddSingleton(new ElasticClient(settings)); + return new ElasticClient(options.GetConnectionSettings() ?? new ConnectionSettings()); + }); services.Configure(o => { + var configuration = _shellConfiguration.GetSection(ElasticConnectionOptionsConfigurations.ConfigSectionName); + o.IndexPrefix = configuration.GetValue(nameof(o.IndexPrefix)); var jsonNode = configuration.GetSection(nameof(o.Analyzers)).AsJsonNode(); @@ -109,30 +104,6 @@ public override void ConfigureServices(IServiceCollection services) services.AddScoped, ElasticQueryDisplayDriver>(); } - private static ConnectionSettings GetConnectionSettings(ElasticConnectionOptions elasticConfiguration) - { - var pool = GetConnectionPool(elasticConfiguration); - - var settings = new ConnectionSettings(pool); - - if (elasticConfiguration.ConnectionType != "CloudConnectionPool" && !string.IsNullOrWhiteSpace(elasticConfiguration.Username) && !string.IsNullOrWhiteSpace(elasticConfiguration.Password)) - { - settings.BasicAuthentication(elasticConfiguration.Username, elasticConfiguration.Password); - } - - if (!string.IsNullOrWhiteSpace(elasticConfiguration.CertificateFingerprint)) - { - settings.CertificateFingerprint(elasticConfiguration.CertificateFingerprint); - } - - if (elasticConfiguration.EnableApiVersioningHeader) - { - settings.EnableApiVersioningHeader(); - } - - return settings; - } - public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider) { var adminControllerName = typeof(AdminController).ControllerName(); @@ -179,69 +150,6 @@ public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder ro defaults: new { controller = adminControllerName, action = nameof(AdminController.SyncSettings) } ); } - - private static bool CheckOptions(ElasticConnectionOptions elasticConnectionOptions, ILogger logger) - { - if (elasticConnectionOptions == null) - { - logger.LogError("Elasticsearch is enabled but not active because the configuration is missing."); - return false; - } - - var optionsAreValid = true; - - if (string.IsNullOrWhiteSpace(elasticConnectionOptions.Url)) - { - logger.LogError("Elasticsearch is enabled but not active because the 'Url' is missing or empty in application configuration."); - optionsAreValid = false; - } - - if (elasticConnectionOptions.Ports?.Length == 0) - { - logger.LogError("Elasticsearch is enabled but not active because a port is missing in application configuration."); - optionsAreValid = false; - } - - return optionsAreValid; - } - - private static IConnectionPool GetConnectionPool(ElasticConnectionOptions elasticConfiguration) - { - var uris = elasticConfiguration.Ports.Select(port => new Uri($"{elasticConfiguration.Url}:{port}")).Distinct(); - IConnectionPool pool = null; - switch (elasticConfiguration.ConnectionType) - { - case "SingleNodeConnectionPool": - pool = new SingleNodeConnectionPool(uris.First()); - break; - - case "CloudConnectionPool": - if (!string.IsNullOrWhiteSpace(elasticConfiguration.Username) && !string.IsNullOrWhiteSpace(elasticConfiguration.Password) && !string.IsNullOrWhiteSpace(elasticConfiguration.CloudId)) - { - var credentials = new BasicAuthenticationCredentials(elasticConfiguration.Username, elasticConfiguration.Password); - pool = new CloudConnectionPool(elasticConfiguration.CloudId, credentials); - } - break; - - case "StaticConnectionPool": - pool = new StaticConnectionPool(uris); - break; - - case "SniffingConnectionPool": - pool = new SniffingConnectionPool(uris); - break; - - case "StickyConnectionPool": - pool = new StickyConnectionPool(uris); - break; - - default: - pool = new SingleNodeConnectionPool(uris.First()); - break; - } - - return pool; - } } [RequireFeatures("OrchardCore.Search")] diff --git a/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Views/Admin/NotConfigured.cshtml b/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Views/Admin/NotConfigured.cshtml new file mode 100644 index 00000000000..13ee5b42c9c --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Views/Admin/NotConfigured.cshtml @@ -0,0 +1,3 @@ + diff --git a/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Views/ElasticSettings.Edit.cshtml b/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Views/ElasticSettings.Edit.cshtml index a9f10b96004..245c402ebd2 100644 --- a/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Views/ElasticSettings.Edit.cshtml +++ b/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Views/ElasticSettings.Edit.cshtml @@ -1,7 +1,18 @@ +@using Microsoft.Extensions.Options @using OrchardCore.Contents.Indexing @using OrchardCore.Search.Elasticsearch.Core.Models @model ElasticSettingsViewModel +@inject IOptions ElasticConnectionOptions + +@if (!ElasticConnectionOptions.Value.IsFileConfigurationExists()) +{ + + + return; +} @if (!Model.SearchIndexes.Any()) { diff --git a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Models/ElasticConnectionOptions.cs b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Models/ElasticConnectionOptions.cs index 0826913f6a5..7ae8ba39ff3 100644 --- a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Models/ElasticConnectionOptions.cs +++ b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Models/ElasticConnectionOptions.cs @@ -1,3 +1,5 @@ +using Nest; + namespace OrchardCore.Search.Elasticsearch.Core.Models { public class ElasticConnectionOptions @@ -38,13 +40,27 @@ public class ElasticConnectionOptions public string CertificateFingerprint { get; set; } /// - /// Enables compatibility mode for Elasticsearch 8.x + /// Enables compatibility mode for Elasticsearch 8.x. /// public bool EnableApiVersioningHeader { get; set; } = false; /// /// Whether the configuration section exists. /// - public bool ConfigurationExists { get; set; } + private bool _fileConfigurationExists { get; set; } + + private IConnectionSettingsValues _conntectionSettings; + + public void SetFileConfigurationExists(bool fileConfigurationExists) + => _fileConfigurationExists = fileConfigurationExists; + + public bool IsFileConfigurationExists() + => _fileConfigurationExists; + + public void SetConnectionSettings(IConnectionSettingsValues settings) + => _conntectionSettings = settings; + + public IConnectionSettingsValues GetConnectionSettings() + => _conntectionSettings; } } diff --git a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Providers/ElasticContentPickerResultProvider.cs b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Providers/ElasticContentPickerResultProvider.cs index 81a30d4940d..c4ea3d4e2fa 100644 --- a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Providers/ElasticContentPickerResultProvider.cs +++ b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Providers/ElasticContentPickerResultProvider.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.Extensions.Options; using Nest; using OrchardCore.ContentManagement; using OrchardCore.Environment.Shell; @@ -13,12 +14,15 @@ public class ElasticContentPickerResultProvider : IContentPickerResultProvider { private readonly ElasticIndexManager _elasticIndexManager; private readonly string _indexPrefix; + private readonly ElasticConnectionOptions _elasticConnectionOptions; public ElasticContentPickerResultProvider( ShellSettings shellSettings, + IOptions elasticConnectionOptions, ElasticIndexManager elasticIndexManager) { _indexPrefix = shellSettings.Name.ToLowerInvariant() + "_"; + _elasticConnectionOptions = elasticConnectionOptions.Value; _elasticIndexManager = elasticIndexManager; } @@ -26,6 +30,11 @@ public ElasticContentPickerResultProvider( public async Task> Search(ContentPickerSearchContext searchContext) { + if (!_elasticConnectionOptions.IsFileConfigurationExists()) + { + return Enumerable.Empty(); + } + string indexName = null; var fieldSettings = searchContext.PartFieldDefinition?.GetSettings(); @@ -37,7 +46,7 @@ public async Task> Search(ContentPickerSearchCo if (indexName != null && !await _elasticIndexManager.ExistsAsync(indexName)) { - return new List(); + return Enumerable.Empty(); } var results = new List(); diff --git a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticConnectionOptionsConfigurations.cs b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticConnectionOptionsConfigurations.cs new file mode 100644 index 00000000000..0f1d0ebf884 --- /dev/null +++ b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticConnectionOptionsConfigurations.cs @@ -0,0 +1,135 @@ +using System; +using System.Linq; +using Elasticsearch.Net; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Nest; +using OrchardCore.Environment.Shell.Configuration; +using OrchardCore.Search.Elasticsearch.Core.Models; + +namespace OrchardCore.Search.Elasticsearch.Core.Services; + +public class ElasticConnectionOptionsConfigurations : IConfigureOptions +{ + public const string ConfigSectionName = "OrchardCore_Elasticsearch"; + + private readonly IShellConfiguration _shellConfiguration; + private readonly ILogger _logger; + + public ElasticConnectionOptionsConfigurations( + IShellConfiguration shellConfiguration, + ILogger logger) + { + _shellConfiguration = shellConfiguration; + _logger = logger; + } + + public void Configure(ElasticConnectionOptions options) + { + var fileOptions = _shellConfiguration.GetSection(ConfigSectionName).Get() + ?? new ElasticConnectionOptions(); + + options.Url = fileOptions.Url; + options.Ports = fileOptions.Ports; + options.ConnectionType = fileOptions.ConnectionType; + options.CloudId = fileOptions.CloudId; + options.Username = fileOptions.Username; + options.Password = fileOptions.Password; + options.CertificateFingerprint = fileOptions.CertificateFingerprint; + options.EnableApiVersioningHeader = fileOptions.EnableApiVersioningHeader; + + if (HasConnectionInfo(options)) + { + options.SetFileConfigurationExists(true); + options.SetConnectionSettings(GetConnectionSettings(options)); + } + } + + private bool HasConnectionInfo(ElasticConnectionOptions elasticConnectionOptions) + { + if (elasticConnectionOptions == null) + { + _logger.LogError("Elasticsearch is enabled but not active because the configuration is missing."); + return false; + } + + var optionsAreValid = true; + + if (string.IsNullOrWhiteSpace(elasticConnectionOptions.Url)) + { + _logger.LogError("Elasticsearch is enabled but not active because the 'Url' is missing or empty in application configuration."); + optionsAreValid = false; + } + + if (elasticConnectionOptions.Ports?.Length == 0) + { + _logger.LogError("Elasticsearch is enabled but not active because a port is missing in application configuration."); + optionsAreValid = false; + } + + return optionsAreValid; + } + + private static ConnectionSettings GetConnectionSettings(ElasticConnectionOptions elasticConfiguration) + { + var pool = GetConnectionPool(elasticConfiguration); + + var settings = new ConnectionSettings(pool); + + if (elasticConfiguration.ConnectionType != "CloudConnectionPool" && !string.IsNullOrWhiteSpace(elasticConfiguration.Username) && !string.IsNullOrWhiteSpace(elasticConfiguration.Password)) + { + settings.BasicAuthentication(elasticConfiguration.Username, elasticConfiguration.Password); + } + + if (!string.IsNullOrWhiteSpace(elasticConfiguration.CertificateFingerprint)) + { + settings.CertificateFingerprint(elasticConfiguration.CertificateFingerprint); + } + + if (elasticConfiguration.EnableApiVersioningHeader) + { + settings.EnableApiVersioningHeader(); + } + + return settings; + } + + private static IConnectionPool GetConnectionPool(ElasticConnectionOptions elasticConfiguration) + { + var uris = elasticConfiguration.Ports.Select(port => new Uri($"{elasticConfiguration.Url}:{port}")).Distinct(); + IConnectionPool pool = null; + switch (elasticConfiguration.ConnectionType) + { + case "SingleNodeConnectionPool": + pool = new SingleNodeConnectionPool(uris.First()); + break; + + case "CloudConnectionPool": + if (!string.IsNullOrWhiteSpace(elasticConfiguration.Username) && !string.IsNullOrWhiteSpace(elasticConfiguration.Password) && !string.IsNullOrWhiteSpace(elasticConfiguration.CloudId)) + { + var credentials = new BasicAuthenticationCredentials(elasticConfiguration.Username, elasticConfiguration.Password); + pool = new CloudConnectionPool(elasticConfiguration.CloudId, credentials); + } + break; + + case "StaticConnectionPool": + pool = new StaticConnectionPool(uris); + break; + + case "SniffingConnectionPool": + pool = new SniffingConnectionPool(uris); + break; + + case "StickyConnectionPool": + pool = new StickyConnectionPool(uris); + break; + + default: + pool = new SingleNodeConnectionPool(uris.First()); + break; + } + + return pool; + } +} diff --git a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticIndexingService.cs b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticIndexingService.cs index 866041a0c3b..93a5e072307 100644 --- a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticIndexingService.cs +++ b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticIndexingService.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Newtonsoft.Json.Linq; using OrchardCore.ContentLocalization; using OrchardCore.ContentManagement; @@ -29,6 +30,7 @@ public class ElasticIndexingService private readonly ElasticIndexSettingsService _elasticIndexSettingsService; private readonly ElasticIndexManager _indexManager; private readonly IIndexingTaskManager _indexingTaskManager; + private readonly ElasticConnectionOptions _elasticConnectionOptions; private readonly ISiteService _siteService; private readonly IContentDefinitionManager _contentDefinitionManager; private readonly ILogger _logger; @@ -39,6 +41,7 @@ public ElasticIndexingService( ElasticIndexSettingsService elasticIndexSettingsService, ElasticIndexManager indexManager, IIndexingTaskManager indexingTaskManager, + IOptions elasticConnectionOptions, ISiteService siteService, IContentDefinitionManager contentDefinitionManager, ILogger logger) @@ -48,6 +51,7 @@ public ElasticIndexingService( _elasticIndexSettingsService = elasticIndexSettingsService; _indexManager = indexManager; _indexingTaskManager = indexingTaskManager; + _elasticConnectionOptions = elasticConnectionOptions.Value; _siteService = siteService; _contentDefinitionManager = contentDefinitionManager; _logger = logger; @@ -55,6 +59,11 @@ public ElasticIndexingService( public async Task ProcessContentItemsAsync(string indexName = default) { + if (!_elasticConnectionOptions.IsFileConfigurationExists()) + { + return; + } + var allIndices = new Dictionary(); var lastTaskId = long.MaxValue; IEnumerable indexSettingsList = null; diff --git a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/IndexingBackgroundTask.cs b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/IndexingBackgroundTask.cs index 1cb0d6abfc1..7cdff32aac8 100644 --- a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/IndexingBackgroundTask.cs +++ b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/IndexingBackgroundTask.cs @@ -4,34 +4,31 @@ using Microsoft.Extensions.DependencyInjection; using OrchardCore.BackgroundTasks; -namespace OrchardCore.Search.Elasticsearch.Core.Services +namespace OrchardCore.Search.Elasticsearch.Core.Services; + +/// +/// This background task will index content items using Elasticsearch. +/// +/// +/// This services is only registered from OrchardCore.Search.Elasticsearch.Worker feature. +/// +[BackgroundTask( + Title = "Elasticsearch Indexes Updater", + Schedule = "* * * * *", + Description = "Updates Elasticsearch indexes.", + LockTimeout = 1000, + LockExpiration = 300000)] +public class IndexingBackgroundTask : IBackgroundTask { - /// - /// This background task will index content items using Elasticsearch. - /// - /// - /// This services is only registered from OrchardCore.Search.Elasticsearch.Worker feature. - /// - [BackgroundTask( - Title = "Elasticsearch Indexes Updater", - Schedule = "* * * * *", - Description = "Updates Elasticsearch indexes.", - LockTimeout = 1000, - LockExpiration = 300000)] - public class IndexingBackgroundTask : IBackgroundTask + public Task DoWorkAsync(IServiceProvider serviceProvider, CancellationToken cancellationToken) { - public Task DoWorkAsync(IServiceProvider serviceProvider, CancellationToken cancellationToken) - { - var indexingService = serviceProvider.GetService(); + var indexingService = serviceProvider.GetService(); - if (indexingService != null) - { - return indexingService.ProcessContentItemsAsync(); - } - else - { - return Task.CompletedTask; - } + if (indexingService != null) + { + return indexingService.ProcessContentItemsAsync(); } + + return Task.CompletedTask; } } From 66e171db51b3e6c24f07731546e77a8cacc9046c Mon Sep 17 00:00:00 2001 From: Mike Alhayek Date: Fri, 19 Jan 2024 17:10:20 -0800 Subject: [PATCH 2/2] Update ElasticSearchService.cs --- .../Services/ElasticSearchService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Services/ElasticSearchService.cs b/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Services/ElasticSearchService.cs index 509175dcba9..2b4a6cde10a 100644 --- a/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Services/ElasticSearchService.cs +++ b/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Services/ElasticSearchService.cs @@ -61,7 +61,7 @@ public async Task SearchAsync(string indexName, string term, int s if (!_elasticConnectionOptions.IsFileConfigurationExists()) { - _logger.LogWarning("Elasticsearch: Couldn't execute search. The Elasticsearch has not yet been configured."); + _logger.LogWarning("Elasticsearch: Couldn't execute search. The Elasticsearch has yet been configured."); return result; }