Preventing Indexing AdminController and Commands from operating with unsafe index names (#8845)
Some checks failed
Compile / Compile .NET solution (push) Has been cancelled
Compile / Compile Client-side Assets (push) Has been cancelled
SpecFlow Tests / Define Strategy Matrix (push) Has been cancelled
SpecFlow Tests / SpecFlow Tests (push) Has been cancelled

This commit is contained in:
Benedek Farkas
2025-10-02 19:18:19 +02:00
committed by GitHub
parent 8851f622a3
commit 971a97874c
4 changed files with 61 additions and 46 deletions

View File

@@ -4,7 +4,7 @@ using Orchard.Commands;
using Orchard.ContentManagement; using Orchard.ContentManagement;
using Orchard.Indexing.Services; using Orchard.Indexing.Services;
using Orchard.Tasks.Indexing; using Orchard.Tasks.Indexing;
using Orchard.Utility.Extensions; using static Orchard.Indexing.Helpers.IndexingHelpers;
namespace Orchard.Indexing.Commands { namespace Orchard.Indexing.Commands {
public class IndexingCommands : DefaultOrchardCommandHandler { public class IndexingCommands : DefaultOrchardCommandHandler {
@@ -38,27 +38,22 @@ namespace Orchard.Indexing.Commands {
return; return;
} }
if (string.IsNullOrWhiteSpace(index)) { if (!IsValidIndexName(index)) {
Context.Output.WriteLine(T("Invalid index name.")); Context.Output.WriteLine(T("Invalid index name."));
return; return;
} }
if (index.ToSafeName() != index) { var indexProvider = _indexManager.GetSearchIndexProvider();
Context.Output.WriteLine(T("Invalid index name.")); if (indexProvider == null) {
Context.Output.WriteLine(T("No indexing service was found. Please enable a module like Lucene."));
} }
else { else {
var indexProvider = _indexManager.GetSearchIndexProvider(); if (indexProvider.Exists(index)) {
if(indexProvider == null) { Context.Output.WriteLine(T("The specified index already exists."));
Context.Output.WriteLine(T("No indexing service was found. Please enable a module like Lucene."));
} }
else { else {
if (indexProvider.Exists(index)) { _indexManager.GetSearchIndexProvider().CreateIndex(index);
Context.Output.WriteLine(T("The specified index already exists.")); Context.Output.WriteLine(T("New index has been created successfully."));
}
else {
_indexManager.GetSearchIndexProvider().CreateIndex(index);
Context.Output.WriteLine(T("New index has been created successfully."));
}
} }
} }
} }
@@ -66,7 +61,7 @@ namespace Orchard.Indexing.Commands {
[CommandName("index update")] [CommandName("index update")]
[CommandHelp("index update <index>\r\n\t" + "Updates the specified index")] [CommandHelp("index update <index>\r\n\t" + "Updates the specified index")]
public void Update(string index) { public void Update(string index) {
if (string.IsNullOrWhiteSpace(index)) { if (!IsValidIndexName(index)) {
Context.Output.WriteLine(T("Invalid index name.")); Context.Output.WriteLine(T("Invalid index name."));
return; return;
} }
@@ -78,7 +73,7 @@ namespace Orchard.Indexing.Commands {
[CommandName("index rebuild")] [CommandName("index rebuild")]
[CommandHelp("index rebuild <index> \r\n\t" + "Rebuilds the specified index")] [CommandHelp("index rebuild <index> \r\n\t" + "Rebuilds the specified index")]
public void Rebuild(string index) { public void Rebuild(string index) {
if (string.IsNullOrWhiteSpace(index)) { if (!IsValidIndexName(index)) {
Context.Output.WriteLine(T("Invalid index name.")); Context.Output.WriteLine(T("Invalid index name."));
return; return;
} }
@@ -91,24 +86,24 @@ namespace Orchard.Indexing.Commands {
[CommandHelp("index query <index> /Query:<query>\r\n\t" + "Searches the specified <query> terms in the specified index")] [CommandHelp("index query <index> /Query:<query>\r\n\t" + "Searches the specified <query> terms in the specified index")]
[OrchardSwitches("Query")] [OrchardSwitches("Query")]
public void Search(string index) { public void Search(string index) {
if (string.IsNullOrWhiteSpace(index)) { if (!IsValidIndexName(index)) {
Context.Output.WriteLine(T("Invalid index name.")); Context.Output.WriteLine(T("Invalid index name."));
return; return;
} }
if ( !_indexManager.HasIndexProvider() ) { if (!_indexManager.HasIndexProvider()) {
Context.Output.WriteLine(T("No index available")); Context.Output.WriteLine(T("No index available"));
return; return;
} }
var searchBuilder = _indexManager.GetSearchIndexProvider().CreateSearchBuilder(index); var searchBuilder = _indexManager.GetSearchIndexProvider().CreateSearchBuilder(index);
var results = searchBuilder.Parse( new [] {"body", "title"}, Query).Search(); var results = searchBuilder.Parse(new[] { "body", "title" }, Query).Search();
Context.Output.WriteLine("{0} result{1}\r\n-----------------\r\n", results.Count(), results.Count() > 0 ? "s" : ""); Context.Output.WriteLine("{0} result{1}\r\n-----------------\r\n", results.Count(), results.Count() > 0 ? "s" : "");
Context.Output.WriteLine("┌──────────────────────────────────────────────────────────────┬────────┐"); Context.Output.WriteLine("┌──────────────────────────────────────────────────────────────┬────────┐");
Context.Output.WriteLine("│ {0} │ {1,6} │", "Title" + new string(' ', 60 - "Title".Length), "Score"); Context.Output.WriteLine("│ {0} │ {1,6} │", "Title" + new string(' ', 60 - "Title".Length), "Score");
Context.Output.WriteLine("├──────────────────────────────────────────────────────────────┼────────┤"); Context.Output.WriteLine("├──────────────────────────────────────────────────────────────┼────────┤");
foreach ( var searchHit in results ) { foreach (var searchHit in results) {
var contentItem = _contentManager.Get(searchHit.ContentItemId); var contentItem = _contentManager.Get(searchHit.ContentItemId);
var metadata = _contentManager.GetItemMetadata(contentItem); var metadata = _contentManager.GetItemMetadata(contentItem);
var title = String.IsNullOrWhiteSpace(metadata.DisplayText) ? "- no title -" : metadata.DisplayText; var title = String.IsNullOrWhiteSpace(metadata.DisplayText) ? "- no title -" : metadata.DisplayText;
@@ -126,12 +121,12 @@ namespace Orchard.Indexing.Commands {
[CommandHelp("index stats <index>\r\n\t" + "Displays some statistics about the search index")] [CommandHelp("index stats <index>\r\n\t" + "Displays some statistics about the search index")]
[OrchardSwitches("IndexName")] [OrchardSwitches("IndexName")]
public void Stats(string index) { public void Stats(string index) {
if (string.IsNullOrWhiteSpace(index)) { if (!IsValidIndexName(index)) {
Context.Output.WriteLine(T("Invalid index name.")); Context.Output.WriteLine(T("Invalid index name."));
return; return;
} }
if ( !_indexManager.HasIndexProvider() ) { if (!_indexManager.HasIndexProvider()) {
Context.Output.WriteLine(T("No index available")); Context.Output.WriteLine(T("No index available"));
return; return;
} }
@@ -140,11 +135,10 @@ namespace Orchard.Indexing.Commands {
} }
[CommandName("index refresh")] [CommandName("index refresh")]
[CommandHelp("index refresh /ContentItem:<content item id> \r\n\t" + "Refreshes the index for the specifed <content item id>")] [CommandHelp("index refresh /ContentItem:<content item id> \r\n\t" + "Refreshes the index for the specified <content item id>")]
[OrchardSwitches("ContentItem")] [OrchardSwitches("ContentItem")]
public void Refresh() { public void Refresh() {
int contentItemId; if (!int.TryParse(ContentItem, out int contentItemId)) {
if ( !int.TryParse(ContentItem, out contentItemId) ) {
Context.Output.WriteLine(T("Invalid content item id. Not an integer.")); Context.Output.WriteLine(T("Invalid content item id. Not an integer."));
return; return;
} }
@@ -152,19 +146,18 @@ namespace Orchard.Indexing.Commands {
var contentItem = _contentManager.Get(contentItemId); var contentItem = _contentManager.Get(contentItemId);
_indexingTaskManager.CreateUpdateIndexTask(contentItem); _indexingTaskManager.CreateUpdateIndexTask(contentItem);
Context.Output.WriteLine(T("Content Item marked for reindexing")); Context.Output.WriteLine(T("Content Item marked for re-indexing"));
} }
[CommandName("index delete")] [CommandName("index delete")]
[CommandHelp("index delete /ContentItem:<content item id>\r\n\t" + "Deletes the specifed <content item id> from the index")] [CommandHelp("index delete /ContentItem:<content item id>\r\n\t" + "Deletes the specified <content item id> from the index")]
[OrchardSwitches("ContentItem")] [OrchardSwitches("ContentItem")]
public void Delete() { public void Delete() {
int contentItemId; if (!int.TryParse(ContentItem, out int contentItemId)) {
if(!int.TryParse(ContentItem, out contentItemId)) {
Context.Output.WriteLine(T("Invalid content item id. Not an integer.")); Context.Output.WriteLine(T("Invalid content item id. Not an integer."));
return; return;
} }
var contentItem = _contentManager.Get(contentItemId); var contentItem = _contentManager.Get(contentItemId);
_indexingTaskManager.CreateDeleteIndexTask(contentItem); _indexingTaskManager.CreateDeleteIndexTask(contentItem);

View File

@@ -2,12 +2,12 @@
using System.Linq; using System.Linq;
using System.Web.Mvc; using System.Web.Mvc;
using Orchard.Indexing.Services; using Orchard.Indexing.Services;
using Orchard.Indexing.ViewModels;
using Orchard.Localization; using Orchard.Localization;
using Orchard.Logging; using Orchard.Logging;
using Orchard.Security; using Orchard.Security;
using Orchard.Indexing.ViewModels;
using Orchard.UI.Notify; using Orchard.UI.Notify;
using Orchard.Utility.Extensions; using static Orchard.Indexing.Helpers.IndexingHelpers;
namespace Orchard.Indexing.Controllers { namespace Orchard.Indexing.Controllers {
public class AdminController : Controller { public class AdminController : Controller {
@@ -15,7 +15,7 @@ namespace Orchard.Indexing.Controllers {
private readonly IIndexManager _indexManager; private readonly IIndexManager _indexManager;
public AdminController( public AdminController(
IIndexingService indexingService, IIndexingService indexingService,
IOrchardServices services, IOrchardServices services,
IIndexManager indexManager) { IIndexManager indexManager) {
_indexingService = indexingService; _indexingService = indexingService;
@@ -34,23 +34,21 @@ namespace Orchard.Indexing.Controllers {
IndexEntries = Enumerable.Empty<IndexEntry>(), IndexEntries = Enumerable.Empty<IndexEntry>(),
IndexProvider = _indexManager.GetSearchIndexProvider() IndexProvider = _indexManager.GetSearchIndexProvider()
}; };
if (_indexManager.HasIndexProvider()) { if (_indexManager.HasIndexProvider()) {
viewModel.IndexEntries = _indexManager.GetSearchIndexProvider().List().Select(x => { viewModel.IndexEntries = _indexManager.GetSearchIndexProvider().List().Select(x => {
try { try {
return _indexingService.GetIndexEntry(x); return _indexingService.GetIndexEntry(x);
} }
catch(Exception e) { catch (Exception e) {
Logger.Error(e, "Index couldn't be read: " + x); Logger.Error(e, "Index couldn't be read: " + x);
return new IndexEntry { return new IndexEntry {
IndexName = x, IndexName = x,
IndexingStatus = IndexingStatus.Unavailable IndexingStatus = IndexingStatus.Unavailable
}; };
} }
}); });
} }
// Services.Notifier.Information(T("The index might be corrupted. If you can't recover click on Rebuild."));
return View(viewModel); return View(viewModel);
} }
@@ -59,7 +57,7 @@ namespace Orchard.Indexing.Controllers {
if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not allowed to manage the search index."))) if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not allowed to manage the search index.")))
return new HttpUnauthorizedResult(); return new HttpUnauthorizedResult();
return View("Create", String.Empty); return View("Create");
} }
[HttpPost, ActionName("Create")] [HttpPost, ActionName("Create")]
@@ -68,7 +66,7 @@ namespace Orchard.Indexing.Controllers {
return new HttpUnauthorizedResult(); return new HttpUnauthorizedResult();
var provider = _indexManager.GetSearchIndexProvider(); var provider = _indexManager.GetSearchIndexProvider();
if (String.IsNullOrWhiteSpace(id) || id.ToSafeName() != id) { if (!IsValidIndexName(id)) {
Services.Notifier.Error(T("Invalid index name.")); Services.Notifier.Error(T("Invalid index name."));
return View("Create", id); return View("Create", id);
} }
@@ -82,9 +80,9 @@ namespace Orchard.Indexing.Controllers {
provider.CreateIndex(id); provider.CreateIndex(id);
Services.Notifier.Information(T("Index named {0} created successfully", id)); Services.Notifier.Information(T("Index named {0} created successfully", id));
} }
catch(Exception e) { catch (Exception e) {
Services.Notifier.Error(T("An error occurred while creating the index: {0}", id)); Services.Notifier.Error(T("An error occurred while creating the index: {0}", id));
Logger.Error("An error occurred while creatign the index " + id, e); Logger.Error("An error occurred while creating the index " + id, e);
return View("Create", id); return View("Create", id);
} }
@@ -96,7 +94,12 @@ namespace Orchard.Indexing.Controllers {
if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not allowed to manage the search index."))) if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not allowed to manage the search index.")))
return new HttpUnauthorizedResult(); return new HttpUnauthorizedResult();
_indexingService.UpdateIndex(id); if (IsValidIndexName(id)) {
_indexingService.UpdateIndex(id);
}
else {
Services.Notifier.Error(T("Invalid index name."));
}
return RedirectToAction("Index"); return RedirectToAction("Index");
} }
@@ -106,7 +109,12 @@ namespace Orchard.Indexing.Controllers {
if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not allowed to manage the search index."))) if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not allowed to manage the search index.")))
return new HttpUnauthorizedResult(); return new HttpUnauthorizedResult();
_indexingService.RebuildIndex(id); if (IsValidIndexName(id)) {
_indexingService.RebuildIndex(id);
}
else {
Services.Notifier.Error(T("Invalid index name."));
}
return RedirectToAction("Index"); return RedirectToAction("Index");
} }
@@ -116,7 +124,12 @@ namespace Orchard.Indexing.Controllers {
if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not allowed to manage the search index."))) if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not allowed to manage the search index.")))
return new HttpUnauthorizedResult(); return new HttpUnauthorizedResult();
_indexingService.DeleteIndex(id); if (IsValidIndexName(id)) {
_indexingService.DeleteIndex(id);
}
else {
Services.Notifier.Error(T("Invalid index name."));
}
return RedirectToAction("Index"); return RedirectToAction("Index");
} }

View File

@@ -0,0 +1,8 @@
using Orchard.Utility.Extensions;
namespace Orchard.Indexing.Helpers {
public static class IndexingHelpers {
public static bool IsValidIndexName(string name) =>
!string.IsNullOrWhiteSpace(name) && name.ToSafeName() == name;
}
}

View File

@@ -95,6 +95,7 @@
<Compile Include="AdminMenu.cs" /> <Compile Include="AdminMenu.cs" />
<Compile Include="Commands\IndexingCommands.cs" /> <Compile Include="Commands\IndexingCommands.cs" />
<Compile Include="Controllers\AdminController.cs" /> <Compile Include="Controllers\AdminController.cs" />
<Compile Include="Helpers\IndexingHelpers.cs" />
<Compile Include="Migrations.cs" /> <Compile Include="Migrations.cs" />
<Compile Include="Handlers\CreateIndexingTaskHandler.cs" /> <Compile Include="Handlers\CreateIndexingTaskHandler.cs" />
<Compile Include="Handlers\InfosetFieldIndexingHandler.cs" /> <Compile Include="Handlers\InfosetFieldIndexingHandler.cs" />
@@ -180,4 +181,4 @@
</FlavorProperties> </FlavorProperties>
</VisualStudio> </VisualStudio>
</ProjectExtensions> </ProjectExtensions>
</Project> </Project>