Not a final solution,dealing with 403 status codes ...
This commit is contained in:
@ -1,29 +1,37 @@
|
||||
using Microsoft.AspNet.Authorization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Yavsc.Services;
|
||||
using Yavsc.ViewModels.Auth;
|
||||
|
||||
namespace Yavsc.AuthorizationHandlers {
|
||||
namespace Yavsc.AuthorizationHandlers
|
||||
{
|
||||
|
||||
public class ViewFileHandler : AuthorizationHandler<ViewRequirement, ViewFileContext> {
|
||||
public class ViewFileHandler : AuthorizationHandler<ViewRequirement, ViewFileContext>
|
||||
{
|
||||
readonly IFileSystemAuthManager _authManager;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public ViewFileHandler (IFileSystemAuthManager authManager) {
|
||||
public ViewFileHandler(IFileSystemAuthManager authManager, ILoggerFactory logFactory)
|
||||
{
|
||||
_authManager = authManager;
|
||||
_logger = logFactory.CreateLogger<ViewFileHandler>();
|
||||
}
|
||||
|
||||
protected override void Handle (AuthorizationContext context, ViewRequirement requirement, ViewFileContext fileContext) {
|
||||
// TODO file access rules
|
||||
if (fileContext.Path.StartsWith ("/pub/"))
|
||||
context.Succeed (requirement);
|
||||
else {
|
||||
if (!fileContext.Path.StartsWith ("/"))
|
||||
context.Fail ();
|
||||
else {
|
||||
var rights = _authManager.GetFilePathAccess (context.User, fileContext.Path);
|
||||
if ((rights & FileAccessRight.Read) > 0)
|
||||
context.Succeed (requirement);
|
||||
else context.Fail ();
|
||||
}
|
||||
protected override void Handle(AuthorizationContext context, ViewRequirement requirement, ViewFileContext fileContext)
|
||||
{
|
||||
|
||||
var rights = _authManager.GetFilePathAccess(context.User, fileContext.File);
|
||||
_logger.LogInformation("Got access value : " + rights);
|
||||
if ((rights & FileAccessRight.Read) > 0)
|
||||
{
|
||||
_logger.LogInformation("Allowing access");
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("Denying access");
|
||||
context.Fail();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ BASERESX= Resources/Yavsc.Models.IT.Fixing.Resources.resx \
|
||||
Resources/Yavsc.YavscLocalisation.resx
|
||||
BASERESXGEN=$(BASERESX:.resx=.Designer.cs)
|
||||
MCS_OPTIONS=-debug
|
||||
MONO_OPTIONS="--debug"
|
||||
MONO_OPTIONS=--debug
|
||||
|
||||
include $(MAKEFILE_DIR)/dnx.mk
|
||||
include $(MAKEFILE_DIR)/versioning.mk
|
||||
|
@ -289,6 +289,7 @@ namespace Yavsc.Models
|
||||
|
||||
public DbSet<ChatRoomAccess> ChatRoomAccess { get; set; }
|
||||
|
||||
[Obsolete("use pazof.rules from .access files")]
|
||||
public DbSet<CircleAuthorizationToFile> CircleAuthorizationToFile { get; set; }
|
||||
|
||||
public DbSet<InstrumentRating> InstrumentRating { get; set; }
|
||||
|
@ -8,6 +8,7 @@ using Microsoft.Extensions.OptionsModel;
|
||||
using System.IO;
|
||||
using rules;
|
||||
using Microsoft.Data.Entity;
|
||||
using Microsoft.AspNet.FileProviders;
|
||||
|
||||
namespace Yavsc.Services
|
||||
{
|
||||
@ -46,45 +47,38 @@ namespace Yavsc.Services
|
||||
_logger = loggerFactory.CreateLogger<FileSystemAuthManager>();
|
||||
SiteSettings = sitesOptions.Value;
|
||||
aclfileName = SiteSettings.AccessListFileName;
|
||||
ruleSetParser = new RuleSetParser(true);
|
||||
ruleSetParser = new RuleSetParser(false);
|
||||
}
|
||||
|
||||
public FileAccessRight GetFilePathAccess(ClaimsPrincipal user, string normalizedFullPath)
|
||||
public FileAccessRight GetFilePathAccess(ClaimsPrincipal user, IFileInfo file)
|
||||
{
|
||||
var parts = file.PhysicalPath.Split(Path.DirectorySeparatorChar);
|
||||
var cwd = Environment.CurrentDirectory.Split(Path.DirectorySeparatorChar).Length;
|
||||
|
||||
// Assert (normalizedFullPath!=null)
|
||||
var parts = normalizedFullPath.Split('/');
|
||||
|
||||
// below 4 parts, no file name.
|
||||
if (parts.Length < 4) return FileAccessRight.None;
|
||||
// below 4 parts behind cwd, no file name.
|
||||
if (parts.Length < cwd + 4) return FileAccessRight.None;
|
||||
|
||||
var fileDir = string.Join("/", parts.Take(parts.Length - 1));
|
||||
var fileName = parts[parts.Length - 1];
|
||||
|
||||
|
||||
var firstFileNamePart = parts[3];
|
||||
if (firstFileNamePart == "pub" && aclfileName != fileName)
|
||||
{
|
||||
_logger.LogInformation("Serving public file.");
|
||||
return FileAccessRight.Read;
|
||||
}
|
||||
if (user == null) return FileAccessRight.None;
|
||||
|
||||
var funame = parts[2];
|
||||
var cusername = user.GetUserName();
|
||||
|
||||
if (string.IsNullOrEmpty(cusername)) return FileAccessRight.None;
|
||||
|
||||
var funame = parts[cwd+1];
|
||||
if (funame == cusername)
|
||||
{
|
||||
_logger.LogInformation("Serving file to owner.");
|
||||
return FileAccessRight.Read | FileAccessRight.Write;
|
||||
}
|
||||
if (aclfileName == fileName)
|
||||
|
||||
if (aclfileName == fileName)
|
||||
return FileAccessRight.None;
|
||||
|
||||
_logger.LogInformation($"Access to {normalizedFullPath} for {cusername}");
|
||||
|
||||
ruleSetParser.Reset();
|
||||
var cuserid = user.GetUserId();
|
||||
var fuserid = _dbContext.Users.Single(u => u.UserName == funame).Id;
|
||||
|
||||
var fuserid = _dbContext.Users.SingleOrDefault(u => u.UserName == funame).Id;
|
||||
if (string.IsNullOrEmpty(fuserid)) return FileAccessRight.None;
|
||||
var circles = _dbContext.Circle.Include(mb => mb.Members).Where(c => c.OwnerId == fuserid).ToArray();
|
||||
foreach (var circle in circles)
|
||||
{
|
||||
@ -93,31 +87,24 @@ namespace Yavsc.Services
|
||||
else ruleSetParser.Definitions.Add(circle.Name, Out);
|
||||
}
|
||||
|
||||
// _dbContext.Circle.Select(c => c.OwnerId == )
|
||||
for (int dirlevel = parts.Length - 1; dirlevel>0; dirlevel--)
|
||||
for (int dirlevel = parts.Length - 1; dirlevel > cwd + 1; dirlevel--)
|
||||
{
|
||||
var aclfi = new FileInfo(Path.Combine(Environment.CurrentDirectory, fileDir, aclfileName));
|
||||
fileDir = string.Join(Path.DirectorySeparatorChar.ToString(), parts.Take(dirlevel));
|
||||
|
||||
|
||||
var aclfin = Path.Combine(fileDir, aclfileName);
|
||||
var aclfi = new FileInfo(aclfin);
|
||||
if (!aclfi.Exists) continue;
|
||||
ruleSetParser.ParseFile(aclfi.FullName);
|
||||
}
|
||||
// TODO default user scoped file access policy
|
||||
|
||||
if (ruleSetParser.Rules.Allow(user.GetUserName()))
|
||||
return FileAccessRight.Read;
|
||||
|
||||
var ucl = user.Claims.Where(c => c.Type == YavscClaimTypes.CircleMembership).Select(c => long.Parse(c.Value)).Distinct().ToArray();
|
||||
|
||||
var uclString = string.Join(",", ucl);
|
||||
_logger.LogInformation($"{uclString} ");
|
||||
foreach (
|
||||
var cid in ucl
|
||||
)
|
||||
if (ruleSetParser.Rules.Allow(cusername))
|
||||
{
|
||||
var ok = _dbContext.CircleAuthorizationToFile.Any(a => a.CircleId == cid && a.FullPath == fileDir);
|
||||
if (ok) return FileAccessRight.Read;
|
||||
return FileAccessRight.Read;
|
||||
}
|
||||
return FileAccessRight.None;
|
||||
|
||||
return FileAccessRight.None;
|
||||
}
|
||||
|
||||
public string NormalizePath(string path)
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Principal;
|
||||
using Microsoft.AspNet.FileProviders;
|
||||
|
||||
namespace Yavsc.Services
|
||||
{
|
||||
@ -23,9 +24,9 @@ namespace Yavsc.Services
|
||||
/// <param name="user"></param>
|
||||
/// <param name="normalizedFullPath"></param>
|
||||
/// <returns></returns>
|
||||
FileAccessRight GetFilePathAccess(ClaimsPrincipal user, string normalizedFullPath);
|
||||
FileAccessRight GetFilePathAccess(ClaimsPrincipal user, IFileInfo file);
|
||||
|
||||
void SetAccess (long circleId, string normalizedFullPath, FileAccessRight access);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
66
src/Yavsc/Startup/SendFileWrapper.cs
Normal file
66
src/Yavsc/Startup/SendFileWrapper.cs
Normal file
@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http.Features;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Yavsc
|
||||
{
|
||||
|
||||
class YaSendFileWrapper : IHttpSendFileFeature
|
||||
{
|
||||
private readonly Stream _output;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
|
||||
internal YaSendFileWrapper(Stream output, ILogger logger)
|
||||
{
|
||||
_output = output;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task SendFileAsync(string fileName, long offset, long? length, CancellationToken cancel)
|
||||
{
|
||||
cancel.ThrowIfCancellationRequested();
|
||||
if (string.IsNullOrWhiteSpace(fileName))
|
||||
{
|
||||
throw new ArgumentNullException("fileName");
|
||||
}
|
||||
if (!File.Exists(fileName))
|
||||
{
|
||||
throw new FileNotFoundException(string.Empty, fileName);
|
||||
}
|
||||
FileInfo fileInfo = new FileInfo(fileName);
|
||||
if (offset < 0 || offset > fileInfo.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("offset", offset, string.Empty);
|
||||
}
|
||||
if (length.HasValue && (length.Value < 0 || length.Value > fileInfo.Length - offset))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("length", length, string.Empty);
|
||||
}
|
||||
FileStream fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, maxbufferlen, FileOptions.Asynchronous | FileOptions.SequentialScan);
|
||||
try
|
||||
{
|
||||
fileStream.Seek(offset, SeekOrigin.Begin);
|
||||
_logger.LogInformation(string.Format("Copying bytes range:{0},{1} filename:{2} ", offset, (!length.HasValue) ? null : (offset + length), fileName));
|
||||
// await CopyToAsync(fileStream, _output, length, cancel);
|
||||
await CopyToAsync(fileStream, _output);
|
||||
}
|
||||
finally
|
||||
{
|
||||
fileStream.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private const int maxbufferlen = 65536;
|
||||
|
||||
private byte[] buffer = new byte[maxbufferlen];
|
||||
|
||||
private async Task CopyToAsync(FileStream fileStream, Stream output)
|
||||
{
|
||||
await Task.Run(()=> fileStream.CopyTo(output, maxbufferlen));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +1,13 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Authorization;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.FileProviders;
|
||||
using Microsoft.AspNet.Hosting;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Features;
|
||||
using Microsoft.AspNet.StaticFiles;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Yavsc.Helpers;
|
||||
@ -13,18 +16,60 @@ using Yavsc.ViewModels.Auth;
|
||||
|
||||
namespace Yavsc
|
||||
{
|
||||
|
||||
public static class YaFileSServerExtenstions
|
||||
{
|
||||
public static IApplicationBuilder UseYaFileServer(this IApplicationBuilder builder, FileServerOptions options)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException("builder");
|
||||
}
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException("options");
|
||||
}
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException("options");
|
||||
}
|
||||
if (options.EnableDefaultFiles)
|
||||
{
|
||||
builder = builder.UseDefaultFiles(options.DefaultFilesOptions);
|
||||
}
|
||||
if (options.EnableDirectoryBrowsing)
|
||||
{
|
||||
builder = builder.UseDirectoryBrowser(options.DirectoryBrowserOptions);
|
||||
}
|
||||
return builder.UseYaSendFileFallback().UseStaticFiles(options.StaticFileOptions);
|
||||
}
|
||||
|
||||
public static IApplicationBuilder UseYaSendFileFallback(this IApplicationBuilder builder)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException("builder");
|
||||
}
|
||||
return builder.UseMiddleware<YaSendFileMiddleware>(new object[0]);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class Startup
|
||||
{
|
||||
public static FileServerOptions UserFilesOptions { get; private set; }
|
||||
public static FileServerOptions GitOptions { get; private set; }
|
||||
public static FileServerOptions UserFilesOptions { get; private set; }
|
||||
public static FileServerOptions GitOptions { get; private set; }
|
||||
|
||||
public static FileServerOptions AvatarsOptions { get; set; }
|
||||
|
||||
static IAuthorizationService AuthorizationService { get; set; }
|
||||
|
||||
public static FileServerOptions AvatarsOptions { get; set; }
|
||||
public void ConfigureFileServerApp(IApplicationBuilder app,
|
||||
SiteSettings siteSettings, IHostingEnvironment env,
|
||||
IAuthorizationService authorizationService)
|
||||
{
|
||||
var userFilesDirInfo = new DirectoryInfo( siteSettings.Blog );
|
||||
AbstractFileSystemHelpers.UserFilesDirName = userFilesDirInfo.FullName;
|
||||
AuthorizationService = authorizationService;
|
||||
var userFilesDirInfo = new DirectoryInfo(siteSettings.Blog);
|
||||
AbstractFileSystemHelpers.UserFilesDirName = userFilesDirInfo.FullName;
|
||||
|
||||
if (!userFilesDirInfo.Exists) userFilesDirInfo.Create();
|
||||
|
||||
@ -34,21 +79,11 @@ namespace Yavsc
|
||||
RequestPath = PathString.FromUriComponent(Constants.UserFilesPath),
|
||||
EnableDirectoryBrowsing = env.IsDevelopment(),
|
||||
};
|
||||
UserFilesOptions.EnableDefaultFiles=true;
|
||||
UserFilesOptions.StaticFileOptions.ServeUnknownFileTypes=true;
|
||||
UserFilesOptions.EnableDefaultFiles = true;
|
||||
UserFilesOptions.StaticFileOptions.ServeUnknownFileTypes = true;
|
||||
|
||||
UserFilesOptions.StaticFileOptions.OnPrepareResponse = OnPrepareUserFileResponse;
|
||||
|
||||
/* TODO needs a better design, at implementation time (don't use database, but in memory data) */
|
||||
UserFilesOptions.StaticFileOptions.OnPrepareResponse += async context =>
|
||||
{
|
||||
var uname = context.Context.User?.GetUserName();
|
||||
var path = context.Context.Request.Path;
|
||||
var result = await authorizationService.AuthorizeAsync(context.Context.User, new ViewFileContext
|
||||
{ UserName = uname, File = context.File, Path = path } , new ViewRequirement());
|
||||
if (!result) {
|
||||
context.Context.Response.StatusCode = 403;
|
||||
context.Context.Abort();
|
||||
}
|
||||
};
|
||||
var avatarsDirInfo = new DirectoryInfo(Startup.SiteSetup.Avatars);
|
||||
if (!avatarsDirInfo.Exists) avatarsDirInfo.Create();
|
||||
AvatarsDirName = avatarsDirInfo.FullName;
|
||||
@ -72,8 +107,8 @@ namespace Yavsc
|
||||
};
|
||||
GitOptions.DefaultFilesOptions.DefaultFileNames.Add("index.md");
|
||||
GitOptions.StaticFileOptions.ServeUnknownFileTypes = true;
|
||||
_logger.LogInformation( $"{GitDirName}");
|
||||
GitOptions.StaticFileOptions.OnPrepareResponse+= OnPrepareGitRepoResponse;
|
||||
_logger.LogInformation($"{GitDirName}");
|
||||
GitOptions.StaticFileOptions.OnPrepareResponse += OnPrepareGitRepoResponse;
|
||||
|
||||
app.UseFileServer(UserFilesOptions);
|
||||
|
||||
@ -82,12 +117,28 @@ namespace Yavsc
|
||||
app.UseFileServer(GitOptions);
|
||||
app.UseStaticFiles();
|
||||
}
|
||||
|
||||
|
||||
private async void OnPrepareUserFileResponse(StaticFileResponseContext context)
|
||||
{
|
||||
var uname = context.Context.User?.GetUserName();
|
||||
var path = context.Context.Request.Path;
|
||||
var result = await AuthorizationService.AuthorizeAsync(context.Context.User,
|
||||
new ViewFileContext{ UserName = uname, File = context.File, Path = path }, new ViewRequirement());
|
||||
if (!result)
|
||||
{
|
||||
_logger.LogInformation("403");
|
||||
// TODO prettier
|
||||
context.Context.Response.StatusCode = 403;
|
||||
context.Context.Response.Redirect("/Home/Status/403");
|
||||
}
|
||||
}
|
||||
|
||||
static void OnPrepareGitRepoResponse(StaticFileResponseContext context)
|
||||
{
|
||||
if (context.File.Name.EndsWith(".ansi.log"))
|
||||
{
|
||||
context.Context.Response.Redirect("/Git"+context.Context.Request.Path);
|
||||
context.Context.Response.Redirect("/Git" + context.Context.Request.Path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -331,7 +331,7 @@ namespace Yavsc
|
||||
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
|
||||
loggerFactory.AddDebug();
|
||||
_logger = loggerFactory.CreateLogger<Startup>();
|
||||
app.UseStatusCodePagesWithReExecute("/Home/Status/{0}");
|
||||
|
||||
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
@ -410,6 +410,7 @@ namespace Yavsc
|
||||
|
||||
ConfigureOAuthApp(app);
|
||||
ConfigureFileServerApp(app, SiteSetup, env, authorizationService);
|
||||
|
||||
app.UseRequestLocalization(localizationOptions.Value, (RequestCulture)new RequestCulture((string)"en-US"));
|
||||
|
||||
ConfigureWorkflow();
|
||||
@ -498,6 +499,9 @@ namespace Yavsc
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// FIXME
|
||||
app.UseStatusCodePages();
|
||||
CheckApp(env, loggerFactory);
|
||||
}
|
||||
|
||||
|
54
src/Yavsc/Startup/YaSendFileMiddleware.cs
Normal file
54
src/Yavsc/Startup/YaSendFileMiddleware.cs
Normal file
@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Features;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Yavsc
|
||||
{
|
||||
public class YaSendFileMiddleware
|
||||
{
|
||||
|
||||
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
|
||||
//
|
||||
// Résumé :
|
||||
// Creates a new instance of the SendFileMiddleware.
|
||||
//
|
||||
// Paramètres :
|
||||
// next:
|
||||
// The next middleware in the pipeline.
|
||||
//
|
||||
// loggerFactory:
|
||||
// An Microsoft.Extensions.Logging.ILoggerFactory instance used to create loggers.
|
||||
public YaSendFileMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
|
||||
{
|
||||
if (next == null)
|
||||
{
|
||||
throw new ArgumentNullException("next");
|
||||
}
|
||||
if (loggerFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException("loggerFactory");
|
||||
}
|
||||
_next = next;
|
||||
_logger = loggerFactory.CreateLogger<YaSendFileMiddleware>();
|
||||
}
|
||||
|
||||
public Task Invoke(HttpContext context)
|
||||
{
|
||||
if (context.Response.StatusCode < 400 || context.Response.StatusCode >= 600 )
|
||||
{
|
||||
if (context.Features.Get<IHttpSendFileFeature>() == null)
|
||||
{
|
||||
context.Features.Set((IHttpSendFileFeature)new YaSendFileWrapper(context.Response.Body, _logger));
|
||||
}
|
||||
}
|
||||
return _next(context);
|
||||
}
|
||||
}
|
||||
}
|
@ -153,7 +153,7 @@
|
||||
"target": "project",
|
||||
"type": "build"
|
||||
},
|
||||
"pazof.rules": "1.1.1"
|
||||
"pazof.rules": "1.1.3"
|
||||
},
|
||||
"commands": {
|
||||
"ef": "EntityFramework.Commands",
|
||||
|
Reference in New Issue
Block a user