diff --git a/.vscode/launch.json b/.vscode/launch.json index 59243ba7..888fd93b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -16,37 +16,14 @@ "stopAtEntry": false, "console": "internalConsole" }, - { - "name": "org", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/src/Org/bin/Debug/net9.0/Org.dll", - "args": [], - "cwd": "${workspaceFolder}/src/Org", - "stopAtEntry": false, - "justMyCode": false, - "serverReadyAction": { - "action": "openExternally", - "pattern": "\\bNow listening on:\\s+(https?://\\S+)", - "killOnServerStop": true - }, - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "sourceFileMap": { - "/Views": "${workspaceFolder}/src/Org/Views" - } - }, - { "name": "Web", "type": "coreclr", "request": "launch", "preLaunchTask": "build", - "program": "${workspaceFolder}/src/Web/bin/Debug/net9.0/Web.dll", + "program": "${workspaceFolder}/src/Yavsc.Web/bin/Debug/net9.0/Yavsc.Web.dll", "args": [], - "cwd": "${workspaceFolder}/src/Web", + "cwd": "${workspaceFolder}/src/Yavsc.Web", "stopAtEntry": false, "serverReadyAction": { "action": "openExternally", @@ -64,7 +41,7 @@ "name": "Api", "type": "coreclr", "request": "launch", - "preLaunchTask": "build-web", + "preLaunchTask": "build-webapi", "program": "${workspaceFolder}/src/Api/bin/Debug/net9.0/Api.dll", "args": [], "cwd": "${workspaceFolder}/src/Api", @@ -72,10 +49,10 @@ "console": "internalConsole" }, { - "name": "dotnet org", + "name": "org", "type": "dotnet", "request": "launch", - "projectPath": "${workspaceFolder}/src/Org/Org.csproj", + "projectPath": "${workspaceFolder}/src/Yavsc.Org/Yavsc.Org.csproj", "serverReadyAction": { "action": "openExternally", "pattern": "\\bNow listening on:\\s+(https?://\\S+)" @@ -85,6 +62,7 @@ "group": "run", "order": 1 } + } ] } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 9ee9424e..90a0e0a5 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,97 +1,91 @@ { - "version": "2.0.0", - "tasks": [ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": ["build", "/property:GenerateFullPaths=true"], + "group": "build", + "isBuildCommand": true, + "isTestCommand": false, + "problemMatcher": ["$msCompile"], + "isBackground": true + }, + { + "label": "build-web", + "type": "process", + "problemMatcher": ["$msCompile"], + "command": "dotnet", + "args": ["build"], + "options": { + "cwd": "src/Yavsc" + }, + "group": { + "kind": "build" + }, + "isBackground": true + }, + { + "label": "build-webapi", + "type": "process", + "problemMatcher": ["$msCompile"], + "command": "dotnet", + "args": ["build"], + "options": { + "cwd": "src/Api" + }, + "group": { + "kind": "build" + }, + "isBackground": true + }, + { + "label": "build-web", + "type": "process", + "problemMatcher": ["$msCompile"], + "command": "dotnet", + "args": ["build"], + "runOptions": {}, + "options": { + "cwd": "src/Yavsc.Web" + }, + "group": { + "kind": "build" + }, + "isBackground": true, + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "shared", + "showReuseMessage": true, + "clear": false + } + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": ["watch", "--project", + "src/Yavsc.Org/Yavsc.Org.csproj" + ], + "problemMatcher": "$msCompile", + "runOptions": { + + } - { - "label": "build", - "command": "dotnet", - "type": "process", - "args": [ - "build", - "/property:GenerateFullPaths=true" - ], - "group": "build", - "isBuildCommand": true, - "isTestCommand": false - }, - { - "label": "build-web", - "type": "process", - "problemMatcher": ["$msCompile"], - "command": "dotnet", - "args": [ - "build" - ], - "options": { - "cwd": "src/Yavsc" - }, - "group": { - "kind": "build" - }, - "isBackground": true - }, - { - "label": "build-webapi", - "type": "process", - "problemMatcher": ["$msCompile"], - "command": "dotnet", - "args": [ - "build" - ], - "options": { - "cwd": "src/Api" - }, - "group": { - "kind": "build" - }, - "isBackground": true - }, - { - "label": "build-web", - "type": "process", - "problemMatcher": ["$msCompile"], - "command": "dotnet", - "args": [ - "build" - ], - "runOptions": { - }, - "options": { - "cwd": "src/Web" - }, - "group": { - "kind": "build" - }, - "isBackground": true, - "presentation": { - "echo": true, - "reveal": "always", - "focus": false, - "panel": "shared", - "showReuseMessage": true, - "clear": false - } - }, - { - "label": "publish", - "command": "dotnet", - "type": "process", - "args": [ - "publish", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary;ForceNoAlign" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "watch", - "command": "dotnet", - "type": "process", - "args": [ - "watch", - "run" - ], - "problemMatcher": "$msCompile" - } - ] + } + ] } diff --git a/src/Yavsc.Blogs/Program.cs b/src/Yavsc.Blogs/Program.cs new file mode 100644 index 00000000..a9d08352 --- /dev/null +++ b/src/Yavsc.Blogs/Program.cs @@ -0,0 +1,105 @@ +/* + Copyright (c) 2024 HigginsSoft, Alexander Higgins - https://github.com/alexhiggins732/ + + Copyright (c) 2018, Brock Allen & Dominick Baier. All rights reserved. + + Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + Source code and license this software can be found + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. +*/ + +using IdentityModel; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Yavsc; +using Yavsc.Helpers; +using Yavsc.Interface; +using Yavsc.Models; +using Yavsc.Services; + +internal class Program +{ + private static async Task Main(string[] args) + { + Console.Title = "API"; + + var builder = WebApplication.CreateBuilder(args); + + var services = builder.Services; + // builder.Services.AddDistributedMemoryCache(); + + // accepts any access token issued by identity server + // adds an authorization policy for scope 'scope1' + + services + .AddAuthorization(options => + { + options.AddPolicy("ApiScope", policy => + { + policy + .RequireAuthenticatedUser() + .RequireClaim(JwtClaimTypes.Scope, new string[] { "scope2" }); + }); + }) + .AddCors(options => + { + // this defines a CORS policy called "default" + options.AddPolicy("default", policy => + { + policy.WithOrigins("https://localhost:5003") + .AllowAnyHeader() + .AllowAnyMethod(); + }); + }) + .AddControllers(); + + // accepts any access token issued by identity server + var authenticationBuilder = services.AddAuthentication("Bearer") + .AddJwtBearer("Bearer", options => + { + options.IncludeErrorDetails = true; + options.Authority = "https://localhost:5001"; + options.TokenValidationParameters = + new() { ValidateAudience = false, RoleClaimType = Constants.RoleClaimType }; + options.MapInboundClaims = true; + }); + + services.AddDbContext(options => + options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"))); + + services.AddTransient() + .AddTransient() + .AddTransient(); + services.AddTransient(); + + WorkflowHelpers.ConfigureBillingService(); + using (var app = builder.Build()) + { + if (app.Environment.IsDevelopment()) + app.UseDeveloperExceptionPage(); + + app + .UseRouting() + .UseAuthentication() + .UseAuthorization() + .UseCors("default") + /* .UseEndpoints(endpoints => + { + endpoints.MapDefaultControllerRoute() + .RequireAuthorization(); + })*/ + + ; + // app.MapIdentityApi().RequireAuthorization("ApiScope"); + app.MapDefaultControllerRoute(); + app.MapGet("/identity", (HttpContext context) => + new JsonResult(context?.User?.Claims.Select(c => new { c.Type, c.Value })) + ); + + // app.UseSession(); + await app.RunAsync(); + } + } +} diff --git a/src/Yavsc.Org/Controllers/Communicating/BlogspotController.cs b/src/Yavsc.Org/Controllers/Communicating/BlogspotController.cs index 3cbf9eb3..ec1e2dd4 100644 --- a/src/Yavsc.Org/Controllers/Communicating/BlogspotController.cs +++ b/src/Yavsc.Org/Controllers/Communicating/BlogspotController.cs @@ -109,7 +109,7 @@ namespace Yavsc.Controllers if (ModelState.IsValid) { BlogPost post = blogSpotService.Create(User.GetUserId(), - blogInput); + blogInput, Request.Form.Files); return RedirectToAction("Index"); } return View("Edit", blogInput); diff --git a/src/Yavsc.Org/Services/BlogSpotService.cs b/src/Yavsc.Org/Services/BlogSpotService.cs index 399a3e0f..119a128d 100644 --- a/src/Yavsc.Org/Services/BlogSpotService.cs +++ b/src/Yavsc.Org/Services/BlogSpotService.cs @@ -1,6 +1,7 @@ using System.Diagnostics; using System.Security.Claims; using Microsoft.AspNetCore.Authorization; +using Microsoft.DotNet.Scaffolding.Shared; using Microsoft.EntityFrameworkCore; using Yavsc; using Yavsc.Helpers; @@ -8,22 +9,29 @@ using Yavsc.Models; using Yavsc.Models.Blog; using Yavsc.Server.Exceptions; using Yavsc.Server.Helpers; +using Yavsc.Services; using Yavsc.ViewModels.Auth; public class BlogSpotService { private readonly ApplicationDbContext _context; private readonly IAuthorizationService _authorizationService; + private readonly IFileSystemAuthManager fileSystemAuthManager; public BlogSpotService(ApplicationDbContext context, - IAuthorizationService authorizationService) + IAuthorizationService authorizationService, + IFileSystemAuthManager fileSystemAuthManager) { _authorizationService = authorizationService; _context = context; + this.fileSystemAuthManager = fileSystemAuthManager; } - public BlogPost Create(string userId, BlogPost post) + public BlogPost Create(string userId, BlogPost post, IFormFileCollection files) { + foreach (var file in files) + { + } _context.BlogSpot.Add(post); _context.SaveChanges(userId); return post; diff --git a/src/Yavsc.Server/Models/ApplicationDbContext.cs b/src/Yavsc.Server/Models/ApplicationDbContext.cs index 66043647..99bc2bbf 100644 --- a/src/Yavsc.Server/Models/ApplicationDbContext.cs +++ b/src/Yavsc.Server/Models/ApplicationDbContext.cs @@ -76,7 +76,7 @@ namespace Yavsc.Models builder.Entity().Property(n => n.icon).HasDefaultValue("exclam"); builder.Entity().HasKey(p => new { room = p.ChannelName, user = p.UserId }); builder.Entity().HasAlternateKey(i => new { Instrument = i.InstrumentId, owner = i.OwnerId }); - + builder.Entity().HasKey(i => new { i.FileId, i.PostId}); foreach (var et in builder.Model.GetEntityTypes()) { if (et.ClrType.GetInterface("IBaseTrackedEntity") != null) @@ -283,5 +283,8 @@ namespace Yavsc.Models public DbSet ApiResourceProperties { get; set; } public DbSet ApiScopeClaims { get; set; } public DbSet ApiScopeProperties { get; set; } + public DbSet blogAttachedFiles { get; set; } + public DbSet UploadedFiles { get; set; } + } } diff --git a/src/Yavsc.Server/Models/Blog/BlogAttachedFile.cs b/src/Yavsc.Server/Models/Blog/BlogAttachedFile.cs new file mode 100644 index 00000000..10360b69 --- /dev/null +++ b/src/Yavsc.Server/Models/Blog/BlogAttachedFile.cs @@ -0,0 +1,59 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Org.BouncyCastle.Crypto.Modes; + +namespace Yavsc.Models.Blog +{ + public class UploadedFile + { + /// + /// File Identifier + /// + /// + [Key(), DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [Display(Name = "Identifiant du fichier")] + public long Id { get; set; } + + /// + /// relative path to the file, relative to the poster space prefix + /// + /// + public string Path { get; set; } + + /// + /// Length + /// + /// + /// + /// + public long Length { get; set; } + + /// + /// File Content type + /// + /// + public string ContentType { get; set; } + } + public class BlogAttachedFile + { + + /// + /// Post Id + /// + /// + public long FileId { get; set; } + + [ForeignKey("FileId")] + public virtual UploadedFile File { get; set; } + + /// + /// Post Id + /// + /// + public long PostId { get; set; } + + [ForeignKey("PostId")] + public virtual BlogPost Post { get; set; } + + } +} diff --git a/src/Yavsc.Server/Models/Blog/BlogPost.cs b/src/Yavsc.Server/Models/Blog/BlogPost.cs index 7349c6f3..eb58cb62 100644 --- a/src/Yavsc.Server/Models/Blog/BlogPost.cs +++ b/src/Yavsc.Server/Models/Blog/BlogPost.cs @@ -9,26 +9,6 @@ using Yavsc.Models.Relationship; namespace Yavsc.Models.Blog { - public class BlogPostEditViewModel : BlogPost - { - public bool Publish { get; set; } - public BlogPostEditViewModel() - { - } - - public BlogPostEditViewModel(BlogPost post, bool publish) - { - Id = post.Id; - AuthorId = post.AuthorId; - Photo = post.Photo; - Title = post.Title; - Article = post.Article; - Publish = publish; - ACL = post.ACL; - } - - - } public class BlogPost : IBlogPost, ICircleAuthorized, ITaggable diff --git a/src/Yavsc.Server/Models/Blog/BlogPostEditViewModel.cs b/src/Yavsc.Server/Models/Blog/BlogPostEditViewModel.cs new file mode 100644 index 00000000..152e2e9a --- /dev/null +++ b/src/Yavsc.Server/Models/Blog/BlogPostEditViewModel.cs @@ -0,0 +1,23 @@ +namespace Yavsc.Models.Blog +{ + public class BlogPostEditViewModel : BlogPost + { + public bool Publish { get; set; } + public BlogPostEditViewModel() + { + } + + public BlogPostEditViewModel(BlogPost post, bool publish) + { + Id = post.Id; + AuthorId = post.AuthorId; + Photo = post.Photo; + Title = post.Title; + Article = post.Article; + Publish = publish; + ACL = post.ACL; + } + + + } +} diff --git a/src/Yavsc.Server/Models/HairCut/HairCutQuery.cs b/src/Yavsc.Server/Models/HairCut/HairCutQuery.cs index e8c36fad..c70afdf5 100644 --- a/src/Yavsc.Server/Models/HairCut/HairCutQuery.cs +++ b/src/Yavsc.Server/Models/HairCut/HairCutQuery.cs @@ -20,8 +20,6 @@ namespace Yavsc.Models.Haircut public class HairCutQuery : NominativeServiceCommand { - // Bill description - string _description = null; public override string Description { get; set; @@ -62,7 +60,6 @@ namespace Yavsc.Models.Haircut public string AdditionalInfo { get; set; } - public override List GetBillItems() { string longhairsuffix = " (cheveux longs)"; diff --git a/src/Yavsc.Server/Services/FileSystemAuthManager.cs b/src/Yavsc.Server/Services/FileSystemAuthManager.cs index dd18d964..973ccffc 100644 --- a/src/Yavsc.Server/Services/FileSystemAuthManager.cs +++ b/src/Yavsc.Server/Services/FileSystemAuthManager.cs @@ -42,29 +42,33 @@ namespace Yavsc.Services ruleSetParser = new RuleSetParser(false); } - public FileAccessRight GetFilePathAccess(ClaimsPrincipal user, string fileRelativePath) + /// + /// Get the file access info, for a given user, to a given FS absolute path + /// + /// + /// + /// + public FileAccessRight GetFilePathAccess(ClaimsPrincipal clientUser, string providerUserFilePath) { - - var cusername = user.GetUserName(); + + var clientUserName = clientUser.GetUserName(); FileInfo fi = new FileInfo( - Path.Combine(Config.UserFilesDirName, fileRelativePath)); - - if (fileRelativePath.StartsWith(cusername+'/')) + Path.Combine(Config.UserFilesDirName, providerUserFilePath)); + + if (providerUserFilePath.StartsWith(clientUserName + '/')) { return FileAccessRight.Read | FileAccessRight.Write; } - var funame = fileRelativePath.Split('/')[0]; - - // TODO Assert valid user name + var providerUserName = providerUserFilePath.Split('/')[0]; ruleSetParser.Reset(); - var cuserid = user.GetUserId(); + var cuserid = clientUser.GetUserId(); - var fuserid = _dbContext.Users.SingleOrDefault(u => u.UserName == funame).Id; + var providerUserId = _dbContext.Users.SingleOrDefault(u => u.UserName == providerUserName).Id; - if (string.IsNullOrEmpty(fuserid)) return FileAccessRight.None; + if (string.IsNullOrEmpty(providerUserId)) return FileAccessRight.None; - var circles = _dbContext.Circle.Include(mb => mb.Members).Where(c => c.OwnerId == fuserid).ToArray(); + var circles = _dbContext.Circle.Include(mb => mb.Members).Where(c => c.OwnerId == providerUserId).ToArray(); foreach (var circle in circles) { if (circle.Members.Any(m => m.MemberId == cuserid)) @@ -72,11 +76,11 @@ namespace Yavsc.Services else ruleSetParser.Definitions.Add(circle.Name, Out); } var userFilesDir = new DirectoryInfo( - Path.Combine(Config.UserFilesDirName, funame)); + Path.Combine(Config.UserFilesDirName, providerUserName)); var currentACLDir = fi.Directory; do { - var aclfileName = Path.Combine(currentACLDir.FullName, + var aclfileName = Path.Combine(currentACLDir.FullName, SiteSettings.AccessListFileName); FileInfo accessFileInfo = new FileInfo(aclfileName); if (accessFileInfo.Exists) @@ -85,20 +89,15 @@ namespace Yavsc.Services } while (currentACLDir != userFilesDir); - if (ruleSetParser.Rules.Allow(cusername)) + if (ruleSetParser.Rules.Allow(clientUserName)) { return FileAccessRight.Read; } - return FileAccessRight.None; + return FileAccessRight.None; // TODO default user scoped file access policy } - public string NormalizePath(string path) - { - throw new NotImplementedException(); - } - public void SetAccess(long circleId, string normalizedFullPath, FileAccessRight access) { throw new NotImplementedException(); diff --git a/src/Yavsc.Server/Services/IFileSystemAuthManager.cs b/src/Yavsc.Server/Services/IFileSystemAuthManager.cs index 94b431e9..9551cc6a 100644 --- a/src/Yavsc.Server/Services/IFileSystemAuthManager.cs +++ b/src/Yavsc.Server/Services/IFileSystemAuthManager.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.FileProviders; namespace Yavsc.Services { [Flags] - public enum FileAccessRight { + public enum FileAccessRight : Byte { None = 0, Read = 1, @@ -13,7 +13,6 @@ namespace Yavsc.Services } public interface IFileSystemAuthManager { - string NormalizePath (string path); /// ///