From e2cbcfc2d0125117850aeccad2bb9816aafdb064 Mon Sep 17 00:00:00 2001 From: Matthew Wigley Date: Thu, 9 Mar 2023 13:25:56 -0800 Subject: [PATCH 01/19] Block windows exectuable files based on their file contents if they are not whitelisted --- .../Library/Services/FileSystem/FileManager.cs | 12 ++++++++++-- .../Internal/FileSecurityController.cs | 17 +++++++++++++++++ .../Internal/IFileSecurityController.cs | 7 +++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/DNN Platform/Library/Services/FileSystem/FileManager.cs b/DNN Platform/Library/Services/FileSystem/FileManager.cs index da11b4c4847..e38369b9fa6 100644 --- a/DNN Platform/Library/Services/FileSystem/FileManager.cs +++ b/DNN Platform/Library/Services/FileSystem/FileManager.cs @@ -211,7 +211,7 @@ public virtual IFileInfo AddFile(IFolderInfo folder, string fileName, Stream fil usingSeekableStream = true; } - this.CheckFileWritingRestrictions(folder, fileName, fileContent, oldFile, createdByUserID); + this.CheckFileWritingRestrictions(folder, fileName, fileContent, oldFile, createdByUserID, ignoreWhiteList); // Retrieve Metadata this.SetInitialFileMetadata(ref fileContent, file, folderProvider); @@ -1823,7 +1823,7 @@ private void SetImageProperties(IFileInfo file, Stream fileContent) } } - private void CheckFileWritingRestrictions(IFolderInfo folder, string fileName, Stream fileContent, IFileInfo oldFile, int createdByUserId) + private void CheckFileWritingRestrictions(IFolderInfo folder, string fileName, Stream fileContent, IFileInfo oldFile, int createdByUserId, bool ignoreWhiteList) { if (!PortalController.Instance.HasSpaceAvailable(folder.PortalID, fileContent.Length)) { @@ -1848,6 +1848,14 @@ private void CheckFileWritingRestrictions(IFolderInfo folder, string fileName, S var errorMessage = Localization.GetExceptionMessage("AddFileInvalidContent", defaultMessage); throw new InvalidFileContentException(string.Format(errorMessage, fileName)); } + + var checkWhiteList = !(UserController.Instance.GetCurrentUserInfo().IsSuperUser && ignoreWhiteList); + if (checkWhiteList && !this.WhiteList.IsAllowedExtension(".exe") && !FileSecurityController.Instance.ValidateNotExectuable(fileContent)) + { + var defaultMessage = "The content of '{0}' is not valid. The file has not been added."; + var errorMessage = Localization.GetExceptionMessage("AddFileInvalidContent", defaultMessage); + throw new InvalidFileContentException(string.Format(errorMessage, fileName)); + } } private void ManageFileAdding(int createdByUserID, Workflow folderWorkflow, bool fileExists, FileInfo file) diff --git a/DNN Platform/Library/Services/FileSystem/Internal/FileSecurityController.cs b/DNN Platform/Library/Services/FileSystem/Internal/FileSecurityController.cs index 5f99d054a0d..685ad0af65f 100644 --- a/DNN Platform/Library/Services/FileSystem/Internal/FileSecurityController.cs +++ b/DNN Platform/Library/Services/FileSystem/Internal/FileSecurityController.cs @@ -41,6 +41,23 @@ public bool Validate(string fileName, Stream fileContent) } } + /// + public bool ValidateNotExectuable(Stream fileContent) + { + Requires.NotNull("fileContent", fileContent); + + using (var copyStream = this.CopyStream(fileContent)) + { + using (var binaryReader = new BinaryReader(copyStream)) + { + var firstBytes = binaryReader.ReadBytes(2); + + // Windows exectuable files start with 0x4D 0x5A + return firstBytes.Length < 2 || firstBytes[0] != 0x4D || firstBytes[1] != 0x5A; + } + } + } + /// protected override Func GetFactory() { diff --git a/DNN Platform/Library/Services/FileSystem/Internal/IFileSecurityController.cs b/DNN Platform/Library/Services/FileSystem/Internal/IFileSecurityController.cs index 3cd55c767ab..6a52d4ba794 100644 --- a/DNN Platform/Library/Services/FileSystem/Internal/IFileSecurityController.cs +++ b/DNN Platform/Library/Services/FileSystem/Internal/IFileSecurityController.cs @@ -13,5 +13,12 @@ public interface IFileSecurityController /// The File Content. /// if the file has valid content, otherwise . bool Validate(string fileName, Stream fileContent); + + /// + /// Checks the file content isn't an exectuable file. + /// + /// The File Content. + /// Whether the file is an exectuable file. + bool ValidateNotExectuable(Stream fileContent); } } From 9b7f9c0afdea5122f22bd63f3d17712f99c6be5d Mon Sep 17 00:00:00 2001 From: Matthew Wigley Date: Thu, 9 Mar 2023 16:14:42 -0800 Subject: [PATCH 02/19] Don't copy the stream --- .../Internal/FileSecurityController.cs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/DNN Platform/Library/Services/FileSystem/Internal/FileSecurityController.cs b/DNN Platform/Library/Services/FileSystem/Internal/FileSecurityController.cs index 685ad0af65f..e216ac69d04 100644 --- a/DNN Platform/Library/Services/FileSystem/Internal/FileSecurityController.cs +++ b/DNN Platform/Library/Services/FileSystem/Internal/FileSecurityController.cs @@ -44,18 +44,14 @@ public bool Validate(string fileName, Stream fileContent) /// public bool ValidateNotExectuable(Stream fileContent) { - Requires.NotNull("fileContent", fileContent); - - using (var copyStream = this.CopyStream(fileContent)) - { - using (var binaryReader = new BinaryReader(copyStream)) - { - var firstBytes = binaryReader.ReadBytes(2); + Requires.NotNull("fileContent", fileContent); - // Windows exectuable files start with 0x4D 0x5A - return firstBytes.Length < 2 || firstBytes[0] != 0x4D || firstBytes[1] != 0x5A; - } - } + var firstBytes = new byte[2]; + int bytesRead = fileContent.Read(firstBytes, 0, 2); + fileContent.Position = 0; + + // Windows exectuable files start with 0x4D 0x5A + return bytesRead < 2 || firstBytes[0] != 0x4D || firstBytes[1] != 0x5A; } /// From ed9bf54377312a923e49d8df92679d18df269c53 Mon Sep 17 00:00:00 2001 From: Matthew Wigley Date: Thu, 27 Apr 2023 17:32:26 -0700 Subject: [PATCH 03/19] Fix the unit tests --- .../DotNetNuke.Tests.Core/Providers/Folder/FileManagerTests.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/DNN Platform/Tests/DotNetNuke.Tests.Core/Providers/Folder/FileManagerTests.cs b/DNN Platform/Tests/DotNetNuke.Tests.Core/Providers/Folder/FileManagerTests.cs index 0ddd620c0a4..2c4ba952303 100644 --- a/DNN Platform/Tests/DotNetNuke.Tests.Core/Providers/Folder/FileManagerTests.cs +++ b/DNN Platform/Tests/DotNetNuke.Tests.Core/Providers/Folder/FileManagerTests.cs @@ -200,6 +200,8 @@ public void AddFile_Checks_Space_For_Stream_Length() var fileContent = new MemoryStream(Encoding.ASCII.GetBytes("some data here")); + this.hostController.Setup(c => c.GetString("FileExtensions")).Returns(""); + this.portalController.Setup(pc => pc.HasSpaceAvailable(It.IsAny(), It.IsAny())).Returns(true); this.globals.Setup(g => g.GetSubFolderPath(Constants.FOLDER_ValidFilePath, Constants.CONTENT_ValidPortalId)).Returns(Constants.FOLDER_ValidFolderRelativePath); @@ -267,6 +269,7 @@ public void AddFile_No_Error_When_File_Content_Is_Valid() this.portalController.Setup(pc => pc.HasSpaceAvailable(Constants.CONTENT_ValidPortalId, fileContent.Length)).Returns(true); this.mockFileManager.Setup(mfm => mfm.IsAllowedExtension(Constants.FOLDER_ValidSvgFileName)).Returns(true); this.mockFileManager.Setup(mfm => mfm.IsImageFile(It.IsAny())).Returns(false); + this.hostController.Setup(c => c.GetString("FileExtensions")).Returns(""); this.mockFileManager.Object.AddFile(this.folderInfo.Object, Constants.FOLDER_ValidSvgFileName, fileContent, false, false, Constants.CONTENTTYPE_ValidContentType); } From a47bb949810af660ccdb2f681c9550257d1787c3 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 22 Dec 2023 15:41:12 -0500 Subject: [PATCH 04/19] Fixed an issue that kept rm items selected after changing folders This could cause confusion and users to accidently perform batch operations (like deletions) by accident on items they were not expecting to be selected. Closes #5758 --- .../src/components/dnn-rm-left-pane/dnn-rm-left-pane.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-left-pane/dnn-rm-left-pane.tsx b/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-left-pane/dnn-rm-left-pane.tsx index 31238f5c549..23c6d9dda00 100644 --- a/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-left-pane/dnn-rm-left-pane.tsx +++ b/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-left-pane/dnn-rm-left-pane.tsx @@ -17,6 +17,7 @@ export class DnnRmLeftPane { } private handleFolderClicked(e: CustomEvent): void { + state.selectedItems = []; this.itemsClient.getFolderContent( Number.parseInt(e.detail.data.key), 0, From 2dd8e84980f926536efa9cbaa8a499547617bb2d Mon Sep 17 00:00:00 2001 From: Daniel Valadas Date: Tue, 28 Nov 2023 16:12:05 -0500 Subject: [PATCH 05/19] Update azure-pipelines.yml for Azure Pipelines Ensures the yarn cache folder exists before doing yarn caching --- azure-pipelines.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 40a217b8185..d16b9649394 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -46,6 +46,19 @@ pr: steps: +- task: PowerShell@2 + displayName: 'Create Yarn Cache Folder' + inputs: + targetType: 'inline' + script: | + $yarnCacheFolder = "$(YARN_CACHE_FOLDER)" + if (-not (Test-Path $yarnCacheFolder)) { + Write-Host "Creating Yarn cache folder at $yarnCacheFolder" + New-Item -ItemType Directory -Path $yarnCacheFolder -Force + } else { + Write-Host "Yarn cache folder already exists." + } + - task: Cache@2 displayName: Cache Yarn packages inputs: From 2aaf4277250e5a74d2955fed590741f0f3ac6210 Mon Sep 17 00:00:00 2001 From: "stephane.tetard@live.com" <62659880+stetard@users.noreply.github.com> Date: Sat, 30 Dec 2023 15:08:46 +0100 Subject: [PATCH 06/19] #5885 Open commands added for files: double click, action bar and single file context menu. Need to replace the Open button with a file link. --- .../App_LocalResources/ResourceManager.resx | 3 ++ .../ResourceManager.Web/src/components.d.ts | 23 +++++++++++++ .../dnn-action-open-file.tsx | 34 +++++++++++++++++++ .../dnn-rm-file-context-menu.tsx | 2 ++ .../dnn-rm-actions-bar/dnn-rm-actions-bar.tsx | 3 ++ .../dnn-rm-files-pane/dnn-rm-files-pane.tsx | 9 +++++ .../dnn-rm-items-cardview.tsx | 5 +++ .../dnn-rm-items-listview.tsx | 5 +++ .../src/services/LocalizationClient.ts | 1 + 9 files changed, 85 insertions(+) create mode 100644 DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/actions/dnn-action-open-file/dnn-action-open-file.tsx diff --git a/DNN Platform/Modules/ResourceManager/App_LocalResources/ResourceManager.resx b/DNN Platform/Modules/ResourceManager/App_LocalResources/ResourceManager.resx index 1d1851172a5..277736c9b09 100644 --- a/DNN Platform/Modules/ResourceManager/App_LocalResources/ResourceManager.resx +++ b/DNN Platform/Modules/ResourceManager/App_LocalResources/ResourceManager.resx @@ -435,4 +435,7 @@ {0} Assets + + Open + \ No newline at end of file diff --git a/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components.d.ts b/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components.d.ts index b0449927cf3..b3dc071c540 100644 --- a/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components.d.ts +++ b/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components.d.ts @@ -26,6 +26,9 @@ export namespace Components { interface DnnActionMoveItems { "items": Item[]; } + interface DnnActionOpenFile { + "item": Item; + } interface DnnActionUnlinkItems { "items": Item[]; } @@ -252,6 +255,12 @@ declare global { prototype: HTMLDnnActionMoveItemsElement; new (): HTMLDnnActionMoveItemsElement; }; + interface HTMLDnnActionOpenFileElement extends Components.DnnActionOpenFile, HTMLStencilElement { + } + var HTMLDnnActionOpenFileElement: { + prototype: HTMLDnnActionOpenFileElement; + new (): HTMLDnnActionOpenFileElement; + }; interface HTMLDnnActionUnlinkItemsElement extends Components.DnnActionUnlinkItems, HTMLStencilElement { } var HTMLDnnActionUnlinkItemsElement: { @@ -409,6 +418,7 @@ declare global { "dnn-action-download-item": HTMLDnnActionDownloadItemElement; "dnn-action-edit-item": HTMLDnnActionEditItemElement; "dnn-action-move-items": HTMLDnnActionMoveItemsElement; + "dnn-action-open-file": HTMLDnnActionOpenFileElement; "dnn-action-unlink-items": HTMLDnnActionUnlinkItemsElement; "dnn-action-upload-file": HTMLDnnActionUploadFileElement; "dnn-resource-manager": HTMLDnnResourceManagerElement; @@ -455,6 +465,9 @@ declare namespace LocalJSX { interface DnnActionMoveItems { "items": Item[]; } + interface DnnActionOpenFile { + "item": Item; + } interface DnnActionUnlinkItems { "items": Item[]; } @@ -570,6 +583,10 @@ declare namespace LocalJSX { * The list of current items. */ "currentItems": GetFolderContentResponse; + /** + * Fires when a file is double-clicked and emits the file ID into the event.detail + */ + "onDnnRmFileDoubleClicked"?: (event: DnnRmItemsCardviewCustomEvent) => void; /** * Fires when a folder is double-clicked and emits the folder ID into the event.detail */ @@ -580,6 +597,10 @@ declare namespace LocalJSX { * The list of current items. */ "currentItems": GetFolderContentResponse; + /** + * Fires when a file is double-clicked and emits the file ID into the event.detail + */ + "onDnnRmFileDoubleClicked"?: (event: DnnRmItemsListviewCustomEvent) => void; /** * Fires when a folder is double-clicked and emits the folder ID into the event.detail */ @@ -658,6 +679,7 @@ declare namespace LocalJSX { "dnn-action-download-item": DnnActionDownloadItem; "dnn-action-edit-item": DnnActionEditItem; "dnn-action-move-items": DnnActionMoveItems; + "dnn-action-open-file": DnnActionOpenFile; "dnn-action-unlink-items": DnnActionUnlinkItems; "dnn-action-upload-file": DnnActionUploadFile; "dnn-resource-manager": DnnResourceManager; @@ -695,6 +717,7 @@ declare module "@stencil/core" { "dnn-action-download-item": LocalJSX.DnnActionDownloadItem & JSXBase.HTMLAttributes; "dnn-action-edit-item": LocalJSX.DnnActionEditItem & JSXBase.HTMLAttributes; "dnn-action-move-items": LocalJSX.DnnActionMoveItems & JSXBase.HTMLAttributes; + "dnn-action-open-file": LocalJSX.DnnActionOpenFile & JSXBase.HTMLAttributes; "dnn-action-unlink-items": LocalJSX.DnnActionUnlinkItems & JSXBase.HTMLAttributes; "dnn-action-upload-file": LocalJSX.DnnActionUploadFile & JSXBase.HTMLAttributes; "dnn-resource-manager": LocalJSX.DnnResourceManager & JSXBase.HTMLAttributes; diff --git a/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/actions/dnn-action-open-file/dnn-action-open-file.tsx b/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/actions/dnn-action-open-file/dnn-action-open-file.tsx new file mode 100644 index 00000000000..f2167676a2f --- /dev/null +++ b/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/actions/dnn-action-open-file/dnn-action-open-file.tsx @@ -0,0 +1,34 @@ +import { Component, Host, h, Prop } from '@stencil/core'; +import { Item, ItemsClient } from '../../../services/ItemsClient'; +import state from "../../../store/store"; + +@Component({ + tag: 'dnn-action-open-file', + styleUrl: '../dnn-action.scss', + shadow: true, +}) +export class DnnActionOpenFile { + + @Prop() item!: Item; + + private itemsClient: ItemsClient; + + constructor(){ + this.itemsClient = new ItemsClient(state.moduleId); + } + + private handleClick(): void { + this.itemsClient.download(this.item.itemId, false); + } + + render() { + return ( + + + + ); + } +} diff --git a/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/context-menus/dnn-rm-file-context-menu/dnn-rm-file-context-menu.tsx b/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/context-menus/dnn-rm-file-context-menu/dnn-rm-file-context-menu.tsx index 80276d1075a..0cc180416e2 100644 --- a/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/context-menus/dnn-rm-file-context-menu/dnn-rm-file-context-menu.tsx +++ b/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/context-menus/dnn-rm-file-context-menu/dnn-rm-file-context-menu.tsx @@ -23,6 +23,8 @@ export class DnnRmFileContextMenu { , + , + , ] diff --git a/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-actions-bar/dnn-rm-actions-bar.tsx b/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-actions-bar/dnn-rm-actions-bar.tsx index 7ba42d25cc2..e1ac47ef275 100644 --- a/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-actions-bar/dnn-rm-actions-bar.tsx +++ b/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-actions-bar/dnn-rm-actions-bar.tsx @@ -123,6 +123,9 @@ export class DnnRmActionsBar { {state.selectedItems.length == 1 && !state.selectedItems[0].isFolder && location.protocol == "https:" && } + {state.selectedItems.length == 1 && !state.selectedItems[0].isFolder && + + } {state.selectedItems.length == 1 && !state.selectedItems[0].isFolder && } diff --git a/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-files-pane/dnn-rm-files-pane.tsx b/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-files-pane/dnn-rm-files-pane.tsx index 34a5a7e34b4..4806346a8cd 100644 --- a/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-files-pane/dnn-rm-files-pane.tsx +++ b/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-files-pane/dnn-rm-files-pane.tsx @@ -37,6 +37,15 @@ export class DnnRmFilesPane { this.checkIfMoreItemsNeeded(); } + @Listen("dnnRmFileDoubleClicked", {target: "document"}) + handleFileDoubleClicked(e: CustomEvent) { + //unnecessary? + // if (state.selectedItems.length >= 1) { + // state.selectedItems = []; + // } + this.itemsClient.download(e.detail, false); + } + componentDidUpdate() { const loadedFilesHeight = this.loadedFilesArea.getBoundingClientRect().height; const heightPerItem = loadedFilesHeight / state.currentItems.items.length; diff --git a/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-items-cardview/dnn-rm-items-cardview.tsx b/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-items-cardview/dnn-rm-items-cardview.tsx index e48f9495bda..dcb733d6f6f 100644 --- a/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-items-cardview/dnn-rm-items-cardview.tsx +++ b/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-items-cardview/dnn-rm-items-cardview.tsx @@ -18,6 +18,9 @@ export class DnnRmItemsCardview { /** Fires when a folder is double-clicked and emits the folder ID into the event.detail */ @Event() dnnRmFolderDoubleClicked: EventEmitter; + /** Fires when a file is double-clicked and emits the file ID into the event.detail */ + @Event() dnnRmFileDoubleClicked: EventEmitter; + componentWillLoad() { document.addEventListener("click", this.dismissContextMenu.bind(this)); } @@ -54,6 +57,8 @@ export class DnnRmItemsCardview { private handleDoubleClick(item: Item): void { if (item.isFolder) { this.dnnRmFolderDoubleClicked.emit(item.itemId); + } else { + this.dnnRmFileDoubleClicked.emit(item.itemId); } } diff --git a/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-items-listview/dnn-rm-items-listview.tsx b/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-items-listview/dnn-rm-items-listview.tsx index 30d0fb813b1..5510ca50de2 100644 --- a/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-items-listview/dnn-rm-items-listview.tsx +++ b/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-items-listview/dnn-rm-items-listview.tsx @@ -18,6 +18,9 @@ export class DnnRmItemsListview { /** Fires when a folder is double-clicked and emits the folder ID into the event.detail */ @Event() dnnRmFolderDoubleClicked: EventEmitter; + /** Fires when a file is double-clicked and emits the file ID into the event.detail */ + @Event() dnnRmFileDoubleClicked: EventEmitter; + componentWillLoad() { document.addEventListener("click", this.dismissContextMenu.bind(this)); } @@ -85,6 +88,8 @@ export class DnnRmItemsListview { private handleDoubleClick(item: Item): void { if (item.isFolder) { this.dnnRmFolderDoubleClicked.emit(item.itemId); + } else { + this.dnnRmFileDoubleClicked.emit(item.itemId); } } diff --git a/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/services/LocalizationClient.ts b/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/services/LocalizationClient.ts index ff27978c2fc..93160a4307f 100644 --- a/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/services/LocalizationClient.ts +++ b/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/services/LocalizationClient.ts @@ -142,6 +142,7 @@ export interface LocalizedStrings { Unlink: string; CopyUrl: string; StatusBarMessage: string; + OpenFile: string; Download: string; Upload: string; }; \ No newline at end of file From ce69fdd6a3cf9f7d4889cbbb41bf0b0d2e9310fa Mon Sep 17 00:00:00 2001 From: "stephane.tetard@live.com" <62659880+stetard@users.noreply.github.com> Date: Sat, 30 Dec 2023 16:24:26 +0100 Subject: [PATCH 07/19] Replaced the usage of the download function to a js window open. closes #5885 --- .../ResourceManager.Web/src/components.d.ts | 4 ++-- .../actions/dnn-action-open-file/dnn-action-open-file.tsx | 7 ++----- .../components/dnn-rm-files-pane/dnn-rm-files-pane.tsx | 8 ++------ .../dnn-rm-items-cardview/dnn-rm-items-cardview.tsx | 4 ++-- .../dnn-rm-items-listview/dnn-rm-items-listview.tsx | 4 ++-- 5 files changed, 10 insertions(+), 17 deletions(-) diff --git a/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components.d.ts b/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components.d.ts index b3dc071c540..91058ceba56 100644 --- a/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components.d.ts +++ b/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components.d.ts @@ -586,7 +586,7 @@ declare namespace LocalJSX { /** * Fires when a file is double-clicked and emits the file ID into the event.detail */ - "onDnnRmFileDoubleClicked"?: (event: DnnRmItemsCardviewCustomEvent) => void; + "onDnnRmFileDoubleClicked"?: (event: DnnRmItemsCardviewCustomEvent) => void; /** * Fires when a folder is double-clicked and emits the folder ID into the event.detail */ @@ -600,7 +600,7 @@ declare namespace LocalJSX { /** * Fires when a file is double-clicked and emits the file ID into the event.detail */ - "onDnnRmFileDoubleClicked"?: (event: DnnRmItemsListviewCustomEvent) => void; + "onDnnRmFileDoubleClicked"?: (event: DnnRmItemsListviewCustomEvent) => void; /** * Fires when a folder is double-clicked and emits the folder ID into the event.detail */ diff --git a/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/actions/dnn-action-open-file/dnn-action-open-file.tsx b/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/actions/dnn-action-open-file/dnn-action-open-file.tsx index f2167676a2f..67f654165e4 100644 --- a/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/actions/dnn-action-open-file/dnn-action-open-file.tsx +++ b/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/actions/dnn-action-open-file/dnn-action-open-file.tsx @@ -1,5 +1,5 @@ import { Component, Host, h, Prop } from '@stencil/core'; -import { Item, ItemsClient } from '../../../services/ItemsClient'; +import { Item } from '../../../services/ItemsClient'; import state from "../../../store/store"; @Component({ @@ -11,14 +11,11 @@ export class DnnActionOpenFile { @Prop() item!: Item; - private itemsClient: ItemsClient; - constructor(){ - this.itemsClient = new ItemsClient(state.moduleId); } private handleClick(): void { - this.itemsClient.download(this.item.itemId, false); + window.open(this.item.path, "_blank"); } render() { diff --git a/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-files-pane/dnn-rm-files-pane.tsx b/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-files-pane/dnn-rm-files-pane.tsx index 4806346a8cd..ae0bdcd6b47 100644 --- a/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-files-pane/dnn-rm-files-pane.tsx +++ b/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-files-pane/dnn-rm-files-pane.tsx @@ -38,12 +38,8 @@ export class DnnRmFilesPane { } @Listen("dnnRmFileDoubleClicked", {target: "document"}) - handleFileDoubleClicked(e: CustomEvent) { - //unnecessary? - // if (state.selectedItems.length >= 1) { - // state.selectedItems = []; - // } - this.itemsClient.download(e.detail, false); + handleFileDoubleClicked(e: CustomEvent) { + window.open(e.detail, "_blank"); } componentDidUpdate() { diff --git a/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-items-cardview/dnn-rm-items-cardview.tsx b/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-items-cardview/dnn-rm-items-cardview.tsx index dcb733d6f6f..e97ea57d0b4 100644 --- a/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-items-cardview/dnn-rm-items-cardview.tsx +++ b/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-items-cardview/dnn-rm-items-cardview.tsx @@ -19,7 +19,7 @@ export class DnnRmItemsCardview { @Event() dnnRmFolderDoubleClicked: EventEmitter; /** Fires when a file is double-clicked and emits the file ID into the event.detail */ - @Event() dnnRmFileDoubleClicked: EventEmitter; + @Event() dnnRmFileDoubleClicked: EventEmitter; componentWillLoad() { document.addEventListener("click", this.dismissContextMenu.bind(this)); @@ -58,7 +58,7 @@ export class DnnRmItemsCardview { if (item.isFolder) { this.dnnRmFolderDoubleClicked.emit(item.itemId); } else { - this.dnnRmFileDoubleClicked.emit(item.itemId); + this.dnnRmFileDoubleClicked.emit(item.path); } } diff --git a/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-items-listview/dnn-rm-items-listview.tsx b/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-items-listview/dnn-rm-items-listview.tsx index 5510ca50de2..e5a5779598f 100644 --- a/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-items-listview/dnn-rm-items-listview.tsx +++ b/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/dnn-rm-items-listview/dnn-rm-items-listview.tsx @@ -19,7 +19,7 @@ export class DnnRmItemsListview { @Event() dnnRmFolderDoubleClicked: EventEmitter; /** Fires when a file is double-clicked and emits the file ID into the event.detail */ - @Event() dnnRmFileDoubleClicked: EventEmitter; + @Event() dnnRmFileDoubleClicked: EventEmitter; componentWillLoad() { document.addEventListener("click", this.dismissContextMenu.bind(this)); @@ -89,7 +89,7 @@ export class DnnRmItemsListview { if (item.isFolder) { this.dnnRmFolderDoubleClicked.emit(item.itemId); } else { - this.dnnRmFileDoubleClicked.emit(item.itemId); + this.dnnRmFileDoubleClicked.emit(item.path); } } From c36f83c4ef6ce76400d5fe662dbe7f95a64a8f4d Mon Sep 17 00:00:00 2001 From: David Poindexter Date: Sat, 30 Dec 2023 17:04:42 -0500 Subject: [PATCH 08/19] Remove unused constructor --- .../actions/dnn-action-open-file/dnn-action-open-file.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/actions/dnn-action-open-file/dnn-action-open-file.tsx b/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/actions/dnn-action-open-file/dnn-action-open-file.tsx index 67f654165e4..c3876118317 100644 --- a/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/actions/dnn-action-open-file/dnn-action-open-file.tsx +++ b/DNN Platform/Modules/ResourceManager/ResourceManager.Web/src/components/actions/dnn-action-open-file/dnn-action-open-file.tsx @@ -11,8 +11,6 @@ export class DnnActionOpenFile { @Prop() item!: Item; - constructor(){ - } private handleClick(): void { window.open(this.item.path, "_blank"); From 592cfb5eb7ac6a2c58d4b97f096fe575103c4448 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 23:04:14 +0000 Subject: [PATCH 09/19] Bump Cake.BuildSystems.Module from 5.0.0 to 6.1.0 Bumps [Cake.BuildSystems.Module](https://github.com/cake-contrib/Cake.BuildSystems.Module) from 5.0.0 to 6.1.0. - [Release notes](https://github.com/cake-contrib/Cake.BuildSystems.Module/releases) - [Changelog](https://github.com/cake-contrib/Cake.BuildSystems.Module/blob/develop/GitReleaseManager.yaml) - [Commits](https://github.com/cake-contrib/Cake.BuildSystems.Module/compare/5.0.0...6.1.0) --- updated-dependencies: - dependency-name: Cake.BuildSystems.Module dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- Build/Build.csproj | 66 +++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/Build/Build.csproj b/Build/Build.csproj index 40c6b91c322..f3414cf46a9 100644 --- a/Build/Build.csproj +++ b/Build/Build.csproj @@ -1,33 +1,33 @@ - - - Exe - net8.0 - true - - $(MSBuildProjectDirectory) - DotNetNuke.Build - true - CS0618 - true - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + Exe + net8.0 + true + + $(MSBuildProjectDirectory) + DotNetNuke.Build + true + CS0618 + true + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + From 7cadef4653d78e424ba15571c656ea4160047b19 Mon Sep 17 00:00:00 2001 From: Brian Dukes Date: Fri, 5 Jan 2024 08:24:13 -0600 Subject: [PATCH 10/19] Clean up UrlUtils.Combine --- .../Library/Common/Utilities/UrlUtils.cs | 11 ++++- .../Common/UrlUtilsTests.cs | 47 +++++++++++++++++++ .../DotNetNuke.Tests.Core.csproj | 1 + DNN_Platform.sln.DotSettings | 1 + 4 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs diff --git a/DNN Platform/Library/Common/Utilities/UrlUtils.cs b/DNN Platform/Library/Common/Utilities/UrlUtils.cs index 0c36255ecd2..a4f980f92de 100644 --- a/DNN Platform/Library/Common/Utilities/UrlUtils.cs +++ b/DNN Platform/Library/Common/Utilities/UrlUtils.cs @@ -17,10 +17,15 @@ namespace DotNetNuke.Common.Utilities using DotNetNuke.Security; using Microsoft.Extensions.DependencyInjection; - public class UrlUtils + /// Provides utilities for dealing with DNN's URLs. Consider using if applicable. + public static class UrlUtils { private static readonly INavigationManager NavigationManager = Globals.DependencyProvider.GetRequiredService(); + /// Combines two URLs, trimming any slashes between them. + /// The base URL. + /// The URL to add to the base URL. + /// A new URL that combines and . public static string Combine(string baseUrl, string relativeUrl) { if (baseUrl.Length == 0) @@ -33,7 +38,9 @@ public static string Combine(string baseUrl, string relativeUrl) return baseUrl; } - return string.Format("{0}/{1}", baseUrl.TrimEnd(new[] { '/', '\\' }), relativeUrl.TrimStart(new[] { '/', '\\' })); + baseUrl = baseUrl.TrimEnd('/', '\\'); + relativeUrl = relativeUrl.TrimStart('/', '\\'); + return $"{baseUrl}/{relativeUrl}"; } public static string DecodeParameter(string value) diff --git a/DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs b/DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs new file mode 100644 index 00000000000..9a65f91a9f6 --- /dev/null +++ b/DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information + +namespace DotNetNuke.Tests.Core.Common; + +using DotNetNuke.Abstractions; +using DotNetNuke.Abstractions.Application; +using DotNetNuke.Common; +using DotNetNuke.Common.Utilities; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using NUnit.Framework; + +[TestFixture] +public class UrlUtilsTests +{ + [OneTimeSetUp] + public static void OneTimeSetUp() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddTransient(container => Mock.Of()); + serviceCollection.AddTransient(container => Mock.Of()); + Globals.DependencyProvider = serviceCollection.BuildServiceProvider(); + } + + [Test] + public void CombineEmptyBase() + { + var result = UrlUtils.Combine(string.Empty, "a/b/c"); + Assert.AreEqual("a/b/c", result); + } + + [Test] + public void CombineEmptyRelative() + { + var result = UrlUtils.Combine("/a/b/c", string.Empty); + Assert.AreEqual("/a/b/c", result); + } + + [Test] + public void CombineRelativeWithBaseTrimsSlashes() + { + var result = UrlUtils.Combine("/a/b/c/", "/d/e/f/"); + Assert.AreEqual("/a/b/c/d/e/f/", result); + } +} diff --git a/DNN Platform/Tests/DotNetNuke.Tests.Core/DotNetNuke.Tests.Core.csproj b/DNN Platform/Tests/DotNetNuke.Tests.Core/DotNetNuke.Tests.Core.csproj index c7b005ec7f3..eef43699581 100644 --- a/DNN Platform/Tests/DotNetNuke.Tests.Core/DotNetNuke.Tests.Core.csproj +++ b/DNN Platform/Tests/DotNetNuke.Tests.Core/DotNetNuke.Tests.Core.csproj @@ -146,6 +146,7 @@ + diff --git a/DNN_Platform.sln.DotSettings b/DNN_Platform.sln.DotSettings index df1eaae620f..5bf3af7b796 100644 --- a/DNN_Platform.sln.DotSettings +++ b/DNN_Platform.sln.DotSettings @@ -153,6 +153,7 @@ Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE file in the project root for more information + True True True True From 81533644a2cbebfd33519320e0ce4f53bd15f2fc Mon Sep 17 00:00:00 2001 From: Brian Dukes Date: Fri, 5 Jan 2024 08:36:46 -0600 Subject: [PATCH 11/19] Clean up UrlUtils.DecodeParameter --- .../Library/Common/Utilities/UrlUtils.cs | 5 ++++- .../Common/UrlUtilsTests.cs | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/DNN Platform/Library/Common/Utilities/UrlUtils.cs b/DNN Platform/Library/Common/Utilities/UrlUtils.cs index a4f980f92de..6cb0a778480 100644 --- a/DNN Platform/Library/Common/Utilities/UrlUtils.cs +++ b/DNN Platform/Library/Common/Utilities/UrlUtils.cs @@ -43,9 +43,12 @@ public static string Combine(string baseUrl, string relativeUrl) return $"{baseUrl}/{relativeUrl}"; } + /// Decodes a base64 encoded value generated via . + /// The encoded value. + /// The decoded value. public static string DecodeParameter(string value) { - value = value.Replace("-", "+").Replace("_", "/").Replace("$", "="); + value = value.Replace('-', '+').Replace('_', '/').Replace('$', '='); byte[] arrBytes = Convert.FromBase64String(value); return Encoding.UTF8.GetString(arrBytes); } diff --git a/DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs b/DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs index 9a65f91a9f6..3b58fdebd7c 100644 --- a/DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs +++ b/DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs @@ -44,4 +44,20 @@ public void CombineRelativeWithBaseTrimsSlashes() var result = UrlUtils.Combine("/a/b/c/", "/d/e/f/"); Assert.AreEqual("/a/b/c/d/e/f/", result); } + + [Test] + public void DecodeParameterHandlesRoundTrip() + { + const string input = "DNN Platform!"; + var encodedValue = UrlUtils.EncodeParameter(input); + var result = UrlUtils.DecodeParameter(encodedValue); + Assert.AreEqual(input, result); + } + + [Test] + public void DecodeParameterHandlesSpecialCharacters() + { + var result = UrlUtils.DecodeParameter("RE5_O1-$"); + Assert.AreEqual("DN;_", result); + } } From 67ea19b4248cebf2136c14a6369e3382966e47e9 Mon Sep 17 00:00:00 2001 From: Brian Dukes Date: Fri, 5 Jan 2024 08:59:50 -0600 Subject: [PATCH 12/19] Clean up UrlUtils.DecryptParameter --- DNN Platform/Library/Common/Utilities/UrlUtils.cs | 15 ++++++++++----- .../DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/DNN Platform/Library/Common/Utilities/UrlUtils.cs b/DNN Platform/Library/Common/Utilities/UrlUtils.cs index 6cb0a778480..c58b9fcc7b9 100644 --- a/DNN Platform/Library/Common/Utilities/UrlUtils.cs +++ b/DNN Platform/Library/Common/Utilities/UrlUtils.cs @@ -53,21 +53,26 @@ public static string DecodeParameter(string value) return Encoding.UTF8.GetString(arrBytes); } + /// Decrypts an encrypted value generated via . + /// The encrypted value. + /// The decrypted value. public static string DecryptParameter(string value) { return DecryptParameter(value, PortalController.Instance.GetCurrentSettings().GUID.ToString()); } + /// Decrypts an encrypted value generated via . + /// The encrypted value. + /// The key used to encrypt the value. + /// The decrypted value. public static string DecryptParameter(string value, string encryptionKey) { - var objSecurity = PortalSecurity.Instance; - // [DNN-8257] - Can't do URLEncode/URLDecode as it introduces issues on decryption (with / = %2f), so we use a modified Base64 var toDecrypt = new StringBuilder(value); - toDecrypt.Replace("_", "/"); - toDecrypt.Replace("-", "+"); + toDecrypt.Replace('_', '/'); + toDecrypt.Replace('-', '+'); toDecrypt.Replace("%3d", "="); - return objSecurity.Decrypt(encryptionKey, toDecrypt.ToString()); + return PortalSecurity.Instance.Decrypt(encryptionKey, toDecrypt.ToString()); } public static string EncodeParameter(string value) diff --git a/DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs b/DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs index 3b58fdebd7c..4bebdf19d19 100644 --- a/DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs +++ b/DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs @@ -4,10 +4,13 @@ namespace DotNetNuke.Tests.Core.Common; +using System; using DotNetNuke.Abstractions; using DotNetNuke.Abstractions.Application; using DotNetNuke.Common; using DotNetNuke.Common.Utilities; +using DotNetNuke.ComponentModel; +using DotNetNuke.Services.Cryptography; using Microsoft.Extensions.DependencyInjection; using Moq; using NUnit.Framework; @@ -18,6 +21,8 @@ public class UrlUtilsTests [OneTimeSetUp] public static void OneTimeSetUp() { + ComponentFactory.RegisterComponent(); + var serviceCollection = new ServiceCollection(); serviceCollection.AddTransient(container => Mock.Of()); serviceCollection.AddTransient(container => Mock.Of()); @@ -60,4 +65,14 @@ public void DecodeParameterHandlesSpecialCharacters() var result = UrlUtils.DecodeParameter("RE5_O1-$"); Assert.AreEqual("DN;_", result); } + + [Test] + public void DecryptParameterHandlesRoundTrip() + { + const string input = "DNN Platform!"; + var key = Guid.NewGuid().ToString(); + var encodedValue = UrlUtils.EncryptParameter(input, key); + var result = UrlUtils.DecryptParameter(encodedValue, key); + Assert.AreEqual(input, result); + } } From bfd1783b116d23f5561a5e046dac5335fc2bd4ee Mon Sep 17 00:00:00 2001 From: Brian Dukes Date: Fri, 5 Jan 2024 09:04:40 -0600 Subject: [PATCH 13/19] Clean up UrlUtils.EncodeParameter --- DNN Platform/Library/Common/Utilities/UrlUtils.cs | 9 ++++++--- .../Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs | 7 +++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/DNN Platform/Library/Common/Utilities/UrlUtils.cs b/DNN Platform/Library/Common/Utilities/UrlUtils.cs index c58b9fcc7b9..e4afeec8df0 100644 --- a/DNN Platform/Library/Common/Utilities/UrlUtils.cs +++ b/DNN Platform/Library/Common/Utilities/UrlUtils.cs @@ -75,13 +75,16 @@ public static string DecryptParameter(string value, string encryptionKey) return PortalSecurity.Instance.Decrypt(encryptionKey, toDecrypt.ToString()); } + /// Encodes a value (using base64) for placing in a URL. + /// The value to encode. + /// The encoded value. public static string EncodeParameter(string value) { byte[] arrBytes = Encoding.UTF8.GetBytes(value); var toEncode = new StringBuilder(Convert.ToBase64String(arrBytes)); - toEncode.Replace("+", "-"); - toEncode.Replace("/", "_"); - toEncode.Replace("=", "$"); + toEncode.Replace('+', '-'); + toEncode.Replace('/', '_'); + toEncode.Replace('=', '$'); return toEncode.ToString(); } diff --git a/DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs b/DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs index 4bebdf19d19..b0efcc171e8 100644 --- a/DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs +++ b/DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs @@ -75,4 +75,11 @@ public void DecryptParameterHandlesRoundTrip() var result = UrlUtils.DecryptParameter(encodedValue, key); Assert.AreEqual(input, result); } + + [Test] + public void EncodeParameterReplacesPaddingSymbols() + { + var result = UrlUtils.EncodeParameter("D"); + Assert.AreEqual("RA$$", result); + } } From 96b698ff22b94d75fc4fac5e5a58cacdcd0b28cc Mon Sep 17 00:00:00 2001 From: Brian Dukes Date: Fri, 5 Jan 2024 09:12:16 -0600 Subject: [PATCH 14/19] Clean up UrlUtils.EncryptParameter --- .../Library/Common/Utilities/UrlUtils.cs | 17 ++++++++++++----- .../Common/UrlUtilsTests.cs | 7 +++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/DNN Platform/Library/Common/Utilities/UrlUtils.cs b/DNN Platform/Library/Common/Utilities/UrlUtils.cs index e4afeec8df0..e50af12b513 100644 --- a/DNN Platform/Library/Common/Utilities/UrlUtils.cs +++ b/DNN Platform/Library/Common/Utilities/UrlUtils.cs @@ -53,7 +53,7 @@ public static string DecodeParameter(string value) return Encoding.UTF8.GetString(arrBytes); } - /// Decrypts an encrypted value generated via . + /// Decrypts an encrypted value generated via . Decrypted using the current portal's . /// The encrypted value. /// The decrypted value. public static string DecryptParameter(string value) @@ -88,19 +88,26 @@ public static string EncodeParameter(string value) return toEncode.ToString(); } + /// Encrypt a parameter for placing in a URL. Encrypted using the current portal's . + /// The value to encrypt. + /// The encrypted value. public static string EncryptParameter(string value) { return EncryptParameter(value, PortalController.Instance.GetCurrentSettings().GUID.ToString()); } + /// Encrypt a parameter for placing in a URL. + /// The value to encrypt. + /// The key to use when encrypting the value. This key must be used to decrypt the value. + /// The encrypted value. public static string EncryptParameter(string value, string encryptionKey) { - var objSecurity = PortalSecurity.Instance; - var parameterValue = new StringBuilder(objSecurity.Encrypt(encryptionKey, value)); + var encryptedValue = PortalSecurity.Instance.Encrypt(encryptionKey, value); + var parameterValue = new StringBuilder(encryptedValue); // [DNN-8257] - Can't do URLEncode/URLDecode as it introduces issues on decryption (with / = %2f), so we use a modified Base64 - parameterValue.Replace("/", "_"); - parameterValue.Replace("+", "-"); + parameterValue.Replace('/', '_'); + parameterValue.Replace('+', '-'); parameterValue.Replace("=", "%3d"); return parameterValue.ToString(); } diff --git a/DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs b/DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs index b0efcc171e8..7d3b8c6aeff 100644 --- a/DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs +++ b/DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs @@ -82,4 +82,11 @@ public void EncodeParameterReplacesPaddingSymbols() var result = UrlUtils.EncodeParameter("D"); Assert.AreEqual("RA$$", result); } + + [Test] + public void EncryptParameterReplacesPaddingSymbols() + { + var result = UrlUtils.EncryptParameter("D", "key"); + Assert.IsTrue(result.EndsWith("%3d")); + } } From e8b9b2ede5eee676549aa0d4ff19e5b8c5eb8746 Mon Sep 17 00:00:00 2001 From: Brian Dukes Date: Fri, 5 Jan 2024 09:15:08 -0600 Subject: [PATCH 15/19] Clean up UrlUtils.StripQSParam --- .../Library/Common/Utilities/UrlUtils.cs | 62 ++++-- .../Common/UrlUtilsTests.cs | 135 +++++++++++- .../DotNetNuke.Tests.Utilities.csproj | 1 + .../Fakes/FakeHostController.cs | 195 ++++++++++++++++++ 4 files changed, 371 insertions(+), 22 deletions(-) create mode 100644 DNN Platform/Tests/DotNetNuke.Tests.Utilities/Fakes/FakeHostController.cs diff --git a/DNN Platform/Library/Common/Utilities/UrlUtils.cs b/DNN Platform/Library/Common/Utilities/UrlUtils.cs index e50af12b513..cbbbfdcc0a7 100644 --- a/DNN Platform/Library/Common/Utilities/UrlUtils.cs +++ b/DNN Platform/Library/Common/Utilities/UrlUtils.cs @@ -4,6 +4,7 @@ namespace DotNetNuke.Common.Utilities { using System; + using System.Net; using System.Text; using System.Text.RegularExpressions; using System.Web; @@ -112,21 +113,32 @@ public static string EncryptParameter(string value, string encryptionKey) return parameterValue.ToString(); } + /// Gets the name from a query string pair. + /// The pair, e.g. "name=value". + /// The name. public static string GetParameterName(string pair) { - string[] nameValues = pair.Split('='); - return nameValues[0]; + var length = pair.IndexOf('='); + if (length == -1) + { + length = pair.Length; + } + + return pair.Substring(0, length); } + /// Gets the value from a query string pair. + /// The pair, e.g. "name=value". + /// The value. public static string GetParameterValue(string pair) { - string[] nameValues = pair.Split('='); - if (nameValues.Length > 1) + var start = pair.IndexOf('=') + 1; + if (start == 0) { - return nameValues[1]; + return string.Empty; } - return string.Empty; + return pair.Substring(start); } /// @@ -295,41 +307,51 @@ public static string PopUpUrl(string url, Control control, PortalSettings portal return popUpUrl; } + /// Creates a URL (or script) to close a pop-up. + /// Whether to refresh the page when the pop-up is closed. + /// The URL. + /// Whether to generate a script for an onClick event (rather than a URL with a javascript: protocol). + /// The URL or script. public static string ClosePopUp(bool refresh, string url, bool onClickEvent) { - var closePopUpStr = "dnnModal.closePopUp({0}, {1})"; - closePopUpStr = "javascript:" + string.Format(closePopUpStr, refresh.ToString().ToLowerInvariant(), "'" + url + "'"); - - // Removes the javascript txt for onClick scripts) - if (onClickEvent && closePopUpStr.StartsWith("javascript:")) - { - closePopUpStr = closePopUpStr.Replace("javascript:", string.Empty); - } - - return closePopUpStr; + var protocol = onClickEvent ? string.Empty : "javascript:"; + var refreshBool = refresh.ToString().ToLowerInvariant(); + var urlString = HttpUtility.JavaScriptStringEncode(url, addDoubleQuotes: true); + return $"{protocol}dnnModal.closePopUp({refreshBool}, {urlString})"; } + /// Replaces a query string parameter's value in a URL. + /// The URL. + /// The parameter name. + /// The parameter value. + /// The updated URL. public static string ReplaceQSParam(string url, string param, string newValue) { if (Host.UseFriendlyUrls) { - return Regex.Replace(url, "(.*)(" + param + "/)([^/]+)(/.*)", "$1$2" + newValue + "$4", RegexOptions.IgnoreCase); + var escapedReplacementValue = newValue.Replace("$1", "$$1").Replace("$2", "$$2").Replace("$3", "$$3").Replace("$4", "$$4"); + return Regex.Replace(url, $@"(.*)({Regex.Escape(param)}/)([^/]+)(/.*)", $"$1$2{escapedReplacementValue}$4", RegexOptions.IgnoreCase); } else { - return Regex.Replace(url, "(.*)(&|\\?)(" + param + "=)([^&\\?]+)(.*)", "$1$2$3" + newValue + "$5", RegexOptions.IgnoreCase); + var escapedReplacementValue = newValue.Replace("$1", "$$1").Replace("$2", "$$2").Replace("$3", "$$3").Replace("$4", "$$4").Replace("$5", "$$5"); + return Regex.Replace(url, $@"(.*)(&|\?)({Regex.Escape(param)}=)([^&\?]+)(.*)", $"$1$2$3{escapedReplacementValue}$5", RegexOptions.IgnoreCase); } } + /// Removes the query string parameter with the given name from the URL. + /// The URL. + /// The parameter name. + /// The updated URL. public static string StripQSParam(string url, string param) { if (Host.UseFriendlyUrls) { - return Regex.Replace(url, "(.*)(" + param + "/[^/]+/)(.*)", "$1$3", RegexOptions.IgnoreCase); + return Regex.Replace(url, $"(.*)({Regex.Escape(param)}/[^/]+/)(.*)", "$1$3", RegexOptions.IgnoreCase); } else { - return Regex.Replace(url, "(.*)(&|\\?)(" + param + "=)([^&\\?]+)([&\\?])?(.*)", "$1$2$6", RegexOptions.IgnoreCase).Replace("(.*)([&\\?]$)", "$1"); + return Regex.Replace(url, $@"(.*)(&|\?)({Regex.Escape(param)}=)([^&\?]+)([&\?])?(.*)", "$1$2$6", RegexOptions.IgnoreCase).Replace("(.*)([&\\?]$)", "$1"); } } diff --git a/DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs b/DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs index 7d3b8c6aeff..3f9e235f839 100644 --- a/DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs +++ b/DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs @@ -5,12 +5,19 @@ namespace DotNetNuke.Tests.Core.Common; using System; +using System.Collections.Generic; +using System.Web.Caching; using DotNetNuke.Abstractions; using DotNetNuke.Abstractions.Application; +using DotNetNuke.Abstractions.Settings; using DotNetNuke.Common; using DotNetNuke.Common.Utilities; using DotNetNuke.ComponentModel; +using DotNetNuke.Entities; +using DotNetNuke.Entities.Controllers; using DotNetNuke.Services.Cryptography; +using DotNetNuke.Tests.Utilities.Fakes; +using DotNetNuke.Tests.Utilities.Mocks; using Microsoft.Extensions.DependencyInjection; using Moq; using NUnit.Framework; @@ -18,14 +25,18 @@ namespace DotNetNuke.Tests.Core.Common; [TestFixture] public class UrlUtilsTests { + private static readonly Dictionary HostSettings = new Dictionary(); + [OneTimeSetUp] public static void OneTimeSetUp() { ComponentFactory.RegisterComponent(); + MockComponentProvider.CreateDataCacheProvider(); var serviceCollection = new ServiceCollection(); - serviceCollection.AddTransient(container => Mock.Of()); - serviceCollection.AddTransient(container => Mock.Of()); + serviceCollection.AddSingleton(Mock.Of()); + serviceCollection.AddSingleton(Mock.Of()); + serviceCollection.AddSingleton(new FakeHostController(HostSettings)); Globals.DependencyProvider = serviceCollection.BuildServiceProvider(); } @@ -89,4 +100,124 @@ public void EncryptParameterReplacesPaddingSymbols() var result = UrlUtils.EncryptParameter("D", "key"); Assert.IsTrue(result.EndsWith("%3d")); } + + [Test] + public void GetParameterNameReturnsName() + { + var result = UrlUtils.GetParameterName("key=value"); + Assert.AreEqual("key", result); + } + + [Test] + public void GetParameterNameReturnsEntireStringIfNoEqualsSign() + { + var result = UrlUtils.GetParameterName("just-a-key"); + Assert.AreEqual("just-a-key", result); + } + + [Test] + public void GetParameterNameReturnsEmptyIfStartsWithEqualsSign() + { + var result = UrlUtils.GetParameterName("=just-a-value"); + Assert.AreEqual(string.Empty, result); + } + + [Test] + public void GetParameterValueReturnsName() + { + var result = UrlUtils.GetParameterValue("key=value"); + Assert.AreEqual("value", result); + } + + [Test] + public void GetParameterValueReturnsEmptyStringIfNoEqualsSign() + { + var result = UrlUtils.GetParameterValue("just-a-key"); + Assert.AreEqual(string.Empty, result); + } + + [Test] + public void GetParameterValueReturnsEntireStringIfStartsWithEqualsSign() + { + var result = UrlUtils.GetParameterValue("=just-a-value"); + Assert.AreEqual("just-a-value", result); + } + + [Test] + public void ClosePopUpGeneratesAJavaScriptUrlWithValues() + { + var result = UrlUtils.ClosePopUp(false, "/hello", false); + Assert.AreEqual("""javascript:dnnModal.closePopUp(false, "/hello")""", result); + + result = UrlUtils.ClosePopUp(true, "blah", false); + Assert.AreEqual("""javascript:dnnModal.closePopUp(true, "blah")""", result); + } + + [Test] + public void ClosePopUpGeneratesAScriptWhenOnClickEventIsTrue() + { + var result = UrlUtils.ClosePopUp(false, "/somewhere", true); + Assert.AreEqual("""dnnModal.closePopUp(false, "/somewhere")""", result); + } + + [Test] + public void ClosePopUpEncodesUrlParameter() + { + var result = UrlUtils.ClosePopUp(false, "/somewhere?value=%20hi&two='hey'", true); + Assert.AreEqual("""dnnModal.closePopUp(false, "/somewhere?value=%20hi\u0026two=\u0027hey\u0027")""", result); + } + + [Test] + public void ReplaceQSParamReplacesUnfriendlyParam() + { + HostSettings["UseFriendlyUrls"] = new ConfigurationSetting { Key = "UseFriendlyUrls", Value = "false", }; + + var result = UrlUtils.ReplaceQSParam("/somewhere?value=hi&two=hey", "two", "what"); + Assert.AreEqual("/somewhere?value=hi&two=what", result); + } + + [Test] + public void ReplaceQSParamReplacesFriendlyParam() + { + HostSettings["UseFriendlyUrls"] = new ConfigurationSetting { Key = "UseFriendlyUrls", Value = "true", }; + + var result = UrlUtils.ReplaceQSParam("/somewhere/value/hi/two/hey/", "two", "what"); + Assert.AreEqual("/somewhere/value/hi/two/what/", result); + } + + [Test] + public void ReplaceQSParamHandlesSpecialCharacters() + { + HostSettings["UseFriendlyUrls"] = new ConfigurationSetting { Key = "UseFriendlyUrls", Value = "false", }; + + var result = UrlUtils.ReplaceQSParam("/somewhere?one.two=three$four&one_two=123", "one.two", "four$3"); + Assert.AreEqual("/somewhere?one.two=four$3&one_two=123", result); + } + + [Test] + public void StripQSParamRemovesUnfriendlyParam() + { + HostSettings["UseFriendlyUrls"] = new ConfigurationSetting { Key = "UseFriendlyUrls", Value = "false", }; + + var result = UrlUtils.StripQSParam("/somewhere?value=hi&two=hey&three=x", "two"); + Assert.AreEqual("/somewhere?value=hi&three=x", result); + } + + [Test] + public void StripQSParamRemovesFriendlyParam() + { + HostSettings["UseFriendlyUrls"] = new ConfigurationSetting { Key = "UseFriendlyUrls", Value = "true", }; + + var result = UrlUtils.StripQSParam("/somewhere/value/hi/two/hey/", "two"); + Assert.AreEqual("/somewhere/value/hi/", result); + } + + [Test] + public void StripQSParamHandlesSpecialCharacters() + { + HostSettings["UseFriendlyUrls"] = new ConfigurationSetting { Key = "UseFriendlyUrls", Value = "false", }; + + var result = UrlUtils.StripQSParam("/somewhere?one.two=three$four&one_two=123", "one.two"); + Assert.AreEqual("/somewhere?one_two=123", result); + } } diff --git a/DNN Platform/Tests/DotNetNuke.Tests.Utilities/DotNetNuke.Tests.Utilities.csproj b/DNN Platform/Tests/DotNetNuke.Tests.Utilities/DotNetNuke.Tests.Utilities.csproj index 41c136137c8..f7d78d5db80 100644 --- a/DNN Platform/Tests/DotNetNuke.Tests.Utilities/DotNetNuke.Tests.Utilities.csproj +++ b/DNN Platform/Tests/DotNetNuke.Tests.Utilities/DotNetNuke.Tests.Utilities.csproj @@ -119,6 +119,7 @@ + diff --git a/DNN Platform/Tests/DotNetNuke.Tests.Utilities/Fakes/FakeHostController.cs b/DNN Platform/Tests/DotNetNuke.Tests.Utilities/Fakes/FakeHostController.cs new file mode 100644 index 00000000000..8389a8ed723 --- /dev/null +++ b/DNN Platform/Tests/DotNetNuke.Tests.Utilities/Fakes/FakeHostController.cs @@ -0,0 +1,195 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information + +namespace DotNetNuke.Tests.Utilities.Fakes; + +using System; +using System.Collections.Generic; +using DotNetNuke.Abstractions.Application; +using DotNetNuke.Abstractions.Settings; +using DotNetNuke.Common.Utilities; +using DotNetNuke.Entities; +using DotNetNuke.Entities.Controllers; + +public class FakeHostController : IHostController, IHostSettingsService +{ + private readonly IReadOnlyDictionary settings; + + public FakeHostController(IReadOnlyDictionary settings) + { + this.settings = settings; + } + + public bool GetBoolean(string key) => this.GetBoolean(key, Null.NullBoolean); + + public bool GetBoolean(string key, bool defaultValue) + { + if (this.settings.TryGetValue(key, out var setting)) + { + return setting.Value.StartsWith("Y", StringComparison.InvariantCultureIgnoreCase) || setting.Value.Equals("TRUE", StringComparison.InvariantCultureIgnoreCase); + } + + return defaultValue; + } + + double IHostSettingsService.GetDouble(string key) + { + throw new System.NotImplementedException(); + } + + double IHostSettingsService.GetDouble(string key, double defaultValue) + { + throw new System.NotImplementedException(); + } + + string IHostSettingsService.GetEncryptedString(string key, string passPhrase) + { + throw new System.NotImplementedException(); + } + + int IHostSettingsService.GetInteger(string key) + { + throw new System.NotImplementedException(); + } + + int IHostSettingsService.GetInteger(string key, int defaultValue) + { + throw new System.NotImplementedException(); + } + + IDictionary IHostSettingsService.GetSettings() + { + throw new System.NotImplementedException(); + } + + IDictionary IHostSettingsService.GetSettingsDictionary() + { + return this.GetSettingsDictionary(); + } + + string IHostSettingsService.GetString(string key) + { + throw new System.NotImplementedException(); + } + + string IHostSettingsService.GetString(string key, string defaultValue) + { + throw new System.NotImplementedException(); + } + + void IHostSettingsService.IncrementCrmVersion(bool includeOverridingPortals) + { + throw new System.NotImplementedException(); + } + + public void Update(IConfigurationSetting config) + { + throw new System.NotImplementedException(); + } + + public void Update(IConfigurationSetting config, bool clearCache) + { + throw new System.NotImplementedException(); + } + + public void Update(IDictionary settings) + { + throw new System.NotImplementedException(); + } + + void IHostSettingsService.Update(string key, string value) + { + throw new System.NotImplementedException(); + } + + void IHostSettingsService.Update(string key, string value, bool clearCache) + { + throw new System.NotImplementedException(); + } + + void IHostSettingsService.UpdateEncryptedString(string key, string value, string passPhrase) + { + throw new System.NotImplementedException(); + } + + double IHostController.GetDouble(string key, double defaultValue) + { + throw new System.NotImplementedException(); + } + + double IHostController.GetDouble(string key) + { + throw new System.NotImplementedException(); + } + + int IHostController.GetInteger(string key) + { + throw new System.NotImplementedException(); + } + + int IHostController.GetInteger(string key, int defaultValue) + { + throw new System.NotImplementedException(); + } + + Dictionary IHostController.GetSettings() + { + throw new System.NotImplementedException(); + } + + public Dictionary GetSettingsDictionary() + { + throw new System.NotImplementedException(); + } + + string IHostController.GetEncryptedString(string key, string passPhrase) + { + throw new System.NotImplementedException(); + } + + string IHostController.GetString(string key) + { + throw new System.NotImplementedException(); + } + + string IHostController.GetString(string key, string defaultValue) + { + throw new System.NotImplementedException(); + } + + public void Update(Dictionary settings) + { + throw new System.NotImplementedException(); + } + + public void Update(ConfigurationSetting config) + { + throw new System.NotImplementedException(); + } + + public void Update(ConfigurationSetting config, bool clearCache) + { + throw new System.NotImplementedException(); + } + + void IHostController.Update(string key, string value, bool clearCache) + { + throw new System.NotImplementedException(); + } + + void IHostController.Update(string key, string value) + { + throw new System.NotImplementedException(); + } + + void IHostController.UpdateEncryptedString(string key, string value, string passPhrase) + { + throw new System.NotImplementedException(); + } + + void IHostController.IncrementCrmVersion(bool includeOverridingPortals) + { + throw new System.NotImplementedException(); + } +} From 62d0cdccd01a83a63b8acefa867f839b334a5132 Mon Sep 17 00:00:00 2001 From: Brian Dukes Date: Fri, 5 Jan 2024 11:04:27 -0600 Subject: [PATCH 16/19] Clean up UrlUtils.ValidReturnUrl --- .../Library/Common/Utilities/UrlUtils.cs | 15 ++-- .../Common/UrlUtilsTests.cs | 90 +++++++++++++++++++ 2 files changed, 99 insertions(+), 6 deletions(-) diff --git a/DNN Platform/Library/Common/Utilities/UrlUtils.cs b/DNN Platform/Library/Common/Utilities/UrlUtils.cs index cbbbfdcc0a7..e2eb34f48f3 100644 --- a/DNN Platform/Library/Common/Utilities/UrlUtils.cs +++ b/DNN Platform/Library/Common/Utilities/UrlUtils.cs @@ -355,6 +355,9 @@ public static string StripQSParam(string url, string param) } } + /// Determines whether a is valid as a return URL. + /// The URL string. + /// The normalized return URL or . public static string ValidReturnUrl(string url) { try @@ -365,8 +368,8 @@ public static string ValidReturnUrl(string url) return url; } - url = url.Replace("\\", "/"); - if (url.ToLowerInvariant().Contains("data:")) + url = url.Replace('\\', '/'); + if (url.IndexOf("data:", StringComparison.OrdinalIgnoreCase) > -1) { return string.Empty; } @@ -380,12 +383,12 @@ public static string ValidReturnUrl(string url) // redirect url should never contain a protocol ( if it does, it is likely a cross-site request forgery attempt ) var urlWithNoQuery = url; - if (urlWithNoQuery.Contains("?")) + if (urlWithNoQuery.IndexOf('?') > -1) { urlWithNoQuery = urlWithNoQuery.Substring(0, urlWithNoQuery.IndexOf("?", StringComparison.InvariantCultureIgnoreCase)); } - if (urlWithNoQuery.Contains("://")) + if (urlWithNoQuery.IndexOf(':') > -1) { var portalSettings = PortalSettings.Current; var aliasWithHttp = Globals.AddHTTP(((IPortalAliasInfo)portalSettings.PortalAlias).HttpAlias); @@ -405,12 +408,12 @@ public static string ValidReturnUrl(string url) } } - while (url.StartsWith("///")) + while (url.StartsWith("///", StringComparison.Ordinal)) { url = url.Substring(1); } - if (url.StartsWith("//")) + if (url.StartsWith("//", StringComparison.Ordinal)) { var urlWithNoProtocol = url.Substring(2); var portalSettings = PortalSettings.Current; diff --git a/DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs b/DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs index 3f9e235f839..2666c189042 100644 --- a/DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs +++ b/DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs @@ -9,12 +9,14 @@ namespace DotNetNuke.Tests.Core.Common; using System.Web.Caching; using DotNetNuke.Abstractions; using DotNetNuke.Abstractions.Application; +using DotNetNuke.Abstractions.Portals; using DotNetNuke.Abstractions.Settings; using DotNetNuke.Common; using DotNetNuke.Common.Utilities; using DotNetNuke.ComponentModel; using DotNetNuke.Entities; using DotNetNuke.Entities.Controllers; +using DotNetNuke.Entities.Portals; using DotNetNuke.Services.Cryptography; using DotNetNuke.Tests.Utilities.Fakes; using DotNetNuke.Tests.Utilities.Mocks; @@ -220,4 +222,92 @@ public void StripQSParamHandlesSpecialCharacters() var result = UrlUtils.StripQSParam("/somewhere?one.two=three$four&one_two=123", "one.two"); Assert.AreEqual("/somewhere?one_two=123", result); } + + [Test] + public void ValidateReturnUrlReturnsNullWhenInputIsNull() + { + var result = UrlUtils.ValidReturnUrl(null); + Assert.IsNull(result); + } + + [Test] + public void ValidateReturnUrlReturnsEmptyWhenInputIsEmpty() + { + var result = UrlUtils.ValidReturnUrl(string.Empty); + Assert.IsEmpty(result); + } + + [Test] + public void ValidateReturnUrlDoesNotAcceptDataUrl() + { + var result = UrlUtils.ValidReturnUrl("data:text/plain,I am text file"); + Assert.IsEmpty(result); + } + + [Test] + public void ValidateReturnUrlDoesNotAcceptXssAttack() + { + var result = UrlUtils.ValidReturnUrl("/return?onclick=alert()"); + Assert.IsEmpty(result); + } + + [Test] + public void ValidateReturnUrlDoesNotAcceptAbsoluteUrlWithoutMatchingDomain() + { + ComponentFactory.RegisterComponentInstance("PortalSettingsController", Mock.Of()); + + var portalAlias = new PortalAliasInfo { HTTPAlias = "dnncommunity.org", }; + var portalSettings = new PortalSettings(-1, portal: null) { PortalAlias = portalAlias, }; + var portalControllerMock = new Mock(); + portalControllerMock.Setup(c => c.GetCurrentPortalSettings()).Returns(portalSettings); + PortalController.SetTestableInstance(portalControllerMock.Object); + + var result = UrlUtils.ValidReturnUrl("https://another.evil/return"); + Assert.IsEmpty(result); + } + + [Test] + public void ValidateReturnUrlDoesAcceptAbsoluteUrlWithMatchingDomain() + { + ComponentFactory.RegisterComponentInstance("PortalSettingsController", Mock.Of()); + + var portalAlias = new PortalAliasInfo { HTTPAlias = "dnncommunity.org", }; + var portalSettings = new PortalSettings(-1, portal: null) { PortalAlias = portalAlias, }; + var portalControllerMock = new Mock(); + portalControllerMock.Setup(c => c.GetCurrentPortalSettings()).Returns(portalSettings); + PortalController.SetTestableInstance(portalControllerMock.Object); + + var result = UrlUtils.ValidReturnUrl("https://dnncommunity.org/return"); + Assert.AreEqual("https://dnncommunity.org/return", result); + } + + [Test] + public void ValidateReturnUrlDoesNotAcceptAbsoluteUrlWithoutProtocolWhenDomainDoesNotMatch() + { + ComponentFactory.RegisterComponentInstance("PortalSettingsController", Mock.Of()); + + var portalAlias = new PortalAliasInfo { HTTPAlias = "dnncommunity.org", }; + var portalSettings = new PortalSettings(-1, portal: null) { PortalAlias = portalAlias, }; + var portalControllerMock = new Mock(); + portalControllerMock.Setup(c => c.GetCurrentPortalSettings()).Returns(portalSettings); + PortalController.SetTestableInstance(portalControllerMock.Object); + + var result = UrlUtils.ValidReturnUrl("/////dnncommunity.net/return"); + Assert.IsEmpty(result); + } + + [Test] + public void ValidateReturnUrlAcceptsAbsoluteUrlWithoutProtocolWhenDomainDoesMatches() + { + ComponentFactory.RegisterComponentInstance("PortalSettingsController", Mock.Of()); + + var portalAlias = new PortalAliasInfo { HTTPAlias = "dnncommunity.org", }; + var portalSettings = new PortalSettings(-1, portal: null) { PortalAlias = portalAlias, }; + var portalControllerMock = new Mock(); + portalControllerMock.Setup(c => c.GetCurrentPortalSettings()).Returns(portalSettings); + PortalController.SetTestableInstance(portalControllerMock.Object); + + var result = UrlUtils.ValidReturnUrl("/////dnncommunity.org/return"); + Assert.AreEqual("//dnncommunity.org/return", result); + } } From b312fe4922b68461586991974b64acd71a128345 Mon Sep 17 00:00:00 2001 From: Brian Dukes Date: Fri, 5 Jan 2024 11:07:11 -0600 Subject: [PATCH 17/19] Clean up UrlUtils.IsPopUp --- DNN Platform/Library/Common/Utilities/UrlUtils.cs | 8 ++++++-- .../DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/DNN Platform/Library/Common/Utilities/UrlUtils.cs b/DNN Platform/Library/Common/Utilities/UrlUtils.cs index e2eb34f48f3..4894b62c6bc 100644 --- a/DNN Platform/Library/Common/Utilities/UrlUtils.cs +++ b/DNN Platform/Library/Common/Utilities/UrlUtils.cs @@ -433,12 +433,16 @@ public static string ValidReturnUrl(string url) } } - // Whether current page is show in popup. + /// Determines whether the current page is being shown in a pop-up. + /// if the current page is in a pop-up, otherwise . public static bool InPopUp() { - return HttpContext.Current != null && HttpContext.Current.Request.Url.ToString().IndexOf("popUp=true", StringComparison.OrdinalIgnoreCase) >= 0; + return HttpContext.Current != null && IsPopUp(HttpContext.Current.Request.Url.ToString()); } + /// Determines whether the given URL is for a page being shown in a pop-up. + /// The URL. + /// if the URL is for a page in a pop-up, otherwise . public static bool IsPopUp(string url) { return url.IndexOf("popUp=true", StringComparison.OrdinalIgnoreCase) >= 0; diff --git a/DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs b/DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs index 2666c189042..fde0822bcdc 100644 --- a/DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs +++ b/DNN Platform/Tests/DotNetNuke.Tests.Core/Common/UrlUtilsTests.cs @@ -310,4 +310,18 @@ public void ValidateReturnUrlAcceptsAbsoluteUrlWithoutProtocolWhenDomainDoesMatc var result = UrlUtils.ValidReturnUrl("/////dnncommunity.org/return"); Assert.AreEqual("//dnncommunity.org/return", result); } + + [Test] + public void IsPopUpIsTrueWhenPopUpParameterIsOnUrl() + { + var result = UrlUtils.IsPopUp("/page?popUp=true"); + Assert.IsTrue(result); + } + + [Test] + public void IsPopUpIsFalseWhenPopUpParameterIsNotOnUrl() + { + var result = UrlUtils.IsPopUp("/page"); + Assert.IsFalse(result); + } } From ce20da84331c4899dda2216028cbe1011cac6888 Mon Sep 17 00:00:00 2001 From: Brian Dukes Date: Fri, 5 Jan 2024 11:08:09 -0600 Subject: [PATCH 18/19] Clean up UrlUtils.Handle404Exception --- DNN Platform/Library/Common/Utilities/UrlUtils.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DNN Platform/Library/Common/Utilities/UrlUtils.cs b/DNN Platform/Library/Common/Utilities/UrlUtils.cs index 4894b62c6bc..bb399433a09 100644 --- a/DNN Platform/Library/Common/Utilities/UrlUtils.cs +++ b/DNN Platform/Library/Common/Utilities/UrlUtils.cs @@ -449,8 +449,8 @@ public static bool IsPopUp(string url) } /// Redirect current response to 404 error page or output 404 content if error page not defined. - /// - /// + /// The response. + /// The portal settings. public static void Handle404Exception(HttpResponse response, PortalSettings portalSetting) { if (portalSetting?.ErrorPage404 > Null.NullInteger) From 8ba9122a072125a1a3aa6f91ad28aebf387bcbef Mon Sep 17 00:00:00 2001 From: Daniel Valadas Date: Wed, 17 Jan 2024 00:53:33 -0500 Subject: [PATCH 19/19] Re-applies #5924 onto the release branch This should fix the build issue. --- Build/Build.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Build/Build.csproj b/Build/Build.csproj index f3414cf46a9..bf0e19cd734 100644 --- a/Build/Build.csproj +++ b/Build/Build.csproj @@ -28,6 +28,6 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - +