Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

made folder paths cross platform, introduced file handler for abstracting the serving of files, enabled url mapping for broken file links, resolved public folder deletion issue #2384

Merged
merged 1 commit into from
Aug 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion Oqtane.Client/Modules/Admin/Pages/Add.razor
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@
}
}

if(PagePathIsDeleted(page.Path, page.SiteId, _pageList))
if (PagePathIsDeleted(page.Path, page.SiteId, _pageList))
{
AddModuleMessage(string.Format(Localizer["Message.Page.Deleted"], _path), MessageType.Warning);
return;
Expand All @@ -341,6 +341,12 @@
return;
}

if (page.ParentId == null && Constants.ReservedRoutes.Contains(page.Name.ToLower()))
{
AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], page.Name), MessageType.Warning);
return;
}

Page child;
switch (_insert)
{
Expand Down
6 changes: 6 additions & 0 deletions Oqtane.Client/Modules/Admin/Pages/Edit.razor
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,12 @@
return;
}

if (page.ParentId == null && Constants.ReservedRoutes.Contains(page.Name.ToLower()))
{
AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], page.Name), MessageType.Warning);
return;
}

if (_insert != "=")
{
Page child;
Expand Down
3 changes: 3 additions & 0 deletions Oqtane.Client/Resources/Modules/Admin/Pages/Add.resx
Original file line number Diff line number Diff line change
Expand Up @@ -237,4 +237,7 @@
<data name="Meta.Text" xml:space="preserve">
<value>Meta:</value>
</data>
<data name="Message.Page.Reserved" xml:space="preserve">
<value>The page name {0} is reserved. Please enter a different name for your page.</value>
</data>
</root>
3 changes: 3 additions & 0 deletions Oqtane.Client/Resources/Modules/Admin/Pages/Edit.resx
Original file line number Diff line number Diff line change
Expand Up @@ -270,4 +270,7 @@
<data name="Meta.Text" xml:space="preserve">
<value>Meta:</value>
</data>
<data name="Message.Page.Reserved" xml:space="preserve">
<value>The page name {0} is reserved. Please enter a different name for your page.</value>
</data>
</root>
33 changes: 13 additions & 20 deletions Oqtane.Server/Controllers/FolderController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,13 @@ namespace Oqtane.Controllers
[Route(ControllerRoutes.ApiRoute)]
public class FolderController : Controller
{
private readonly IWebHostEnvironment _environment;
private readonly IFolderRepository _folders;
private readonly IUserPermissions _userPermissions;
private readonly ILogManager _logger;
private readonly Alias _alias;

public FolderController(IWebHostEnvironment environment, IFolderRepository folders, IUserPermissions userPermissions, ILogManager logger, ITenantManager tenantManager)
public FolderController(IFolderRepository folders, IUserPermissions userPermissions, ILogManager logger, ITenantManager tenantManager)
{
_environment = environment;
_folders = folders;
_userPermissions = userPermissions;
_logger = logger;
Expand Down Expand Up @@ -78,10 +76,10 @@ public Folder Get(int id)
[HttpGet("{siteId}/{path}")]
public Folder GetByPath(int siteId, string path)
{
var folderPath = WebUtility.UrlDecode(path);
if (!(folderPath.EndsWith(System.IO.Path.DirectorySeparatorChar) || folderPath.EndsWith(System.IO.Path.AltDirectorySeparatorChar)))
var folderPath = WebUtility.UrlDecode(path).Replace("\\", "/");
if (!folderPath.EndsWith("/"))
{
folderPath = Utilities.PathCombine(folderPath, System.IO.Path.DirectorySeparatorChar.ToString());
folderPath += "/";
}
Folder folder = _folders.GetFolder(siteId, folderPath);
if (folder != null && folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.Browse, folder.Permissions))
Expand Down Expand Up @@ -121,9 +119,9 @@ public Folder Post([FromBody] Folder folder)
if (string.IsNullOrEmpty(folder.Path) && folder.ParentId != null)
{
Folder parent = _folders.GetFolder(folder.ParentId.Value);
folder.Path = Utilities.PathCombine(parent.Path, folder.Name);
folder.Path = Utilities.UrlCombine(parent.Path, folder.Name);
}
folder.Path = Utilities.PathCombine(folder.Path, Path.DirectorySeparatorChar.ToString());
folder.Path = folder.Path + "/";
folder = _folders.AddFolder(folder);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Folder Added {Folder}", folder);
}
Expand Down Expand Up @@ -162,14 +160,14 @@ public Folder Put(int id, [FromBody] Folder folder)
if (folder.ParentId != null)
{
Folder parent = _folders.GetFolder(folder.ParentId.Value);
folder.Path = Utilities.PathCombine(parent.Path, folder.Name);
folder.Path = Utilities.UrlCombine(parent.Path, folder.Name);
}
folder.Path = Utilities.PathCombine(folder.Path, Path.DirectorySeparatorChar.ToString());
folder.Path = folder.Path + "/";

Models.Folder _folder = _folders.GetFolder(id, false);
if (_folder.Path != folder.Path && Directory.Exists(GetFolderPath(_folder)))
Folder _folder = _folders.GetFolder(id, false);
if (_folder.Path != folder.Path && Directory.Exists(_folders.GetFolderPath(_folder)))
{
Directory.Move(GetFolderPath(_folder), GetFolderPath(folder));
Directory.Move(_folders.GetFolderPath(_folder), _folders.GetFolderPath(folder));
}

folder = _folders.UpdateFolder(folder);
Expand Down Expand Up @@ -226,9 +224,9 @@ public void Delete(int id)
var folder = _folders.GetFolder(id, false);
if (folder != null && folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Folder, id, PermissionNames.Edit))
{
if (Directory.Exists(GetFolderPath(folder)))
if (Directory.Exists(_folders.GetFolderPath(folder)))
{
Directory.Delete(GetFolderPath(folder));
Directory.Delete(_folders.GetFolderPath(folder));
}
_folders.DeleteFolder(id);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Folder Deleted {FolderId}", id);
Expand All @@ -239,10 +237,5 @@ public void Delete(int id)
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}

private string GetFolderPath(Folder folder)
{
return Utilities.PathCombine(_environment.ContentRootPath, "Content", "Tenants", _alias.TenantId.ToString(), "Sites", folder.SiteId.ToString(), folder.Path);
}
}
}
2 changes: 1 addition & 1 deletion Oqtane.Server/Controllers/UserController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ public async Task Delete(int id, string siteid)
}

// remove user folder for site
var folder = _folders.GetFolder(SiteId, Utilities.PathCombine("Users", user.UserId.ToString(), Path.DirectorySeparatorChar.ToString()));
var folder = _folders.GetFolder(SiteId, $"Users{user.UserId}/");
if (folder != null)
{
if (Directory.Exists(_folders.GetFolderPath(folder)))
Expand Down
2 changes: 1 addition & 1 deletion Oqtane.Server/Infrastructure/TenantManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public Alias GetAlias()
// legacy support for client api requests which would include the alias as a path prefix ( ie. {alias}/api/[controller] )
int aliasId;
string[] segments = httpcontext.Request.Path.Value.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
if (segments.Length > 1 && (segments[1] == "api" || segments[1] == "pages") && int.TryParse(segments[0], out aliasId))
if (segments.Length > 1 && Shared.Constants.ReservedRoutes.Contains(segments[1]) && int.TryParse(segments[0], out aliasId))
{
alias = _aliasRepository.GetAliases().ToList().FirstOrDefault(item => item.AliasId == aliasId);
}
Expand Down
27 changes: 27 additions & 0 deletions Oqtane.Server/Infrastructure/UpgradeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ public void Upgrade(Tenant tenant, string version)
case "3.1.4":
Upgrade_3_1_4(tenant, scope);
break;
case "3.2.0":
Upgrade_3_2_0(tenant, scope);
break;
}
}
}
Expand Down Expand Up @@ -238,5 +241,29 @@ private void Upgrade_3_1_4(Tenant tenant, IServiceScope scope)
}
}
}

private void Upgrade_3_2_0(Tenant tenant, IServiceScope scope)
{
try
{
// convert folder paths cross platform format
var siteRepository = scope.ServiceProvider.GetRequiredService<ISiteRepository>();
var folderRepository = scope.ServiceProvider.GetRequiredService<IFolderRepository>();
foreach (Site site in siteRepository.GetSites().ToList())
{
var folders = folderRepository.GetFolders(site.SiteId);
foreach (Folder folder in folders)
{
folder.Path = folder.Path.Replace("\\", "/");
folderRepository.UpdateFolder(folder);
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"Oqtane Error: Error In 3.2.0 Upgrade Logic - {ex}");
}
}

}
}
3 changes: 3 additions & 0 deletions Oqtane.Server/Pages/Files.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@page "/files/{**path}"
@namespace Oqtane.Pages
@model Oqtane.Pages.FilesModel
98 changes: 98 additions & 0 deletions Oqtane.Server/Pages/Files.cshtml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using System;
using System.IO;
using System.Net;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Oqtane.Enums;
using Oqtane.Extensions;
using Oqtane.Infrastructure;
using Oqtane.Models;
using Oqtane.Repository;
using Oqtane.Security;
using Oqtane.Shared;

namespace Oqtane.Pages
{
[AllowAnonymous]
public class FilesModel : PageModel
{
private readonly IWebHostEnvironment _environment;
private readonly IFileRepository _files;
private readonly IUserPermissions _userPermissions;
private readonly IUrlMappingRepository _urlMappings;
private readonly ILogManager _logger;
private readonly Alias _alias;

public FilesModel(IWebHostEnvironment environment, IFileRepository files, IUserPermissions userPermissions, IUrlMappingRepository urlMappings, ILogManager logger, ITenantManager tenantManager)
{
_environment = environment;
_files = files;
_userPermissions = userPermissions;
_urlMappings = urlMappings;
_logger = logger;
_alias = tenantManager.GetAlias();
}

public IActionResult OnGet(string path)
{
path = path.Replace("\\", "/");
var folderpath = "";
var filename = "";

var segments = path.Split('/');
if (segments.Length > 0)
{
filename = segments[segments.Length - 1].ToLower();
if (segments.Length > 1)
{
folderpath = string.Join("/", segments, 0, segments.Length - 1).ToLower() + "/";
}
}

var file = _files.GetFile(_alias.SiteId, folderpath, filename);
if (file != null)
{
if (_userPermissions.IsAuthorized(User, PermissionNames.View, file.Folder.Permissions))
{
var filepath = _files.GetFilePath(file);
if (System.IO.File.Exists(filepath))
{
return PhysicalFile(filepath, file.GetMimeType());
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Read, "File Does Not Exist {FilePath}", filepath);
HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
}
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized File Access Attempt {SiteId} {Path}", _alias.SiteId, path);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
else
{
// look for url mapping
var urlMapping = _urlMappings.GetUrlMapping(_alias.SiteId, "files/" + folderpath + filename);
if (urlMapping != null && !string.IsNullOrEmpty(urlMapping.MappedUrl))
{
var url = urlMapping.MappedUrl;
if (!url.StartsWith("http"))
{
var uri = new Uri(HttpContext.Request.GetEncodedUrl());
url = uri.Scheme + "://" + uri.Authority + ((!string.IsNullOrEmpty(_alias.Path)) ? "/" + _alias.Path : "") + "/" + url;
}
return RedirectPermanent(url);
}
}

// broken link
string errorPath = Path.Combine(Utilities.PathCombine(_environment.ContentRootPath, "wwwroot\\images"), "error.png");
return PhysicalFile(errorPath, MimeUtilities.GetMimeType(errorPath));
}
}
}
2 changes: 1 addition & 1 deletion Oqtane.Server/Repository/AliasRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public Alias GetAlias(string url)
int start = segments.Length;
for (int i = 0; i < segments.Length; i++)
{
if (segments[i] == "api" || segments[i] == "pages" || segments[i] == Constants.ModuleDelimiter)
if (Constants.ReservedRoutes.Contains(segments[i]) || segments[i] == Constants.ModuleDelimiter)
{
start = i;
break;
Expand Down
31 changes: 20 additions & 11 deletions Oqtane.Server/Repository/FileRepository.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
Expand Down Expand Up @@ -82,6 +83,24 @@ public File GetFile(int fileId, bool tracking)
return file;
}

public File GetFile(int siteId, string folderPath, string fileName)
{
var file = _db.File.AsNoTracking()
.Include(item => item.Folder)
.FirstOrDefault(item => item.Folder.SiteId == siteId &&
item.Folder.Path.ToLower() == folderPath &&
item.Name.ToLower() == fileName);

if (file != null)
{
IEnumerable<Permission> permissions = _permissions.GetPermissions(EntityNames.Folder, file.FolderId).ToList();
file.Folder.Permissions = permissions.EncodePermissions();
file.Url = GetFileUrl(file, _tenants.GetAlias());
}

return file;
}

public void DeleteFile(int fileId)
{
File file = _db.File.Find(fileId);
Expand All @@ -105,17 +124,7 @@ public string GetFilePath(File file)

private string GetFileUrl(File file, Alias alias)
{
string url = "";
switch (file.Folder.Type)
{
case FolderTypes.Private:
url = Utilities.ContentUrl(alias, file.FileId);
break;
case FolderTypes.Public:
url = alias.BaseUrl + Utilities.UrlCombine("Content", "Tenants", alias.TenantId.ToString(), "Sites", file.Folder.SiteId.ToString(), file.Folder.Path) + file.Name;
break;
}
return url;
return Utilities.FileUrl(alias, file.Folder.Path, file.Name);
}
}
}
3 changes: 2 additions & 1 deletion Oqtane.Server/Repository/Interfaces/IFileRepository.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using Oqtane.Models;

namespace Oqtane.Repository
Expand All @@ -10,6 +10,7 @@ public interface IFileRepository
File UpdateFile(File file);
File GetFile(int fileId);
File GetFile(int fileId, bool tracking);
File GetFile(int siteId, string folderPath, string fileName);
void DeleteFile(int fileId);
string GetFilePath(int fileId);
string GetFilePath(File file);
Expand Down
4 changes: 2 additions & 2 deletions Oqtane.Server/Repository/SiteRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ private void CreateSite(Site site)
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
}.EncodePermissions()
});
_folderRepository.AddFolder(new Folder { SiteId = site.SiteId, ParentId = folder.FolderId, Name = "Public", Type = FolderTypes.Public, Path = Utilities.PathCombine("Public", Path.DirectorySeparatorChar.ToString()), Order = 1, ImageSizes = "", Capacity = 0, IsSystem = false,
_folderRepository.AddFolder(new Folder { SiteId = site.SiteId, ParentId = folder.FolderId, Name = "Public", Type = FolderTypes.Public, Path = "Public/", Order = 1, ImageSizes = "", Capacity = 0, IsSystem = false,
Permissions = new List<Permission>
{
new Permission(PermissionNames.Browse, RoleNames.Admin, true),
Expand All @@ -144,7 +144,7 @@ private void CreateSite(Site site)
});
_folderRepository.AddFolder(new Folder
{
SiteId = site.SiteId, ParentId = folder.FolderId, Name = "Users", Type = FolderTypes.Private, Path = Utilities.PathCombine("Users",Path.DirectorySeparatorChar.ToString()), Order = 3, ImageSizes = "", Capacity = 0, IsSystem = true,
SiteId = site.SiteId, ParentId = folder.FolderId, Name = "Users", Type = FolderTypes.Private, Path = "Users/", Order = 3, ImageSizes = "", Capacity = 0, IsSystem = true,
Permissions = new List<Permission>
{
new Permission(PermissionNames.Browse, RoleNames.Admin, true),
Expand Down
Loading