V11: Merge dev to contrib (#13363)
* Bump version * Add IContextCache to deploy connectors (#13287) * Add IContextCache and implementations * Update connector interfaces to use IContextCache * Minor cleanup * Move DeployContextCache prefix to constant * Move default implementations to obsolete methods * Remove DeployContextCache and DictionaryCache Co-authored-by: Andy Butland <abutland73@gmail.com> * Add IContextCache to deploy connectors (#13287) * Add IContextCache and implementations * Update connector interfaces to use IContextCache * Minor cleanup * Move DeployContextCache prefix to constant * Move default implementations to obsolete methods * Remove DeployContextCache and DictionaryCache Co-authored-by: Andy Butland <abutland73@gmail.com> * Parse lockId as invariant (#13284) Co-authored-by: Zeegaan <nge@umbraco.dk> * Fix Sqlite database locking issue (#13246) * Add locking for creating scope * Lock the repository instead * Add scope in action instead of locking in service * Fix up post-merge Co-authored-by: Zeegaan <nge@umbraco.dk> * Bump version to next minor * Fix for UseExceptionHandler no longer working since v10.3 RC (#13218) * Fix for UseExceptionHandler no longer working since v10.3 RC * Update the management api path to match the new one Co-authored-by: Nikolaj <nikolajlauridsen@protonmail.ch> * New backoffice: Cleanup management API routes (#13296) * Rename ModelsBuilderDashboard folder to ModelsBuilder * Fix modelsbuilder paths and related naming * Rename analytics route to telemetry * Fix controller bases - routes and tags * Fix items route * Fix more controllerbase routes * Fix route * Fix OpenApi file * Merging DictionaryItem and Dictionary * Fix TrackedReferences naming * Update OpenApi file * Rename Analytics* related types to Telemetry* * New Backoffice: Return AnalyticsLevelViewModel from Telemetry/ (#13298) * Return TelemetryLevelViewModel instead of TelemetryLevel * Fix schema * Change telemetry/current to telemetry/level (cherry picked from commit f2b8494c669cbbf04b623753abbf1be211973aa9) * Add contants for tree and recycle-bin subpaths (cherry picked from commit 4449f56bc00832ea6d357a3854b454791c80e0e2) Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Updated Smidge, Npoco and MailKit (#13310) * Updated Smidge, Npoco and MailKit * Added missing command (after breaking interface in npoco) * OpenId Connect authentication for new management API (#13318) * First attempt at OpenIddict * Making headway and more TODOs * Redo current policies for multiple schemas + clean up auth controller * Fix bad merge * Clean up some more test code * Fix spacing * Include AddAuthentication() in OpenIddict addition * A little more clean-up * Move application creation to its own implementation + prepare for middleware to handle valid callback URL * Enable refresh token flow * Fix bad merge from v11/dev * Support auth for Swagger and Postman in non-production environments + use default login screen for back-office logins * Add workaround to client side login handling so the OAuth return URL is not corrupted before redirection * Add temporary configuration handling for new backoffice * Restructure the code somewhat, move singular responsibility from management API project * Add recurring task for cleaning up old tokens in the DB * Fix bad merge + make auth controller align with the new management API structure * Explicitly handle the new management API path as a backoffice path (NOTE: this is potentially behaviorally breaking!) * Redo handle the new management API requests as backoffice requests, this time in a non-breaking way * Add/update TODOs * Revert duplication of current auth policies for OpenIddict (as it breaks everything for V11 without the new management APIs) and introduce a dedicated PoC policy setup for OpenIddict. * Fix failing unit tests * Update src/Umbraco.Cms.ManagementApi/Security/BackOfficeApplicationManager.cs Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> * Update src/Umbraco.Cms.ManagementApi/Security/BackOfficeApplicationManager.cs Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> * Update src/Umbraco.Cms.ManagementApi/Security/BackOfficeApplicationManager.cs Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> * Update src/Umbraco.Core/Routing/UmbracoRequestPaths.cs Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> * V11: Using IFileProvider to access assets added from packages (#13141) * Creating a FileProviderFactory for getting the package.manifest and grid.editors.config.js files through a file provider * Collecting the package.manifest-s from different sources * Searching different sources for grid.editors.config.js * Using an IFileProvider to collect all tours * Refactoring IconService.cs * Typo * Optimizations when looping through the file system * Moving WebRootFileProviderFactory to Umbraco.Web.Common proj * Removes double registering * pluginLangFileSources includes the localPluginFileSources * Comments * Remove linq from foreach * Change workflow for grid.editors.config.js so we check first physical file, then RCL, then Embedded * Clean up * Check if config dir exists * Discover nested package.manifest files * Fix IFileInfo.PhysicalPath check * Revert 712810e1fd995720047832ee689f804185ea69d6 as that way files in content root are preferred over those in web root * Adding comments * Refactoring * Remove PhysicalPath check * Fix registration of WebRootFileProviderFactory * Use Swashbuckle instead of NSwag (#13350) * First attempt at OpenIddict * Making headway and more TODOs * Redo current policies for multiple schemas + clean up auth controller * Fix bad merge * Clean up some more test code * Fix spacing * Include AddAuthentication() in OpenIddict addition * A little more clean-up * Move application creation to its own implementation + prepare for middleware to handle valid callback URL * Enable refresh token flow * Fix bad merge from v11/dev * Support auth for Swagger and Postman in non-production environments + use default login screen for back-office logins * Add workaround to client side login handling so the OAuth return URL is not corrupted before redirection * Add temporary configuration handling for new backoffice * Restructure the code somewhat, move singular responsibility from management API project * Add recurring task for cleaning up old tokens in the DB * Fix bad merge + make auth controller align with the new management API structure * Explicitly handle the new management API path as a backoffice path (NOTE: this is potentially behaviorally breaking!) * Redo handle the new management API requests as backoffice requests, this time in a non-breaking way * Add/update TODOs * Replace NSwag with Swashbuckle and clean up unnecessary client secret workaround * Revert duplication of current auth policies for OpenIddict (as it breaks everything for V11 without the new management APIs) and introduce a dedicated PoC policy setup for OpenIddict. * Fix failing unit tests * A little niceness + export new OpenApi.json and fix path in contract unit test * Redo after merge with v11/dev + filter out unwanted mime types * Remove CreatedResult and NotFoundObjectResult where possible * Custom schema IDs - no more "ViewModel" postfix and make generic lists look less clunky too * A little more explanation for generic schema ID generation * Force Swashbuckle to use enum string names * Update OpenApi.json to match new enum string values * Add clarifying comment about weird looking construct * add workflow to schema (#13349) * add workflow to schema * add licenses to CMSDefinition - intentionally only adding to schema, not registered as options Co-authored-by: Nikolaj <nikolajlauridsen@protonmail.ch> Co-authored-by: Ronald Barendse <ronald@barend.se> Co-authored-by: Andy Butland <abutland73@gmail.com> Co-authored-by: Zeegaan <nge@umbraco.dk> Co-authored-by: Justin Neville <67802060+justin-nevitech@users.noreply.github.com> Co-authored-by: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com> Co-authored-by: Bjarke Berg <mail@bergmania.dk> Co-authored-by: Kenn Jacobsen <kja@umbraco.dk> Co-authored-by: Nathan Woulfe <nathan@nathanw.com.au>
This commit is contained in:
@@ -1,16 +1,21 @@
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Tour;
|
||||
using Umbraco.Cms.Web.Common.Attributes;
|
||||
using Umbraco.Cms.Web.Common.DependencyInjection;
|
||||
using Umbraco.Extensions;
|
||||
using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment;
|
||||
|
||||
namespace Umbraco.Cms.Web.BackOffice.Controllers;
|
||||
|
||||
@@ -20,22 +25,44 @@ public class TourController : UmbracoAuthorizedJsonController
|
||||
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
|
||||
private readonly IContentTypeService _contentTypeService;
|
||||
private readonly TourFilterCollection _filters;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly IWebHostEnvironment _webHostEnvironment;
|
||||
private readonly TourSettings _tourSettings;
|
||||
|
||||
// IHostingEnvironment is still injected as when removing it, the number of
|
||||
// parameters matches with the obsolete ctor and the two ctors become ambiguous
|
||||
// [ActivatorUtilitiesConstructor] won't solve the problem in this case
|
||||
// IHostingEnvironment can be removed when the obsolete ctor is removed
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public TourController(
|
||||
TourFilterCollection filters,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IOptionsSnapshot<TourSettings> tourSettings,
|
||||
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
|
||||
IContentTypeService contentTypeService,
|
||||
IWebHostEnvironment webHostEnvironment)
|
||||
{
|
||||
_filters = filters;
|
||||
_tourSettings = tourSettings.Value;
|
||||
_backofficeSecurityAccessor = backofficeSecurityAccessor;
|
||||
_contentTypeService = contentTypeService;
|
||||
_webHostEnvironment = webHostEnvironment;
|
||||
}
|
||||
|
||||
[Obsolete("Use other ctor - Will be removed in Umbraco 13")]
|
||||
public TourController(
|
||||
TourFilterCollection filters,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IOptionsSnapshot<TourSettings> tourSettings,
|
||||
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
|
||||
IContentTypeService contentTypeService)
|
||||
: this(
|
||||
filters,
|
||||
hostingEnvironment,
|
||||
tourSettings,
|
||||
backofficeSecurityAccessor,
|
||||
contentTypeService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IWebHostEnvironment>())
|
||||
{
|
||||
_filters = filters;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
|
||||
_tourSettings = tourSettings.Value;
|
||||
_backofficeSecurityAccessor = backofficeSecurityAccessor;
|
||||
_contentTypeService = contentTypeService;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<BackOfficeTourFile>> GetTours()
|
||||
@@ -53,69 +80,55 @@ public class TourController : UmbracoAuthorizedJsonController
|
||||
return result;
|
||||
}
|
||||
|
||||
//get all filters that will be applied to all tour aliases
|
||||
// Get all filters that will be applied to all tour aliases
|
||||
var aliasOnlyFilters = _filters.Where(x => x.PluginName == null && x.TourFileName == null).ToList();
|
||||
|
||||
//don't pass in any filters for core tours that have a plugin name assigned
|
||||
// Don't pass in any filters for core tours that have a plugin name assigned
|
||||
var nonPluginFilters = _filters.Where(x => x.PluginName == null).ToList();
|
||||
|
||||
//add core tour files
|
||||
IEnumerable<string> embeddedTourNames = GetType()
|
||||
.Assembly
|
||||
.GetManifestResourceNames()
|
||||
.Where(x => x.StartsWith("Umbraco.Cms.Web.BackOffice.EmbeddedResources.Tours."));
|
||||
|
||||
foreach (var embeddedTourName in embeddedTourNames)
|
||||
// Get core tour files
|
||||
IFileProvider toursProvider = new EmbeddedFileProvider(GetType().Assembly, "Umbraco.Cms.Web.BackOffice.EmbeddedResources.Tours");
|
||||
|
||||
IEnumerable<IFileInfo> embeddedTourFiles = toursProvider.GetDirectoryContents(string.Empty)
|
||||
.Where(x => !x.IsDirectory && x.Name.EndsWith(".json"));
|
||||
|
||||
foreach (var embeddedTour in embeddedTourFiles)
|
||||
{
|
||||
await TryParseTourFile(embeddedTourName, result, nonPluginFilters, aliasOnlyFilters, async x => await GetContentFromEmbeddedResource(x));
|
||||
using Stream stream = embeddedTour.CreateReadStream();
|
||||
await TryParseTourFile(embeddedTour.Name, result, nonPluginFilters, aliasOnlyFilters, stream);
|
||||
}
|
||||
|
||||
// Collect all tour files from packages - /App_Plugins physical or virtual locations
|
||||
IEnumerable<Tuple<IFileInfo, string>> toursFromPackages = GetTourFiles(_webHostEnvironment.WebRootFileProvider, Constants.SystemDirectories.AppPlugins);
|
||||
|
||||
//collect all tour files in packages
|
||||
var appPlugins = _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.AppPlugins);
|
||||
if (Directory.Exists(appPlugins))
|
||||
foreach (var tuple in toursFromPackages)
|
||||
{
|
||||
foreach (var plugin in Directory.EnumerateDirectories(appPlugins))
|
||||
string pluginName = tuple.Item2;
|
||||
var pluginFilters = _filters.Where(x => x.PluginName != null && x.PluginName.IsMatch(pluginName)).ToList();
|
||||
|
||||
// Combine matched package filters with filters not specific to a package
|
||||
var combinedFilters = nonPluginFilters.Concat(pluginFilters).ToList();
|
||||
|
||||
IFileInfo tourFile = tuple.Item1;
|
||||
using (Stream stream = tourFile.CreateReadStream())
|
||||
{
|
||||
var pluginName = Path.GetFileName(plugin.TrimEnd(Constants.CharArrays.Backslash));
|
||||
var pluginFilters = _filters.Where(x => x.PluginName != null && x.PluginName.IsMatch(pluginName))
|
||||
.ToList();
|
||||
|
||||
//If there is any filter applied to match the plugin only (no file or tour alias) then ignore the plugin entirely
|
||||
var isPluginFiltered = pluginFilters.Any(x => x.TourFileName == null && x.TourAlias == null);
|
||||
if (isPluginFiltered)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//combine matched package filters with filters not specific to a package
|
||||
var combinedFilters = nonPluginFilters.Concat(pluginFilters).ToList();
|
||||
|
||||
foreach (var backofficeDir in Directory.EnumerateDirectories(plugin, "backoffice"))
|
||||
{
|
||||
foreach (var tourDir in Directory.EnumerateDirectories(backofficeDir, "tours"))
|
||||
{
|
||||
foreach (var tourFile in Directory.EnumerateFiles(tourDir, "*.json"))
|
||||
{
|
||||
await TryParseTourFile(
|
||||
tourFile,
|
||||
result,
|
||||
combinedFilters,
|
||||
aliasOnlyFilters,
|
||||
async x => await System.IO.File.ReadAllTextAsync(x),
|
||||
pluginName);
|
||||
}
|
||||
}
|
||||
}
|
||||
await TryParseTourFile(
|
||||
tourFile.Name,
|
||||
result,
|
||||
combinedFilters,
|
||||
aliasOnlyFilters,
|
||||
stream,
|
||||
pluginName);
|
||||
}
|
||||
}
|
||||
|
||||
//Get all allowed sections for the current user
|
||||
// Get all allowed sections for the current user
|
||||
var allowedSections = user.AllowedSections.ToList();
|
||||
|
||||
var toursToBeRemoved = new List<BackOfficeTourFile>();
|
||||
|
||||
//Checking to see if the user has access to the required tour sections, else we remove the tour
|
||||
// Checking to see if the user has access to the required tour sections, else we remove the tour
|
||||
foreach (BackOfficeTourFile backOfficeTourFile in result)
|
||||
{
|
||||
if (backOfficeTourFile.Tours != null)
|
||||
@@ -140,21 +153,70 @@ public class TourController : UmbracoAuthorizedJsonController
|
||||
return result.Except(toursToBeRemoved).OrderBy(x => x.FileName, StringComparer.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
private async Task<string> GetContentFromEmbeddedResource(string fileName)
|
||||
private IEnumerable<Tuple<IFileInfo, string>> GetTourFiles(IFileProvider fileProvider, string folder)
|
||||
{
|
||||
Stream? resourceStream = GetType().Assembly.GetManifestResourceStream(fileName);
|
||||
IEnumerable<IFileInfo> pluginFolders = fileProvider.GetDirectoryContents(folder).Where(x => x.IsDirectory);
|
||||
|
||||
if (resourceStream is null)
|
||||
foreach (IFileInfo pluginFolder in pluginFolders)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
var pluginFilters = _filters.Where(x => x.PluginName != null && x.PluginName.IsMatch(pluginFolder.Name)).ToList();
|
||||
|
||||
using var reader = new StreamReader(resourceStream, Encoding.UTF8);
|
||||
return await reader.ReadToEndAsync();
|
||||
// If there is any filter applied to match the plugin only (no file or tour alias) then ignore the plugin entirely
|
||||
var isPluginFiltered = pluginFilters.Any(x => x.TourFileName == null && x.TourAlias == null);
|
||||
if (isPluginFiltered)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// get the full virtual path for the plugin folder
|
||||
var pluginFolderPath = WebPath.Combine(folder, pluginFolder.Name);
|
||||
|
||||
// loop through the folder(s) in order to find tours
|
||||
// - there could be multiple on case sensitive file system
|
||||
// Hard-coding the "backoffice" directory name to gain a better performance when traversing the App_Plugins directory
|
||||
foreach (var subDir in GetToursFolderPaths(fileProvider, pluginFolderPath, "backoffice"))
|
||||
{
|
||||
IEnumerable<IFileInfo> tourFiles = fileProvider
|
||||
.GetDirectoryContents(subDir)
|
||||
.Where(x => x.Name.InvariantEndsWith(".json"));
|
||||
|
||||
foreach (IFileInfo file in tourFiles)
|
||||
{
|
||||
yield return new Tuple<IFileInfo, string>(file, pluginFolder.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetToursFolderPaths(IFileProvider fileProvider, string path, string subDirName)
|
||||
{
|
||||
// Hard-coding the "tours" directory name to gain a better performance when traversing the sub directories
|
||||
const string toursDirName = "tours";
|
||||
|
||||
// It is necessary to iterate through the subfolders because on Linux we'll get casing issues when
|
||||
// we try to access {path}/{pluginDirectory.Name}/backoffice/tours directly
|
||||
foreach (IFileInfo subDir in fileProvider.GetDirectoryContents(path))
|
||||
{
|
||||
// InvariantEquals({dirName}) is used to gain a better performance when looking for the tours folder
|
||||
if (subDir.IsDirectory && subDir.Name.InvariantEquals(subDirName))
|
||||
{
|
||||
var virtualPath = WebPath.Combine(path, subDir.Name);
|
||||
|
||||
if (subDir.Name.InvariantEquals(toursDirName))
|
||||
{
|
||||
yield return virtualPath;
|
||||
}
|
||||
|
||||
foreach (var nested in GetToursFolderPaths(fileProvider, virtualPath, toursDirName))
|
||||
{
|
||||
yield return nested;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a tours for a specific doctype
|
||||
/// Gets a tours for a specific doctype.
|
||||
/// </summary>
|
||||
/// <param name="doctypeAlias">The documenttype alias</param>
|
||||
/// <returns>A <see cref="BackOfficeTour" /></returns>
|
||||
@@ -190,7 +252,7 @@ public class TourController : UmbracoAuthorizedJsonController
|
||||
ICollection<BackOfficeTourFile> result,
|
||||
List<BackOfficeTourFilter> filters,
|
||||
List<BackOfficeTourFilter> aliasOnlyFilters,
|
||||
Func<string, Task<string>> fileNameToFileContent,
|
||||
Stream fileStream,
|
||||
string? pluginName = null)
|
||||
{
|
||||
var fileName = Path.GetFileNameWithoutExtension(tourFile);
|
||||
@@ -199,24 +261,25 @@ public class TourController : UmbracoAuthorizedJsonController
|
||||
return;
|
||||
}
|
||||
|
||||
//get the filters specific to this file
|
||||
// Get the filters specific to this file
|
||||
var fileFilters = filters.Where(x => x.TourFileName != null && x.TourFileName.IsMatch(fileName)).ToList();
|
||||
|
||||
//If there is any filter applied to match the file only (no tour alias) then ignore the file entirely
|
||||
// If there is any filter applied to match the file only (no tour alias) then ignore the file entirely
|
||||
var isFileFiltered = fileFilters.Any(x => x.TourAlias == null);
|
||||
if (isFileFiltered)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//now combine all aliases to filter below
|
||||
// Now combine all aliases to filter below
|
||||
var aliasFilters = aliasOnlyFilters.Concat(filters.Where(x => x.TourAlias != null))
|
||||
.Select(x => x.TourAlias)
|
||||
.ToList();
|
||||
|
||||
try
|
||||
{
|
||||
var contents = await fileNameToFileContent(tourFile);
|
||||
using var reader = new StreamReader(fileStream, Encoding.UTF8);
|
||||
var contents = reader.ReadToEnd();
|
||||
BackOfficeTour[]? tours = JsonConvert.DeserializeObject<BackOfficeTour[]>(contents);
|
||||
|
||||
IEnumerable<BackOfficeTour>? backOfficeTours = tours?.Where(x =>
|
||||
@@ -234,7 +297,7 @@ public class TourController : UmbracoAuthorizedJsonController
|
||||
Tours = localizedTours ?? new List<BackOfficeTour>()
|
||||
};
|
||||
|
||||
//don't add if all of the tours are filtered
|
||||
// Don't add if all of the tours are filtered
|
||||
if (tour.Tours.Any())
|
||||
{
|
||||
result.Add(tour);
|
||||
|
||||
Reference in New Issue
Block a user