Not a final solution,dealing with 403 status codes ...

vnext
Paul Schneider 4 years ago
parent 9f5951e6c4
commit 7800d1a447
10 changed files with 253 additions and 81 deletions

@ -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 cusername = user.GetUserName();
var firstFileNamePart = parts[3];
if (firstFileNamePart == "pub" && aclfileName != fileName)
{
_logger.LogInformation("Serving public file.");
return FileAccessRight.Read;
}
if (user == null) return FileAccessRight.None;
if (string.IsNullOrEmpty(cusername)) return FileAccessRight.None;
var funame = parts[2];
var cusername = user.GetUserName();
var funame = parts[cwd+1];
if (funame == cusername)
{
_logger.LogInformation("Serving file to owner.");
return FileAccessRight.Read | FileAccessRight.Write;
}
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,7 +24,7 @@ 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);

@ -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;
/* 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();
}
};
UserFilesOptions.EnableDefaultFiles = true;
UserFilesOptions.StaticFileOptions.ServeUnknownFileTypes = true;
UserFilesOptions.StaticFileOptions.OnPrepareResponse = OnPrepareUserFileResponse;
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);
@ -83,11 +118,27 @@ namespace Yavsc
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);
}

@ -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",

Loading…